如何在PHP中压缩/解压长的查询字符串?

36

我怀疑这不是加密,但我找不到更好的词语了。我需要传递一个长的查询字符串,像这样:

http://test.com/test.php?key=[some_very_loooooooooooooooooooooooong_query_string]

查询字符串不包含敏感信息,所以在这种情况下我并不担心安全问题。只是...太长而且难看。是否有一个库函数可以让我将查询字符串编码/加密/压缩成类似于md5()结果的东西(类似于始终为32个字符的字符串),但可以解码/解密/解压缩?


1
请注意,由于服务器和浏览器的限制,GET字符串的大小不应超过1-2千字节。 - Pekka
如果有一个<form>可以使用,那么POST会更好。但在这种情况下,只是一个简单的URL,希望我不必将其膨胀到一个<form>才能使其工作!SESSION实际上是我的首选,但不幸的是我需要处理多个实例,所以它也行不通。恐怕我只能使用URI了。 - jodeci
1
@jodeci,如果您为每个查询字符串分配一个唯一的随机标识符,多个实例不应该成为问题。 - Pekka
@Pekka Ah...我一定会尝试的! - jodeci
至少@Pekka你给出了一个真正的答案 :) - Your Common Sense
显示剩余4条评论
9个回答

51

你可以尝试结合使用gzdeflate(原始deflate格式)对数据进行压缩,然后使用base64_encode仅使用那些不需要百分号编码的字符(另外将字符+/替换为-_):

$output = rtrim(strtr(base64_encode(gzdeflate($input, 9)), '+/', '-_'), '=');

反之亦然:

$output = gzinflate(base64_decode(strtr($input, '-_', '+/')));

这里有一个例子:

$input = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';

// percent-encoding on plain text
var_dump(urlencode($input));

// deflated input
$output = rtrim(strtr(base64_encode(gzdeflate($input, 9)), '+/', '-_'), '=');
var_dump($output);

在这种情况下,节约约为23%。但是,此压缩过程的实际效率取决于您使用的数据。


1
完全符合我的要求,可以百分之百地复制粘贴,正是我所需要的 :) - Mike McCabe
1
这太棒了,就像上面所说的,我能够将其复制并粘贴到我的应用程序中。但是,撇号会被返回为'。 - anastymous
1
您可以使用以下代码对输出文本进行解码和解压缩:$decoded = gzinflate(base64_decode(strtr($output, '-_', '+/') . '=')); - Sajjad Moghayyad

39

这个基本前提很困难。在URL中传输任何值意味着您受限于ASCII字符子集。使用诸如gzcompress的任何压缩都会减小字符串的大小,但会产生二进制blob。然而,不能使用该二进制blob在URL中进行传输,因为它会产生无效字符。为了使用ASCII字符子集传输该二进制blob,您需要以某种方式对其进行编码并将其转换为ASCII字符。

因此,您需要将ASCII字符转换为其他内容,然后再将其转换为ASCII字符。

但实际上,大多数情况下,您最初使用的ASCII字符已经是最优长度。以下是一个快速测试:

$str = 'Hello I am a very very very very long search string';
echo $str . "\n";
echo base64_encode(gzcompress($str, 9)) . "\n";
echo bin2hex(gzcompress($str, 9)) . "\n";
echo urlencode(gzcompress($str, 9)) . "\n";

Hello I am a very very very very long search string
eNrzSM3JyVfwVEjMVUhUKEstqkQncvLz0hWKUxOLkjMUikuKMvPSAc+AEoI=
78daf348cdc9c957f05448cc554854284b2daa442772f2f3d2158a53138b9233148a4b8a32f3d201cf801282
x%DA%F3H%CD%C9%C9W%F0TH%CCUHT%28K-%AAD%27r%F2%F3%D2%15%8AS%13%8B%923%14%8AK%8A2%F3%D2%01%CF%80%12%82

从中可以看出,原始字符串最短。在编码压缩中,Base64是最短的,因为它使用最大的字母表来表示二进制数据。但仍然比原始字符串长。

