调整JavaTM 的I/O性能(一)(zt)

性能

调整JavaTM 的I/O性能
这篇文章讨论并举例阐述了提高JavaTM I/O性能的多种技术。绝大多数技术是围绕着磁盘文件I/O的调整来谈 的,但是,有些技术对网络I/O和视窗输出也同样适用。首先介绍的技术包含底层I/O问题,然后对诸如压 缩、格式化和序列化这样的高层I/O进行讨论。但是,请注意,本讨论不涉及应用设计问题, 搜索算法和数 据结构的选择,也不讨论类似文件高速缓存(file caching)这样的系统级问题。

当讨论Java I/O时,Java编程语言所假定的两种不同的磁盘文件组织是没有任何意义的。这两种磁盘文件组 织,一种基于字节流,另一种基于字符序列。在 Java语言中,一个字符使用两个字节表示,而不是象C语言 那样使用一个字节表示一个字符。正因为如此,从文件中读取字符时需要一些转换。在某些情况下,这样的 区别非常重要,我们将用几个例子对此进行说明。

底层I/O问题
简介
加速I/O的基本规则
缓冲
读/写文本文件
格式化的开销
随机存储

高层I/O问题
压缩
高速缓存
标志化(Tokenization)
序列化(Serialization)
获取文件信息
更多的信息
加速I/O的基本规则

作为开始讨论的一种方法,下面列出了加速I/O的一些基本规则:

1.避免访问磁盘

2.避免访问下面的操作系统

3.避免方法调用

4.避免对字节和字符的单独处理

显然,这些规则不能被全面而严格地应用,因为如果那样的话,I/O就不可能工作了。但是,为了查看规则是 如何被应用的,就考虑下面的三个例子,这些例子计算一个文件中换行符('\n')的数目。

方法一:读取的方法

第一个方法简单地利用一个文件输入流(FileInputStream)上的读方法:

      import java.io.*;

      public class intro1 {

          public static void main(String args[]) {

              if (args.length != 1) {

                 System.err.println("missing filename");

                 System.exit(1);

              }

              try {

                  FileInputStream fis =

                      new FileInputStream(args[0]);

                  int cnt = 0;

                  int b;

                  while ((b = fis.read()) != -1) {

                      if (b == '\n')

                          cnt++;

                  }

                  fis.close();

                  System.out.println(cnt);

              }

              catch (IOException e) {

                  System.err.println(e);

              }

          }

      }

然而,这个方法触发了大量对底层运行系统的调用,这就是FileInputStream.read, 返回文件下一个字节的本 机方法。

方法二:采用一个大缓冲区

第二种方法通过采用一个大缓冲区,避免了上述问题:

      import java.io.*;

      public class intro2 {

          public static void main(String args[]) {

              if (args.length != 1) {

                  System.err.println("missing filename");

                  System.exit(1);

              }

              try {

                  FileInputStream fis =

                      new FileInputStream(args[0]);

                  BufferedInputStream bis =

                      new BufferedInputStream(fis);

                  int cnt = 0;

                  int b;

                  while ((b = bis.read()) != -1) {

                      if (b == '\n')

                          cnt++;

                  }

                  bis.close();

                  System.out.println(cnt);

              }

              catch (IOException e) {

                  System.err.println(e);

              }

          }

      }

BufferedInputStream.read从输入缓冲区中获取下一个字节,极少访问底层系统。

方法三:直接缓冲(Direct Buffering)

第三种方法避免使用缓冲的输入流(BufferedInputStream),而直接进行缓冲,因此避免了读取方法的调用:

      import java.io.*;

      public class intro3 {

          public static void main(String args[]) {

              if (args.length != 1) {

                  System.err.println("missing filename");

                  System.exit(1);

              }

              try {

                  FileInputStream fis =

                      new FileInputStream(args[0]);

                  byte buf[] = new byte[2048];

                  int cnt = 0;

                  int n;

                  while ((n = fis.read(buf)) != -1) {

                      for (int i = 0; i < n; i++) {

                          if (buf[i] == '\n')

                              cnt++;

                      }

                  }

                  fis.close();

                  System.out.println(cnt);

              }

              catch (IOException e) {

                  System.err.println(e);

              }

          }

      }

