原文:http://www.javaworld.com/article/2077523/build-ci-sdlc/java-tip-26--how-to-improve-java-s-i-o-performance.html

JDK 1.0.2 的 java.io 包暴露了非常多I/O性能问题。这里将介绍一个优化方案,附加一个关闭同步的方法。

Java的I/O性能以前是非常多Java应用的瓶颈。主要原因就是JDK1.0.2的java.io包的不良设计和实现。关键问题是缓冲。绝大多数java.io中的类都未做缓冲。其实,仅仅有BufferedInputStream 和 BufferedOutputStream两个类做了缓冲。但他们提供的方法有限。比如。在大多数涉及文件操作的应用中,你须要逐行解析一个文件。但是唯一提供了readLine方法的类是DataInputStream。但是它却没有内部缓冲。DataInputStream的readLine方法其实是从输入流中逐个读取字符直到遇到
“n” 或 “rn”字符。每一个读取字符操作都涉及到一次文件I/O。这在读取一个大文件时是极其低效的。没有缓冲的情况下一个5兆字节的文件就须要至少5百万次读取字符的文件I/O操作。

新版本号JDK1.1通过添加一套Reader、Writer类改进了I/O性能。在大文件读取中BufferedReader的readLine方法至少比曾经的DataInputStream快10到20倍。

不幸的是,JDK1.1没有解决所有的性能问题。比方,当你想解析一个大文件可是又不希望所有读到内存中时,须要使用到RandomAccessFile类。可是在JDK1.1里它也没有做缓冲,也没有提供其它类似的Reader类。

How to tackle the I/O problem

To tackle the problem of inefficient file I/O, we need a buffered RandomAccessFile class. A new class is derived from the RandomAccessFile class, in order to reuse all the methods in it. The new class is named Braf(Bufferedrandomaccessfile).

怎样解决I/O难题?

解决低效的文件I/O,我们须要一个提供缓冲的RandomAccessFile类。

有一个类继承自RandomAccessFile,而且重用了RandomAccessFile中的全部方法,它就是Braf(Bufferedrandomaccessfile)。

 public class Braf extends RandomAccessFile {
}

出于效率原因,我们定义了一个字节缓冲区而不是字符缓冲区。使用buf_end、buf_pos和real_pos三个变量来记录缓冲区上实用的位置信息。

For efficiency reasons, we define a byte buffer instead of char buffer. The variables buf_end, buf_pos, and real_pos are used to record the effective positions on the buffer:



  byte buffer[];

  int buf_end = 0;

  int buf_pos = 0;

  long real_pos = 0;

添加了一个新的构造函数,里面多了一个指定缓冲区大小的參数:

A new constructor is added with an additional parameter to specify the size of the buffer:

public Braf(String filename, String mode, int bufsize)
throws IOException{
super(filename,mode);
invalidate();
BUF_SIZE = bufsize;
buffer = new byte[BUF_SIZE];
}

新写了一个read方法,它永远优先读取缓冲区。它覆盖了原来的read方法,在缓冲区读完时,会调用fillBuffer,它将调用父类的read方法读取字节,填充到缓冲区中。

私有函数invalidate被用来推断缓冲区中是否包括合法数据。它在seek方法被调用、文件指针可能被定位到缓冲区之外时是很有必要的。

The new read method is written such that it always reads from the buffer first. It overrides the native read method in the original class, which is never engaged until the buffer has run out of room. In that case, the fillBuffer method is called to fill
in the buffer. In fillBuffer, the original read is invoked. The private method invalidateis used to indicate that the buffer no longer contains valid contents. This is necessary when the seek method moves the file pointer out of the buffer.

public final int read() throws IOException{
if(buf_pos >= buf_end) {
if(fillBuffer() < 0)
return -1;
}
if(buf_end == 0) {
return -1;
} else {
return buffer[buf_pos++];
}
}
private int fillBuffer() throws IOException {
int n = super.read(buffer, 0, BUF_SIZE);
if(n >= 0) {
real_pos +=n;
buf_end = n;
buf_pos = 0;
}
return n;
}
private void invalidate() throws IOException {
buf_end = 0;
buf_pos = 0;
real_pos = super.getFilePointer();
}

还有一个參数化的读取方法也被重载,代码例如以下。假设缓冲足够的话。它就会调用System.arraycopy 方法直接从缓冲中拷贝一部分到用户区。这个也能显著提升性能。由于getNextLine方法中read()方法被大量使用,getNextLine也是readLine的替代品。

The other parameterized read method also is overridden. The code for the new read is listed below. If there is enough buffer, it will simply call System.arraycopy to copy a portion of the buffer directly into the user-provided area. This presents the most
significant performance gain because the read method is heavily used in the getNextLine method, which is our replacement for readLine.

public int read(byte b[], int off, int len) throws IOException {
int leftover = buf_end - buf_pos;
if(len <= leftover) {
System.arraycopy(buffer, buf_pos, b, off, len);
buf_pos += len;
return len;
}
for(int i = 0; i < len; i++) {
int c = this.read();
if(c != -1)
b[off+i] = (byte)c;
else {
return i;
}
}
return len;
}

