IO性能

转自:http://topic.csdn.net/u/20080310/10/bd73f657-f100-4e41-ad55-c8389251452d.html

java总是假定只有两种文件组织形式。
第一种,基于字节流(stream of bytes)
第二种,基于字符序列(character sequences)

java 中一个字符有两个字节。所以在从文件读字符时需要转换。

提高io性能的基本原则
1,尽量避免访问磁盘
2,尽量避免访问OS
3,尽量避免方法调用
4,尽量避免单独处理字节或字符

下面通过一个计算换行数的例子来说明这些原则是如何被应用的。

1,读
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);
  }
  }
  }
这段代码中频繁调用了OS 函数,也就是FileInputStream.read
一个本地方法负责读取文件的下一个字节。

2,采用大缓存

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从缓存中读取字节。减少了底层api访问。

3,直接缓存
我们不使用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);
  }
  }
  }

对于1m的文件 执行时间分别是
 intro1 6.9
 intro2 0.9
 intro3 0.4

最慢的和最快的时间比是 17 比 1

当然这并不是说我们总是要采用第三种方式。
其实第三种方式可能产生错误,特别是在处理end-of-file事件时(要仔细处理这类事件).
并且它可读性比较差.但是作为一个方法还是应该引起我们的注意.

第二种方式可能是最普通的方式.


【缓存】
第二种和第三种方式都用到了缓存.它确实是提高IO速度的基本手段。
这很容易让我们想起一个问题。是不是缓存越大IO越快。
JAVA 的缓存默认一般是1024 或 4048 比特。
更大的缓存可能加快IO但是也就是5% 10% 左右的小幅提升。

4,整个文件读取



  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默认是行缓存,
也就是说缓存在遇到一个换行符的时候会将内容强行输出。

5,阻止行缓存


  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);
  }
  }
  }
 
这个方法会快一些。比如6m 200,000行的文件读取第二种方式可以有20%的提升。
但即使没有提升也应该注意到一点。第一个方法会有警告,因为DataInputStream.readLine  
这个方法没有正确变换字符,所以不要用它来处理文本文件。它可以处理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);
  }
  }
  }

显然他没有写我们能看懂的字符。

☆Reader/Writer IO类是基于字符的,它可以来解决这类问题。
OutputStreamWriter 是设置编码的地方。
 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。

你可能感兴趣的:(IO)