闲谈数据压缩

图片 3

本文将会对常用的几个压缩算法的性能作一下比较。结果表明,某些算法在极端苛刻的CPU限制下仍能正常工作。

1.数据压缩算法你用过几种?
之前知道压缩有文件类ZIP,GZIP,图片类,质量压缩,采样率压缩等,当然图片也是文件的一种,类似于采样率压缩只适合图片类;
这些说出来后,估计大牛们会吐槽:“真low,就知道这点,市面上有好多开源的,分分钟给你写个。。。。”。
确实市面上开源的或已集成近个语言开发的sdk中有很多。。。。
简要统计了下有如下一些:
ZLIB、GZIP、BZIP、snappy、LZO、LZF、FastLZ和QuickLZ等等
那么他们有什么不同呢?
ZLIB,GZIP采用DEFLATE压缩,那DEFLATE又是什么呢
DEFLATE是同时使用了LZ77算法与哈夫曼编码(Huffman
Coding)的一个无损数据压缩算法;
jdk中对zlib压缩库提供了支持,压缩类Deflater和解压类Inflater,Deflater和Inflater都提供了native方法

atitit.压缩算法 ZLib ,gzip ,zip   最佳实践  java .net php

文中进行比较的算有:

private native int deflateBytes(long addr, byte[] b, int off, int len,
int flush);
private native int inflateBytes(long addr, byte[] b, int off, int len)
throws DataFormatException;
所有可以直接使用jdk提供的压缩类Deflater和解压类Inflater,代码如下:
 public static byte[] compress(byte input[]) {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        Deflater compressor = new Deflater(1);
        try {
            compressor.setInput(input);
            compressor.finish();
            final byte[] buf = new byte[2048];
            while (!compressor.finished()) {
                int count = compressor.deflate(buf);
                bos.write(buf, 0, count);
            }
        } finally {
            compressor.end();
        }
        return bos.toByteArray();
    }

      public static byte[] uncompress(byte[] input) throws DataFormatException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        Inflater decompressor = new Inflater();
        try {
            decompressor.setInput(input);
            final byte[] buf = new byte[2048];
            while (!decompressor.finished()) {
                int count = decompressor.inflate(buf);
                bos.write(buf, 0, count);
            }
        } finally {
            decompressor.end();
        }
        return bos.toByteArray();
    }

 

  • JDK GZIP
    ——这是一个压缩比高的慢速算法,压缩后的数据适合长期使用。JDK中的java.util.zip.GZIPInputStream
    / GZIPOutputStream便是这个算法的实现。
  • JDK deflate
    ——这是JDK中的又一个算法(zip文件用的就是这一算法)。它与gzip的不同之处在于,你可以指定算法的压缩级别,这样你可以在压缩时间和输出文件大小上进行平衡。可选的级别有0(不压缩),以及1(快速压缩)到9(慢速压缩)。它的实现是java.util.zip.DeflaterOutputStream
    / InflaterInputStream。
  • LZ4压缩算法的Java实现——这是本文介绍的算法中压缩速度最快的一个,与最快速的deflate相比,它的压缩的结果要略微差一点。如果想搞清楚它的工作原理,我建议你读一下这篇文章。它是基于友好的Apache
    2.0许可证发布的。
  • Snappy——这是Google开发的一个非常流行的压缩算法,它旨在提供速度与压缩比都相对较优的压缩算法。我用来测试的是这个实现。它也是遵循Apache
    2.0许可证发布的。

可以指定算法的压缩级别,这样你可以在压缩时间和输出文件大小上进行平衡。可选的级别有0(不压缩),以及1(快速压缩)到9(慢速压缩),这里使用的是以速度为优先。
GZIP只是在deflate格式上增加了文件头和文件尾,同样jdk也对gzip提供了支持,分别是GZIPOutputStream和GZIPInputStream类,同样可以发现GZIPOutputStream是继承于DeflaterOutputStream的,GZIPInputStream继承于InflaterInputStream,并且可以在源码中发现writeHeader和writeTrailer方法:

1. 压缩算法的归类::: 纯算法,带归档算法
1

压缩测试

要找出哪些既适合进行数据压缩测试又存在于大多数Java开发人员的电脑中(我可不希望你为了运行这个测试还得个几百兆的文件)的文件也着实费了我不少工夫。最后我想到,大多数人应该都会在本地安装有JDK的文档。因此我决定将javadoc的目录整个合并成一个文件——拼接所有文件。这个通过tar命令可以很容易完成,但并非所有人都是Linux用户,因此我写了个程序来生成这个文件:

public class InputGenerator {
    private static final String JAVADOC_PATH = "your_path_to_JDK/docs";
    public static final File FILE_PATH = new File( "your_output_file_path" );

    static
    {
        try {
            if ( !FILE_PATH.exists() )
                makeJavadocFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void makeJavadocFile() throws IOException {
        try( OutputStream os = new BufferedOutputStream( new FileOutputStream( FILE_PATH ), 65536 ) )
        {
            appendDir(os, new File( JAVADOC_PATH ));
        }
        System.out.println( "Javadoc file created" );
    }

    private static void appendDir( final OutputStream os, final File root ) throws IOException {
        for ( File f : root.listFiles() )
        {
            if ( f.isDirectory() )
                appendDir( os, f );
            else
                Files.copy(f.toPath(), os);
        }
    }
}

在我的机器上整个文件的大小是354,509,602字节(338MB)。

private void writeHeader() throws IOException {
     ......
}
private void writeTrailer(byte[] buf, int offset) throws IOException {
     ......
}

2. zlib(适合字符串压缩)
1

测试

一开始我想把整个文件读进内存里,然后再进行压缩。不过结果表明这么做的话即便是4G的机器上也很容易把堆内存空间耗尽。

于是我决定使用操作系统的文件缓存。这里我们用的测试框架是JMH。这个文件在预热阶段会被操作系统加载到缓存中(在预热阶段会先压缩两次)。我会将内容压缩到ByteArrayOutputStream流中(我知道这并不是最快的方法,但是对于各个测试而言它的性能是比较稳定的,并且不需要花费时间将压缩后的数据写入到磁盘里),因此还需要一些内存空间来存储这个输出结果。

下面是测试类的基类。所有的测试不同的地方都只在于压缩的输出流的实现不同,因此可以复用这个测试基类,只需从StreamFactory实现中生成一个流就好了:

@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Thread)
@Fork(1)
@Warmup(iterations = 2)
@Measurement(iterations = 3)
@BenchmarkMode(Mode.SingleShotTime)
public class TestParent {
    protected Path m_inputFile;

    @Setup
    public void setup()
    {
        m_inputFile = InputGenerator.FILE_PATH.toPath();
    }

    interface StreamFactory
    {
        public OutputStream getStream( final OutputStream underlyingStream ) throws IOException;
    }

    public int baseBenchmark( final StreamFactory factory ) throws IOException
    {
        try ( ByteArrayOutputStream bos = new ByteArrayOutputStream((int) m_inputFile.toFile().length());
              OutputStream os = factory.getStream( bos ) )
        {
            Files.copy(m_inputFile, os);
            os.flush();
            return bos.size();
        }
    }
}

这些测试用例都非常相似(在文末有它们的源代码),这里只列出了其中的一个例子——JDK
deflate的测试类;

public class JdkDeflateTest extends TestParent {
    @Param({"1", "2", "3", "4", "5", "6", "7", "8", "9"})
    public int m_lvl;

