`
tomcat_oracle
  • 浏览: 316939 次
社区版块
存档分类
最新评论

Java性能调优指南—-java.io.BufferedInputStream和java.util.zip.GZIPInputStream ...

    博客分类:
  • Java
阅读更多

BufferedInputStreamGZIPInputStream是在读取文件数据中经常使用到的两个类(至少后者在Linux系统中被广泛使用)。一般来说,缓冲输入数据是一种很好的想法,这在许多关于Java性能的书籍中都有描述。对于这些流,仍然有许多问题值得我们了解。

 

何时不需要缓冲

缓冲是用来减少来自输入设备的单独读取操作数的数量,许多开发者往往忽视这一点,并经常将InputStream包含进BufferedInputStream中,如

1
final InputStream is = new BufferedInputStream( new FileInputStream( file ) );

是否使用缓冲的简略规则如下:当你的数据块足够大的时候(100K+),你不需要使用缓冲,你可以处理任何长度的块(不需要保证在缓冲前缓冲区中至少有N bytes可用字节)。在所有的其他情况下,你都需要缓冲输入数据。最简单的不需要缓冲的例子就是手动复制文件的过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void copyFile( final File from, final File to ) throws IOException {
    final InputStream is = new FileInputStream( from );
    try
    {
        final OutputStream os = new FileOutputStream( to );
        try
        {
            final byte[] buf = new byte[ 8192 ];
            int read = 0;
            while ( ( read = is.read( buf ) ) != -1 )
            {
                os.write( buf, 0, read );
            }
        }
        finally {
            os.close();
        }
    }
    finally {
        is.close();
    }
}

注1:衡量文件复制的性能是非常困难的,因为这很大程度上收到操作系统写入缓存的影响。在我的机器上复制一个4.5G的文件到相同的硬盘所花费的时间在68至107s之间变化。

注2:文件复制经常通过Java NIO实现,使用FileChannel.transferTo或者transferFrom的方法。使用这些方法不需要再在内核和用户态之间频繁的转换(在用户的java程序中将读入数据转换为字节缓存,再通过内核调用将它复制回输出文件中)。相反它们在内核模式中传输尽可能多的数据(直达231-1字节),尽可能做到不返回用户的代码中。因此,Java NIO会使用较少的CPU周期,并腾出留给其他程序。然而,只有高负荷的环境下才能看到这当中的差异(在我的机器中,NIO模式的CPU总占用率为4%,而旧的流模式CPU总占用率则为8-9%)。以下是一个可能的Java NIO实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private static void copyFileNio( final File from, final File to ) throws IOException {
    final RandomAccessFile inFile = new RandomAccessFile( from, "r" );
    try
    {
        final RandomAccessFile outFile = new RandomAccessFile( to, "rw" );
        try
        {
            final FileChannel inChannel = inFile.getChannel();
            final FileChannel outChannel = outFile.getChannel();
            long pos = 0;
            long toCopy = inFile.length();
            while ( toCopy > 0 )
            {
                final long bytes = inChannel.transferTo( pos, toCopy, outChannel );
                pos += bytes;
                toCopy -= bytes;
            }
        }
        finally {
            outFile.close();
        }
    }
    finally {
        inFile.close();
    }
}

 

缓冲大小

BufferedInputStream中默认的缓冲大小是8192个字节。缓冲大小实际上是从输入设备中准备读取的块的平均大小。这就是为什么它经常值得精确地提高至64K(65536), 万一有非常大的输入文件 — 那些在512K和2M的文件,为了更深一层地减少磁盘读入的数量。许多专家也建议将此值设置为4096的整数倍 — 一个普通磁盘扇区的大小。所以,不要将缓冲区的大小设置为,像125000这样的大小,取而代之的应该是像131072(128K)这样的大小。

java.util.zip.GZIPInputStream 是一个能够很好处理gzip文件输入的输入流。它经常被用来做这样的事情:

1
final InputStream is = new GZIPInputStream( new BufferedInputStream( new FileInputStream( file ) ) );

这样的初始化已经足够好了,不过BufferedInputStream在此处是多余的,因为GZIPInputStream已经拥有了自己的内建缓冲区,它里面有一个字节缓冲区(实际上,它是InflaterInputStream的成员),被用做此从底层流中读取压缩的数据,并且将其传递给一个inflater。这个缓冲区默认的大小是512字节,所以它必须被设置成一个更高的数值。一个更理想地使用GZIPInputStream的方式如下:

1
final InputStream is = new GZIPInputStream( new FileInputStream( file ), 65536 );

 

BufferedInputStream.available

BufferedInputStream.available 方法有一个可能的性能问题,取决于你真正想要接收到的东西。它的标准实现将返回BufferedInputStream自身的内部缓冲区可用字节数和底层输入流调用avalibale()结果的总和。所以,它将尽可能地返回一个精确的数值。但是在很多案例中,用户想知道的仅仅是缓冲区中是否还有空间可用( available() > 0). 在这种情况下,即使BufferedInputStream的缓冲区中只有一个字节余留,我们都不需要去查询底层的输入流。这显得非常重要,如果我们有一个FileInputStream包含在BufferedInputStream中–这样的优化会节省我们在FileInputStream.available()中的磁盘访问时间。

幸运的是,我们可以简单地解决这样的问题。BufferedInputStream不是一个final类,所以我们可以继承并且重载available方法。我们可以看看JDK的源代码着手准备。从这里我们还可以发现Java 6中的实现有一个bug — 如果BufferedInputStream可用的字节数和底层流available()调用结果的总和大于Integer.MaxVALUE,这样就会因为溢出而返回一个负数结果,不过这在Java 7中已经得到了解决。

以下是我们改进的实现,它将返回BufferedInputStream的内置缓冲区中可用的字节数,又或者是,如果它里面没有剩余的字节数,底层流中的available()方法会被调用,并且返回调用后的结果。在大多数情况下,这种实现会极少调用到底层流中的available()方法,因为当BufferedInputStream缓冲区被读取到最后,这个类会读取从底层流中读取更多的数据,所以,我们只会在输入文件的末尾中调用底层流的available()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class FasterBufferedInputStream extends BufferedInputStream
{
    public FasterBufferedInputStream(InputStream in, int size) {
        super(in, size);
    }
    //如果有东西可用,该方法将返回一个正数,否则它会返回0。
    public int available() throws IOException {
        if (in == null)
            throw new IOException( "Stream closed" );
        final int n = count - pos;
        return n > 0 ? n : in.available();
    }
}

为了测试这个实现,我尝试使用标准版的和改进版的BufferedInputStream去读取4.5G的文件,它们都有64K的缓冲区大小,并且每读取512或者1024字节的时候就调用一次available()方法。干净的测试需要操作系统在每一次测试之后重启以清除磁盘缓存。于是我决定在热身阶段读取文件,当文件已经在磁盘缓存时就用两种方法测试性能。测试显示,标准类的运行时间与available()调用的数量呈线性关系。而改进的方法运行时间看起来却与调用的次数无关。

  standard, once per 512 bytes improved, once per 512 bytes standard, once per 1024 bytes improved, once per 1024 bytes
Java 6 17.062 sec 2.11 sec 9.592 sec 2.047 sec
Java 7 17.337 sec 2.125 sec 9.748 sec 2.044 sec

这里是测试的源代码:

1
2
3
4
5
6
7
8
9
10
11
private static void testRead( final InputStream is ) throws IOException {
    final long start = System.currentTimeMillis();
    final byte[] buf = new byte[ 512 ];   //or 1024 bytes
    while ( true )
    {
        if ( is.available() == 0 ) break;
        is.read( buf );
    }
    final long time = System.currentTimeMillis() - start;
    System.out.println( "Impl: " + is.getClass().getCanonicalName() + " time = " + time / 1000.0 + " sec");
}
1
2
3
4
使用以下的声明变量调用以上方法:
final InputStream is1 = new BufferedInputStream( new FileInputStream( file ), 65536 );
and
final InputStream is2 = new FasterBufferedInputStream( new FileInputStream( file ), 65536 );

 

总结

  • BufferedInputStream和GZIPInputStream 都有内建的缓冲区。前者默认的缓冲大小是8192字节,后者则为512字节。一般而言,它值得增加任何它们的整数倍大小到至少65536。
  • 不要使用BufferedInputStream作为GZIPInputStream的输入,相反,显示地在构造器中设置GZIPInputStream的缓存大小。虽然,保持BufferedInputStream仍然是安全的。
  • 如果你有一个new BufferedInputStream( new FileInputStream( file ) )对象,并且需要频繁地调用它的available方法(例如,每输入一次信息都需要调用一次或者两次),考虑重载 BufferedInputStream.available方法,它将极大地提高文件读取的速度。
13
0
分享到:
评论
3 楼 cwqcwqmax9 2014-02-17  
xc_wangwang 写道
存个普通文件真犯不着用NIO,标准IO就可以了

还有 就是标准 io 内部实现不是在 jdk1.4后就已经用NIO改造过了吗?
2 楼 cwqcwqmax9 2014-02-17  
缓冲是用来减少来自输入设备的单独读取操作数的数量,许多开发者往往忽视这一点,并经常将InputStream包含进BufferedInputStream中,如



1


final InputStream is = new BufferedInputStream( new FileInputStream( file ) );


上面这句话  和  这个例子没懂,请作者分析一下  @tomcat_oracle
1 楼 xc_wangwang 2014-02-17  
存个普通文件真犯不着用NIO,标准IO就可以了

相关推荐

    基于Java的用GZIP压缩解压文件.zip

    首先,我们要了解Java中的`java.util.zip.GZIPOutputStream`和`java.util.zip.GZIPInputStream`类,这两个类是Java标准库提供的用于GZIP压缩和解压缩的核心工具。`GZIPOutputStream`用于写入压缩数据,而`...

    java_gzip.rar_java GZ_java gzip

    在Java中,我们可以使用`java.util.zip.GZIPInputStream`来解压缩GZIP格式的数据。以下是一个简单的解压缩示例: ```java import java.io.*; import java.util.zip.*; public class GZipDecompressor { public ...

    java_gzip.rar_Compression_gzip_j2me gz_java压缩

    GZIP是一种广泛使用的数据压缩格式,其在Java中的实现基于Java IO和Java util.zip库。本篇将详细介绍Java中如何利用GZIP进行数据压缩和解压缩,并结合`java_gzip.txt`和`www.pudn.com.txt`这两个文件来展示实际操作...

    Java用GZIP压缩解压文件.7z

    首先,我们要导入相关的Java.IO和Java.util.zip库,它们提供了对GZIP文件格式的支持: ```java import java.io.*; import java.util.zip.*; ``` **一、文件压缩** 1. **创建GZIPOutputStream**: 使用`...

    JAVA100例之实例40 压缩和解压文件

    我们将探讨使用Java的内置库`java.util.zip`来执行Gzip和Zip两种常见的压缩格式。 首先,让我们讨论如何压缩文件。在Java中,我们可以使用`GZIPOutputStream`类来实现Gzip压缩,而`ZipOutputStream`则用于创建Zip...

    Java压缩和解压

    在Java中,处理这两种操作的核心类包括`ZipOutputStream`和`ZipInputStream`用于处理ZIP格式,`GZIPOutputStream`和`GZIPInputStream`用于处理GZ格式。 **ZIP文件操作** 1. **压缩文件**:使用`ZipOutputStream`,...

    Java用GZIP压缩解压文件.rar

    总结,Java通过`java.util.zip`包提供的GZIPOutputStream和GZIPInputStream类,使得开发者能够方便地对文件进行GZIP压缩和解压缩。在实际项目中,根据具体需求选择合适的压缩和解压缩策略,可以有效提高存储和网络...

    JAVA文件压缩与解压缩实践(源代码+论文).rar

    对于ZIP和GZIP文件的解压缩,Java提供了对应的输入流类:`ZipInputStream`和`GZIPInputStream`。解压过程就是读取压缩流,然后将内容写入目标文件。例如,要解压ZIP或GZIP格式的"a.txt",我们创建相应的输入流,...

    JAVA文件压缩与解压缩实践(源代码+论文)

    1. **Java压缩库**:Java标准库提供了`java.util.zip`包,它包含了多种压缩和解压缩的类,如`ZipOutputStream`、`ZipInputStream`、`GZIPOutputStream`和`GZIPInputStream`,用于处理ZIP和GZIP格式的文件。...

    Java用GZIP压缩解压文件

    首先,我们需要了解Java中的`java.util.zip`包,这个包提供了对ZIP和GZIP格式的支持。在GZIP操作中,我们主要会用到`GZIPOutputStream`和`GZIPInputStream`这两个类,它们分别用于文件的压缩和解压缩。 **文件压缩*...

    毕业设计项目开发-JAVA文件压缩与解压缩实践(源代码+论文).zip

    在Java中,文件压缩通常使用`java.util.zip`包中的`ZipOutputStream`类来实现。它允许我们创建ZIP格式的压缩文件,将多个文件或目录打包在一起。通过`FileInputStream`读取待压缩文件,并将其写入`ZipOutputStream`...

    文件压缩,解压(java实现)

    Java中的`java.util.zip`包提供了对ZIP和GZIPOutputStream类,可以用来创建和写入压缩文件。例如,使用GZIPOutputStream可以将一个文件压缩为.GZ格式: ```java import java.io.*; import java.util.zip.*; public...

    JAVA实现解压缩源码

    `java.io`包提供了各种输入/输出流,如FileInputStream和FileOutputStream用于读写文件,而BufferedInputStream和BufferedOutputStream则提高了流的效率。解压缩源码会用到这些类来读取和写入文件内容。 2. **...

    JAVA 文件压缩及加密

    在Java编程语言中,文件压缩和加密是两个重要的概念,它们在数据存储、传输和安全保护方面发挥着关键作用。...在实际应用中,需要根据具体需求选择合适的加密算法和压缩方式,并考虑错误处理和性能优化。

    java压缩和解压缩文件

    Java编程语言提供了丰富的库来处理文件的压缩和解压缩任务,这主要归功于Java标准库中的`java.util.zip`包。在这个包中,我们有`ZipOutputStream`和`ZipInputStream`类用于创建和读取ZIP文件,以及`GZIPOutputStream...

    java实现的文件解压和压缩,没有中文编码问题

    Java标准库提供了`java.util.zip`包,它包含了处理ZIP和GZIPTar等压缩格式的类。`ZipOutputStream`和`ZipInputStream`用于处理ZIP文件,而`GZIPOutputStream`和`GZIPInputStream`则用于GZIP压缩。 2. **编码处理**...

    java实现的文件管理系统

    6. **文件压缩和解压**:Java的标准库并没有内置的Zip或RAR压缩/解压功能,但可以借助第三方库,如Apache Commons Compress或Java.util.zip包。ZipOutputStream和ZipInputStream用于创建和读取ZIP文件,...

    使用GZip解压文件

    在Java中,我们可以使用java.util.zip.GZIPInputStream和java.util.zip.GZIPOutputStream类来处理GZip压缩的文件和数据流。下面是一个简单的Java示例,演示如何使用GZip解压一个名为"UnGzipFile"的压缩文件: ```...

Global site tag (gtag.js) - Google Analytics