对于1MB的输入文件,以秒为单位,各个程序的执行时间为:

      intro1    6.9

      intro2    0.9

      intro3    0.4

或者,在最快和最慢之间存在一个17比1的差距。

这巨大的加速性能并没有必然地证明,应该总是效仿第三种方法,因为此方法中需要自己进行缓冲。如果事 先没有进行仔细的实现,这样的方法可能容易造成错误,特别是在处理文件结束 (end-of-file)事件时。它 也可能在可读性上比其他的方法差。但是,记住时间都花费到什么地方去了,记住在需要时如何纠正是很有 用的。

对绝大多数应用程序而言,方法二可能是正确的选择。

缓冲

方法二和方法三使用了缓冲技术,其中,文件中的一整块从磁盘中读取出来,然后再一 次一个字节或者字符 地进行访问。缓冲是加速I/O的一种基本和重要的技术,而且许多Java类都支持缓冲(BufferedInputStream用于 字节,BufferedReader用于字符)。

一个明显的问题是:是否缓冲区越大就能够使I/O越快呢?Java缓冲区典型的缺省值是1024或者2048个字节。 大于此值的缓冲区可能能够帮助加速I/O,但通常只有几个百分点,即5%到10%。

方法四:整个文件

这个极端的例子需要确定文件的长度,然后将整个文件读取到缓冲区中。

      import java.io.*;

      public class readfile {

          public static void main(String args[]) {

              if (args.length != 1) {

                  System.err.println("missing filename");

                  System.exit(1);

              }

              try {

                  int len = (int)(new File(args[0]).length());

                  FileInputStream fis =

                      new FileInputStream(args[0]);

                  byte buf[] = new byte[len];

                  fis.read(buf);

                  fis.close();

                  int cnt = 0;

                  for (int i = 0; i < len; i++) {

                      if (buf[i] == '\n')

                          cnt++;

                  }

                  System.out.println(cnt);

              }

              catch (IOException e) {

                  System.err.println(e);

              }

          }

      }

这种方法很方便,因为文件可以被当作字节数组来对待。但是,一个明显的问题是可能没有足够的内存来读 取一个非常大的文件。

缓冲的另一方面涉及到终端窗口的文本输出。缺省情况下,System.out(一 个打印流——PrintStream)是行缓 冲的,也就是说,当遇到一个换行符时输出队列被清空。对于交互式应用来说,这是很重要的,阅赡芟不 对谑导适输入前,有一个输入提示符。

方法五:禁止行缓冲

但是行缓冲可以被禁止,正如下面例子中所示的:

      import java.io.*;

      public class bufout {

          public static void main(String args[]) {

              FileOutputStream fdout =

                  new FileOutputStream(FileDescriptor.out);

              BufferedOutputStream bos =

                  new BufferedOutputStream(fdout, 1024);

              PrintStream ps =

                  new PrintStream(bos, false);

              System.setOut(ps);

              final int N = 100000;

              for (int i = 1; i <= N; i++)

                  System.out.println(i);

              ps.close();

          }

      }

该程序输出整数1至100000,并且比使用行缓冲的程序快三倍。 缓冲也是下面几个例子之一的一个重要部分,其中缓冲区被用来加速对随机文件的访问。

读/写文本文件

先前提及的一个想法是,在从文件中读取字符时,方法调用的系统开销非常可观。这种情况的另一个例子可 以在这样一个程序中找到,该程序计算一个文本文件的行数。

      import java.io.*;

      public class line1 {

          public static void main(String args[]) {

              if (args.length != 1) {

                  System.err.println("missing filename");

                  System.exit(1);

              }

              try {

                  FileInputStream fis =

                      new FileInputStream(args[0]);

                  BufferedInputStream bis =

                      new BufferedInputStream(fis);

                  DataInputStream dis =

                      new DataInputStream(bis);

                  int cnt = 0;

                  while (dis.readLine() != null)

                      cnt++;

                  dis.close();

                  System.out.println(cnt);

              }

              catch (IOException e) {

                  System.err.println(e);

              }

          }

      }

