Java Io原理及应用

概述

IO是编程中无法回避的问题,它往往会成为程序运行的性能瓶颈(JAVA在1.4后引入了NIO提高了IO性能),JAVA的IO流分类很丰富,IO包下含有大量的类和接口,从分类上看,主要有以下分类:

  1. 基于字节操作的 I/O 接口:InputStream 和 OutputStream
  2. 基于字符操作的 I/O 接口:Writer 和 Reader
  3. 基于磁盘操作的 I/O 接口:File
  4. 基于网络操作的 I/O 接口:Socket

在看具体的接口前,首先介绍一个设计模式:Decorator Pattern 装饰者模式,因为整个 JAVA IO 的核心就是采用了Decorator(装饰)模式。

使用JAVA IO的时候经常会看到以下写法:

InputStream in=new BufferedInputStream(new FileInputStream("filename"));

这几个类的关系如下:

public class BufferedInputStream extends FilterInputStream
public class FilterInputStream extends InputStream
public class FileInputStream extends InputStream

我模仿这几个类的关系写了一个装饰关系模型:

/**
 * 程序员
 * 具有写代码功能的抽象被装饰器。
 * 对应Inputstream
 */
interface Coder {
    void writeCode();
}

/**
 * java程序员
 * 具有写JAVA代码的具体被装饰器
 * 对应FileInputStream
 */
class JavaCoder implements Coder {
    @Override
    public void writeCode() {
        System.out.println("write java code!");
    }
}

/**
 * 有梦想的程序员(本身和JAVA程序员没有直接关系)
 * 具备装饰器的抽象意义
 * 对应FilterInputStream
 */
class HavaDreamCoder implements Coder {
    Coder coder;

    protected HavaDreamCoder(Coder coder) {
        this.coder = coder;
    }

    @Override
    public void writeCode() {
        coder.writeCode();
    }
}

/**
 * 高级的JAVA程序员
 * 具有具体功能 (设计代码) 的装饰器
 * 对应BufferedInputStream
 */
class SeniorJavaCoder extends HavaDreamCoder {

    SeniorJavaCoder(Coder coder) {
        super(coder);
    }

    @Override
    public void writeCode() {
        design();
        coder.writeCode();
    }

    public void design() {
        System.out.println("design code!");
    }

    //额外方法--高级程序员会读优秀的源码
    public void readCode() {
        System.out.println("read nice code!");
    }
}

使用方法如下:

 SeniorJavaCoder coder = new SeniorJavaCoder(new JavaCoder());
 coder.writeCode();

打印结果:

design code!
write java code!

高级JAVA程序员可以解决大部分问题,但是有一天我们发现一个非常复杂的难题,高级JAVA程序员已经解决不了了,但是我们又不忍心解雇高级JAVA程序员(因为她是一个PLMM),所以我们需要招聘一个大牛JAVA程序员(比如DataInputStream),这个大牛JAVA程序员比高级JAVA程序员写代码的方法又更进步了,而且他会写C/C++等代码..于是再次装饰。

基于字节的 I/O 操作接口

InputStream 相关类层次结构


OutputStream 相关类层次结构



基于字符的 I/O 操作接口

Reader 相关类层次结构

Writer 相关类层次结构

数据持久化或网络传输都是以字节进行的,所以必须要有字符到字节或字节到字符的转化。字符到字节需要转化,其中读的转化过程如下图所示:

 

字节与字符的转化接口

读入:

 

按字符方式读入文件我们可以这样写:

 

 FileReader  f=new FileReader("C:\\log.txt");

我们看看FileReader源码:

   public FileReader(String fileName) throws FileNotFoundException {
    super(new FileInputStream(fileName));
    }

从它的构造函数可以看出读文件实际上是读取文件流,只是在它的的父类:

  public InputStreamReader(InputStream in) {
    super(in);
        try {
        sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object
        } catch (UnsupportedEncodingException e) {
        // The default encoding should always be available
        throw new Error(e);
    }
    }

通过StreamDecoder将字节转化为字符。

 

写出:

 

 

 实例测试

 缓冲提高IO性能测试,并解释了一个使用上的误区:是不是用了带Buffer的类一定会比不带Buffer的快,看下面代码:

