前言
因为底层设备永远只接受字节数据,所以在数据传输的时候,都要把字符串或文件等数据转成二进制数据逐一输出到某个设备中,不管输入输出设备是什么,统一抽象成相同方式,这个方式起名为IO流,对应的抽象类为InputStream
/OutputStream
,不同实现类代表不同的输入和输出设备,它们都是针对字节进行操作的。
对于字符流:字符流是字节流的包装,字符流则是直接接受字符串,它内部将串转成字节,再写入底层设备,这为我们向IO设别写入或读取字符串提供了一点点方便。
- 字节流继承
inputStream
和OutputStream
- 字符流继承自
Reader
和Writer
InputStream
提供的是字节流的读取,而非文本读取,这是和Reader
类的根本区别。
即用Reader
读取出来的是char数组或者String ,使用InputStream
读取出来的是byte数组
InputStream子类
ByteArrayInputStream
:字节数组输入流,该类的功能就是从字节数组 byte[] 中进行以字节为单位的读取,也就是将资源文件都以字节形式存入到该类中的字节数组中去,我们拿数据也是从这个字节数组中拿。PipedInputStream
:管道字节输入流,它和 PipedOutputStream 一起使用,能实现多线程间的管道通信。FilterInputStream
:装饰者模式中充当装饰者的角色,具体的装饰者都要继承它,所以在该类的子类下都是用来装饰别的流的,也就是处理类。BufferedInputStream
:缓冲流,对处理流进行装饰、增强,内部会有一个缓冲区,用来存放字节,每次都是将缓冲区存满然后发送,而不是一个字节或两个字节这样发送,效率更高。DataInputStream
:数据输入流,用来装饰其他输入流,它允许通过数据流来读写Java基本类型。FileInputStream
:文件输入流,通常用于对文件进行读取操作。File
:对指定目录的文件进行操作。ObjectInputStream
:对象输入流,用来提供对“基本数据或对象”的持久存储。通俗点讲,就是能直接传输Java对象(序列化、反序列化用)。
一、举栗子
1. 字节流InputStream读取文件内容
public static void readFileByInputStream(String fileName) {
File file = new File(fileName);
InputStream in = null;
try {
in = new BufferedInputStream(new FileInputStream(file));
byte[] buffer = new byte[1024];
// 每次读取到的字节数组的长度
int bytesRead = 0;
//从文件中按字节读取内容,到文件尾部时read方法将返回-1
while ((bytesRead = in.read(buffer)) != -1) {
String chunk = new String(buffer, 0, bytesRead);
System.out.print(chunk);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e1) {
}
}
}
}
2. 字节流OutputStream写入文件
public static void writeFileByOutputStream(String fileName) {
File file = new File(fileName);
OutputStream os = null;
try {
// 加上BufferedInputStream是增强流,可以提高输出效率,也可以直接用FileOutputStream
// FileOutputStream构造函数的第二个参数代表是否覆盖原有内容
os = new BufferedOutputStream(new FileOutputStream(file, true));
byte[] data = "append String by java program......".getBytes();
os.write(data, 0, data.length);
os.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
}
}
}
}
3. 字符流Reader读取文件内容
public static void readFileByReader(String fileName) {
File file = new File(fileName);
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(file));
String tempString = null;
int line = 1;
// 一次读入一行,直到读入null为文件结束
while ((tempString = reader.readLine()) != null) {
// 显示行号
System.out.println("line " + line + ": " + tempString);
line++;
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e1) {
}
}
}
}
4. 字符流Writer写入文件
public static void writeFileByWriter(String fileName) {
File file = new File(fileName);
PrintWriter printWriter = null;
try {
// FileWriter构造函数的第二个参数代表是否覆盖原有内容
printWriter = new PrintWriter(new FileWriter(file, true), true);
printWriter.println("testing Write to file......");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (printWriter != null) {
printWriter.close();
}
}
}
二、知识点复盘
阻塞方法
java中的阻塞式方法是指在程序调用改方法时,必须等待输入数据可用或者检测到输入结束或者抛出异常,否则程序会一直停留在该语句上,不会执行下面的语句。比如read()和readLine()方法。
InputStream方法解析
-
int read():从输入流中读取一个字节的二进制数据。
-
int read(byte[] b):将多个字节读到数组中,填满整个数组。
-
int read(byte[] b, int off, int len):从输入流中读取长度为 len 的数据,从数组 b 中下标为 off 的位置开始放置读入的数据,读完返回读取的字节数。
-
void close():关闭数据流。
-
int available():返回目前可以从数据流中读取的字节数(但实际的读操作所读得的字节数可能大于该返回值)。
-
long skip(long l):跳过数据流中指定数量的字节不读取,返回值表示实际跳过的字节数。
对数据流中字节的读取通常是按从头到尾顺序进行的,如果需要以反方向读取,则需要使用回推(Push Back)操作。在支持回推操作的数据流中经常用到如下几个方法: -
boolean markSupported():用于测试数据流是否支持回推操作,当一个数据流支持 mark() 和 reset() 方法时,返回 true,否则返回 false。
-
void mark(int readlimit):用于标记数据流的当前位置,并划出一个缓冲区,其大小至少为指定参数的大小。
-
void reset():将输入流重新定位到对此流最后调用 mark() 方法时的位置。
缓冲流工作原理
BufferedInputStream
继承FilterInputStream
;BufferedOutputStream
继承FilterOutputStream
;BufferedReader
继承Reader
;BufferedWriter
继承Writer
;
提供缓冲输入流功能。缓冲输入流相对于普通输入流的优势是,它提供了一个缓冲数组,每次调用read方法的时候,它首先尝试从缓冲区里读取数据,若读取失败(缓冲区无可读数据),则选择从文件读取数据并放入到缓冲区中,最后再将缓冲区中的内容部分或全部返回给用户。如果没有缓冲,则每次调用 read() 或 readLine() 都会导致从文件中读取字节(造成线程阻塞),并转换为字符后返回,而这是极其低效的。
简单解析FileReader
FileReader
继承了InputStreamReader
,FileReader
初始化时内部维护一个 FileInputStream
,几乎所有的字符流都离不开某个字节流实例。 FileInputStream
对象中的成员StreamDecoder
是一个解码器,用于将字节的各种操作转换成字符的相应操作。
FileReader
构造器
public FileReader(File file) throws FileNotFoundException {
super(new FileInputStream(file));
}
InputStreamReader
构造器
...
private final StreamDecoder sd;
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);
}
}
装饰者模式
BufferedInputStream/OutputStream 和 BufferedReader/Writer 提供缓冲功能,使用了装饰者模式,用于IO流类的增强。
适配器模式
InputStreamReader
是InputStream
和Reader
的适配器,是字节流通向字符流的桥梁,它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。
public static void inputStream2Reader(String fileName) {
File file = new File(fileName);
Reader reader = null;
BufferedReader bfreader = null;
try {
reader = new InputStreamReader(new FileInputStream(file), "UTF-8");
bfreader = new BufferedReader(reader);
String line;
while ((line = bfreader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e1) {
}
}
if (bfreader != null) {
try {
bfreader.close();
} catch (IOException e1) {
}
}
}
}