从零学习Java之旅 Part6 I/O流部分
-
- IO 产生的原因
- IO流
-
- IO流的分类
- IO流的基类
- 字节流
-
- 输出流OuputStream
-
- FileOutputStream构造方法
- FileOutputStream使用
- 字节流写数据常见问题
- 给I/0流操作加上异常处理
- 输入流InputStream
-
- FileInputStream构造方法
- jvm做了什么
- FileInputStream使用
- 一个字节数组效率高
- 缓冲流
IO 产生的原因
在操作系统中,一切数据都以文件的形式存储。 需要长久保存的文件数据,存储在外部设备。
程序运行时,所有的数据都需要在内存中,同时,内存的大小有限,因此常常需要在内存和外设之间交换数据,即I/O。
而Java语言主要通过输入流和输出流,完成I/O的功能,从而实现和外设的数据交互。
IO流
IO流用来处理JVM和外部设备之间的数据传输,Java通过流(Stream)的方式,完成数据的传输过程。
IO流的分类
数据流动的方向
- 输入流
对应数据的读入,将外设数据读入内存 - 输出流
对应数据的写出,将数据写入到外设
流中的内容(数据类型)
- 字节流
流中的数据,是以字节为单位的二进制数据 - 字符流
字符流,是以字符为单位的字符数据
区分使用字节流还是字符流
字符流:专门用于传输文本数据,文本编辑器可以打开,并且人可以看懂的数据。
字节流:字节流可以操作一切类型的数据,只是有时操作字符数据不太方便,所以字符数据专门交给字符流。
当不确定所要传输的数据类型,一律使用字节流
IO流的基类
字节流
- 输入流InputStream(抽象类)
- 输出流OutputStream(抽象类)
字符流
- 输入流Reader(抽象类)
- 输出流Writer(抽象类)
都是抽象类,作为所有对应流的抽象父类。
由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。
如:InputStream的子类FileInputStream。
如:Reader的子类FileReader
字节流
输出流OuputStream
实例化:OutputStream是抽象类,无法直接实例化,只能间接实例化,因为,通常操作的都是文件数据,所以使用其操作文件的具体子类FileOutputStream实例化。
FileOutputStream构造方法
- FileOutputStream(String name)
向由参数name(路径名字符串)指定的目标文件中写入数据。 - FileOutputStream(File file)
向由参数file(File对象)指定的目标文件中写入数据。
FileOutputStream使用
使用,即向外设写入数据
- public void write(int b)
将指定的字节写入此输出流。常规协定:向输出流写入一个字节。要写入的字节是参数b的八个低位,b的24个高位将被忽略。 - public void write(byte[] b)
将b.length个字节从指定的byte数组写入此输出流,实际开发中不使用该方法(不免出错)。 - public void write(byte[] b,int off,int len)
将指定byte数组中从偏移量off开始的len个字节写入此输出流。
字节流写数据常见问题
创建字节输出流到底做了哪些事情?
- FileOutputStream会先在操作系统中,找目标文件。
a. 如果说目标文件不存在,FileOutputStream创建这个文件。
b. 如果改文件存在,则不再重新创建,清空文件内容,准备从文件最开始的地方写入(从文件头开始写)。 - 在内存中,创建FileOutputStream对象。
- 在FileOutputStream和目标文件之间建立数据传输通道。
数据写成功后,为什么要close()?
关闭此输出流并释放与此流有关的所有系统资源。close常规协定:关闭后不能再使用,也不能打开。
如何实现数据的换行? 利用换行符
windows操作系统: ‘\r’’\n’
linux,Mac OS: ‘\n’
对于一些高级记事本,都可以识别。
但是系统自带的不能识别。因为不同操作系统中,默认换行符表示不一样。
有一个方法可以自动获取和操作系统相关的换行符:
System lineSeparator = System.lineSeparator();
System.out.println("lineSeparator");
写入一个换行符对象名.write(System.lineSeparator().getBytes());
如何实现数据的追加写入?
FileOutputStream(File file, boolean append)
创建一个向指定 File 对象表示的文件中写入数据的文件输出流。如果第二个参数为 true,则将字节写入文件末尾处,而不是写入文件开始处。
FileOutputStream(String name, boolean append)
创建一个向指定name的文件中写入数据的文件输出流。
给I/0流操作加上异常处理
子主题 1
输入流InputStream
实例化:InputStream是抽象类,无法直接实例化,只能间接实例化,因为,通常操作的都是文件数据,所以使用其操作文件的具体子类FileInputStream实例化。
FileInputStream构造方法
FileInputStream(File file)
FileInputStream(String name)
创建一个从目标文件中,以字节为单位读取数据到内存,文件字节输入流对象。
读取数据的目标文件,由参数对象file/name来指定。
jvm做了什么
- FileInputStream对象被创建之前,jvm会首先到操作系统中,找目标文件:
a.找到,就不做任何额外操作
b.找不到,则直接抛出异常FileNotFoundException - 在jvm内存中,创建FileInputStream对象。
- 在FileInputStream对象和目标文件之间建立通信。
FileInputStream使用
使用,即从外设读入数据。
- public int read()
从输入流中读取数据的下一个字节,返回0到255范围内的int字节值。如果因为已经到达流末尾而没有可用的字节,则返回值-1。 - public int read(byte[] b)
从输入流中读取一定数量的字节,并将其储存在缓冲区数组b中。返回读入缓冲区的总字节数,如果因为已经到达流末尾而不在有数据可用,则返回值-1。
实际开发中,我们习惯是,通常给1024的整数倍。 - public int read(byte[] b,int off,int len)
将输入流中最多len个数据字节读入byte数组。
返回值读入缓冲区的总字节数,如果因为已经到达流末尾而不在有数据可用,则返回值-1。
尝试读取len个字节,但读取的字节不能小于该值。
指定了off之后,最多还可以向字节数组填充的数据b.length - off个。
一个字节数组效率高
一次读入或写出一个字节效率高,还是一次读入或写出一个字节数组效率高。
原因
每次,读入或写出,即每次和外设的数据交互都需要依赖操作系统内核实现。
这意味着每次读入或写出,都需要付出额外的通信代价(时间)。
一次读入或写出一个字节数组的数据,平均到每个字节,付出的额外代价少很多。
缓冲流
目的:提升数据传输效率。
产生原因:字节流一次读写一个数组的速度明显比一次读写一个字节的速度快很多。
这是加入了数组这样的缓冲区效果,java本身在设计的时候,也考虑到了这样的情况,所以提供了字节缓冲区流 。
- 字节缓冲输出流BufferedOutputStream(OutputStream out)
创建一个新的缓冲输出流,以将数据写入指定的底层输出流。 - 字节缓冲输入流BufferedInputStream(InStream in)
创建一个新的缓冲输出流,并保存其参数,即输入流in,一边将来使用。
看起来缓冲流,是通过包装了一个底层得到的,这种流我们统称为包装流。
在关闭流的时候,只需要关闭包装流,不需要关闭包装流所包装的底层流,因为包装流自己会负责自己所包装的底层流。
缓冲流注意事项:
使用缓冲字节输出流的write方法,写数据到外部设备,当我们写入少量数据的时候(缓冲区数据还没满)默认情况下,数据是不会写入外部设备。
如果我们希望,把缓冲区中的数据,强行写入底层流,从而写入外部设备,可以使用:
- 缓冲流对象上调用.flush
public void flush()刷新此缓冲的输出流。这迫使所有缓冲的输出字节被写出到底层输出流中。 - 缓冲流对象.close()
public void close()关闭此输出流并释放于此流有关的系统资源。
补充:
装饰者模式
1、最大的优点,基于一个已有的类,在这基础上实现更强大的功能。
2、装饰类和原类能相互替代。