import java.io.*;

/**
 * Created with IntelliJ IDEA.
 * User: wangq
 * Date: 13-3-28
 * Time: 下午1:03
 * To change this template use File | Settings | File Templates.
 */
public class TestIo {


    /**
     * 带缓冲的复制文件
     *
     * @param sourceFile
     * @param targetFile
     * @throws IOException
     */
    public static void copyFileWithBuffer(File sourceFile, File targetFile) throws IOException {
        BufferedInputStream inBuff = null;
        BufferedOutputStream outBuff = null;
        try {
            // 新建文件输入流并对它进行缓冲
            inBuff = new BufferedInputStream(new FileInputStream(sourceFile));

            // 新建文件输出流并对它进行缓冲
            outBuff = new BufferedOutputStream(new FileOutputStream(targetFile));
            // 缓冲数组
            byte[] b = new byte[1024 * 5];
            int len;
            while ((len = inBuff.read(b)) != -1) {
                outBuff.write(b, 0, len);
            }
            // 刷新此缓冲的输出流
            outBuff.flush();
        } finally {
            // 关闭流
            if (inBuff != null)
                inBuff.close();
            if (outBuff != null)
                outBuff.close();
        }
    }

    /**
     * 无缓冲的文件复制
     *
     * @param sourceFile
     * @param targetFile
     * @throws IOException
     */
    public static void copyFileWithOutBuffer(File sourceFile, File targetFile) throws IOException {
        FileInputStream fin = null;
        FileOutputStream fout = null;
        try {
            fin = new FileInputStream(sourceFile);
            fout = new FileOutputStream(targetFile);
            byte[] buff = new byte[1024 * 5];
            int len;
            while ((len = fin.read(buff)) != -1) {
                fout.write(buff, 0, len);
            }
        } finally {
            // 关闭流
            if (fin != null)
                fin.close();
            if (fout != null)
                fout.close();
        }
    }

