一文搞懂IO流

前言

io流在Java的知识体系中,属于很杂的那种了,主要是涉及的类太多,输入流、输出流、文件输入流、文件输出流、字节输入流和输出流、字符输入流和输出流等等,经常把人弄晕。但是它的用途却非常广泛,文件的操作、网络的操作等都离不开它,本文通过平时常用操作来梳理一下io流中涉及的重要类及方法。

什么是流

在Java中,文件一般不是单独处理的,而是视为输入输出设备的一种,Java使用一个统一的概念来处理所有的输入输出,包括键盘输入和网络输入输出等。这个统一的概念就是,流又分为输入流和输出流,InputStream和OutputStream分别表示输入流和输出流,但这两个类是抽象类,大部分方法都是子类来实现的,如:

  1. BufferedInputStream和BufferedOutputStream对流起缓冲作用
  2. DataInputStream和DataOutputStream可以按8种基本类型和字符串类型对流进行读写
  3. ZipInputStream和ZipOutputStream可以对流进行压缩和解压缩
  4. printStream可以对基本类型和对象输出为其字符串表示

以InputStream和OutputStream为基类的流基本都是以二进制形式处理数据的,不能够方便地处理文本文件,没有编码的概念,能够方便地处理文本数据的基类是Reader和Writer,它也有很多子类,如:

  1. FileReader和FilerWriter用来读写文件
  2. BufferedReader和BufferedWriter起缓存装饰
  3. CharArrayReader和CharArrayWriter可以将字符数组包装为Reader和Writer
  4. StringReader和StringWriter可以将字符串包装为Reader和Writer
  5. InputStreamReader和OutputStreamWriter可以将InputStream和OutputStream转换为Reader和Writer
  6. printWriter可以将基本类型和对象输出为其字符串表示

二进制文件和字节流

二进制文件:所有文件里面的数据都是通过二进制来保存的,但为了方便处理数据,引入了文件类型的概念,主要分为文本文件和二进制文件。文本文件是指那些文本编辑器可以直接查看和编辑的文件,如.txt、.html、.java等文件;二进制文件是指一些特殊格式和功能的文件,如.zip、.jpg、.doc、.pdf等文件

Java主要通过InputStream和OutputStream来实现二进制字节形式处理文件。

字节流

OutputStream

OutputStream主要有下面几个方法:

public abstract void write(int b) throws IOException;
public void write(byte b[]) throws IOException
public void write(byte b[], int off, int len) throws IOException
public void close() throws IOException
public void flush() throws IOException

write(int b)方法向流中写入一个字节,需要子类来实现。

write(byte b[])方法里面调用write(byte b[], int off, int len)方法来实现,将b[]数组从第off位置开始,len长度的数据写入到流中。

close()关闭流,上面几个方法就算流中的数据都操作完了,也会阻塞,等待流中继续添加数据,因此我们操作完数据以后,需要在finally方法中通过close()方法来关闭流。

flush()将缓冲而未实际写入的数据进行实际写入,一般作用于带缓冲的流中,如BufferedOutputStream。

举例

OutputStream outputStream = null;
try {
    outputStream = new FileOutputStream("hello.txt");
    String data = "hello world";
    outputStream.write(data.getBytes());
} catch (Exception e) {
    e.printStackTrace();
} finally {
    outputStream.close();
}

InputStream

InputStream主要有下面几个方法:

public abstract int read() throws IOException;
public int read(byte b[]) throws IOException 
public int read(byte b[], int off, int len) throws IOException
public void close() throws IOException

read()方法从流中读取下一个字节,需要子类来实现,返回这个字节的值,当读到流结尾时返回-1。

read(byte b[])里面也是调用read(byte b[], int off, int len)来实现,从流的第off位置开始,读取len长度放入b[]数组中。

close()关闭流,同OutputStream。

举例:

InputStream inputStream = null;
try {
    inputStream = new FileInputStream("hello.txt");
    byte[] arr = new byte[1024];
    inputStream.read(arr);
    System.out.println(new String(arr));
} catch (Exception e) {
    e.printStackTrace();
} finally {
    inputStream.close();
}