这个程序使用老版本的DataInputStream.readLine方法,该方法的实现是通过使用读取方法调用来获得每个字 符。一个更新的方法是:

      import java.io.*;

      public class line2 {

          public static void main(String args[]) {

              if (args.length != 1) {

                  System.err.println("missing filename");

                  System.exit(1);

              }

              try {

                  FileReader fr = new FileReader(args[0]);

                  BufferedReader br = new BufferedReader(fr);

                  int cnt = 0;

                  while (br.readLine() != null)

                      cnt++;

                  br.close();

                  System.out.println(cnt);

              }

              catch (IOException e) {

                  System.err.println(e);

              }

          }

      }

这种方法能够快一些。例如,对于6MB字节200,000行的文本文件,第二个程序比第一个大约快20%。

但是,即使是第二个程序,也并不够快,这里有一个重要问题值得注意。第一个程序在Javatm 2编译器上会 导致一个严厉的警告,这是因为DataInputStream.readLine太陈旧了,不能正确地将字节转换为字符,并且也不 是操作某些包含有非ASCII字节文本文件的恰当选 择,(请注意,Java语言使用Unicode字符集,而不是 ASCII字符集)。

前面提到的字节流和字符流的不同,在下列程序中产生了效果:

      import java.io.*;

      public class conv1 {

          public static void main(String args[]) {

              try {

                  FileOutputStream fos =

                      new FileOutputStream("out1");

                  PrintStream ps =

                      new PrintStream(fos);

                  ps.println("\uffff\u4321\u1234");

                  ps.close();

              }

              catch (IOException e) {

                  System.err.println(e);

              }

          }

      }

写入一个输出文件,但没有保留实际输出的Unicode字符。读/写I/O类是基于字符的,并且是设计用来解决这 个问题的。在OutputStreamWriter中,薪辛舜幼址蜃纸诘谋嗦胱弧?

使用PrintWriter输出Unicode字符的程序看上去象这样:

      import java.io.*;

      public class conv2 {

          public static void main(String args[]) {

              try {

                  FileOutputStream fos =

                      new FileOutputStream("out2");

                  OutputStreamWriter osw =

                      new OutputStreamWriter(fos, "UTF8");

                  PrintWriter pw = new PrintWriter(osw);

                  pw.println("\uffff\u4321\u1234");

                  pw.close();

              }

              catch (IOException e) {

                  System.err.println(e);

              }

          }

      }

本程序使用了UTF8编码,它将ASCII编码为其本身,其他字符编码为两个或者三个字节。

时间: 2025-01-24 08:00:36

调整JavaTM 的I/O性能(一)(zt)的相关文章

调整JavaTM 的I/O性能(四)(zt)

性能 第二个程序比第一个大约快20%,代价是不得不写一些技巧性的底层代码. StreamTokenizer是一个杂和的类,因为它从基于字符的流中读取(象BufferedReader)数据,但同时又以字节进 行操作,即尽管它们是字母,也要用两字节的值来处理所有的字符(大于0xff). 序列化 序列化使用一个标准格式,将任意一个Java数据结构转换为字节流.例如,如下程序输出一个随机的整数数 组:       import java.io.*;       import java.util.*;  

调整JavaTM 的I/O性能(三)(zt)