    public static void main(String[] args) {
        File oldfile1 = new File("E:\\影视\\电影\\Lost in Thailand1.rmvb");//546mb
        File oldfile2 = new File("E:\\影视\\电影\\Lost in Thailand2.rmvb");//546mb
        File newFileNoBuff = new File("D:\\a.rmvb");
        File newFileWithBuff = new File("D:\\b.rmvb");
        long begtime = System.currentTimeMillis();
        try {
            copyFileWithOutBuffer(oldfile1, newFileNoBuff);
            System.out.println("无Buffer耗时:" + (System.currentTimeMillis() - begtime));
        } catch (IOException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
        begtime = System.currentTimeMillis();
        try {
            copyFileWithBuffer(oldfile2, newFileWithBuff);
            System.out.println("含Buffer耗时:" + (System.currentTimeMillis() - begtime));
        } catch (IOException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
    }
}

测试结果:

无Buffer耗时:17523
含Buffer耗时:26176

结果为什么会这样?其实这两个方法都使用了缓冲技术, FileInputStream并不是一个字节一个字节的读,而是自己做缓冲。大块文件被从磁盘读取,然后每次访问一个字节或字符。缓冲是一个基本而重要的加速I/O 的技术。尽管FileInputStream自己做缓冲速度是最快的,但是这却不是一个值得推荐的实现,或许BufferedInputStream的实现才更加适合应用。(上述例子中并没有给出FileInputStream直接read()的例子,这种方式肯定是最慢的)

既然使用缓冲可以减少读写次数加速IO,那缓冲区是不是越大越好?是不是可以这么写

  int len= (int) new File("filename").length();
  byte[] buff=new byte[len];

这么写虽然把文件读取降到最小,但是如果文件过大的话,这么写会直接耗尽内存。

还有一点需要注意的是,BufferedInputStream默认缓冲区大小是:8192,使用时可以不显示的声明一个byte数组,这样也能降低代码的复杂度,类似下面的写法:

 BufferedInputStream inBuff = null;
        BufferedOutputStream outBuff = null;
        try {
            // 新建文件输入流并对它进行缓冲
            inBuff = new BufferedInputStream(new FileInputStream(sourceFile));

            // 新建文件输出流并对它进行缓冲
            outBuff = new BufferedOutputStream(new FileOutputStream(targetFile));
            // 缓冲数组
            int data;
            while ((data = inBuff.read()) != -1) {
                outBuff.write(data);
            }
            // 刷新此缓冲的输出流
            outBuff.flush();
        } finally {
            // 关闭流
            if (inBuff != null)
                inBuff.close();
            if (outBuff != null)
                outBuff.close();
        }

大数据处理方式,接下来我们从内存中制造100万数据,使用串行化和非串行化方式将这些数据存储到文件,并读回到内存。

import java.io.*;
import java.util.ArrayList;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 * User: wangq
 * Date: 13-3-28
 * Time: 下午3:32
 * To change this template use File | Settings | File Templates.
 */
public class TestIo2 {
    /**
     * 串行化写入数据结
     *
     * @param list
     */
    public static void writeObject(List<Integer> list) {
        ObjectOutputStream out = null;
        try {
            out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream("D:\\obj")));
            out.writeObject(list);
        } catch (FileNotFoundException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } catch (IOException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } finally {
            if (out != null)
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                }
        }
    }

    /**串行化读取数据
     *
     */
    public static void readObj() {
        ObjectInputStream in = null;
        try {
            in = new ObjectInputStream(new BufferedInputStream(new FileInputStream("D:\\obj")));
            List<Integer> list = (List<Integer>) in.readObject();
            for (int i = 0; i < 10; i++) {
                System.out.println("list2=" + list.get(i));
            }
            System.out.println("list2.size=" + list.size());
        } catch (FileNotFoundException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } catch (IOException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } catch (ClassNotFoundException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } finally {
            if (in != null)
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                }
        }
    }

    /**非串行化写入数据
     *
     * @param list
     */
    public static void write(List<Integer> list) {
        BufferedOutputStream out = null;
        try {
            out = new BufferedOutputStream(new FileOutputStream("D:\\num"));
            for (Integer data : list) {
                out.write(intToBytes(data.intValue()));
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } catch (IOException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } finally {
            if (out != null)
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                }
        }
    }

    /**非串行化读入数据
     *
     */
    public static void read() {
        BufferedInputStream in = null;
        try {
            in = new BufferedInputStream(new FileInputStream("D:\\num"));
            List<Integer> list = new ArrayList<Integer>();
            byte[] buff = new byte[4];
            while ((in.read(buff)) != -1) {
                list.add(byteToInt2(buff));
            }
            for (int i = 0; i < 10; i++) {
                System.out.println("list1=" + list.get(i));
            }
            System.out.println("list1.size=" + list.size());
        } catch (FileNotFoundException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } catch (IOException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } finally {
            if (in != null)
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                }
        }
    }

    public static byte[] intToBytes(int n) {
        byte[] b = new byte[4];
        for (int i = 0; i < 4; i++) {
            b[i] = (byte) (n >> (24 - i * 8));
        }
        return b;
    }

    public static int byteToInt2(byte[] b) {
        int mask = 0xff;
        int temp = 0;
        int n = 0;
        for (int i = 0; i < 4; i++) {
            n <<= 8;
            temp = b[i] & mask;
            n |= temp;
        }
        return n;
    }

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<Integer>();
        //测试数据为100万
        for (int i = 0; i < 1000000; i++) {
            list.add(i);
        }
        long beg = System.currentTimeMillis();
        write(list);
        read();
        System.out.println("非串行化耗时:" + (System.currentTimeMillis() - beg));
        beg = System.currentTimeMillis();
        writeObject(list);
        readObj();
        System.out.println("串行化耗时:" + (System.currentTimeMillis() - beg));
    }
}

测试结果:


list1=0
list1=1
list1=2
list1=3
list1=4
list1=5
list1=6
list1=7
list1=8
list1=9
list1.size=1000000
非串行化耗时:89
list2=0
list2=1
list2=2
list2=3
list2=4
list2=5
list2=6
list2=7
list2=8
list2=9
list2.size=1000000
串行化耗时:466