首先构建一个需要读取的文件的输入流,然后通过read(byte b[])方法将流中的数据读取到字节数组中,这样就简单实现了一个读取文件的功能。但这里有一个前提,文件中的字节长度不超过1024,那如果超过这个长度,我们怎么样才能将流中数据读取到一个字节数组中呢?

可以借助ByteArrayOutputStream。通过名字可以看出是将流中数据输出到字节数组中。

InputStream inputStream = null;
try {
    inputStream = new FileInputStream("hello.txt");
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    byte[] arr = new byte[4];
    int len;
    while ((len = inputStream.read(arr)) > -1) {
        byteArrayOutputStream.write(arr, 0, len);
    }
    String data = byteArrayOutputStream.toString();
    System.out.println(data);
} catch (Exception e) {
    e.printStackTrace();
} finally {
    inputStream.close();
}

ByteArrayOutputStream的输出目标是byte数组,这个数组的长度是根据数据内容动态扩展的,当调用write方法的过程中,如果数组大小不够,会进行扩展,每次扩展长度翻倍。

BufferedInputStream和BufferedOutputStream

FileInputStream和FileOutputStream是没有缓冲的,按单个字节读写时性能比较低,虽然可以按字节数组读取来提高性能,但有时必须按字节读写,这时就需要将文件流包装到缓冲流中来提高性能。

使用方法只要在对应的流外层包上对应的缓冲类就行了,如下:

BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream("hello.txt"));
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream("hello.txt"));

文本文件和字符流

字符流

上面介绍了如何以字节流的方式处理文件,对于文本文件,字节流没有编码的概念,不能按行处理,使用不太方便,更适合用字符流来处理。

字符流和字节流类似,也是有两个基类,Reader和Writer。Reader和InputStream类似,Writer和OutputStream类似,具体的操作都是通过子类来实现。

InputStreamReader和OutputStreamWriter

InputStreamReader可以将InputStream转换为Reader,构造方法如下:

public InputStreamReader(InputStream in)
public InputStreamReader(InputStream in, String charsetName)

一个重要的参数是编码类型,对于字符流来说,编码是很重要的,相同二进制内容,不同编码情况下,可能会出现不同结果。通过传入输入流和编码类型来构造出字符输入流,如果不传编码类型,则为系统默认编码,默认编码可以通过Charset.defaultCharset()得到

使用举例

Reader reader = null;
try {
    reader = new InputStreamReader(new FileInputStream("hello.txt"));
    char[] chars = new char[1024];
    reader.read(chars);
    System.out.println(new String(chars));
}finally {
    reader.close();
}

将文件中数据读取到内存中,这段代码一次reader就读取了所有的内容,前提是文件内容不超过数组长度,如果文件内容很长,可以使用CharArrayWriter或StringWriter,后面会介绍。

OutputStreamWriter可以将OutputStream转换为Writer,构造方法如下:

public OutputStreamWriter(OutputStream out)
public OutputStreamWriter(OutputStream out, String charsetName)

构造方法和InputStreamReader类似

使用举例

Writer writer = null;
try {
    writer = new OutputStreamWriter(new FileOutputStream("hello.txt"),"UTF-8");
    writer.write("hello pipi蛋");
}finally {
    writer.close();
}

InputStreamReader和OutputStreamWriter内部都有一个类型为StreamDecoder的解码器,能将字节根据编码转换为char。

CharArrayReader和CharArrayWriter

CharArrayWriter与ByteArrayOutputStream类似,它的输出目标是char数组,这个数组长度可以根据内容动态扩展。

上面通过字符流读取文件内容的代码,我们可以优化一下,如下:

Reader reader = null;
try {
    reader = new InputStreamReader(new FileInputStream("hello.txt"));
    CharArrayWriter arrayWriter = new CharArrayWriter();
    char[] chars = new char[1024];
    int len = 0;
    while ((len = reader.read(chars))> -1) {
        arrayWriter.write(chars, 0, len);
    }
    System.out.println(arrayWriter.toString());
}finally {
    reader.close();
}

StringReader和StringWriter与CharArrayReader和CharArrayWriter类似,只是输出目标是StringBuffer,而且String和StringBuffer内部都是由char数组组成的,所以他们本质上是一样的。

BufferedReader和BufferedWriter

