[Java IO]02_字节流

 

概要

字节流有两个核心抽象类:InputStream 和 OutputStream。所有的字节流类都继承自这两个抽象类。

InputStream 负责输入,OutputStream 负责输出。

字节流主要操作byte类型数据。

以下为 JDK8 版本中字节流的族谱图:

具体详情可查看Java API文档。

[Java IO]02_字节流

由上图可以看出,InputStream 和 OutputStream对于数据源的操作往往是成对出现的。

 

InputStream

InputStream的作用是用来表示哪些从不同数据源产生输入的类。

InputStream类型表

功能 构造器
ByteArrayInputStream 允许将内存的缓冲区当做InputStream使用 缓冲区,字节将从中取出
StringBufferInputStream 将String转换成InputStream 字符串。底层实现实际使用StringBuffer
FileInputStream 从文件中读取信息 字符串,表示文件名、文件或FileDescriptor对象
PipedInputStream 产生用于写入相关PipedOutputStream的数据。实现“管道化”概念 PipedOutputStream
SequenceInputStream 将多个InputStream对象合并为一个InputStream 两个InputStream对象或一个容纳InputStream对象的容器Enumeration
FilterInputStream 抽象类,作为“装饰器”的接口。其中“装饰器”为其他InputStream类提供有用功能


OutputStream

OutputStream决定了数据的输出形式。

OutputStream类型表

功能 构造器
ByteArrayOutputStream 允许将内存的缓冲区当做InputStream使用 缓冲区初始化尺寸(可选的)
FileOutputStream 从文件中读取信息 字符串,表示文件名、文件或FileDescriptor对象
PipedOutputStream 产生用于写入相关PipedOutputStream的数据。实现“管道化”概念 PipedInputStream
FilterOutputStream 抽象类,作为“装饰器”的接口。其中“装饰器”为其他OutputStream类提供有用功能


文件字节流

文件字节流有两个类:FileOutputStream 和 FileInputStream。

它们提供了方法将字节写入到文件和将数据以字节形式从文件中读取出来。

一般情形下,文件字节流操作遵循以下几个步骤:

(1)使用File类绑定一个文件。

(2)把File对象绑定到流对象上。

(3)进行读、写操作。

(4)关闭输入、输出。

 

FileOutputStream


向文件写入数据

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class FileOutputStreamDemo {
    public static void write1(OutputStream out, byte[] b) throws IOException {
        out.write(b); // 将内容输出,保存文件
    }

    public static void write2(OutputStream out, byte[] b) throws IOException {
        for (int i = 0; i < b.length; i++) { // 采用循环方式写入
            out.write(b[i]); // 每次只写入一个内容
        }
    }

    public static void main(String args[]) throws Exception {
        // 第1步、使用File类找到一个文件
        File f = new File("d:" + File.separator + "test.txt"); // 声明File对象

        
// 第2步、通过子类实例化父类对象
        OutputStream out = new FileOutputStream(f); // 通过对象多态性,进行实例化
        
// 实例化时,默认为覆盖原文件内容方式;如果添加true参数,则变为对原文件追加内容的方式。
        
// OutputStream out = new FileOutputStream(f, true);

        
// 第3步、进行写操作
        String str = "Hello World\r\n"; // 准备一个字符串
        byte b[] = str.getBytes(); // 只能输出byte数组,所以将字符串变为byte数组
        write1(out, b);
        // write2(out, b);

        
// 第4步、关闭输出流
        out.close();
    }
};

 

FileInputStream