Process finished with exit code 0
注意我们使用缓冲提高I/O操作的速度。

在具体的使用中是不是串行化数据,还需要根据自己方案来进行权衡。

 字符流使用,理解了字节流,再来操作字符流就很简单了:

 

import java.io.*;
import java.util.ArrayList;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 * User: wangq
 * Date: 13-3-29
 * Time: 上午10:37
 * To change this template use File | Settings | File Templates.
 */
public class TestIoReaderWriter {
    /**
     * 写入字符串到文件,没个字符串占用一行
     *
     * @param strList
     */
    public static void writeStr(List<String> strList) {
        BufferedWriter writer = null;
        try {
            writer = new BufferedWriter(new FileWriter("D:\\test.txt")); //注意FileWriter有个构造函数可以附加内容
            for (String data : strList) {
                writer.write(data);
                writer.newLine(); //写入一个行分隔符。行分隔符字符串由系统属性 line.separator 定义,并且不一定是单个新行 ('\n') 符。
            }
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } finally {
            if (writer != null)
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                }
        }

    }

    /**
     * 一次读取一行字符串
     */
    public static void readStr() {
        BufferedReader reader = null;
        List<String> list = new ArrayList<String>();
        try {
            reader = new BufferedReader(new FileReader("D:\\test.txt"));
            String data = null;
            while ((data = reader.readLine()) != null) {   //读取一个文本行。通过下列字符之一即可认为某行已终止:换行 ('\n')、回车 ('\r') 或回车后直接跟着换行。
                list.add(data);
            }
            System.out.println(list.size());
        } catch (FileNotFoundException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } catch (IOException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } finally {
            if (reader != null)
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                }
        }

    }
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            list.add(String.valueOf(i));
        }
        long beg = System.currentTimeMillis();
        writeStr(list);
        readStr();
        System.out.println("耗时:" + (System.currentTimeMillis() - beg));
    }
}

其他IO类常用特殊功能介绍

ByteArrayOutputStream:此类实现了一个输出流,其中的数据被写入一个 byte 数组。

public byte[] toByteArray()
可以将输出流中的内容复制到byte数组中返回。比如将一个文件转成byte数组,就可以使用该功能。

FilePermission:此类表示对文件和目录的访问。FilePermission 由路径名和对该路径名有效的操作集合组成。

public String getActions()
返回文件的操作权限,此方法总是以下列顺序返回存在的操作:read、write、execute、delete。例如,如果此 FilePermission 对象允许写和读操作,则调用 getActions 将返回字符串 "read,write"。

LineNumberReader:跟踪行号的缓冲字符输入流。此类定义了方法 setLineNumber(int)getLineNumber(),它们可分别用于设置和获取当前行号。

PipedInputStream  PipedOutputStream:管道流,这两个类的实例对象必须要通过connect方法连接,这两个类主要用来完成线程之间的通信,一个线程的PipedInputStream对象能够从另外一个线程的PipedOutputStream对象中读取数据。这两个类的使用场景应该类似于生产者--消费者模式,并且需要多线程环境。

import java.io.PipedInputStream;
import java.io.PipedOutputStream;

public class TestPiped {
    public static void main(String[] args) {
        Sender s = new Sender();
        Receiver r = new Receiver();
        PipedOutputStream out = s.getOut();
        PipedInputStream in = r.getIn();
        try {
            in.connect(out);
            s.start();
            r.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class Sender extends Thread {
    PipedOutputStream out = new PipedOutputStream();

    public PipedOutputStream getOut() {
        return out;
    }

    public void run() {
        String str = "Hello,receiver ! I`m sender\n";
        try {
            out.write(str.getBytes());
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
//receiver.java

class Receiver extends Thread {
    PipedInputStream in = new PipedInputStream();

    public PipedInputStream getIn() {
        return in;
    }

    public void run() {
        byte[] buf = new byte[1024];
        try {
            int len = in.read(buf);
            System.out.println("the following is from sender:\n" + new String(buf, 0, len));
            in.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

 

 

转载于:https://www.cnblogs.com/zhishan/archive/2013/03/29/2986436.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值