    @Benchmark
    public int deflate() throws IOException
    {
        return baseBenchmark(new StreamFactory() {
            @Override
            public OutputStream getStream(OutputStream underlyingStream) throws IOException {
                final Deflater deflater = new Deflater( m_lvl, true );
                return new DeflaterOutputStream( underlyingStream, deflater, 512 );
            }
        });
    }
}

bzip2是Julian
Seward开发并按照自由软件/开源软件协议发布的数据压缩算法及程序。Seward在1996年7月第一次公开发布了bzip2
0.15版,在随后几年中这个压缩工具稳定性得到改善并且日渐流行,Seward在2000年晚些时候发布了1.0版。
bzip2比传统的gzip的压缩效率更高,但是它的压缩速度较慢。
jdk中没有对bzip2实现,不过在commons-compress中进行了实现,maven引入:

3. gzip( 适合单个的文件)
1

测试结果

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-compress</artifactId>
    <version>1.12</version>
</dependency>

4. zip 2

输出文件的大小

首先我们来看下输出文件的大小:

||实现||文件大小(字节)|| ||GZIP||64,200,201|| ||Snappy
(normal)||138,250,196|| ||Snappy (framed)|| 101,470,113|| ||LZ4 (fast)||
98,316,501|| ||LZ4 (high) ||82,076,909|| ||Deflate (lvl=1)
||78,369,711|| ||Deflate (lvl=2) ||75,261,711|| ||Deflate (lvl=3)
||73,240,781|| ||Deflate (lvl=4) ||68,090,059|| ||Deflate (lvl=5)
||65,699,810|| ||Deflate (lvl=6) ||64,200,191|| ||Deflate (lvl=7)
||64,013,638|| ||Deflate (lvl=8) ||63,845,758|| ||Deflate (lvl=9)
||63,839,200||

图片 1

可以看出文件的大小相差悬殊(从60Mb到131Mb)。我们再来看下不同的压缩方法需要的时间是多少。

未完待续,,,,

5. java jdk 给zlib,gzip,zip的支持
2

压缩时间

||实现||压缩时间(ms)|| ||Snappy.framedOutput ||2264.700||
||Snappy.normalOutput ||2201.120|| ||Lz4.testFastNative ||1056.326||
||Lz4.testFastUnsafe ||1346.835|| ||Lz4.testFastSafe ||1917.929||
||Lz4.testHighNative ||7489.958|| ||Lz4.testHighUnsafe ||10306.973||
||Lz4.testHighSafe ||14413.622|| ||deflate (lvl=1) ||4522.644||
||deflate (lvl=2) ||4726.477|| ||deflate (lvl=3) ||5081.934|| ||deflate
(lvl=4) ||6739.450|| ||deflate (lvl=5) ||7896.572|| ||deflate (lvl=6)
||9783.701|| ||deflate (lvl=7) ||10731.761|| ||deflate (lvl=8)
||14760.361|| ||deflate (lvl=9) ||14878.364|| ||GZIP ||10351.887||

图片 2

我们再将压缩时间和文件大小合并到一个表中来统计下算法的吞吐量,看看能得出什么结论。

6. zlib—gzip 压缩在后长度比较
2

吞吐量及效率

||实现||时间(ms)||未压缩文件大小||吞吐量(Mb/秒)||压缩后文件大小(Mb)||
||Snappy.normalOutput ||2201.12 ||338 ||153.5581885586
||131.8454742432|| ||Snappy.framedOutput ||2264.7 ||338 ||149.2471409017
||96.7693328857|| ||Lz4.testFastNative ||1056.326 ||338 ||319.9769768045
||93.7557220459|| ||Lz4.testFastSafe ||1917.929 ||338 ||176.2317583185
||93.7557220459|| ||Lz4.testFastUnsafe ||1346.835 ||338 ||250.9587291688
||93.7557220459|| ||Lz4.testHighNative ||7489.958 ||338 ||45.1270888301
||78.2680511475|| ||Lz4.testHighSafe ||14413.622 ||338 ||23.4500391366
||78.2680511475|| ||Lz4.testHighUnsafe ||10306.973 ||338 ||32.7933332124
||78.2680511475|| ||deflate (lvl=1) ||4522.644 ||338 ||74.7350443679
||74.7394561768|| ||deflate (lvl=2) ||4726.477 ||338 ||71.5120374012
||71.7735290527|| ||deflate (lvl=3) ||5081.934 ||338 ||66.5101120951
||69.8471069336|| ||deflate (lvl=4) ||6739.45 ||338 ||50.1524605124
||64.9452209473|| ||deflate (lvl=5) ||7896.572 ||338 ||42.8033835442
||62.6564025879|| ||deflate (lvl=6) ||9783.701 ||338 ||34.5472536415
||61.2258911133|| ||deflate (lvl=7) ||10731.761 ||338 ||31.4952969974
||61.0446929932|| ||deflate (lvl=8) ||14760.361 ||338 ||22.8991689295
||60.8825683594|| ||deflate (lvl=9) ||14878.364 ||338 ||22.7175514727
||60.8730316162|| ||GZIP ||10351.887 ||338 ||32.651051929
||61.2258911133||

图片 3

可以看到,其中大多数实现的效率是非常低的:在Xeon
E5-2650处理器上,高级别的deflate大约是23Mb/秒,即使是GZIP也就只有33Mb/秒,这大概很难令人满意。同时,最快的defalte算法大概能到75Mb/秒,Snappy是150Mb/秒,而LZ4(快速,JNI实现)能达到难以置信的320Mb/秒!

从表中可以清晰地看出目前有两种实现比较处于劣势:Snappy要慢于LZ4(快速压缩),并且压缩后的文件要更大。相反,LZ4(高压缩比)要慢于级别1到4的deflate,而输出文件的大小即便和级别1的deflate相比也要大上不少。

因此如果需要进行“实时压缩”的话我肯定会在LZ4(快速)的JNI实现或者是级别1的deflate中进行选择。当然如果你的公司不允许使用第三方库的话你也只能使用deflate了。你还要综合考虑有多少空闲的CPU资源以及压缩后的数据要存储到哪里。比方说,如果你要将压缩后的数据存储到HDD的话,那么上述100Mb/秒的性能对你而言是毫无帮助的(假设你的文件足够大的话)——HDD的速度会成为瓶颈。同样的文件如果输出到SSD硬盘的话——即便是LZ4在它面前也显得太慢了。如果你是要先压缩数据再发送到网络上的话,最好选择LZ4,因为deflate75Mb/秒的压缩性能跟网络125Mb/秒的吞吐量相比真是小巫见大巫了(当然,我知道网络流量还有包头,不过即使算上了它这个差距也是相当可观的)。

7. 别的bzip,,tar
2

总结

