IO流(输入输出流)
- 按照流向分
-
- 输入流:从硬盘上读取数据到内存(读)
-
- 输出流:从内存写出数据到硬盘(写)
一个文件在传输过程中经历了很多次的拷贝,IO性能很低
零拷贝、Nio附加题!!!
- 按照操作单元分
-
- 字节流:是一个字节一个字节的操作。二进制操作。操作任意类型的文件
-
- 字符流:是一个字符一个字符的操作,一个字符两个字节,主要用来处理文本文件
- 按照角色划分
-
- 节点流:直接操作一个特定的IO设备
-
- 处理流:在节点流的基础上,做进一步处理
Java中输入/输出流常用的流:
字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 | |
---|---|---|---|---|
抽象基类 | InputStream | OutputStream | Reader | Writer |
操作对象 | ObjectInputStream | ObjectOutputStream | --- | --- |
访问文件(节点流) | FileInputStream | FileOutputStream | FileReader | FileWrite |
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
File类不能够操作文件的内容
流到底怎么用?
输入流:就是一点一点的往内存中读数据
- 字节输入流
创建一个FileInputStream对象
定义一个标记,用来控制输入流的读取
循环读取,如果读到-1,说明读取到了文件的末尾,循环结束
关闭资源
@Test
public void test01() {
InputStream inputStream = null;
try {
inputStream = new FileInputStream("e:/aaa.txt");
// 开始读的操作,read方法,返回值是int,当返回值为-1时,说明文件读取到了末尾
// 读取文件是否结束的标记
int read;
// 字节流读数据的时候一个字节一个字节去读
// 循环读取
while((read = inputStream.read()) != -1) {
System.out.print(read + " ");
}
System.out.println();
System.out.println("读取完毕,再读一次。。。。");
// 字节流读数据的时候一个字节一个字节去读
// 循环读取
while((read = inputStream.read()) != -1) {
System.out.print(read + " ");
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
// 关闭流
inputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
注意: 当一个流读完了就没有了,不能再读了。当一个流读完之后就会默认调用mark和reset方法来进行记录和重置,这个流就已经重置到了上次读完的位置,所以无法再次读取内容,并不是读完一次之后就关闭
- 字节输出流
FileOutputStream构造器:
-
- 传入参数true,则代表在原有的内容上追加,不覆盖
-
- 传入参数false:则代表覆盖原有的内容,不追加
@Test
public void test02() {
OutputStream outputStream = null;
try {
outputStream = new FileOutputStream("e:/aaa.txt", true);
// 一个字节一个字节的写
outputStream.write("\r\n".getBytes());
outputStream.write("八月正午的阳光都没你耀眼".getBytes());
System.out.println("数据写出成功...");
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if(Objects.nonNull(outputStream)){
outputStream.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
- boolean append参数:如果传入true,则代表在原有基础上追加,不覆盖;如果传入false,或者不传,覆盖原有内容
- 写的操作,目标文件如果不存在,会自动新建。
字符流
- 字符处理流——只能处理纯文本文件
- 访问文件
public class Ch04 {
@Test
public void test01() {
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
fileInputStream = new FileInputStream("E:/aaa.txt");
fileOutputStream = new FileOutputStream("E:/bbb.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
int i = 0;
byte[] read = new byte[1024];
try {
//fileInputStream.read(read))把读取到的数据存入数组中,每次读取1024个字节,如果read()内不填默认读取1字节
if (((i = fileInputStream.read(read)) != -1)) {
// System.out.println(i + " ");
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (Objects.nonNull(fileInputStream)) {
fileOutputStream.write(read);
fileOutputStream.write("\n".getBytes());//写入时,提供换行功能
fileInputStream.close();
}
} catch (Exception e) { //为确保流的正常关闭,抛异常时,选择Exception来抛异常
e.printStackTrace();
}
try {
if (Objects.nonNull(fileOutputStream)) {
fileOutputStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 缓冲流
@Test
public void test01() {
BufferedReader bufferedReader = null;
BufferedWriter bufferedWriter = null;
try {
bufferedReader = new BufferedReader(new FileReader("E:/aaa.txt"));
bufferedWriter = new BufferedWriter(new FileWriter("E:/bbb.txt"));
String str;
while (((str = bufferedReader.readLine()) != null)) {
System.out.println(str + " ");
bufferedWriter.write(str);
bufferedWriter.write("\n");
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
IOUtil.IOclose(bufferedReader,bufferedWriter);
}
序列化与反序列化
- 序列化:将对象写入到IO流中,将内存模型的对象变成字节数字,可以进行存储和传输
- 反序列化:从IO流中恢复对象,将存储在硬盘上或者在网络中接收的数据恢复成对象模型
- 使用场景:所有可在网络上,传输的对象都必须是可序列化的,否则会报错,所有保存在硬盘上的对象也必须可序列化
- 注意事项:
-
- 序列化版本号:反序列化必须拥有class文件,但随着项目的升级,class文件也会升级,序列化保障升级的兼容性【java序列化提供了一个版本号】
-
- 版本号是可以自由指定,如果不指定,JVM会根据类信息自己计算一个版本号,所以无法匹配,则报错
-
- 不指定版本号,还有一个隐患,不利于JVM的移植,可能class文件没有改,但是不同的jvm计算规则不一样,导致无法反序列化
-
- 如果只修改了方法,反序列化不受影响的,无序修改版本号
-
- 修改了静态变量static,瞬态变量transient,反序列化也不受影响,无需修改版本号
总结:
- 所有需要网络传输的对象都需要实现序列化接口
- 对象的类名,实例变量都会被序列化;方法、类变量、transient不会被序列化
- 如果想让某个变量不被序列化,可以用transient修饰
- 序列化对象的引用类型成员变量,也必须是可序列化的,否则会报错
- 反序列化时,必须有序列化对象的class文件
- 如果同一个对象被序列化多次,只有第一次序列化为二进制流,以后都只是保存序列化的版本号
- 建议所有可序列化的类加上版本号,方便项目的升级