如何在Java中有效地压缩长度为10-1000个字符的字符串?

8

我需要将长度在10到1000个字符之间的字符串(使用已知但可变语言编写)压缩为单独的UDP数据包。

有哪些适合这项任务的Java压缩算法?

也许有开源的Java库可以完成这个任务吗?


1
你没有说明你所说的“高效”是指什么。是快速压缩?快速解压缩?最小的压缩大小?你也没有说明这段文本是字母表(对于某些字母表),音节或基于字符的(如中文/日语/韩语)...还是以上所有情况都包括在内。 - Stephen C
4个回答

10

"这要看情况而定"。

我会先从主要的候选方案开始考虑: LZMA(“7-zip”),deflate(直接使用,zlib:deflate + 小包装,gzip:deflate + 稍大的包装,zip:deflate + 更大的包装),bzip2(我觉得在这里可能不太好用,适合于相对较大的窗口),甚至其他LZ*分支之一,如有用于IP Payload压缩的LZS,但是...

...根据实际数据和压缩/吞吐量运行一些分析使用几种不同的方法。Java具有GZIPOutputStream(“gzip包装中的deflate”)和DeflaterOutputStream(“普通deflate”,推荐使用比gzip或zip“包装”更好)的标准,还有LZMA Java实现(只需要压缩器,不需要容器),因此这些都应该很容易模拟。

如果数据包之间存在规律,那么可以利用这些规律,例如构建缓存映射、Huffman表,或只改变另一个算法的“窗口”,但需要考虑数据包丢失和“解压缩性”。但是采用这种方法会增加更多复杂性。有关如何帮助压缩器的更多想法可在SO: 如何在处理给定数据集时为zlib 'setDictionary'找到良好/最优字典?中找到。
此外,协议应该有一个简单的“零压缩”回退,因为一些[特别是小随机]数据可能无法实际压缩或可能“压缩”为较大的大小(zlib 实际上具有此保护,但也具有“包装器开销”,因此对于非常小的数据最好单独编码)。对于这样小于~100个字符的字符串数据,还需要考虑压缩数据的“包装器”开销。
另一个要考虑的问题是用于将字符填入输出流的编码方式。我首先会选择UTF-8,但这并不总是理想的。
请参见SO: 短文本字符串的最佳压缩算法,其中建议使用SMAZ,但我不知道该算法如何转换为Unicode / 二进制。

同时需要考虑到并非所有的deflate(或其他格式)实现都是相同的。我不清楚Java标准的deflate与第三方工具(比如JZlib)在处理小数据方面的效率差别,但请参考压缩小负载[.NET]这篇文章,其中显示“相同压缩”格式的结果非常糟糕。这篇文章也有一个很好的结论:

......通常来说最好还是进行压缩,并确定哪个有效负载(压缩或未压缩的)具有最小的大小,并包含一个小记号以指示是否需要解压缩。

我的最终结论:始终使用真实数据进行测试并衡量好处,否则你最终可能会有点惊喜!

愉快的编码。这次可是真的。


压缩小负载 [.NET],“链接失效。” - John x

6

最简单的方法是在ByteArrayOutputStream上使用GZIPOutputStream进行分层,因为它已经内置于JDK中,使用方式如下:

ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream zos = new GZIPOutputStream(baos);

zos.write(someText.getBytes());
zos.finish();
zos.flush();


byte[] udpBuffer = baos.toByteArray();

可能有其他算法做得更好,但我建议先尝试这个,看看它是否符合您的需求,因为它不需要任何额外的jar包,并且表现相当不错。


1
如果使用DeflaterOutputStream,使用+1。Zip仅将开销添加到压缩协议中,这对于如此小的数据可能会很重要。 - user166390
+1 对于基本代码示例。但是应该使用DeflaterOutputStreamGZipOutputStream中的任何一个。 - Lawrence Dol
转用建议的GZIPOutputStream进行更改。 - MeBigFatGuy
1
+1 :-) DeflaterOutputStream 的开销应该仍然少一点。 - user166390

5
大多数标准压缩算法在处理少量数据时效果不佳。通常会有一个标题和校验和,需要时间来进行压缩,即基于已见数据构建数据字典。
因此,您可能会发现:
- 小数据包未经压缩大小相同或更小。 - 简单的应用程序/协议特定压缩更好。 - 您必须提供预先构建的数据字典给压缩算法,并尽可能剥离标题。
对于小数据包,我通常选择第二个选项。

1

对于短字符串/ URL 的良好压缩算法是 LZW 实现,它在 Java 中,并且可以轻松移植到客户端 GWT: https://code.google.com/p/lzwj/source/browse/src/main/java/by/dev/madhead/lzwj/compress/LZW.java

一些备注

  • 对于小字符串使用 9 位码字长度(尽管您可以尝试哪个更好)。原始比率为从 1(非常小的字符串,压缩后不大于原始字符串)到 0.5(较大的字符串)
  • 对于其他码字长度的客户端 GWT,需要调整输入/输出处理以按字节为基础工作,以避免将位序列缓冲到长型中时出现错误,这是为 JS 模拟的。

我正在使用它来对客户端 GWT 中的复杂 URL 参数进行编码,以及与 Base64 编码和 Autobean 序列化为 JSON 一起使用。

更新:base64 实现在这里:http://www.source-code.biz/base64coder/java 你需要将其改为 url 安全的,即更改以下字符:

  • '+' -> '-'
  • '/' -> '~'
  • '=' -> '_'

  • 失效的链接。新链接:https://github.com/madhead/lzwj/blob/master/src/main/java/by/dev/madhead/lzwj/compress/LZW.java - Chris

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