BufferedReader/BufferedWriter与BufferedInputStream/BufferedOutputStream类似,提供缓冲功能,只需要在外层包装一层缓冲类就行了,而且可以按行读写。

按行写入举例:

BufferedWriter writer = null;
try {
    writer = new BufferedWriter(new FileWriter("hello.txt"));
    writer.write("第一行");
    writer.newLine();
    writer.write("第二行");
    writer.newLine();
}finally {
    writer.close();
}

按行读取举例:

BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("hello.txt"));
    String s = null;
    while ((s = reader.readLine()) != null) {
        System.out.println(s);
    }
}finally {
    reader.close();
}

PrintWriter

PrintWriter有很多构造方法,可以接受文件路径名、文件对象、OutputStream、Writer等。PrintWriter是一个非常方便的类,可以直接指定文件名作为参数,可以指定编码类型,可以自动缓冲,可以字段将多种类型转换为字符串,在输出到文件时,可以优先选择该类

上面按行写入的代码,可以使用PrintWriter写为:

PrintWriter printWriter = null;
try {
    printWriter = new PrintWriter("hello.txt");
    printWriter.println("第一行");
    printWriter.println("第二行");
    printWriter.println("第三行");
}finally {
    printWriter.close();
}

可以看出printWriter相对而言,方便很多。

封装方法

字节流

复制InputSteam到OutputStream

public void copy(InputStream in, OutputStream out) throws Exception{
    byte[] arr = new byte[1024];
    int len = 0;
    while ((len = in.read(arr)) > -1) {
        out.write(arr, 0, len);
    }
}

将文件内容读取到字节数组中

public byte[] file2Array(String fileName) throws Exception{
    InputStream in = null;
    try {
        in = new FileInputStream(fileName);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        copy(in,out);
        return out.toByteArray();
    }finally {
        in.close();
    }
}

再将字节数组写入到文件中

public void array2File(String fileName,byte[] bytes) throws Exception{
    OutputStream out = null;
    try {
        out = new FileOutputStream(fileName);
        out.write(bytes);
    }finally {
        out.close();
    }
}

字符流

复制Reader到Writer中

public void copy(Reader reader, Writer writer) throws Exception{
    char[] chars = new char[1024];
    int len = 0;
    while ((len = reader.read(chars)) > -1) {
        writer.write(chars, 0, len);
    }
}

将文件内容读入到一个字符串中

public String file2String(String fileName,String encoding) throws Exception {
    BufferedReader reader = null;
    try {
        reader = new BufferedReader(new InputStreamReader(new FileInputStream(fileName), encoding));
        StringWriter writer = new StringWriter();
        copy(reader,writer);
        return writer.toString();
    }finally {
        reader.close();
    }
}

将字符串内容写入到文件中

public void string2File(String fileName,String encoding,String data) throws Exception{
    Writer writer = null;
    try {
        writer = new OutputStreamWriter(new FileOutputStream(fileName),encoding);
        writer.write(data);
    }finally {
        writer.close();
    }
}

按行写入到文件中

public void writeLines(String fileName, String encoding, Collection lines) throws Exception{
    PrintWriter writer = null;
    try {
        writer = new PrintWriter(fileName,encoding);
        for (Object line : lines) {
            writer.println(line);
        }
    }finally {
        writer.close();
    }
}

按行读取文件中内容,返回内容集合

public List<String> readLines(String fileName,String encoding)throws Exception {
    BufferedReader reader = null;
    try {
        reader = new BufferedReader(new InputStreamReader(new FileInputStream(fileName),encoding));
        List<String> list = new ArrayList<>();
        String line = null;
        while ((line = reader.readLine()) != null) {
            list.add(line);
        }
        return list;
    }finally {
        reader.close();
    }
}

总结

我们平时开发的时候对文件或者网络内容使用流操作时,可能都是使用第三方类库,比如Apache有一个类库Commons IO,里面提供了很多简单易用的方法,当然使用这些类库一点毛病都没有,但是,如果有些时候不能使用第三方类库的时候,比如开发一些通用的jar包时,肯定越少依赖越好,这时候就要熟悉jdk中的一些常用io操作了。


扫一扫,关注我

猜你喜欢

转载自blog.csdn.net/weixin_43072970/article/details/106848687