对于某些非常特定的字符组合和某些能够将其压缩为ASCII可表示数据的压缩算法,可能会实现一些压缩,但这更多是理论上的。 更新: 实际上,这听起来太消极了。问题在于需要确定压缩是否适用于您的用例。不同的数据具有不同的压缩方式和不同的编码算法。此外,较长的字符串可能会获得更好的压缩比。可能存在某个甜点,在那里可以实现一些压缩。您需要弄清楚自己大多数时间是否处于该甜点区域。

像 md5 这样的东西是不合适的,因为 md5 是一个哈希,这意味着它是不可逆的。您无法从中获取原始值。

如果无法通过URL发送参数,则只能通过POST方式发送。


4
实际上,你不能确定任何编码都会导致字符串比原始字符串更长。这主要取决于原始字符串的长度。如果压缩得足够好,则即使是编码后的版本也可能更短。我用base64尝试了一下我的例子,结果比原始字符串更短。但从某种程度上来说,你是对的,因为大多数情况下,编码后的版本会更长。 - Felix Kling
@gumbo 是的,但那真的不会有太大作用。 - deceze
较长的查询字符串通常会有更多的冗余,因此在base64 / gzcompress解决方案下表现更好。 - ladenedge
是的,你尝试了50个字符的字符串,那么300或700或1500个字符呢?我非常好奇看到那里的结果,特别是对于大多数人类可读的文本... - MBoros
@MBoros 15百万个字符怎么样?这被称为荒谬论证。而deceze的答案是正确的,而其他人则不行。 - user1932079
显示剩余4条评论

14

这对我非常有效:

$out = urlencode(base64_encode(gzcompress($in)));

省很多钱。

$in = 'Hello I am a very very very very long search string' // (51)
$out = 64

$in = 500
$out = 328

$in = 1000
$out = 342

$in = 1500
$out = 352

字符串越长,压缩效果越好。压缩参数似乎没有任何影响。


我用一个被implode成字符串的数组测试了urlencode(base64_encode(gzcompress($in)));。我发现只有在字符串长度超过80个字符时才会出现压缩,例如从110到94。你在回答中没有很好地解释$in/$out变量。我想你是说这些是你的字符串输入/输出大小(压缩前/后)。这是我的答案,以你的答案为起点:http://stackoverflow.com/a/20915918/631764 - Buttle Butkus
如果您通过重复相同的文本块来创建更长的输入字符串,那么您将获得非常好的压缩效果,因为输入中存在重复模式(低熵)。如果您压缩具有较少重复(高熵)的字符串,则会获得较少的压缩效果。长话短说:您需要确保使用真实(逼真)的数据进行基准测试。 - Henry

5
更新:
gzcompress() 不会有所帮助。例如,如果你采用 Pekka 的答案:
字符串长度:640
压缩字符串长度:375
URL 编码字符串长度:925
(使用 base64_encode 后只有 500 个字符;)
因此,通过 URL 传递数据的方式可能不是最佳方法。如果你没有超过 URL 的限制,你为什么要关心字符串的样式呢?我认为它会被自动创建、发送和处理,对吗?但是,如果你想将其用作例如电子邮件中的某种确认链接,你必须考虑一些简短且易于用户键入的东西。例如,你可以在数据库中存储所有需要的数据并创建某种令牌。
也许 gzcompress() 可以帮助你。但是这将导致出现不允许的字符,因此你将不得不再使用 urlencode()(使字符串变长且难看)。

是的。结果需要进行urlencode()处理,这可能对于非常小的字符串来说不太实用,但对于较大的字符串可能有效。 - Pekka
@Pekka:是的,我刚刚注意到了。它似乎对短字符串没有什么影响。 - Felix Kling
Felix,我知道你在Facebook上,关于在Facebook中压缩字符串的问题。你知道Cassandra(数据库)中有UUID,它是一个很长的文本!Twitter和Instagram也在使用Cassandra(最初是在Facebook开发的),当我查找Facebook、Twitter和Instagram的帖子或用户ID时,并不是UUID,我不确定他们是否也在URL中使用UUID(或timeuuid),或者他们有一种函数和算法来缩短UUID的长度。你知道并且能说出Facebook正在做什么以便使用UUID时拥有短URL吗? - Mohammad Kermani