原来的getFilePointer和seek方法也须要被重载来配合缓冲。大多数情况下。两个方法仅仅会简单的在缓冲中进行操作

The original methods getFilePointer and seek need to be overridden as well in order to take advantage of the buffer. Most of time, both methods will simply operate inside the buffer.

public long getFilePointer() throws IOException{
long l = real_pos;
return (l - buf_end + buf_pos) ;
}
public void seek(long pos) throws IOException {
int n = (int)(real_pos - pos);
if(n >= 0 && n <= buf_end) {
buf_pos = buf_end - n;
} else {
super.seek(pos);
invalidate();
}
}

最重要的。一个新的方法。getNextLine,被增加来替换readLine。

我们不能简单的重载readLine。由于它是final定义的。getNextLine方法首先须要确定buffer是否有未读数据。

假设没有,缓冲区须要被填满。

读取时假设遇到换行符,新的一行就从缓冲区中读出转换为String对象。否则,将继续调用read方法逐个读取字节。虽然后面部分的代码和原来的readLine非常像。可是由于read方法做了缓冲,它的性能也要优于曾经。

Most important, a new method, getNextLine, is added to replace the readLine method. We can not simply override the readLine method because it is defined as final in the original class. The getNextLine method first decides if the buffer still contains unread
contents. If it doesn't, the buffer needs to be filled up. If the new line delimiter can be found in the buffer, then a new line is read from the buffer and converted into String. Otherwise, it will simply call the read method to read byte by byte. Although
the code of the latter portion is similar to the original readLine, performance is better here because the read method is buffered in the new class

/**
* return a next line in String
*/
public final String getNextLine() throws IOException {
String str = null;
if(buf_end-buf_pos <= 0) {
if(fillBuffer() < 0) {
throw new IOException("error in filling buffer!");
}
}
int lineend = -1;
for(int i = buf_pos; i < buf_end; i++) {
if(buffer[i] == '\n') {
lineend = i;
break;
}
}
if(lineend < 0) {
StringBuffer input = new StringBuffer(256);
int c;
while (((c = read()) != -1) && (c != '\n')) {
input.append((char)c);
}
if ((c == -1) && (input.length() == 0)) {
return null;
}
return input.toString();
}
if(lineend > 0 && buffer[lineend-1] == '\r')
str = new String(buffer, 0, buf_pos, lineend - buf_pos -1);
else str = new String(buffer, 0, buf_pos, lineend - buf_pos);
buf_pos = lineend +1;
return str;
}

在Braf类的帮助下,我们在逐行读取大文件时至少能得到高过RandomAccessFile类25倍的性能提升。这个方法也应用在其它I/O操作密集的场景中。

关闭同步:额外的提示

除了I/O,还有一个拖累Java性能的因素是同步,大体上,同步方法的成本大约是普通方法的6倍。假设你在写一个没有多线程的应用,或者是一个应用中肯定仅仅会单线程执行的部分。你不须要做不论什么同步声明。

当前,Java还没有机制来关闭同步。

一个非正规的方法是拿到源代码,去掉同步声明然后创建一个新类。比如。BufferedInputStream中两个read方法都是同步的,由于其它I/O方法都依赖它们。你能够在JavaSoft的JDK 1.1中拷贝BufferedInputStream.java 源代码,创建一个新的NewBIS类,删掉同步声明,又一次编译

With the new Braf class, we have experienced at least 25 times performance improvement over RandomAccessFile when a large file needs to be parsed line by line. The method described here also applies to other places where intensive file I/O operations are
involved.

Synchronization turn-off: An extra tip

Another factor responsible for slowing down Java's performance, besides the I/O problem discussed above, is the synchronized statement. Generally, the overhead of a synchronized method is about 6 times that of a conventional method. If you are writing an application
without multithreading -- or a part of an application in which you know for sure that only one thread is involved -- you don't need anything to be synchronized. Currently, there is no mechanism in Java to turn off synchronization. A simple trick is to get
the source code of a class, remove synchronized statements, and generate a new class. For example, in BufferedInputStream, both read methods are synchronized, whereas all other I/O methods depend on them. You can simply rename the class to NewBIS,for example,
copy the source code from BufferedInputStream.java provided by JavaSoft's JDK 1.1, remove synchronized statements from NewBIS.java, and recompile NewBIS.