  • 如果你认为数据压缩非常慢的话,可以考虑下LZ4(快速)实现,它进行文本压缩能达到大约320Mb/秒的速度——这样的压缩速度对大多数应用而言应该都感知不到。
  • 如果你受限于无法使用第三方库或者只希望有一个稍微好一点的压缩方案的话,可以考虑下使用JDK
    deflate(lvl=1)进行编解码——同样的文件它的压缩速度能达到75Mb/秒。

8. 参考 3

源代码

Java压缩测试源码

 

1. 压缩算法的归类::: 纯算法,带归档算法

 

ZIP、RAR等归档算法

ZLib可以简单的理解为压缩/解压缩算法,它与ZIP、RAR等归档算法有所不同, 

 

2. zlib(适合字符串压缩)

zlib是一个通用的压缩开源库,提供了在内存中压缩和解压的函数,包括对解压后数据的校验。目前版本的zlib只支持deflate方法,    它的设计目标是处理单纯的数据(而不管数据的来源是什么)

 

作者::老哇的爪子Attilax艾龙,EMAIL:1466519819@qq.com

转载请注明来源: 

 

3.   gzip( 适合单个的文件)

  gzip也是一种数据压缩格式,可以大体分为头部,数据部和尾部三个部分,其中头部和尾部主要是一些文档属性和校验信息(rfc1952),数据部主要是用deflate方法压缩得到的数据。

gzip是一种文件压缩工具(或该压缩工具产生的压缩文件格式),它的设计目标是处理单个的文件。gzip在压缩文件中的数据时使用的就是zlib。为了 保存与文件属性有关的信息,gzip需要在压缩文件(*.gz)中保存更多的头信息内容,而zlib不用考虑这一点。但gzip只适用于单个文件,所以我 们在UNIX/Linux上经常看到的压缩包后缀都是*.tar.gz或*.tgz,也就是先用tar把多个文件打包成单个文件,再用gzip压缩的结 果。

 

4. zip

zip是适用于压缩多个文件的格式(相应的工具有PkZip和WinZip等),因此,zip文件还要进一步包含文件目录结构的信息,比gzip的头信息 更多。但需要注意,zip格式可采用多种压缩算法,我们常见的zip文件大多不是用zlib的算法压缩的,其压缩数据的格式与gzip大不一样。

 

 

5. java jdk 给zlib,gzip,zip的支持

 

Java SDK提供了对上述三种压缩技术的支持:Inflater类和Deflater类直接用zlib库对数据压缩/解压缩,GZIPInputStream类 和GZIPOutputStream类提供了对gzip格式的支持,ZipFile、ZipInputStream、ZipOutputStream则用 于处理zip格式的文件

6. zlib—gzip 压缩在后长度比较

gzip>>>zlib

244>>>214  

 

7. 别的bzip,,tar

GZIP是JDK自带的算法实现,但BZip2则不曾享受这个待遇。 不过,强大的Apache坚决不会让这些个在Linux下如鱼得水的算法在Java世界中销声匿迹。Apache在Commons Compress中提供了相应的实现。同时,还包括众所周知的tar、cpio、zip等算法实现,其中最为丰富的当属zip

 

在linux下,tar是一个归档命令。当然,如果配合gzip、bzip2就可以达到归档+压缩的效果! 
我们通过tar获得归档压缩文件其实恰恰包含了归档和压缩两个操作,并且其操作次序也是先做归档操作,再做压缩操作! 通常我们忽略了归档的概念,将归档压缩文件简称为压缩文件!~ 

Java压缩技术(七) TAR——Commons实现

8. 参考

Java压缩技术(七) TAR——Commons实现 – Snowolf的意境空间! – ITeye技术网站.htm

有关压缩的一些东西 – wyingquan的专栏 – 博客频道 – CSDN.NET.htm

图片 4

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图