Java IO

本文详细介绍讲述了Java IO的相关内容,主要涉及文件,网络数据流,内存缓冲等的输入输出,版本要求jdk1.8。

概述

Java的IO包主要关注数据源的读取和输出到目标媒介。示意图如下

常用的源数据和目标媒介如下:

  • Files
  • Pipes
  • Network Connections
  • In-memory Buffers (e.g. arrays)
  • System.in, System.out, System.error

流从概念上说一个连续的数据流,既可以从流中读取数据,也可以往流中写入数据。主要分为字节流和字符流,字节流指以字节为单位进行读写,字符流指以字符为单位进行读写。
一个程序需要InputStream或者Reader从数据源读取数据,需要OutputStream或者Writer将数据写入到目标媒介中。示意图如下:

Stream特征和分类

Java IO针对不同的业务场景,不同的功能,设计了不同的类,各类用途如下:

  • File Access
  • Network Access
  • Internal Memory Buffer Access
  • Inter-Thread Communication (Pipes)
  • Buffering
  • Filtering
  • Parsing
  • Reading and Writing Text (Readers / Writers)
  • Reading and Writing Primitive Data (long, int etc.)
  • Reading and Writing Objects

通过输入、输出、基于字节或者字符、以及其他比如缓冲、解析之类的特定用途可以将Java IO流进行如下划分:

  Byte Based Character Based
  Input Output Input Output
Basic InputStream OutputStream Reader
InputStreamReader
Writer
OutputStreamWriter
Arrays ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter
Files FileInputStream
RandomAccessFile
FileOutputStream
RandomAccessFile
FileReader FileWriter
Pipes PipedInputStream PipedOutputStream PipedReader PipedWriter
Buffering BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter
Filtering FilterInputStream FilterOutputStream FilterReader FilterWriter
Parsing PushbackInputStream
StreamTokenizer
  PushbackReader
LineNumberReader
 
Strings     StringReader StringWriter
Data DataInputStream DataOutputStream  
Data – Formatted   PrintStream   PrintWriter
Objects ObjectInputStream ObjectOutputStream    
Utilities SequenceInputStream      

File类

在讨论Stream的具体使用前,我们先看看IO库里面的File类。
Java文件类以抽象的方式代表文件名和目录路径名。该类主要用于文件和目录的创建、文件的查找和文件的删除等,但File对象却不能直接访问文件内容本身,要查看内容,则需要使用IO流。
通过以下构造方法创建一个File对象。

  • 通过给定的父抽象路径名和子路径名字符串创建一个新的File实例。

      File(File parent, String child);
  • 通过将给定路径名字符串转换成抽象路径名来创建一个新 File 实例。

      File(String pathname) 
  • 根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。

      File(String parent, String child) 
  • 通过将给定的 file: URI 转换成一个抽象路径名来创建一个新的 File 实例。

      File(URI uri) 

由于其方法均比较简单,不再罗列,具体见https://docs.oracle.com/javase/8/docs/api/。一个具体的实例如下:

package com.molyeo.java.io;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;

/**
 * Created by zhangkh on 2018/7/19.
 */
public class FileDemo {
    static Logger logger = LoggerFactory.getLogger(FileDemo.class.getName());

    public static void main(String[] args) throws IOException {
        File path = new File(".");
        if (path.isDirectory()) {
            logger.info("root: directory is {}", path.getAbsolutePath());
            String[] list = path.list();
            Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
            for (String dirItem : list) {
                File file = new File(path, dirItem);
                if (file.isDirectory()) {
                    logger.info("child: {} is a directory", dirItem);
                } else {
                    logger.info("child: {} is a file", dirItem);
                }
            }
        } else {
            logger.info("root is not a directory");
        }
    }
}

查看当前项目,按照字典顺序排序后,再判断其子路径是文件还是目录,并输出相关结果。