从文件中读取数据

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class FileInputStreamDemo {
    public static void read1(InputStream input, byte[] b) throws IOException {
        int len = input.read(b); // 读取内容
        System.out.println("读入数据的长度:" + len);
    }

    public static void read2(InputStream input, byte[] b) throws IOException {
        for (int i = 0; i < b.length; i++) {
            b[i] = (byte) input.read(); // 读取内容
        }
    }

    public static void read3(InputStream input, byte[] b) throws IOException {
        int len = 0;
        int temp = 0; // 接收每一个读取进来的数据
        while ((temp = input.read()) != -1) {
            // 表示还有内容,文件没有读完
            b[len] = (byte) temp;
            len++;
        }
    }

    public static void main(String args[]) throws Exception { // 异常抛出,不处理
        
// 第1步、使用File类找到一个文件
        File f = new File("d:" + File.separator + "test.txt"); // 声明File对象

        
// 第2步、通过子类实例化父类对象
        InputStream input = new FileInputStream(f); // 准备好一个输入的对象

        
// 第3步、进行读操作
        
// 有三种读取方式,体会其差异
        byte[] b = new byte[(int) f.length()];
        read1(input, b);
        // read2(input, b);
        
// read3(input, b);

        
// 第4步、关闭输入流
        input.close();
        System.out.println("内容为:\n" + new String(b)); // 把byte数组变为字符串输出
    }
};

 

内存操作流

ByteArrayInputStream 和 ByteArrayOutputStream是用来完成内存的输入和输出功能。

内存操作流一般在生成一些临时信息时才使用。 
如果临时信息保存在文件中,还需要在有效期过后删除文件,这样比较麻烦。


使用内存操作流完成一个转换为小写字母的程序

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class ByteArrayDemo {
    public static void main(String args[]) {
        String str = "HELLOWORLD";
        
        ByteArrayInputStream bis = new ByteArrayInputStream(str.getBytes()); // 内存输入流
        ByteArrayOutputStream bos = new ByteArrayOutputStream(); // 内存输出流
        
        
// 用ByteArrayInputStream读取字符串内容,
        
// 全转为小写后,写入ByteArrayOutputStream
        int temp = 0;
        while((temp = bis.read()) != -1) {
            char c = (char) temp; // 读取的数字变为字符
            bos.write(Character.toLowerCase(c)); // 将字符变为小写
        }
        
        // 所有的数据就全部都在ByteArrayOutputStream中
        String newStr = bos.toString();    // 取出内容
        try {
            bis.close();
            bos.close();
        } catch(IOException e) {
            e.printStackTrace();
        }
        System.out.println(newStr);
    }
};


管道流

管道流的主要作用是可以进行两个线程间的通信。

如果要进行管道通信,则必须把PipedOutputStream连接在PipedInputStream上。

为此,PipedOutputStream中提供了connect()方法。

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

class Send implements Runnable {
    private PipedOutputStream pos = null// 管道输出流

    public Send() {
        this.pos = new PipedOutputStream(); // 实例化输出流
    }

