摘要:本篇文章主要讲javaIO流
这么多流能梳理下嘛,有什么区别啊,什么场合用哪个?
一、流的概念
流是内存与存储设备之间传输数据的通道
数据借助流传输 相当于 水借助水管传输
二、流的分类
所有流都是基于抽象类实现的,Reader、Writer、InputStream、OutputStrean都是抽象类,InputStreamReader和OutputStreamWriter的父类分别是Reader和Writer
按流向分:
输入流(Input):将存储设备中的内容读入内存
输出流(Output:将内存中的内容写入到存储设备中
按单位分:
字节流:在IO过程中以字节为单位,可以读写所有数据
字符流:在IO过程中以字符为单位(1-n个字节),只能读写文本数据
按功能:
节点流:具有实际传输数据的读写功能
过滤流:在节点流的基础之上增强功能
三、字节流:off?
字节:数字
一、抽象类
InputStream类:
抽象类:所有输入流的超类
1
2
3
4
5
6
7
8 //byte 字节
int available();//该次可读字节数
void close();
int read();//从输入流中读入下一个字节
int read(byte[] b);//读入一定数量的字节,并将其存储到缓冲区数组b中
int read(byte[] b,int off, int len);//将输入流中最多len个数据字节读入byte数组:off?
void mark();//在输入流中标记当前位置
void reset();//将此流重新定位到最后一次对此输入流调用mark方法时的位置
OutputStream类:
抽象类:所有输出流的超类
1
2
3
4
5
6 //byte 字节
void close();
void flush();//刷新此输出流并强制写出所有缓冲的输出字节
int write(int b);//将指定的字节写入此输出流
int write(byte[] b);//将b.length个字节从指定的byte数组写入此输出流
int write(byte[] b,int off, int len);//将b.length个字节从指定的byte数组,偏移off后,写入此输出流
二、实现类
1、FileInputStream
1 | public static void main(String[] args) throws Exception{ |
注:byte字节数组nums中输出时转回字符的方法
System.out.println(new String(nums));
2、FileOutputStream
1 | public static void main(String[] args) throws Exception{ |
关于追加的问题:
- 追加还是删除取决于构造输出流对象时true还是false
- 首先明确追加是指这一次书写前的文件原内容会不会被删除,即无论true还是false,这一次的内容会写进去
- 默认是false,可以手动设为true
这两个流是节点流(低级流),其他所有处理流(高级流、包装流)都必须基于节点流实现(包装节点流)
三、字节缓冲流
- (属于处理流:处理流的作用是增强其他流的功能→包装作用)
- 提高IO效率,减少访问磁盘的次数由缓冲区到内存、由硬盘到缓冲区
- 数据存储在缓冲区中,flush是将缓存区的内容写入文件中,也可以直接close(close会自动写入)
1、BufferedInputStream
1 | //1、创建Input类,注意BufferedInputStream需要FileIS作为参数 |
BufferedInputStream比FileInputStream多了一个缓冲区,执行read时先从缓冲区读取,当缓冲区数据读完时再把缓冲区填满。而不是每次都调用系统底层服务
因此,当每次读取的数据量很小时,FileInputStream每次都是从硬盘读入,而BufferedInputStream大部分是从缓冲区读入。读取内存速度比读取硬盘速度快得多,因此BufferedInputStream效率高。
2、BufferedOutputStream
1 | //创建 |
效率更高的原因是:
并非没写入一个字符就调用系统底层服务,而是等缓冲区满了再写。当然也可以及时手动刷新,防止写了一半断电,数据丢失
关于相对路径
java的相对路径是当前项目最外层目录,如本地newWorld项目
1
2 FileInputStream fileInputStream = new FileInputStream("test.txt");
FileOutputStream fileOutputStream = new FileOutputStream("TESTT.txt");如上的文件复制,会把newWorld目录下的test内容复制到TESTT中
四、编码方式
ASCII码共127位,即一个字节;
UTF-8对中文而言,一个汉字3个字节。
GBK是2个字节
当编码方式和解码方式不一致时,会出现乱码
五、字符流
引入:字节流存在的问题
对于中文的UTF-8而言,一个汉字占三个字节,而字节流读入是将一个字节作为一个字符,这样显然会出现问题。所以采用字符流
一个汉字,就是一个字符,也就是一个char。
char是一个字符,而不是一个字节
0 字符流的父类(抽象类)
Reader:字符输入流
Writer:字符输出流
1 文件字符流
1、FileReader
1 | // |
2、FileWriter
1 | // |
字节流可以复制任意文件,字符流只能复制文本文件
2、字符缓冲流
- 高效读写
- 支持输入换行符(newLine)
- 可一次写一行、读一行
- 需要文件字符流作为节点流
BufferedReader
1 | public static void main(String[] args) throws Exception{ |
BufferedWriter
1 | public static void main(String[] args) throws Exception { |
注:
1 | bufferedWriter.write(str); |
不会自动换行,即使源文件里面有换行,一定要newLine
六、打印流
PrintWriter:
封装了print()/println()方法,支持写入后换行
支持数据原样打印
七、转换流(桥转换流)
InputStreamReader
OutputStreamWriter
字节字符的集合体:
Input、Output字节 Reader、Writer字符
作用:
- 字节流输入时转换成字符流,字符流输出时转换为字节流
- 可设置字符的编码方式
1 | public static void main(String[] args) throws Exception{ |
真相大白(关于InputStreamReader的默认编码方式问题)
记一次坑:附源码
下午在看《疯狂java讲义的时候》,书上介绍说,字符流以字符作为读写的基本单位(2个字节)
产生了一个疑问,FileReader也是字符流,为什么FileReader可以读UTF-8中文呢?要知道UTF-8表示的中文是以三个字节存储一个汉字的。那为什么不会乱码呢
经查阅资料,发现FileReader的继承结构是这样的:
1
2
3
4 java.lang.Object
java.io.Reader
java.io.InputStreamReader
java.io.FileReader也就是说,FileReader是InputStreamReader的子类,那么FileReader的编码方式其实就是InputStreamReader的编码方式,而ISR的编码方式在没有明确指明时是UTF-8,所以处理GBK(ANSI)的文件时,就乱码了
上源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45 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);
}
}
//上面的try语句中null的意思就是没有显示指明编码方式,那么我们接下来看一看StreamDecoder.forInputStreamReader()的处理方式
public static StreamDecoder forInputStreamReader(InputStream in,
Object lock,
String charsetName)
throws UnsupportedEncodingException
{
String csn = charsetName;
if (csn == null)
csn = Charset.defaultCharset().name();
//调用了Charset.defaultCharset().name()方法
try {
if (Charset.isSupported(csn))
return new StreamDecoder(in, lock, Charset.forName(csn));
} catch (IllegalCharsetNameException x) { }
throw new UnsupportedEncodingException (csn);
}
//那么我们继续看Charset.defaultCharset().name()有什么名堂
private static volatile Charset defaultCharset;//null
public static Charset defaultCharset() {
if (defaultCharset == null) {
synchronized (Charset.class) {
String csn = GetPropertyAction
.privilegedGetProperty("file.encoding");
Charset cs = lookup(csn);
if (cs != null)
defaultCharset = cs;
else
defaultCharset = sun.nio.cs.UTF_8.INSTANCE;
//上面的代码看不懂,也一眼看到这个显眼的UTF-8了吧
}
}
return defaultCharset;
}
八:File类
- file对象:代表一个文件或一个文件夹
- File类用来操作文件(夹)
FileInputStream(地址)
FileOutputStream(地址)
九、 对象流
对象流:ObjectOutputStream ObjectInputStream
增强了缓冲区的功能
增强了读写8种基本类型和字符串功能
增强了读写对象的功能
readObject()从流中读取一个对象:反序列化
writeObject(Object obj)向流种写入一个对象:序列化
1 ObjectOutputStream
1 | public static void main(String[] args) throws Exception { |
注意:类必须实现标记接口
1 | public class Student implements Serializable {} |
标记接口没有任何方法属性,起到标记作用
除了类以外,类中的对象属性(对象作为属性)也要实现标记接口
2 ObjectInputStream
1 | public static void main(String[] args) throws Exception{ |
注意:不能读多次
如果文件中只有一个类,读了两次,就不抛出EOF异常
3 一些注意
序列化的意义:序列化是Java中实现持久化存储的一种方法,将对象转为二进制文件。
关于序列化版本号ID
1
private static final long serialVersionUID = 2L;
序列化版本号ID的作用原理:
要序列化的类必须设置一个序列化版本号ID,且该版本号尽量保持不变
java在序列化的时候,会把这个版本号随着对象的数据一起封装到二进制文件中,在反序列化的时候,将这个版本号与本地类对比,如果不相同则拒绝反序列化。
意义:保证序列化和反序列化的是同一个类
关于这个本地类有点迷???
使用transient(瞬时的,只存在于内存中)修饰属性,这个属性就不能被序列化,反序列化的时候就成为默认值
静态属性也不能序列化(static):直接在类里面了还序列化个求啊
序列化多个对象:把多个对象放到集合里面去
1
2List<Student> students = new ArrayList<>();
ArrayList<Student> arr =(ArrayList<Student>) ois.readObject();