用哈希缩短长网址?

Posted

技术标签:

【中文标题】用哈希缩短长网址?【英文标题】:Shortening long urls with a hash? 【发布时间】:2011-11-08 15:39:10 【问题描述】:

我有一个文件缓存,文件从不同的 url 下载。我想用它们的 url 名称保存每个文件。不过这些名称可能很长,而且我在使用 FAT32 文件系统的设备上 - 所以在我用完实际磁盘空间之前,这些长名称已经占用了资源。

我正在寻找一种缩短文件名的方法,已经收到了对字符串进行哈希处理的建议。但我不确定哈希是否保证对于两个不同的字符串是唯一的。如果两个散列 url 得到相同的散列值,如果我不小心获取了错误的图像,那就太糟糕了。

谢谢

【问题讨论】:

我想你会在散列文件名时遇到麻烦:散列(恕我直言)可以产生重复的条目...... 当您说“在我用完实际磁盘空间之前,长名称正在消耗资源”时,我有点怀疑。不知道为什么。但是存储不是很便宜吗? @Marco,同意,哈希可以产生重复(“冲突”)。如果发生冲突,您应该创建一些冲突处理程序来尝试新的哈希...... 哈希不能保证是唯一的——有些哈希(例如 md5 或 sha1)仅仅根据它们包含的空间有多大来获得它们的“唯一性”。但是它们可能根本不会更短;-) @Marco, poplitea:散列可以产生冲突,但概率小到可以完全忽略。即使您使用的是 MD5(输出大小为 128 位),那么有十亿个条目,发生冲突的机会仍然小于 10^-18。 【参考方案1】:

您可以为每个 URL 生成一个UUID 并将其用作文件名。

UUID 是唯一的(或“实际上是唯一的”),长度为 36 个字符,所以我想文件名不会有问题。

从版本 5 开始,JDK 附带了一个生成 UUID 的类 (java.util.UUID)。如果有办法将 UUID 与 URL 相关联,您可以使用随机生成的 UUID,或者您可以使用基于名称的 UUID。基于名称的 UUID 始终相同,因此以下情况始终正确:

String url = ...
UUID urlUuid = UUID.nameUUIDFromBytes(url.getBytes);
assertTrue(urlUuid.equals(UUID.nameUUIDFromBytes(url.getBytes)));

【讨论】:

【参考方案2】:

没有(缩短的)散列可以保证每个输入都有不同的散列。这根本不可能。

我通常的做法是将原始名称保存在缓存文件的开头(例如第一行)。所以要在缓存中找到一个文件,你可以这样做:

散列网址 找到与该哈希对应的文件 检查第一行。如果与完整网址相同: 文件的其余部分从第二行开始

您也可以考虑将 URL->文件映射保存在数据库中。

【讨论】:

【参考方案3】:

但我不确定哈希是否保证对于两个不同的字符串是唯一的。

他们不是(而且不可能,因为pigeonhole principle)。但是,如果哈希足够长(至少 64 位)并且分布良好(理想情况下是加密哈希),那么发生冲突的可能性就会变得非常小,不值得担心。

作为粗略的指导方针,一旦文件数量接近可能的不同散列数 (birthday paradox) 的平方根,就很可能发生冲突。因此,对于 64 位散列(10 个字符的文件名),如果您有 40 亿个文件,您有大约 50% 的机会发生一次冲突。

您必须决定这是否是可接受的风险。您可以通过延长哈希值来减少冲突的机会,但当然在某些时候这将意味着与您想要的相反。

【讨论】:

【参考方案4】:

目前推荐使用SHA-1算法。没有已知的方法可以故意引发此算法的冲突,因此您应该是安全的。与具有共同结构的两条数据(例如http:// 前缀)发生冲突更加困难。如果您在收到 HTTP 200 响应后保存这些内容,那么该 URL 显然获取了一些内容,因此获得两个具有相同 SHA-1 哈希的不同有效 URL 真的不应该是一个问题。

如果有任何再保证Git 使用它来识别源代码存储库中的所有对象、提交和文件夹。我还没有听说有人在对象存储中发生碰撞。

【讨论】:

【参考方案5】:

您可以做的是通过索引保存文件并使用索引文件查找实际文件的位置

在您拥有的目录中:

index.txt
file1
file2
...
etc.

在 index.txt 中,您使用一些数据结构来有效地查找文件名(或替换为数据库)

【讨论】:

【参考方案6】:

哈希不保证是唯一的,但发生冲突的可能性非常小。

如果你的散列是 128 位,那么任何一对条目发生冲突的可能性是 2^128 中的 1。根据生日悖论,如果你的表中有 10^18 个条目,那么发生冲突的几率只有 1%,所以你真的不需要担心。如果您特别偏执,请使用 SHA256 或 SHA512 增加散列的大小。

显然,您需要确保散列表示实际上占用的空间比原始文件名少。 Base-64 编码的字符串表示每个字符 6 位,因此您可以通过数学计算首先确定是否值得进行哈希处理。

如果您的文件系统因为名称太长而出错,那么您可以为实际存储创建前缀子目录。例如,如果一个文件映射了哈希 ABCDE,那么您可以将其存储为 /path/to/A/B/CDE,或者可能是 /path/to/ABC/DE,具体取决于最适合您的文件系统的内容。

Git 是这种技术在实践中的一个很好的例子。

【讨论】:

即使是 128 位散列也可能违背缩短文件名的初衷。 Base64 编码只有 22 个字符。如果这对于 FAT32 来说仍然太大,那么使用不同的文件系统可能是一个更好的解决方案。说真的,FAT32 还在使用? FAT32 可以有更长的文件名。问题似乎与非常大的 number 长文件名有关。如果文件名基于 URL,那么使用 22 个字符的哈希可能仍会导致平均长度减少。但有两到四倍,可能不会。【参考方案7】:

看看我的评论。 一种可能的解决方案(有很多)是创建一个本地文件(SQLite?XML?TXT?),您可以在其中存储一对(file_id - file_name),以便您可以将下载的文件及其唯一 ID 保存为文件名。 只是一个想法,不是最好的......

【讨论】:

以上是关于用哈希缩短长网址?的主要内容,如果未能解决你的问题,请参考以下文章

如何从网址中删除哈希“#”符号?

HMAC哈希消息认证码

javascript 滚动到网址哈希

Rails 4 + Less 的图片网址不包括哈希

#yyds干货盘点#哈希算法和多种加密算法综合使用

相似图片搜索:感知哈希算法