我有一堆10位数的整数,我在URL中传递。就像是: “4294965286”,“2292964213”。它们总是积极的,总是10位数。
我想将这些整数压缩成可以在URL中使用的最小可能形式(也就是字母和数字完全正常),然后在以后解压缩它们。我已经看过使用gzipstream,但它创建了更大的字符串,而不是更短。
我目前正在使用asp.net,所以vb.net或c#解决方案最好。
由于
答案 0 :(得分:26)
是。 GZIP是一种压缩算法,它既需要可压缩数据,又需要开销(框架和字典等)。应该使用编码算法。
“简单”方法是使用base-64 encoding。
即,将数字(在字符串中表示为基数10)转换为表示数字的实际字节数组(5个字节将覆盖10位十进制数),然后将结果转换为base-64。每个base-64字符存储6位信息(小数~3.3位/字符),因此大小只有一半以上(在这种情况下,需要6 * base-64输出字符)。
此外,由于输入/输出长度可从数据本身获得,因此“123”可能最初(在进行base-64编码之前)转换为1字节,“30000”转换为2字节等。这将是有利的如果不是所有数字的长度大致相同。
快乐的编码。
* 使用base-64需要6个输出字符。
编辑:我最初错了我说“2.3比特/字符”表示十进制,并提出不到一半的字符是必需的。我已经更新了上面的答案,并在这里显示(应该是正确的)数学,其中lg(n)
记录到基数2。
表示输入数字所需的输入位数是bits/char * chars
- > lg(10) * 10
(或仅lg(9999999999)
) - > ~33.2 bits
。使用jball的操作来首先移动数字,所需的位数是lg(8999999999)
- > ~33.06 bits
。然而,这种转换无法在这种特殊情况下提高效率 (输入位数需要减少到30或更低才能在这里产生差异)。
因此我们尝试找到一个x(base-64编码中的字符数),这样:
lg(64) * x = 33.2
- > 6 * x = 33.2
- > x ~ 5.53
。当然,五个半字符是荒谬的,因此我们选择6作为最大字符数,这些字符在base-64编码中编码值高达999999999。这个数字略多于原始10个字符的一半。
但是,应该注意的是,要在base-64输出中只获得6个字符,需要使用非标准的base-64编码器或一点点操作(大多数base-64编码器只能在整个字节上工作)。这是有效的,因为在原始的5个“必需字节”中,只使用了40个中的34个(前6位始终为0)。它需要7个base-64字符来编码所有40位。
这是Guffa在他的回答中发布的代码的修改(如果你喜欢,请给他一个向上投票),只需要6个base-64个字符。请参阅Guffa答案和Base64 for URL applications中的其他说明,因为以下方法不使用网址友好的映射。
byte[] data = BitConverter.GetBytes(value);
// make data big-endian if needed
if (BitConverter.IsLittleEndian) {
Array.Reverse(data);
}
// first 5 base-64 character always "A" (as first 30 bits always zero)
// only need to keep the 6 characters (36 bits) at the end
string base64 = Convert.ToBase64String(data, 0, 8).Substring(5,6);
byte[] data2 = new byte[8];
// add back in all the characters removed during encoding
Convert.FromBase64String("AAAAA" + base64 + "=").CopyTo(data2, 0);
// reverse again from big to little-endian
if (BitConverter.IsLittleEndian) {
Array.Reverse(data2);
}
long decoded = BitConverter.ToInt64(data2, 0);
让它“更漂亮”
由于base-64已确定使用6个字符,因此仍然将输入位编码为6个字符的任何编码变体将创建同样小的输出。使用base-32 encoding不会完全削减,因为在base-32编码中,6个字符只能存储30位信息(lg(32) * 6
)。
但是,使用自定义base-48(或52/62)编码可以实现相同的输出大小。 (基数48-62的优点是它们只需要一个字母数字字符的子集而不需要符号;对于变体,可以选择“模糊”符号,如1和“I”。使用base-48系统,6个字符可以编码~33.5位(lg(48) * 6
)的信息,这些信息刚好高于所需的~33.2(或~33.06)位(lg(10) * 10
)。
这是一个概念验证:
// This does not "pad" values
string Encode(long inp, IEnumerable<char> map) {
Debug.Assert(inp >= 0, "not implemented for negative numbers");
var b = map.Count();
// value -> character
var toChar = map.Select((v, i) => new {Value = v, Index = i}).ToDictionary(i => i.Index, i => i.Value);
var res = "";
if (inp == 0) {
return "" + toChar[0];
}
while (inp > 0) {
// encoded least-to-most significant
var val = (int)(inp % b);
inp = inp / b;
res += toChar[val];
}
return res;
}
long Decode(string encoded, IEnumerable<char> map) {
var b = map.Count();
// character -> value
var toVal = map.Select((v, i) => new {Value = v, Index = i}).ToDictionary(i => i.Value, i => i.Index);
long res = 0;
// go in reverse to mirror encoding
for (var i = encoded.Length - 1; i >= 0; i--) {
var ch = encoded[i];
var val = toVal[ch];
res = (res * b) + val;
}
return res;
}
void Main()
{
// for a 48-bit base, omits l/L, 1, i/I, o/O, 0
var map = new char [] {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K',
'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't',
'u', 'v', 'x', 'y', 'z', '2', '3', '4',
};
var test = new long[] {0, 1, 9999999999, 4294965286, 2292964213, 1000000000};
foreach (var t in test) {
var encoded = Encode(t, map);
var decoded = Decode(encoded, map);
Console.WriteLine(string.Format("value: {0} encoded: {1}", t, encoded));
if (t != decoded) {
throw new Exception("failed for " + t);
}
}
}
结果是:
value: 0 encoded: A value: 1 encoded: B value: 9999999999 encoded: SrYsNt value: 4294965286 encoded: ZNGEvT value: 2292964213 encoded: rHd24J value: 1000000000 encoded: TrNVzD
以上考虑数字是“随机且不透明”的情况;也就是说,没有什么可以确定数字的内部。但是,如果存在定义的结构(例如,第7,第8和第9位始终为零,第2位和第15位始终相同)则 - 当且仅当4位或更多位信息可被消除时< / em>来自输入 - 只需要5个base-64个字符。增加的复杂性和对结构的依赖很可能超过任何边际收益。
答案 1 :(得分:4)
您可以使用base64编码将数据减少为七个字符。您需要五个字节来表示数字,并且可以使用base64将它们编码为八个字符,但最后一个字符始终是填充=
,因此可以将其删除:
long value = 4294965286;
// get the value as an eight byte array (where the last three are zero)
byte[] data = BitConverter.GetBytes(value);
// encode the first five bytes
string base64 = Convert.ToBase64String(data, 0, 5).Substring(0, 7);
Console.WriteLine(base64);
输出:
Jvj//wA
要对文字进行解码,请再次添加=
,对其进行解码,然后将其作为数字读取:
// create an eight byte array
byte[] data = new byte[8];
// decode the text info five bytes and put in the array
Convert.FromBase64String(base64 + "=").CopyTo(data, 0);
// get the value from the array
long value = BitConverter.ToInt64(data, 0);
Console.WriteLine(value);
输出:
4294965286
base64使用的两个字符不适合在URL中使用,因此您可以将其替换为其他字符,然后将其替换回来。 +
和/
字符可以替换为-
和_
。
答案 2 :(得分:4)
我认为您正在寻找的是哈希ID:http://hashids.org/
他们有多种语言的实现,虽然看起来C#不是其中之一。
我在JavaScript中为您做了一个示例:http://codepen.io/codycraven/pen/MbWwQm
var hashids = new Hashids('my salt', 1, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890');
var input = 4294965286;
var hex = input.toString(16); // 8 characters: fffff826
var hashid = hashids.encode(input); // 7 characters: 0LzaR1Y
var base64 = window.btoa(input).replace(/=+/, ''); // 14 characters: NDI5NDk2NTI4Ng
请注意,HashIDs库可以保护您的哈希不包含粗言秽语。
答案 3 :(得分:3)
除了更改编码的基础(pst并且我在同一时间有同样的想法),由于你的所有数字都是10位十进制数,你可以减去最小的10位数(10E9)在编码之前从每个数字开始,然后在解码后将其添加回来。这会将您的编码数字移动到0 - 8999999999的范围内,从而在基数更改后允许更小的字符串。
答案 4 :(得分:1)
如何将大数字转换为公式:所以我可能会使用4 ^ 34而不是21312312312。 http://mathforum.org/library/drmath/view/65726.html
答案 5 :(得分:0)
我喜欢@ user166390的答案,但我更喜欢一种最不常用的格式,并认为可以改进代码,因为在编码中不需要使用字典,并且不需要在每次解码时都生成字典。另外,由于不支持负值,我还添加了一个异常并更改为ulong。
如果有人进一步提高了性能,请随时写。也许是否有替代StringBuilder的更好的方法
这是我修改的代码。
public static string EncodeNumber(ulong input)
{
return EncodeNumber(input, Mapping85Bit);
}
// This does not "pad" values
private static string EncodeNumber(ulong inp, char[] map)
{
// use ulong count instead of int since does not matter on x64 operating system.
ulong cnt = (ulong)map.Length;
// value -> character
if (inp == 0)
{
return map[0].ToString();
}
var sb = new StringBuilder();
while (inp > 0)
{
// encoded most-to-least significant
ulong val = inp % cnt;
inp = inp / cnt;
sb.Insert(0, map[(int)val]);
}
return sb.ToString();
}
public static ulong DecodeNumber(string encoded)
{
return DecodeNumber(encoded, Mapping85Bit, Mapping85BitDict);
}
private static ulong DecodeNumber(string encoded, char[] map, Dictionary<char, ulong> charMapDict)
{
// use ulong count instead of int since does not matter on x64 operating system.
ulong b = (ulong)map.Length;
ulong res = 0;
for (var i = 0; i < encoded.Length; i++)
{
char ch = encoded[i];
if(!charMapDict.TryGetValue(ch, out ulong val))
{
throw new ArgumentException($"Invalid encoded number: '{encoded}'. '{ch}' is not a valid character for this encoding.");
}
res = (res * b) + val;
}
return res;
}
// Windows file system reserved characters: < > : " / \ | = *
/// <summary>
/// Compatible with file system. Originates from ASCII table except starting like Base64Url and except windows path reserved chars. Skipped '/' and '\' to prevent path problems. Skipped ' for sql problems.
/// https://www.ascii-code.com/
/// Does not need to be encoded for json since it doesn't use \ and ". No encoding also needed for xml since < > are also not used. That is why it is also different to https://en.wikipedia.org/wiki/Ascii85
/// </summary>
public static readonly char[] Mapping85Bit = new char[] {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', '-', '_', ' ', '!', '#', '$', '%', '&',
'(', ')', '+', ',', '.', ';', '?', '@', '[', ']',
'^', '`', '{', '}', '~'
};
private static readonly Dictionary<char, ulong> Mapping85BitDict = Mapping85Bit.Select((v, i) => new { Value = v, Index = (ulong)i }).ToDictionary(i => i.Value, i => i.Index);
[Test]
public void EncodeTest()
{
// 85Bit Encoding:
Assert.AreEqual(EncodeNumber(85), "BA");
Assert.AreEqual(EncodeNumber(86), "BB");
Assert.AreEqual(EncodeNumber(3), "D");
Assert.AreEqual(EncodeNumber(84), "~");
Assert.AreEqual(EncodeNumber(0), "A");
Assert.AreEqual(DecodeNumber("BA"), 85);
Assert.AreEqual(DecodeNumber("BA"), 85);
Assert.AreEqual(DecodeNumber("`"), 81);
}