2
基本上,就像他们所说的那样:压缩文本,并以有用的方式编码发送。但是:
1)通常的压缩方法比文本更重,因为有字典。如果数据总是以确定的数据块的未确定顺序出现(例如在文本中是单词或音节[3]、数字和一些符号),则可以始终使用相同的静态字典,并且不发送它(不将其粘贴到URL上)。然后,你可以节省字典的空间。
1.a)如果你已经发送了语言(或者它总是相同的),你可以针对每种语言生成一个字典。
1.b)利用格式限制。如果你知道它是一个数字,你可以直接编码它(参见3)。如果你知道它是一个日期,你可以将其编码为Unix时间[1](自1970年01/01以来的秒数),因此“21/05/2013 23:45:18”变为“519C070E”(十六进制);如果它是一年的日期,你可以将其编码为包括29/02在内的新年以来的天数(08/25将是237)。
1.3)您知道电子邮件必须遵循特定规则,并且通常来自同几个服务器(如gmail,yahoo等)。您可以利用此进行压缩并使用自己的简单方法:
samplemail1@gmail.com,samplemail2@yahoo.com.ar,samplemail3@idontknowyou.com => samplemail1:1,samplemail2:5,samplemail3@idontknowyou:1

如果数据遵循某种模式,您可以利用它来帮助压缩。例如,如果它总是遵循这个模式:
name=[TEXT 1]&phone=[PHONE]&mail=[MAIL]&desc=[TEXT 2]&create=[DATE 1]&modified=[DATE 2]&first=[NUMBER 1]&last=[NUMBER 2]

你可以: 2.a) 忽略相似的文本,仅压缩变量文本。例如:
[TEXT1]|[PHONE]|[MAIL]|[TEXT 2]|[DATE 1]|[DATE 2]|[NUMBER 1][NUMBER 2]

2.b) 按类型对数据进行编码或压缩(使用base64[2]或类似方法对数字进行编码)。与1)类似,这甚至允许您省略分隔符。例如:
[DATE 1][DATE 2][NUMBER 1][NUMBER 2][PHONE][MAIL]|[TEXT 1]|[TEXT 2]

3) 编码:

3.a) 虽然如果我们使用HTTP不支持的字符压缩编码,它们将被转换为更重的字符(如'año' => 'a%C3%B1o'),但仍然可以有用。也许您想将其压缩以存储在Unicode或二进制数据库中,或将其粘贴到网站(Facebook、Twitter等)上。

3.b) 尽管Base64[2]是一种很好的方法,但您可以通过使用用户函数而不是编译函数来牺牲速度来挤出更多的空间。

至少在Javascript的encodeURI()函数中,您可以使用任何这80个字符作为参数值而不会受到修改:

0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.:,;+*-_/()$=!@?~'

因此,我们可以构建自己的“Base 80”(d)编码功能。

1

虽然不是答案,但这里比较了各种建议的方法。

使用@Gumbo和@deceze的答案来比较我在GET请求中使用的相当长的字符串的长度。

