【问题标题】:Shortest possible query string for a numerically indexed array in PHPPHP中数字索引数组的最短查询字符串
【发布时间】:2014-01-04 00:54:24
【问题描述】:

我正在寻找最简洁的 URL,而不是最短的 PHP 代码。我不希望我的用户被 PHP 在编码数组时创建的可怕 URL 吓到。

如果您只是将数组 ($fn) 填充到 http_build_query,PHP 将在查询字符串中进行大量重复:

$fs = array(5, 12, 99);
$url = "http://$_SERVER[HTTP_HOST]/?" .
    http_build_query(array('c' => 'asdf', 'fs' => $fs));

生成的$url

http://example.com/?c=asdf&fs[0]=5&fs[1]=12&fs[3]=99

我怎样才能把它降到最低限度(使用 PHP 或 PHP 中容易实现的方法)?

【问题讨论】:

  • 最短的网址。这有什么好难理解的?
  • @Palec 你确定吗?这实际上是我网站上的内容,但我认为结尾的斜线是多余的。现在看看你的地址栏。
  • @Palec 好的,我明白了。 “路径”和“查询”之间不需要“/”,但在<host>:<port> 和后面的任何内容之间必须始终有一个“/”,无论是路径还是查询。我已经更新了我的问题。幸运的是,我的网站已经使用了斜线。我只是认为这是额外的。
  • @ButtleButkus 我也调整了我的知识。本来以为原因不一样,结果却记得很清楚。只有当<port> 之后没有任何内容时,我才会说尾部斜杠是强制性的,而实际上并非如此。但是没有斜线的地址(例如http://google.com)对我来说似乎很奇怪。带有斜线,它们更漂亮(http://google.com/)。

标签: php arrays query-string


【解决方案1】:

默认PHP方式

http_build_query 所做的是将数组序列化为 URL 的常用方法。 PHP 在$_GET 中自动反序列化它。

如果只想序列化一个(非关联)整数数组,您还有其他选择。

小数组

对于小型数组,转换为下划线分隔的列表是非常方便和高效的。它由$fs = implode('_', $fs) 完成。那么您的网址将如下所示:

http://example.com/?c=asdf&fs=5_12_99

缺点是您必须明确地 explode('_', $_GET['fs']) 才能将值作为数组返回。

也可以使用其他分隔符。下划线被认为是字母数字,因此很少有特殊含义。在 URL 中,它通常用作空格替换(例如,由 MediaWiki)。在带下划线的文本中使用时很难区分。连字符是另一种常见的空格替代品。它也经常用作减号。逗号是典型的列表分隔符,但与下划线和连字符不同的是,它是由http_build_query 进行百分比编码的,并且几乎在任何地方都具有特殊含义。竖线(“管道”)也有类似的情况。

大型数组

当 URL 中有大型数组时,您应该首先停止编码,开始思考。这几乎总是表明设计不好。 POST HTTP 方法不是更合适吗?您没有任何更具可读性和空间效率的方法来识别所寻址的资源吗?

理想情况下,URL 应该易于理解和(至少部分地)记住。在里面放一个大块真是个坏主意。

现在我警告你。如果您仍然需要在 URL 中嵌入一个大数组,请继续。尽可能压缩数据,base64-encode 将二进制 blob 转换为文本,url-encode 对文本进行清理以嵌入 URL。

修改 base64

嗯。或者更好地使用modified version of base64。我的选择之一是使用

  • - 而不是 +
  • _ 而不是 /
  • 省略填充 =
define('URL_BASE64_FROM', '+/');
define('URL_BASE64_TO', '-_');
function url_base64_encode($data) {
    $encoded = base64_encode($data);
    if ($encoded === false) {
        return false;
    }
    return str_replace('=', '', strtr($encoded, URL_BASE64_FROM, URL_BASE64_TO));
}
function url_base64_decode($data) {
    $len = strlen($data);
    if (is_null($len)) {
        return false;
    }
    $padded = str_pad($data, 4 - $len % 4, '=', STR_PAD_RIGHT);
    return base64_decode(strtr($padded, URL_BASE64_TO, URL_BASE64_FROM));
}

这会在每个字符上节省两个字节,否则将进行百分比编码。也不需要调用urlencode函数。

压缩

应在 gzip (gzcompress) 和 bzip2 (bzcompress) 之间进行选择。不想在比较上花费时间,对于任何块大小设置,gzip 在几个相对较小的输入(大约 100 个字符)上看起来更好。

包装

但是应该将哪些数据输入压缩算法?

在 C 中,可以将整数数组转换为字符数组(字节)并将其交给压缩函数。这是最明显的做事方式。在 PHP 中,最明显的处理方式是将所有整数转换为字符串形式的十进制表示,然后使用分隔符连接,并且仅在压缩之后。太浪费空间了!

那么,让我们使用 C 方法吧!我们将去掉分隔符和其他浪费的空间,并使用 pack 将每个整数编码为 2 个字节:

define('PACK_NUMS_FORMAT', 'n*');
function pack_nums($num_arr) {
    array_unshift($num_arr, PACK_NUMS_FORMAT);
    return call_user_func_array('pack', $num_arr);
}
function unpack_nums($packed_arr) {
    return unpack(PACK_NUMS_FORMAT, $packed_arr);
}

警告:在这种情况下,packunpack 的行为取决于机器。机器之间的字节顺序可能会发生变化。但我认为这在实践中不会有问题,因为应用程序不会同时在两个不同字节序的系统上运行。但是,当集成多个系统时,可能会出现问题。此外,如果您切换到具有不同字节序的系统,使用原始系统的链接将会中断。

一起编码

现在打包、压缩和修改 base64 合二为一:

function url_embed_array($arr) {
    return url_base64_encode(gzcompress(pack_nums($arr)));
}
function url_parse_array($data) {
    return unpack_nums(gzuncompress(url_base64_decode($data)));
}

请参阅result on IdeOne。这比 OP 的回答要好,在他的 40 元素数组上,我的解决方案产生了 91 个字符,而他的一个 98。当使用range(1, 1000)(生成array(1, 2, 3, …, 1000))作为基准时,OP’s solution produces 2712 characters while mine just 2032 characters。这大约好 25%。

为了完整起见,OP的解决方案是

function url_embed_array($arr) {
    return urlencode(base64_encode(gzcompress(implode(',', $arr))));
}

【讨论】:

  • @ButtleButkus 正如我对他的回答所评论的那样,Base64 有开销。它只会混淆 URL。您应该重新考虑是否需要在 URL 中嵌入这样的数组。我认为这几乎总是糟糕的设计决策。
  • 我做了一些研究,虽然http_build_query 会转换逗号,但这实际上没有必要。
【解决方案2】:

有多种可能的方法:

  1. serialize + base64 - 可以吞下任何对象,但数据开销太可怕了。
  2. implode + base64 - 仅限于数组,强制用户查找未使用的字符作为分隔符,数据开销要小得多。
  3. implode - 对未转义的字符串不安全。需要严格的数据控制。
$foo = array('some unsafe data', '&&&==http://', '65535');
$ser = base64_encode(serialize($foo));
$imp = implode($foo, '|');
$imp2 = base64_encode($imp);
echo "$ser\n$imp\n$imp2";

结果如下:

YTozOntpOjA7czoxNjoic29tZSB1bnNhZmUgZGF0YSI7aToxO3M6MTI6IiYmJj09aHR0cDovLyI7aToyO3M6NToiNjU1MzUiO30=
some unsafe data|&&&==http://|65535
c29tZSB1bnNhZmUgZGF0YXwmJiY9PWh0dHA6Ly98NjU1MzU=

虽然 serialize+base64 结果非常长,但 implode+serialize 提供了可管理长度的输出,并且对于 GET 来说是安全的……除了结尾处的 =

【讨论】:

  • Base64 编码有大约 33% 的开销。
  • 你能添加结果 url 的样子吗?
  • @ButtleButkus:嗯...它看起来像 Base64 字符串;p。 YTozOntpOjA7czoxNjoic29tZSB1bnNhZmUgZGF0YSI7aToxO3M6MTI6IiYmJj09aHR0cDovLyI7aToyO3M6NToiNjU1MzUiO30= 100 个字符,来自 33 个字符的文本长度数组。
  • 嗯,我正在寻找更短的,而不是更长的查询字符串。
【解决方案3】:

我相信答案取决于查询字符串的大小。

短查询字符串

对于较短的查询字符串,这可能是最好的方法:

$fs = array(5, 12, 99);
$fs_no_array = implode(',', $fs);
$url = "http://$_SERVER[HTTP_HOST]/?" .
    http_build_query(array('c' => 'asdf', 's' => 'jkl')) . '&fs=' . $fs_no_array;

导致

http://example.com/?c=asdf&s=jkl&fs=5,12,99

另一方面,您这样做是为了取回您的数组:

$fs = array_map('intval', explode(',', $_GET['fs']));

关于分隔符的快速说明:避免逗号的一个正当理由是它们在许多其他应用程序中用作分隔符。例如,如果您可能希望在 Excel 中解析 URL,则逗号可能会使其稍微困难一些。下划线也可以使用,但可以与链接的网络格式标准中的下划线混合。因此,破折号实际上可能是比逗号或下划线更好的选择。

长查询字符串

我遇到了another possible solution

$fs_compressed = urlencode(base64_encode(gzcompress($fs_no_array)));

另一端可以解压

$fs_decompressed = gzuncompress(base64_decode($_GET['fs']));
$fs = array_map('intval', explode(',', $fs_decompressed));

假设它是通过 GET 变量传入的。

有效性测试

31 个元素

$fs = array(7,2,3,4,5,6,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,52,53,54,61);

结果:

eJwFwckBwCAQxLCG%2FMh4D6D%2FxiIdpGiG5fLIR0IkRZoMWXLIJQ8%2FDIqFjYOLBy8jU0yz%2BQGlbxAB

$fs_no_array 长度为 84 个字符,$fs_compressed 长度为 84 个字符。一样!

40 个元素

$fs = array(7,2,3,4,5,6,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,52,53,54,61);

结果:

eJwNzEkBwDAQAzFC84jtPRL%2BxFoB0GJC0QyXhw4SMgoq1GjQoosePljYOLhw48GLL37kEJE%2FDCnSZMjSpkMXow%2BdIBUs

$fs_no_array 长度为 111 个字符,$fs_compressed 长度为 98 个字符。

总结

仅节省约 10 %。但在更长的时间内,节省的费用将增加到 50% 以上。

如果您使用 Yahoo 网站,您会注意到诸如逗号分隔的列表之类的内容,有时还会看到一系列随机字符。他们可能已经在野外使用这些解决方案了。

还可以查看this stack question,它对 URI 中允许的内容进行了太多详细的讨论。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-11-25
    • 2021-10-13
    • 1970-01-01
    • 2011-02-07
    • 1970-01-01
    • 2023-03-10
    • 1970-01-01
    相关资源
    最近更新 更多