18/07/19 17:35:12 INFO io.FileDemo: root: directory is D:\workspace_spark\SparkInAction\.
18/07/19 17:35:12 INFO io.FileDemo: child: .idea is a directory
18/07/19 17:35:12 INFO io.FileDemo: child: data is a directory
18/07/19 17:35:12 INFO io.FileDemo: child: libs is a directory
18/07/19 17:35:12 INFO io.FileDemo: child: out is a directory
18/07/19 17:35:12 INFO io.FileDemo: child: pom.xml is a file
18/07/19 17:35:12 INFO io.FileDemo: child: spark-warehouse is a directory
18/07/19 17:35:12 INFO io.FileDemo: child: SparkInAction.iml is a file
18/07/19 17:35:12 INFO io.FileDemo: child: src is a directory
18/07/19 17:35:12 INFO io.FileDemo: child: target is a directory

InputStream和OutputStream

全局类图

在前面表格中,罗列了Java IO中的流,可以看到相对复杂。我们先看字节流的层次关系。
整体的层次关系如下:

在上面的层次图中,为简便区分,用蓝色表示接口,红色表示抽象类,绿色表示类。
jdk1.8中,主要通过5个接口来定义区别不同的流,分别是Closeable,Flushable,Readable,Appendable。其中Closeable的父类AutoCloseable接口主要是用于基于try-with-resource的异常处理。
InputStream实现Closeable接口,而Closeable的父类AutoCloseable接口主要是用于基于try-with-resource的异常处理,在后续的示例代码中将会说明。

InputStream类图

具体看InputStream的类图如下:

InputStream实现Closeable接口,并有五个子类,而其子类FilterInputStream作为装饰功能类,类图如下:

输入流使用基本流程

我们先看一个从文件读取的实例,以了解IO流的使用流程。
我们将上文判断当前项目下的文件是file还是directory的例子改写一下,改写读取当前项目文件夹为data并且以data开头的文件,并输出文件内容:

package com.molyeo.java.io;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;

/**
 * Created by zhangkh on 2018/7/20.
 */
public class ByteBasedStream {
    static Logger logger = LoggerFactory.getLogger(ByteBasedStream.class.getName());

    public static void main(String[] args) throws IOException {
        File path = new File(".");
        if (path.isDirectory()) {
            logger.info("root: directory is {}", path.getAbsolutePath());
            String[] list;

            list = path.list(new DirFilter("d.*"));
            logger.info("File after first filter:");

            for (String dirItem : list) {
                File file = new File(path, dirItem);
                if (file.isDirectory()) {
                    logger.info("child: {} is a directory", dirItem);
                    String[] childList = file.list(new DirFilter("data*.txt"));
                    logger.info("File after second filter");
                    for (String childItem : childList) {

                        File childFile = new File(file, childItem);
                        if (childFile.isFile()) {
                            logger.info("Secondary child: {} is a file", childItem);
                            logger.info("start read file {}", childFile.getCanonicalPath());
                            read(childFile);
                        }
                    }
                } else {
                    logger.info("child: {} is a file", dirItem);
                }

            }


        } else {
            logger.info("root is not a directory");
        }

    }

