IO1
作用:将数据在虚拟机内存和本地磁盘之间进行传输
I: input 输入
O: output 输出
分类
-
从传输方向上看
- 输入流:本都磁盘的数据想JVM传输
- 输出流:JVM数据想本地磁盘传输
-
从传输单位上看
- 字节流:以字节为单位进行数据传输。可以传输任意类型的数据,如文本、图片、视频、音频等
- 字符流:以字符为单位进行数据传输。只能传输文本类型的数据,如.txt、.java、.html等
-
从传输功能上看
- 节点流:具有传输功能和意义的流
- 过滤流:没有传输功能,用来给节点流增强传输能力或增加附加功能
字节流
- 输入流:InputStream 抽象父类
- 输出流:OutputStream 抽象父类
输入流
- 节点流:FileInputStream
创建
FileInputStream fis=new FileInputStream("本地的文件路径");
-
路径:
-
绝对路径:以电脑磁盘为基点的完整路径
FileInputStream fis = new FileInputStream("D:\\test\\a.txt"); FileInputStream fis = new FileInputStream("D:/test/a.txt");
-
相对路径:以当前项目路径为基点的路径,前提是文件必须在项目下
FileInputStream fis = new FileInputStream("file\\a.txt"); FileInputStream fis = new FileInputStream("file/a.txt");
-
路径书写必须截至至文件
-
文件必须存在,否则抛出异常
-
常用方法
- void close():关闭流链接,释放相关资源。(每个流中都有)
- int read(): 读取一个字节返回,读取到达末尾,返回-1
- int read(byte[] b): 尝试读取数组长度的数据至数组中, 返回实际读取个数.读取到达末尾,返回-1
package com.by.test;
import java.io.FileInputStream;
public class TestFIS {
public static void main(String[] args)throws Exception {
// FileInputStream fis = new FileInputStream("D:\\test\\a.txt");
// FileInputStream fis = new FileInputStream("D:/test/a.txt");
// FileInputStream fis = new FileInputStream("C:\\Users\\Administrator\\IdeaProjects\\Chp16\\file\\a.txt");
FileInputStream fis = new FileInputStream("file\\a.txt");
//FileInputStream fis = new FileInputStream("file/a.txt");
//读取一个字节
//利用循环读取文件所有内容
while (true) {
//先接收本次读取内容
int n = fis.read();
//判断读取是否到达末尾
if (n == -1) {
break;
}
//未到达末尾,输出查看
System.out.println((char) n);
}
//利用read(byte[])+循环读取文件所有内容
while (true) {
byte[] bs = new byte[3];
//接收本次读取结果
int n = fis.read(bs);
//判断读取是否到达末尾
if (n == -1) {
break;
}
//遍历数组查看本次读取结果
for (int i = 0; i < bs.length; i++) {
System.out.println(bs[i]);
}
}
//关流
fis.close();
System.out.println("执行成功!");
}
}
输出流
- 节点流:FileOutputStream
创建
FileOutputStream fos=FileOutputStream("本地的文件路径",true|false);
- 如果文件不存在,会自动创建
- 无法创建文件夹
- true表示数据追加,false表示数据覆盖
- 默认是false
常用方法
- void flush(): 刷新缓冲区,所有输出流中都具备该方法
- void write(int ): 向目标文件写入一个字节
- void write(byte[] ): 向目标文件写入一个数组的数据
package com.by.test;
import java.io.FileOutputStream;
public class TestFOS {
public static void main(String[] args)throws Exception {
FileOutputStream fos=new FileOutputStream("file/b.txt");
//写入一个字节
fos.write(65);
fos.write(66);
fos.write(67);
//写入一个数组
String str = "abcdefg123456";
byte[] bs = str.getBytes();
fos.write(bs);
//关流
fos.close();
System.out.println("执行成功!");
}
}
标准异常处理
- JDK7.0之后,提供了自动关流的语法结构,简化了finally的工作内容
try(
//需要自动关流的创建语句
){
}catch(){
}
- 原理: JDK7.0之后,所有的流都默认实现了AutoCloseable接口,该接口中提供了自动关流所需的close方法
package com.by.test;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class TestFOS2 {
public static void main(String[] args) {
try (
FileOutputStream fos = new FileOutputStream("file/b.txt");
){
//写入一个字节
fos.write(65);
fos.write(66);
fos.write(67);
//写入一个数组
String str = "abcdefg123456";
byte[] bs = str.getBytes();
fos.write(bs);
} catch (FileNotFoundException e) {
System.out.println("文件路径不正确");
} catch (IOException e) {
System.out.println("读写失败");
} catch (Exception e) {
System.out.println("未知异常!");
e.printStackTrace();
}
System.out.println("执行成功!");
}
}
文件复制
- 原理: 先将文件A中的内容读取到JVM中,再从JVM中将读取内容写入到文件B, 借助JVM来实现A与B之间的数据复制
- 先读后写
package com.by.test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class TestFileCopy {
public static void main(String[] args) {
copy1();
copy2();
}
/**
* 一次复制一个字节
*/
public static void copy1(){
try (
//创建输出节点流-复制到的文件路径
FileOutputStream fos=new FileOutputStream("d:/test/2.pdf");
//创建输入节点流-被复制的文件路径
FileInputStream fis=new FileInputStream("d:/test/1.pdf")
) {
//先循环读取文件所有内容
while (true) {
int n = fis.read();
if (n == -1) {
break;
}
//将本次读取的字节写入到目标文件
fos.write(n);
}
System.out.println("复制成功!");
} catch (FileNotFoundException e) {
System.out.println("文件路径不正确");
} catch (IOException e) {
System.out.println("读写失败!");
} catch (Exception e) {
System.out.println("未知异常!");
e.printStackTrace();
}
}
/**
* 一次复制一个字节数组
*/
public static void copy2(){
try (
//创建输出节点流-复制到的文件路径
FileOutputStream fos=new FileOutputStream("d:/test/3.pdf");
//创建输入节点流-被复制的文件路径
FileInputStream fis=new FileInputStream("d:/test/1.pdf")
) {
//先循环读取文件所有内容
while (true) {
//创建数组
byte[] bs = new byte[1024];
//读取一个数组的数据
int n = fis.read(bs);
if (n == -1) {
break;
}
//将数组中的数据写入到目标文件
fos.write(bs);
}
System.out.println("复制成功!");
} catch (FileNotFoundException e) {
System.out.println("文件路径不正确");
} catch (IOException e) {
System.out.println("读写失败!");
} catch (Exception e) {
System.out.println("未知异常!");
e.printStackTrace();
}
}
}
缓冲过滤流
- BufferedInputStream: 输入缓冲过滤流
- BufferedOutputStream: 输出缓冲过滤流
创建
BufferedInputStream bis=new BufferedInputStream(fis对象);
BufferedOutputStream bos=new BufferedOutputStream(fos对象);
作用
拥有一个内置的数据缓冲区, 文件数据将对接至数据缓冲区中,在缓冲区刷新或关闭时再将内部内容一并的给到目标文件
/**
* 一次复制一个字节+缓冲过滤流
*/
public static void copy3(){
try (
//创建输出节点流-复制到的文件路径
FileOutputStream fos=new FileOutputStream("d:/test/4.pdf");
//创建输入节点流-被复制的文件路径
FileInputStream fis=new FileInputStream("d:/test/1.pdf");
//添加缓冲过滤流
BufferedOutputStream bos=new BufferedOutputStream(fos);
BufferedInputStream bis=new BufferedInputStream(fis)
) {
//先循环读取文件所有内容
while (true) {
int n = bis.read();
if (n == -1) {
break;
}
//将本次读取的字节写入到目标文件
bos.write(n);
}
System.out.println("复制成功!");
} catch (FileNotFoundException e) {
System.out.println("文件路径不正确");
} catch (IOException e) {
System.out.println("读写失败!");
} catch (Exception e) {
System.out.println("未知异常!");
e.printStackTrace();
}
}
使用
-
如果先写后读,需要在写入完成后刷新缓冲区才能保证读取的正常进行
- 调用flush():强刷缓冲区 (推荐)
- 调用close(): 关流之前也会刷新缓冲区
-
关流时外层过滤流关闭内层节点流会一并关闭
package com.by.test;
import java.io.*;
public class TestBuffered {
public static void main(String[] args) {
try(
//输出
BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("file/a.txt"));
//输入
BufferedInputStream bis=new BufferedInputStream(new FileInputStream("file/a.txt"))
){
//先写
bos.write("abcd".getBytes());
//刷新缓冲区
bos.flush();
//bos.close();
//再读
while (true) {
int n = bis.read();
if (n == -1) {
break;
}
System.out.println((char) n);
}
}catch (FileNotFoundException e) {
System.out.println("文件路径不正确");
} catch (IOException e) {
System.out.println("读写失败!");
} catch (Exception e) {
System.out.println("未知异常!");
e.printStackTrace();
}
}
}
对象过滤流
- ObjectInputStream 对象输入过滤流
- ObjectOutputStream 对象输出过滤流
- 附加功能1: 读写基本类型
- 附加功能2: 读写引用类型
读写基本类型
读取: ois.readXxx()
写入: oos.writeXxx(值);
注: Xxx对应的为基本类型名,首字母大写
- 由于对象过滤流底层嵌套了缓冲区,所以先写后读操作时,仍然需要在写入完成后刷新缓冲区
- 为了保证数据的安全性,所以在写入时数据会根据魔数机制对其加密,在读取时在进行解密
package com.by.test;
import java.io.*;
public class TestObject_Double {
public static void main(String[] args) {
try (
//输出流
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("file/c.txt"));
//输入流
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("file/c.txt"))
) {
//先写
oos.writeDouble(10.5);
//强刷缓冲区
oos.flush();
//读取
System.out.println(ois.readDouble());
} catch (FileNotFoundException e) {
System.out.println("文件路径不正确");
} catch (IOException e) {
System.out.println("读写失败!");
e.printStackTrace();
} catch (Exception e) {
System.out.println("未知异常!");
e.printStackTrace();
}
}
}
读写引用类型
读取: Object ois.readObject() 读取到达末尾,抛出EOFException异常
写入: oos.writeObject(对象) 可以自动刷新缓冲区,所以先写后读时无需进行flush
读写String
package com.by.test;
import java.io.*;
public class TestObject_String {
public static void main(String[] args) {
try (
//输出流
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("file/c.txt"));
//输入流
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("file/c.txt"))
) {
//先写
oos.writeObject("床前明月光");
oos.writeObject("疑是地上霜");
oos.writeObject("举头望明月");
oos.writeObject("低头思故乡");
//后读
while (true) {
try {
String s =(String) ois.readObject();
System.out.println(s);
} catch (EOFException e) {
//读取到达末尾,跳出循环
break;
}
}
} catch (FileNotFoundException e) {
System.out.println("文件路径不正确");
} catch (IOException e) {
System.out.println("读写失败!");
e.printStackTrace();
} catch (Exception e) {
System.out.println("未知异常!");
e.printStackTrace();
}
}
}
读写自定义类型
-
自定义必须实现Serializable接口,表示允许被序列化,否者IO流没有读写权限
序列化:拆分对象信息的过程
反序列化:通过信息组装对象的过程
-
将属性通过transient修饰符修饰则可以防止其参与序列化
-
如果对象中有自定义类型的属性,则必须使该属性类型也实现序列化接口或者通过transient修饰符对其修饰
package com.by.entity;
import java.io.Serializable;
public class Student implements Serializable {
private String name;
private int age;
//防止被序列化
private transient double score;
// private Teacher tea;
//省略getter、setter、构造、toString
}
ackage com.by.test;
import com.by.entity.Student;
import java.io.*;
public class TestObject_Student {
public static void main(String[] args) {
try (
//输出流
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("file/c.txt"));
//输入流
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("file/c.txt"))
) {
//先写
oos.writeObject(new Student("zhangsan", 20, 88.5));
//后读
System.out.println((Student)ois.readObject());
} catch (FileNotFoundException e) {
System.out.println("文件路径不正确");
} catch (IOException e) {
System.out.println("读写失败!");
e.printStackTrace();
} catch (Exception e) {
System.out.println("未知异常!");
e.printStackTrace();
}
}
}
今日掌握
- 流的分类
- 文件复制的源码(字节复制+缓冲过滤流)
- 对象过滤流读写自定义类型