天天看点

Java的IO系统--字节流的一些子类(高级流)

这篇来介绍一些字节流的高级流,在基础的文件流上加入了相应的功能,比如缓冲区,写基本数据类型和对象.

BufferedOutputStream与BufferedInputStream(缓冲字节流)

简介

缓冲字节流在流中维护了一个缓冲区,与基本的文件流相比,数据首先存储在缓冲区中,缓冲区充满或调用相应的方法时,才会把数据冲刷出去,与一个一个字节的处理相比,效率提升了很多.对于缓冲流,我们可以定义缓冲区大小.

构造器

BufferedInputStream
    BufferedInputStream(InputStream in) 
    封装一个字节输入流,调用了BufferedInputStream(InputStream out, int size),size定义为8192.
    BufferedInputStream(InputStream in, int size) 
    封装一个字节输入流,并定义缓冲区的字节数.  

BufferedOutputStream
    BufferedOutputStream(OutputStream out) 
    封装一个字节输出流,调用了BufferedOutputStream(OutputStream out, int size),size定义为8192.
    BufferedOutputStream(OutputStream out, int size) 
    封装一个字节输出流,并定义缓冲区的字节数.  

           

方法

基本上都是前面介绍过的了,在输出流提供了一个flush(),然后在调用close()的时候会自动调用flush().

先定义几个方法:

/**
     * 读取数据并返回读取到了几个字节.
     * @param is 一个字节输入流
     * @throws IOException IO异常
     */
    private static void read(InputStream is) throws IOException {
        read(is, false);
    }

    /**
     * 写入若干字节到文件
     * @param os 一个字节输出流
     * @param bytes 要写入的字节数
     * @throws IOException IO异常
     */
    private static void write(OutputStream os, int bytes) throws IOException {
        write(os, bytes, false);
    }


    /**
     * 读取数据并返回读取到了几个字节
     * @param is 一个字节输入流
     * @param close 读取结束后是否关闭流
     * @throws IOException IO异常
     */
    private static void read(InputStream is, boolean close) throws IOException {
        int number = 0;
        while (is.read() >= 0) {
            number++;
        }
        if (close) {
            is.close();
        }
        System.out.println("读取到了" + number + "字节.");
    }

    /**
     * 增加了是否自动冲刷缓冲区的判定
     * @param os 一个字节输出流
     * @param bytes 要写入的字节数
     * @param flush 是否调用flush
     * @throws IOException IO异常
     */
    private static void write(OutputStream os, int bytes, boolean flush) throws IOException {
        for (int i = 0; i < bytes; i++) {
            os.write(1);
        }
        if (flush) {
            os.flush();
        }
    }
           

可以看到提供了定义缓冲区大小的构造器,下面来看看缓冲区的作用,首先定义一个默认缓冲区大小的输出流,写入缓冲区大小-1个字节,然后读取:

public static void main(String[] args) throws IOException {
        File file1 = new File("test1.txt");
        if (file1.exists()) {
            System.out.println("文件已删除?" + file1.delete());
        }
        FileOutputStream fos1 = new FileOutputStream(file1);
        BufferedOutputStream bos1 = new BufferedOutputStream(fos1);
        write(bos1, 8191);
        FileInputStream fis1 = new FileInputStream(file1);
        BufferedInputStream bis1 = new BufferedInputStream(fis1);
        read(bis1, true);
    }

        结果:文件已删除?true
             读取到了0字节.
           

可以看到虽然文件是存在的,但是数据实际上并没有写入文件,下面调用flush看看:

public static void main(String[] args) throws IOException {
        ......
        write(bos1, 8191,true);//为true则调用flush().
        ......
    }

    结果:文件已删除?true
         读取到了8191字节.
           

可以看到读取到了写入的字节量,下面再看看写入缓冲区+1字节数且不调用flush()的情况.

public static void main(String[] args) throws IOException {
    ......    
    write(bos1, 8193);//未调用flush(),写入量为缓冲区+1字节数.
    ......
}

    结果:文件已删除?true
         读取到了8192字节.
           

可以看到只写出了缓冲区大小的数据,还剩1个字节在缓冲区中.下面改变缓冲区的大小,这里改为4096字节,写入8191字节:

public static void main(String[] args) throws IOException {
        ......
        BufferedOutputStream bos1 = new BufferedOutputStream(fos1, 4096);
        write(bos1, 8191);
        BufferedInputStream bis1 = new BufferedInputStream(fis1, 4096);
        ......
    }

    结果:文件已删除?true
         读取到了4096字节.
           

确实只写出了缓冲区大小的数据,剩下的4095字节并未写入. 

具体有什么用处,我们实际操作一个大型文件看看用时就知道了,测试文件大小约500MB,在内存盘测试,可以完全消除硬盘速度引起的瓶颈.缓冲流提供了默认缓冲区和1024字节缓冲区的对比,这样可以测试缓冲区大小对效率的影响,测试运行三次,消除影响:

public static void main(String[] args) throws IOException, InterruptedException {
        long bytes = 0;
        File file = new File("Z:" + File.separator + "test1");
        FileInputStream fis = new FileInputStream(file);
        long start = System.currentTimeMillis();
        while (fis.read() >= 0) {
            bytes++;
        }
        long end = System.currentTimeMillis();
        System.out.println("读取" + bytes + "字节," + 
        bytes/1024/1024 + "MB,文件字节流用时" + (end - start)/1000 + "秒.");

        //第二次运行
        file = new File("Z:" + File.separator + "test1");
        fis = new FileInputStream(file);
        BufferedInputStream bis = new BufferedInputStream(fis);
        ......
        System.out.println("读取" + bytes + "字节," + 
        bytes/1024/1024 + "MB,缓冲文件字节流用时" + (end - start)/1000 + "秒,缓冲区大小为默认8192字节");
        
        //第三次运行
        file = new File("Z:" + File.separator + "test1");
        fis = new FileInputStream(file);
        bis = new BufferedInputStream(fis, 1024);
        ......
        System.out.println("读取" + bytes + "字节," +
        bytes/1024/1024 + "MB,缓冲文件字节流用时" + (end - start)/1000 + "秒,缓冲区大小为1024字节");
    }
           

运行结果如下:

读取513409024字节,489MB,文件字节流用时360秒.
    读取513409024字节,489MB,缓冲文件字节流用时7秒,缓冲区大小为默认8192字节.
    读取513409024字节,489MB,缓冲文件字节流用时11秒,缓冲区大小为1024字节
           

可以看到有了缓冲区速率大大提升,缓冲区大小也会影响速率. 

DataOutputStream与DataInputStream(数据字节流)

简介

可以读写基本数据类型的流,封装了读写读写基本数据类型的方法.

构造器

DataInputStream
    DataInputStream(InputStream in) 

DataOutputStream
    DataOutputStream(OutputStream out) 
           

 方法

先来看看不是读写数据的方法:

DataInputStream
    int skipBytes(int n) 
    跳过若干字节读取数据.

DataOutputStream
    void flush() 
    冲刷这个流.  
    int size() 
    返回已经写出的字节数  
           

都是已经见过的方法了,这里就不介绍了,下面看看读写数据的方法:

输入输出共有的:   
    boolean readBoolean() void writeBoolean()  
    byte readByte()       void writeByte()                                          
    char readChar()       void writeChar() 
    double readDouble()   void writeDouble()
    float readFloat()     void writeFloat()
    int readInt()         void writeInt()
    long readLong()       void writeLong()
    short readShort()     void writeShort()
    String readUTF()      void writeUTF()
    int read(byte[] b, int off, int len)  void write(byte[] b, int off, int len) 

DataInputStream独有的读取方法
    int read(byte[] b)
    void readFully(byte[] b)   
    void readFully(byte[] b, int off, int len) 
    String readLine()   已弃用 
    int readUnsignedByte() 
    int readUnsignedShort() 
    static String readUTF(DataInput in) 

DataOutputStream独有的写入方法
    void write(int b) 将传入的b的低八位写入.
           

这里只测试读写基本数据类型的方法,测试方法是先写入再读出:

public static void main(String[] args) throws IOException {
        File data = new File("data.txt");

        FileOutputStream fos = new FileOutputStream(data);
        DataOutputStream dos = new DataOutputStream(fos);
        dos.writeBoolean(true);
        dos.writeByte(1);
        dos.writeChar('c');
        dos.writeDouble(Math.PI);
        dos.writeFloat(3.14F);
        dos.writeInt(Integer.MAX_VALUE);
        dos.writeLong(Long.MAX_VALUE);
        dos.writeShort(987);
        dos.writeUTF("UTF数据");

        FileInputStream fis = new FileInputStream(data);
        DataInputStream dis = new DataInputStream(fis);
        System.out.println("读取写入的boolean类型数据:" + dis.readBoolean());
        System.out.println("读取写入的byte类型数据:" + dis.readByte());
        System.out.println("读取写入的char类型数据:" + dis.readChar());
        System.out.println("读取写入的double类型数据:" + dis.readDouble());
        System.out.println("读取写入的float类型数据:" + dis.readFloat());
        System.out.println("读取写入的int类型数据:" + dis.readInt());
        System.out.println("读取写入的long类型数据:" + dis.readLong());
        System.out.println("读取写入的short类型数据:" + dis.readShort());
        System.out.println("读取写入的String类型数据:" + dis.readUTF());
    }
           

运行结果: 

读取写入的boolean类型数据:true
读取写入的byte类型数据:1
读取写入的char类型数据:c
读取写入的double类型数据:3.141592653589793
读取写入的float类型数据:3.14
读取写入的int类型数据:2147483647
读取写入的long类型数据:9223372036854775807
读取写入的short类型数据:987
读取写入的String类型数据:UTF数据