<?php
    $test_str="33036,33037,33038,38780,38772,37671,36531,38360,39173,38676,37888,36828,39176,39196,37321,36840,38519,37946,36543,39287,38989,38976,36804,38880,38922,38292,38507,38893,38993,39035,37880,38897,38378,36880,38492,38910,36868,38196,38750,37938,39268,38209,36856,36767,37936,36805,39248,36777,39027,39056,38987,38779,38919,38771,36851,38675,37887,38246,38791,38783,38661,37899,36846,36834,39263,37928,36822,37947,38992,38516,39177,38904,38896,37320,39217,37879,38293,38511,38774,37670,38185,37927,37939,38286,38298,38977,37891,38881,38197,38457,36962,39171,36760,36748,39249,39231,39191,36951,36963,36755,38769,38891,38654,38792,36863,36875,36956,36968,38978,38299,36743,36753,37896,38926,39270,38372,37948,39250,38763,38190,38678,36761,37925,36776,36844,37323,38781,38744,38321,38202,38793,38510,38288,36816,38384,37906,38184,38192,38745,39218,38673,39178,39198,39036,38504,36754,39180,37919,38768,38195,36850,38203,38672,38882,38071,39189,36795,36783,38870,38764,39028,36762,36750,38980,36958,37924,38884,37920,38877,36858,38493,36742,37895,36835,37907,36823,38762,38361,37937,38373,37949,36950,39202,38495,38291,36533,39037,36716,38925,37620,38906,37878,37322,38754,36818,39029,39264,38297,38517,36969,38905,36957,36789,36741,37908,38302,38775,39216,36812,38767,36845,36849,39181,39168,38671,39188,38490,36961,39201,36717,38382,38070,37868,38984,36770,38981,38494,36807,38885,36759,36857,38924,39038,38888,38876,36879,37897,36534,36764,37931,38254,39030,38990,37909,38982,38290,36848,37857,37923,38249,38658,38383,36813,36765,36817,37263,36769,37869,38183,36861,38206,39031,36800,36788,36972,38508,38303,39051,38491,38983,38759,36740,37958,36967,37930,39174,39182,36806,36867,36855,39222,37862,36752,38242,37965,38894,38182,37922,37918,36814,36872,38886,36860,36527,38194,38975,36718,39224,37436,39032";

    echo(strlen($test_str)); echo("<br>");

    echo(strlen(base64_encode(gzcompress($test_str,9)))); echo("<br>");

    echo(strlen(bin2hex(gzcompress($test_str, 9)))); echo("<br>");

    echo(strlen(urlencode(gzcompress($test_str, 9)))); echo("<br>");

    echo(strlen(rtrim(strtr(base64_encode(gzdeflate($test_str, 9)), '+/', '-_'), '=')));
?>

Here are the results:

1799  (original length string)
928   (51.58% compression)
1388
1712
918   (51.028% compression)

使用gzcompress和base64_encode以及一些字符串转换可以获得可比较的结果。但是,gzdeflate似乎效率稍微更高一些


1
这些函数可以压缩和解压缩字符串或数组。
有时您可能想要获取一个数组。
function _encode_string_array ($stringArray) {
    $s = strtr(base64_encode(addslashes(gzcompress(serialize($stringArray),9))), '+/=', '-_,');
    return $s;
}

function _decode_string_array ($stringArray) {
    $s = unserialize(gzuncompress(stripslashes(base64_decode(strtr($stringArray, '-_,', '+/=')))));
    return $s;
}

1
请记住,永远不要盲目反序列化数据。这是非常常见的安全漏洞。 - MM.

1

对于长/非常长的字符串值,您应该使用POST方法而不是GET!

为了获得良好的编码,您可以尝试urlencode()/urldecode()

或者htmlentities()/html_entity_decode()

还要注意,'%2F'在浏览器中被翻译为'/'字符(目录分隔符)。如果您只使用urlencode,您可能需要对其进行替换。

我不建议在GET参数上使用gzcompress。


-1

base64_encode函数可以将字符串编码为不可读的形式(当然也很容易解码),但是会使体积增加33%。

urlencode()函数可以将URL中不适合的字符转换为它们的URL编码形式。如果你的目的是让字符串在URL中正常工作,那么这可能是正确的方法。

如果你有一个正在运行的会话,你也可以考虑将查询字符串放入一个带有随机(小)数字的会话变量中,并将该随机数放入GET字符串中。当然,这种方法只能在当前会话中生存。

请注意,由于服务器和浏览器的限制,GET字符串的大小不应超过1-2千字节。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接