    public static void read(File file) throws IOException {
        InputStream inputStream = null;
        try {
            inputStream = new FileInputStream(file);
            int byteData = inputStream.read();
            while (byteData != -1) {
                logger.info("byteData={}, char result={}", byteData, (char) byteData);
                byteData = inputStream.read();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }
}

其中data.txt文件内容为Hadoop,程序运行日志如下:

18/08/04 22:19:58 INFO io.ByteBasedStream: root: directory is D:\workspace_spark\SparkInAction\.
18/08/04 22:19:58 INFO io.ByteBasedStream: File after first filter:
18/08/04 22:19:58 INFO io.ByteBasedStream: child: data is a directory
18/08/04 22:19:58 INFO io.ByteBasedStream: File after second filter
18/08/04 22:19:58 INFO io.ByteBasedStream: Secondary child: data.txt is a file
18/08/04 22:19:58 INFO io.ByteBasedStream: start read file D:\workspace_spark\SparkInAction\data\data.txt
18/08/04 22:19:58 INFO io.ByteBasedStream: byteData=72, char result=H
18/08/04 22:19:58 INFO io.ByteBasedStream: byteData=97, char result=a
18/08/04 22:19:58 INFO io.ByteBasedStream: byteData=100, char result=d
18/08/04 22:19:58 INFO io.ByteBasedStream: byteData=111, char result=o
18/08/04 22:19:58 INFO io.ByteBasedStream: byteData=111, char result=o
18/08/04 22:19:58 INFO io.ByteBasedStream: byteData=112, char result=p

read()
自定义的read方法输入文件,然后我们构造FileInputStream实例,通过循环调用read()方法从FileInputStream流中读取一个字节的内容。

int byteData=inputStream.read();

数据读取后可以将返回的int类型转换成char类型。

char aChar = (char) data;

如果到达流末尾时,read方法返回-1。此时则可以关闭流。
read(byte[])

InputStream包含了2个从InputStream中读取数据并将数据存储到缓冲数组中的read()方法,他们分别是:

int read(byte[])
int read(byte, int offset, int length)

一次性读取一个字节数组的方式,比一次性读取一个字节的方式快的多,所以,尽可能使用这两个方法代替read()方法。
read(byte[])方法会尝试读取与给定字节数组容量一样大的字节数,返回值说明了已经读取过的字节数。如果InputStream内可读的数据不足以填满字节数组,那么数组剩余的部分将包含本次读取之前的数据。记得检查有多少数据实际被写入到了字节数组中。
read(byte, int offset, int length)方法同样将数据读取到字节数组中,不同的是,该方法从数组的offset位置开始,并且最多将length个字节写入到数组中。同样地,read(byte, int offset, int length)方法返回一个int变量,告诉你已经有多少字节已经被写入到字节数组中,所以请记得在读取数据前检查上一次调用read(byte, int offset, int length)的返回值。

这两个方法都会在读取到达到流末尾时返回-1。

IO流异常处理

在读取文件的那个例子中,我们看read方法真实有效的代码知识try中的一部分,而实际我们写了很多异常处理的模板方法。

public static void read(File file) throws IOException {
    InputStream inputStream = null;
    try {
        inputStream = new FileInputStream(file);
        int byteData = inputStream.read();
        while (byteData != -1) {
            logger.info("byteData={}, char result={}", byteData, (char) byteData);
            byteData = inputStream.read();
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (inputStream != null) {
            inputStream.close();
        }
    }
}

在这段代码中共有4个地方可能会抛出异常,分别是new FileInputStream(file)inputStream.read()inputStream.read()inputStream.close()
想象一下,从try块内部抛出异常。然后finally执行该块,而从finally块抛出的异常如果我们不捕获的话,将在调用堆栈中向上传播。
try-catch-finally结构显示的关闭流显得单调乏味,并且异常捕获模板代码众多,看着一点都不优雅。在java7后可以使用try-with-resource结构来处理,示例如下:

public static void read1(File file) throws IOException{
    try(InputStream inputStream=new FileInputStream(file)){
        int byteData = inputStream.read();
        while (byteData != -1) {
            logger.info("byteData={}, char result={}", byteData, (char) byteData);
            byteData = inputStream.read();
        }
    }
}

我们在read1方法中实现了和read方法一样的功能,但代码显得简洁明了。其中在try()括号内创建了FileInputStream这个资源,当程序执行离开try{}块时,该资源将会自动关闭。如果try()括号内有多个资源,资源将按与括号内创建/列出顺序相反的顺序关闭。
IO流中,资源之所以会自动关闭,是因为IO流(包括InputStream,OutputStream,Reader,Writer)均实现了AutoClosable接口,具体可参考全局类图。我们也可以将自定义类实现AutoClosable接口,然后
try-with-resource结构一起使用。

FilterInputStream

FilterInputStream主要有4个子类,可以用来修改InputStream的内部行为。

4个字类添加的功能如下:

类名 功能
DataInputStream 与DataOutputStream配合使用,以一种”可携带的方式(portable fashion)”从流里读取基础类型
BufferedInputStream 从缓冲区读取,而不是每次要用数据的时候都要进行物理读取
LineNumberInputStream 跟踪输入流的行号;有getLineNumber( )和setLineNumber(int)方法
PushbackInputStream 有一个”弹压单字节”的缓冲区,这样你就能把最后读到的那个字节再压回去。

我们用BufferedInputStream给FileInputStream方法添加缓存区,改写后的read方法如下:

public static void readWithBuffer(File file) throws IOException{
    try(FileInputStream fileInputStream=new FileInputStream(file);
        BufferedInputStream input=new BufferedInputStream(fileInputStream);
    ){
        int byteData = input.read();
        while (byteData != -1) {
            logger.info("byteData={}, char result={}", byteData, (char) byteData);
            byteData = input.read();
        }
    }
}

OutputStream类图

OutputStream的层次关系如下:


OutputStream实现CloseableFlushable接口,其装饰功能子类FilterOutputStream,类图如下:

输出流基本使用流程

输出流往往和某些数据的目标媒介相关联,比如文件,网络连接,管道等,如下将字符串apache写入当前项目data目录下的output.txt文件

package com.molyeo.java.io;
    
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;

/**
 * Created by zhangkh on 2018/8/5.
 */
public class BytedBasedOutputStream {
    static Logger logger = LoggerFactory.getLogger(ByteBasedInputStream.class.getName());

    public static void main(String[] args) throws IOException {
        File path = new File(".");
        File file = new File(path, "data/output.txt");
        logger.info(file.getAbsolutePath());
        write(file, "apache");
    }

    public static void write(File file, String content) throws IOException {
        try (FileOutputStream output = new FileOutputStream(file)) {
            byte[] bytesArray = content.getBytes();
            for (int i = 0; i < bytesArray.length; i++) {
                output.write(bytesArray[i]);
                logger.info("byteData={}, char result={}", bytesArray[i], (char) bytesArray[i]);
            }
            output.flush();
        }
    }

}

程序输出如下:

18/08/05 13:00:23 INFO io.BytedBasedOutputStream: D:\workspace_spark\SparkInAction\.\data\output.txt
18/08/05 13:00:23 INFO io.BytedBasedOutputStream: byteData=97, char result=a
18/08/05 13:00:23 INFO io.BytedBasedOutputStream: byteData=112, char result=p
18/08/05 13:00:23 INFO io.BytedBasedOutputStream: byteData=97, char result=a
18/08/05 13:00:23 INFO io.BytedBasedOutputStream: byteData=99, char result=c
18/08/05 13:00:23 INFO io.BytedBasedOutputStream: byteData=104, char result=h
18/08/05 13:00:23 INFO io.BytedBasedOutputStream: byteData=101, char result=e

OutputStream常用方法如下。

write(byte[])
OutputStream同样包含了将字节数据中全部或者部分数据写入到输出流中的方法,分别是write(byte[])write(byte[], int offset, int length)
write(byte[])把字节数组中所有数据写入到输出流中。
write(byte[], int offset, int length)把字节数据中从offset位置开始,length个字节的数据写入到输出流。

flush()
OutputStreamflush()方法将所有写入到OutputStream的数据冲刷到相应的目标媒介中。比如,如果输出流是FileOutputStream,那么写入到其中的数据可能并没有真正写入到磁盘中。即使所有数据都写入到了FileOutputStream,这些数据还是有可能保留在内存的缓冲区中。通过调用flush()方法,可以把缓冲区内的数据刷新到磁盘(或者网络,以及其他任何形式的目标媒介)中。

close()
当你结束数据写入时,需要关闭OutputStream。通过调用close()可以达到这一点。因为OutputStream的各种write()方法可能会抛出IO异常,所以你需要把调用close()的关闭操作方在finally块中执行。如果使用基于try-with-resource的异常处理程序则由于实现了AutoCloseable接口,不用显示关闭。

FilterOutputStream

FilterOutputStream主要有3个子类,可以用来修改OutputStream的内部行为。

3个字类添加的功能如下:

类名 功能
DataOutputStream 与DataInputStream配合使用,可以用可携带的方式往流里写基本类型
BufferedOutputStream 写入缓冲区,而不是每次往流里写数据,都要进行物理操作
PrintStream 负责生成带格式的输出。DataOutputStrem负责数据的存储,而PrintStream负责数据的显示。

我们用BufferedOutputStreamFileOutputStream方法添加缓存区,改写后的write方法如下:

public static void writeWithBuffer(File file, String content) throws IOException {
    try (FileOutputStream fileOutputStream = new FileOutputStream(file);
        BufferedOutputStream output=new BufferedOutputStream(fileOutputStream)
    ) {
        byte[] bytesArray = content.getBytes();
        for (int i = 0; i < bytesArray.length; i++) {
            output.write(bytesArray[i]);
            logger.info("byteData={}, char result={}", bytesArray[i], (char) bytesArray[i]);
        }
        output.flush();
    }
}

Reader和Writer

Reader类图

Reader类图如下,Reader实现Readable和Closeable接口,根据不同的功能有众多的子类。

子类必须实现的方法只有 read() 和 close()

read()

public int read() throws IOException

用于读取单个字符。在字符可用、发生 I/O 错误或者已到达流的末尾前,此方法一直阻塞。用于支持高效的单字符输入的子类应重写此方法。

返回:作为整数读取的字符,范围在 0 到 65535 之间 ( 0x00-0xffff),如果已到达流的末尾,则返回 -1

read(char[] cbuf,int off,int len)

public abstract int read(char[] cbuf,int off,int len) throws IOException

将字符读入数组的某一部分。在某个输入可用、发生 I/O 错误或者到达流的末尾前,此方法一直阻塞。

参数:  
    cbuf - 目标缓冲区
    off - 开始存储字符处的偏移量
    len - 要读取的最多字符数
返回:
  读取的字符数,如果已到达流的末尾,则返回 -1
抛出:
   IOException - 如果发生 I/O 错误

输入字符流使用流程

这里我们还是读取当前项目data目录下的data.txt文件,示例代码如下:

package com.molyeo.java.io;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;

/**
 * Created by zhangkh on 2018/8/5.
 */
public class CharacterBasedReader {
    static Logger logger = LoggerFactory.getLogger(CharacterBasedReader.class.getName());

    public static void main(String[] args) throws IOException {
        File path = new File(".");
        if (path.isDirectory()) {
            logger.info("root: directory is {}", path.getAbsolutePath());
            String[] list;

            list = path.list(new DirFilter("d.*"));
            logger.info("File after first filter:");

            for (String dirItem : list) {
                File file = new File(path, dirItem);
                if (file.isDirectory()) {
                    logger.info("child: {} is a directory", dirItem);
                    String[] childList = file.list(new DirFilter("data*.txt"));
                    logger.info("File after second filter");
                    for (String childItem : childList) {

                        File childFile = new File(file, childItem);
                        if (childFile.isFile()) {
                            logger.info("Secondary child: {} is a file", childItem);
                            logger.info("start read file {}", childFile.getCanonicalPath());
                            read(childFile);
                        }
                    }
                } else {
                    logger.info("child: {} is a file", dirItem);
                }

            }


        } else {
            logger.info("root is not a directory");
        }

    }
    public static void read(File file) throws IOException{
        try(FileReader reader=new FileReader(file)){
            int byteData = reader.read();
            while (byteData != -1) {
                logger.info("byteData={}, char result={}", byteData, (char) byteData);
                byteData = reader.read();
            }
        }
    }
}

输出结果如下:

18/08/05 18:45:15 INFO io.CharacterBasedReader: root: directory is D:\workspace_spark\SparkInAction\.
18/08/05 18:45:15 INFO io.CharacterBasedReader: File after first filter:
18/08/05 18:45:15 INFO io.CharacterBasedReader: child: data is a directory
18/08/05 18:45:15 INFO io.CharacterBasedReader: File after second filter
18/08/05 18:45:15 INFO io.CharacterBasedReader: Secondary child: data.txt is a file
18/08/05 18:45:15 INFO io.CharacterBasedReader: start read file D:\workspace_spark\SparkInAction\data\data.txt
18/08/05 18:45:15 INFO io.CharacterBasedReader: byteData=72, char result=H
18/08/05 18:45:15 INFO io.CharacterBasedReader: byteData=97, char result=a
18/08/05 18:45:15 INFO io.CharacterBasedReader: byteData=100, char result=d
18/08/05 18:45:15 INFO io.CharacterBasedReader: byteData=111, char result=o
18/08/05 18:45:15 INFO io.CharacterBasedReader: byteData=111, char result=o
18/08/05 18:45:15 INFO io.CharacterBasedReader: byteData=112, char result=p

需要注意的是,Java内部使用UTF8编码表示字符串。输入流中一个字节可能并不等同于一个UTF8字符
,如果你从输入流中以字节为单位读取UTF8编码的文本,并且尝试将读取到的字节转换成字符,你可能会得不到预期的结果。
如果输入的文件不是UTF8编码的话,由于FileReader不能指定编码,则需要利用字节流,然后利用转换流将字节流转换为字符流。

InputStream inputStream = new FileInputStream("D:\workspace_spark\SparkInAction\data\data.txt");
Reader reader = new InputStreamReader(inputStream, "UTF-8");

Writer类图

Writer的类图如下,主要实现Appendable,Flushable,Closeable接口,根据不同的功能有众多的子类。


主要的api接口如下:
write(String str)
public void write(String str) throws IOException
写入字符串。
参数:
str – 要写入的字符串
抛出:
IOException – 如果发生 I/O 错误

write(String str,int off,int len)

public void write(String str,int off,int len) throws IOException
写入字符串的某一部分。
参数:
str - 字符串
off - 相对初始写入字符的偏移量
len - 要写入的字符数
抛出:
IndexOutOfBoundsException - 如果 off 或 len 为负,或者 off+len 为负或大于给定字符串的长度
IOException - 如果发生 I/O 错误

flush()

public abstract void flush() throws IOException
刷新该流的缓冲。如果该流已保存缓冲区中各种 write() 方法的所有字符,则立即将它们写入预期目标。然后,如果该目标是另一个字符或字节流,则将其刷新。因此,一次 flush() 调用将刷新 Writer 和 OutputStream 链中的所有缓冲区。
如果此流的预期目标是由底层操作系统提供的一个抽象(如一个文件),则刷新该流只能保证将以前写入到流的字节传递给操作系统进行写入,但不保证能将这些字节实际写入到物理设备(如磁盘驱动器)。
抛出:
IOException - 如果发生 I/O 错误

输出字符流使用流程

输出流往往和某些数据的目标媒介相关联,比如文件,网络连接,管道等,如下将字符串apache写入当前项目data目录下的output.txt文件

package com.molyeo.java.io;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

/**
 * Created by zhangkh on 2018/8/5.
 */
public class CharacterBasedWriter {
    static Logger logger = LoggerFactory.getLogger(CharacterBasedWriter.class.getName());

    public static void main(String[] args) throws IOException {
        File path = new File(".");
        File file = new File(path, "data/output.txt");
        logger.info(file.getAbsolutePath());
        write(file, "apache");

    }

    public static void write(File file, String content) throws IOException {
        try (FileWriter output = new FileWriter(file)) {
            output.write(content);
            output.flush();
        }
    }
}

示例中以文件作为参数构造实例FileWriter,将会新写入的内容将会覆盖原文件。
以下的构造函数取文件名和一个布尔变量作为参数,布尔值表明你是想追加还是覆盖该文件。例子如下:

Writer writer = new FileWriter(file, true); //appends to file
Writer writer = new FileWriter(file, false); //overwrites file

调用write方法写入具体的值,调用flush接口刷新缓冲区,将数据流传递给操作系统进行写入。
需要注意的是,上述输出的内容编码格式为UTF8,如果要输出其他编码格式,和Reader一样,要利用字节流,然后转换为字符流后以便于操作。

OutputStream outputStream = new FileOutputStream(file,charsetName);
Writer writer = new OutputStreamWriter(outputStream);
writer.write("apache");
writer.close();

字节流和字符流转换

输入字节流转换
InputStreamReader是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集,即UTF-8编码。
InputStreamReader的构造函数输入字节流

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类的forInputStreamReader方法,设置var2为null

public static StreamDecoder forInputStreamReader(InputStream var0, Object var1, String var2) throws UnsupportedEncodingException {
    String var3 = var2;
    if(var2 == null) {
        var3 = Charset.defaultCharset().name();
    }

    try {
        if(Charset.isSupported(var3)) {
            return new StreamDecoder(var0, var1, Charset.forName(var3));
        }
    } catch (IllegalCharsetNameException var5) {
        ;
    }

    throw new UnsupportedEncodingException(var3);
}

进而调用类CharsetdefaultCharSet方法,设置编码格式为UTF-8。

public static Charset defaultCharset() {
    if (defaultCharset == null) {
        synchronized (Charset.class) {
            String csn = AccessController.doPrivileged(
                new GetPropertyAction("file.encoding"));
            Charset cs = lookup(csn);
            if (cs != null)
                defaultCharset = cs;
            else
                defaultCharset = forName("UTF-8");
        }
    }
    return defaultCharset;
}

其他指定编码格式的构造函数如下:

public InputStreamReader(InputStream in, String charsetName) throws UnsupportedEncodingException
public InputStreamReader(InputStream in, Charset cs) 
public InputStreamReader(InputStream in, CharsetDecoder dec) 

每次调用 InputStreamReader 中的一个 read() 方法都会导致从底层输入流读取一个或多个字节。要启用从字节到字符的有效转换,可以提前从底层流读取更多的字节,使其超过满足当前读取操作所需的字节。
为了达到最高效率,可要考虑在BufferedReader内包装InputStreamReader。例如:

BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

如下我们看一个实例,从控制台输入字符串,并将输入字节流转换为字符流。

package com.molyeo.java.io;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;

/**
 * Created by zhangkh on 2018/8/22.
 */
public class StreamTransform {
    public static void main(String[] args) {

        String readStr = "";
        try (InputStreamReader inputStreamReader = new InputStreamReader(System.in, "UTF-8");
             BufferedReader bufferReader = new BufferedReader(inputStreamReader)
        ) {
            System.out.println("Please enter a string");
            readStr = bufferReader.readLine();
            System.out.println("The input is " + Integer.valueOf(readStr));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }
    }
}

输出字节流转换
OutputStreamWriter 是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。
每次调用 write() 方法都会导致在给定字符(或字符集)上调用编码转换器。在写入底层输出流之前,得到的这些字节将在缓冲区中累积。可以指定此缓冲区的大小,不过,默认的缓冲区对多数用途来说已足够大。注意,传递给 write() 方法的字符没有缓冲。
为了获得最高效率,可考虑将 OutputStreamWriter 包装到 BufferedWriter 中,以避免频繁调用转换器。例如:

Writer out = new BufferedWriter(new OutputStreamWriter(System.out));

Java IO 的一般使用原则 :

按数据来源或去向

文件: FileInputStream, FileOutputStream,FileReader, FileWriter
byte[] : ByteArrayInputStream, ByteArrayOutputStream
Char[]: CharArrayReader, CharArrayWriter
String: StringBufferInputStream, StringBufferOuputStream,StringReader, StringWriter
网络数据流: InputStream, OutputStream, Reader, Writer

按是否格式化输出

格式化输出: PrintStream, PrintWriter

按是否要缓冲

缓冲: BufferedInputStream, BufferedOutputStream, BufferedReader, BufferedWriter

按数据格式

二进制格式(只要不能确定是纯文本的) : InputStream, OutputStream 及其所有带 Stream 结束的子类。
纯文本格式(含纯英文与汉字或其他编码方式); Reader, Writer 及其所有带 Reader, Writer 的子类。

本文详细介绍讲述了Java IO的相关内容,涉及到字节流和字符流的设计,使用原则等。

本文参考:

http://tutorials.jenkov.com/java-io/index.html

版权声明:本文为molyeo原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/molyeo/p/9523878.html