性能 压缩 Java提供了对字节流进行压缩和解压缩的类.它们可以在java.util.zip包中被找到,同时也作为Jar文件的基 础(Jar文件是具有一个清单的Zip文件). 以下的程序采用一个单一的输入文件,并且生成一个压缩了的Zip输出文件,该程序带有一个表示输入文件的 入口项.       import java.io.*;       import java.util.zip.*;       public class compress {           public static

调整JavaTM 的I/O性能(二)(zt)

性能 格式化开销 实际上,将数据写入文件只是输出开销的一部分.另外一个巨大的开销是数据的格式 化.考虑下面的三个例 子,要求其输出如下的行: The square of 5 is 25 方法 1 第一种方法是简单地输出一个固定串,以得到内部I/O开销的概念:       public class format1 {           public static void main(String args[]) {               final int COUNT = 25000;  

Oracle如何调整RMAN备份与恢复操作的性能

RMAN 实际上即装即用的,我们通常不需要对其做什么调整. 但是,RMAN 体系结构中还包含许多组件,当这些组件构成一个整体时,就必须调整RMAN的设置以从备份进程中得到最佳的性能. 通常RMAN 调整设计到处理逻辑和物理数据库设计中的低效率,调整介质管理库(Media Management Library: MML), 调整RMAN 和MML 层以备份数据库的物理设备更好地共存. 一. 调整RMAN 前的工作 如果RMAN 的备份操作时间非常长,这可能不是RMAN的故障. 在大多数情况下,这可

调整Oracle数据库服务器的性能技巧

oracle是殷墟(Yin Xu)出土的甲骨文(oracle bone inscriptions)的英文翻译的第一个单词,在英语里是"神谕"的意思. 与无压缩格式下http://www.aliyun.com/zixun/aggregation/17326.html">存储数据相比,新的Oracle数据压缩技术能够确保以较小的开销节省三倍以上的磁盘存储空间.这一点比仅节省磁盘空间要具有更大的优势,因为它能够使企业节约更多的开支,以便有更多的资金来巩固自己的地位.自动诊断知

数据库性能分析及调整一例

数据|数据库|性能 故障现象2004年6月8日上午10:00,内蒙古巴盟网通用户反映在OSS系统界面"话单查询"里查询单个用户五天的话单特别慢,查询很长时间无结果. 例如:在OSS系统界面"综合查询"内点击"收费"-〉"话单查询",键入"用户号码,起始时间:2004-01-01 00:00:00,结束时间:2004-06-01 23:00:00",点击查询后,IE进度条缓慢,很长时间不返回结果.故障分析经过

Oracle性能调整的指导纲要

讲优化时大致写的一个提纲,内容分db的物理设计和逻辑设计,主要以物理设计为主,逻辑设计介绍的内容不多,提纲里把物理结构设计和实例优化有机的结合在一起,把逻辑结构设计和应用调整结合在一起...... Oracle性能调整指导纲要 数据库物理结构设计和实例级别的调整 一.Oracle性能优化方法论 1.为什么(what)要优化(系统慢了?慢是表象) 2.怎样(how)优化?(需要找到慢的原因) a.是系统的问题? b.是数据库的问题? 3.谁(who)来优化? a.系统架构师(系统架构设计的有问题,

Oracle基于Client/Server的性能调整

摘要:通过探讨和研究Oracle服务器和Client/Server的特点和原理,阐述了提高.调整Oracle应用系 统性能的一些原则和方法. 关键词:Oracle:客户/服务器:系统全程区:网络I/O:回滚段. Oracle 数据库广泛应用在社会的各个领域,特别是在Client/Server模式的应用,但是应用开发者往 往碰到整个系统的性能随着数据量的增大显著下降的问题,为了解决这个问题,从以下几个方面:数据库 服务器.网络I/O.应用程序等对整个系统加以调整,充分发挥Oracle的效能,提高整

心得总结:Java性能优化技巧集锦

技巧|心得|性能|优化 一.通用篇 "通用篇"讨论的问题适合于大多数Java应用. 1.1 不用new关键词创建类的实例 用new关键词创建类的实例时,构造函数链中的所有构造函数都会被自动调用.但如果一个对象实现了Cloneable接口,我们可以调用它的clone()方法.clone()方法不会调用任何类构造函数. 在使用设计模式(Design Pattern)的场合,如果用Factory模式创建对象,则改用clone()方法创建新的对象实例非常简单.例如,下面是Factory模式的一