How to improve Java&#39;s I/O performance( 提升 java i/o 性能)的更多相关文章

  1. java打字游戏-一款快速提升java程序员打字速度的游戏(附源码)

    一.效果如图: 源码地址:https://gitee.com/hoosson/TYPER 纯干货,别忘了留个赞哦!

  2. java.io.IOException: Connection reset by peer at sun.nio.ch.FileDispatcherImpl.read0(Native Method) at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:39)

    报错: java.io.IOException: Connection reset by peer at sun.nio.ch.FileDispatcherImpl.read0(Native Meth ...

  3. 疯狂Java学习笔记(84)----------大约 Java 对象序列化,你不知道 5 事

    几年前,.当一个软件团队一起用 Java 书面申请.我认识比一般程序猿多知道一点关于 Java 对象序列化的知识所带来的优点. 关于本系列 您认为自己懂 Java 编程?其实,大多数程序猿对于 Jav ...

  4. XML概念定义以及如何定义xml文件编写约束条件java解析xml DTD XML Schema JAXP java xml解析 dom4j 解析 xpath dom sax

    本文主要涉及:xml概念描述,xml的约束文件,dtd,xsd文件的定义使用,如何在xml中引用xsd文件,如何使用java解析xml,解析xml方式dom sax,dom4j解析xml文件 XML来 ...

  5. What is the reason for - java.security.spec.InvalidKeySpecException: Unknown KeySpec type: java.security.spec.ECPublicKeySpec

    支付中心Project重构完成,经过本地测试,并未发现问题.发布到测试环境后,测试发现请求光大扫码https接口时,出现了如下的异常: javax.net.ssl.SSLException: Serv ...

  6. java面试题全集(上)--java基础

    本文转载自:https://blog.csdn.net/jackfrued/article/details/44921941 1.面向对象的特征有哪些方面? 答:面向对象的特征主要有以下几个方面: - ...

  7. java设计模式大全 Design pattern samples in Java(最经典最全的资料)

    java设计模式大全 Design pattern samples in Java(最经典最全的资料) 2015年06月19日 13:10:58 阅读数:11100 Design pattern sa ...

  8. Java Web学习总结(29)——Java Web中的Filter和Interceptor比较

    1. 背景 在设计web应用的时候,用户登录/注册是必不可少的功能,对用户登录信息进行验证的方法也是多种多样,大致可以认为如下模式:前端验证+后台验证.根据笔者的经验,一般会在前端进行一些例如是否输入 ...

  9. Java基础学习总结(48)——Java 文档注释

    Java只是三种注释方式.前两种分别是// 和/* */,第三种被称作说明注释,它以/** 开始,以 */结束. 说明注释允许你在程序中嵌入关于程序的信息.你可以使用javadoc工具软件来生成信息, ...

随机推荐

  1. B1965 [Ahoi2005]SHUFFLE 洗牌 数论

    这个题的规律很好找,就是奇数直接除二,偶数除二加n/2.把这个规律整理一下就是(x * 2) % (n + 1),然后就直接求逆元就行了.一直30分的原因是qpow函数传参的时候用的int,然而变量是 ...

  2. Spring Boot 版本支持对应JDK

    转自:http://www.cnblogs.com/oumi/p/9241424.html 一.Spring Boot 版本支持 Spring Boot Spring Framework Java M ...

  3. FluentScheduler定时器

    项目需要一个按时执行的任务,每隔几分钟执行一个,或者每隔几小时执行一次等等,这个时候就需要一个定时的功能,最简单的就是用Timer自己写一个,但是自己写的性能等各方面有可能不健全等等,而现在开源的库也 ...

  4. 树莓派-基于aria2实现离线下载

    安装aria2 aria2是linux下的一个下载工具,它支持http.bt种子.磁力链接三种方式下载 sudo apt-get install aria2 配置aria2 aria2支持命令参数,也 ...

  5. SQL server存储过程学习

    由于之前使用 Linq to Sql来操作数据库,对于数据库的存储过程.函数等比较薄弱.乘着自己闲着的时候,就百度自学了一点存储过程,以防以后要用. 基础通俗易懂的存储过程通过 存储过程学习 ,然后自 ...

  6. ubuntu16.04 下载 fabric

    1 Fabric源码下载 我们可以使用Git命令下载源码,也可以使用go get命令,偷懒一点,我们直接用go get命令获取最新的Fabric源码: go get github.com/hyperl ...

  7. mybatis 高级映射和spring整合之与Spring整合(6)

    mybatis 高级映射和spring整合之mybatis与Spring整合 3.0 mybatis和spring整合(掌握) 3.1 整合思路 需求spring通过单例方式管理SqlSessionF ...

  8. Python 之 基础知识(五)

    一.变量 1.引用 id() 函数传参 与 返回值 都是传递保存的数据的引用 2.可变和不可变类型(变量的引用地址只在赋值语句后变化) 不可变类型 内存中的数据不允许被修改 数字类型 int,bool ...

  9. 【Oracle】重置参数

    单实例中: alter system reset parameter <scope=memory|spfile|both>: --memory|spfile|both,选其一 集群环境中: ...

  10. ERROR 1840 (HY000) at line 24: @@GLOBAL.GTID_PURGED can only be set when @@GLOBA

    在MySQL 5.7版本中,备份迁移数据库的时候,还原时提示如下报错信息 ERROR 1840 (HY000) at line 24: @@GLOBAL.GTID_PURGED can only be ...