    public void run() {
        String str = "Hello World!!!"; // 要输出的内容
        try {
            this.pos.write(str.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            this.pos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public PipedOutputStream getPos() { // 得到此线程的管道输出流
        return this.pos;
    }
};

class Receive implements Runnable {
    private PipedInputStream pis = null// 管道输入流

    public Receive() {
        this.pis = new PipedInputStream(); // 实例化输入流
    }

    public void run() {
        byte b[] = new byte[1024]; // 接收内容
        int len = 0;
        try {
            len = this.pis.read(b); // 读取内容
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            this.pis.close(); // 关闭
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("接收的内容为:" + new String(b, 0, len));
    }

    public PipedInputStream getPis() {
        return this.pis;
    }
};

public class PipedDemo {
    public static void main(String args[]) {
        Send s = new Send();
        Receive r = new Receive();
        try {
            s.getPos().connect(r.getPis()); // 连接管道
        } catch (IOException e) {
            e.printStackTrace();
        }
        new Thread(s).start(); // 启动线程
        new Thread(r).start(); // 启动线程
    }
};


数据操作流

 

数据操作流提供了格式化读入和输出数据的方法,分别为DataInputStream 和 DataOutputStream。

 

DataOutputStream

将格式化的订单数据写入到文件

 

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class DataOutputStreamDemo {
    public static void main(String args[]) throws IOException {
        // 第1步、使用File类找到一个文件
        File f = new File("d:" + File.separator + "order.txt");
        
        // 第2步、通过子类实例化父类对象
        DataOutputStream dos = new DataOutputStream(new FileOutputStream(f)); // 实例化数据输出流对象

        
// 第3步、进行写操作
        String names[] = { "衬衣", "手套", "围巾" }; // 商品名称
        float prices[] = { 98.3f, 30.3f, 50.5f }; // 商品价格
        int nums[] = { 3, 2, 1 }; // 商品数量
        
// 循环输出
        for (int i = 0; i < names.length; i++) {
            dos.writeChars(names[i]);
            dos.writeChar('\t');
            dos.writeFloat(prices[i]);
            dos.writeChar('\t');
            dos.writeInt(nums[i]);
            dos.writeChar('\n');
        }

        // 第4步、关闭输出流
        dos.close();
    }
};

 

DataInputStream

将订单文件中的格式化数据读取出来

import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;


public class DataInputStreamDemo {
    public static void main(String args[]) throws IOException { // 所有异常抛出
        
// 第1步、使用File类找到一个文件
        File f = new File("d:" + File.separator + "order.txt");

        // 第2步、通过子类实例化父类对象
        DataInputStream dis = new DataInputStream(new FileInputStream(f)); // 实例化数据输入流对象
        
        
// 第3步、进行读操作
        String name = null// 接收名称
        float price = 0.0f; // 接收价格
        int num = 0; // 接收数量
        char temp[] = null// 接收商品名称
        int len = 0; // 保存读取数据的个数
        char c = 0; // '\u0000'
        try {
            while (true) {
                temp = new char[200]; // 开辟空间
                len = 0;
                while ((c = dis.readChar()) != '\t') { // 接收内容
                    temp[len] = c;
                    len++; // 读取长度加1
                }
                name = new String(temp, 0, len); // 将字符数组变为String
                price = dis.readFloat(); // 读取价格
                dis.readChar(); // 读取\t
                num = dis.readInt(); // 读取int
                dis.readChar(); // 读取\n
                System.out.printf("名称:%s;价格:%5.2f;数量:%d\n", name, price, num);
            }
        } catch (Exception e) {
        }
        
        // 第4步、关闭输入流
        dis.close();
    }
};

 

合并流

合并流的主要功能是将多个InputStream合并为一个InputStream流。

合并流的功能由SequenceInputStream完成。

需要稍微留意的是,由前面的字节流族谱图,可以得知并没有对应的OutputStream。

将两个文件内容合并为一个文件

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.SequenceInputStream;

public class SequenceDemo {
    public static void main(String args[]) throws IOException {
        // 第1步,绑定要操作的文件
        InputStream is1 = new FileInputStream("d:" + File.separator + "a.txt"); // 输入流1
        InputStream is2 = new FileInputStream("d:" + File.separator + "b.txt"); // 输入流2    
        OutputStream os = new FileOutputStream("d:" + File.separator + "ab.txt"); // 输出流
        
        
// 第2步,绑定要合并的InputStream对象到SequenceInputStream
        SequenceInputStream sis = new SequenceInputStream(is1, is2); // 实例化合并流

        
// 读取两个InputStream流的数据,然后合并输出到OutputStream
        int temp = 0; // 接收内容
        while ((temp = sis.read()) != -1) { // 循环输出
            os.write(temp); // 保存内容
        }
        
        // 第4步,关闭相关流
        sis.close();
        os.close();    
        is1.close();
        is2.close();
    }
};

 

参考资料

《Java编程思想》

《Java开发实战经典》

上一篇:剑指offer 014 输入一个链表,输出该链表中倒数第k个结点。


下一篇:JAVA与C#比较 (如果你简历中提到C#和JAVA,面试如何回答?)