Chp19-IO1-字节流
IO的概念
使数据在JVM和本地磁盘之间传输的技术
I: input 输入
O: output 输出
流的概念
为数据传输做支持, 相当于管道
流的分类
-
从传输方向上看:
- 输入流: 从本地磁盘传输到JVM
- 输出流: 从JVM传输到本地磁盘
-
从传输单位上看:
- 字节流: 以字节为单位进行数据传输, 可以传输任意类型的文件, 如文本|图片|视频|音频等
- 字符流: 以字符为单位进行数据传输, 只能传输文本类型的文件, 如.txt|.java|.html等
-
从功能上看:
- 节点流: 具有实际传输意义的流
- 过滤流: 没有实际传输功能, 作用为给节点流增强传输能力或者添加附加功能
字节输入流
- InputStream: 抽象父类
- FileInputStream: 字节输入节点流. 是InputStream的子类
创建
FileInputStream fis=new FileInputStream("被读取的文件路径");
-
绝对路径: 以电脑盘符为基点的完整路径
FileInputStream fis = new FileInputStream("D:\\206\\java基础\\testFile.txt"); FileInputStream fis = new FileInputStream("D:/206/java基础/testFile.txt");
\\
: 通过一个转义字符对\
进行语法转义,使其变为普通的路径分隔符 -
相对路径: 以当前项目路径为基点的路径
//绝对路径 FileInputStream fis = new FileInputStream("C:\\Users\\Administrator\\IdeaProjects\\Chp19_206\\file\\a.txt"); //相对路径 FileInputStream fis2 = new FileInputStream("file\\a.txt"); FileInputStream fis3 = new FileInputStream("file/a.txt");
实际开发中, 不约束文件存储位置的绝对路径使用更多
学习过程中, 建议使用查看操作更便捷的相对路径
-
要求:
- 路径必须截止至文件
- 文件必须已经存在
- 构造会抛出
java.io.FileNotFoundException
文件路径找不到的非运行时异常, 必须对其作出处理
常用方法
- void close(): 关闭流链接,释放相关资源. 所有流都具备该方法
- int read(): 读取一个字节并返回, 读取到达末尾, 返回-1
- int read(byte[] b): 读取数组长度个字节存入到数组b中, 返回实际读取个数, 读取到达末尾, 返回-1
public static void main(String[] args)throws Exception {
//字节输入节点流-读取当前项目下的file/a.txt中的"abcdefg"
FileInputStream fis = new FileInputStream("file/a.txt");
//利用read()读取文件所有内容
while (true) {//假设一直读取
//接收本次读取结果
int n = fis.read();
//判断读取是否到达末尾
if (n == -1) {
System.out.println("读取结束");
break;
}
//正常操作当前读取内容
System.out.println((char) n+" ");
}//a b c d e f g
}
//利用read(byte[] b)读取文件所有内容
while (true) {
//创建长度为5的数组
byte[] bs = new byte[5];
//尝试读取5个字节到数组中,并接收返回值
int n = fis.read(bs);
//判断读取是否到达末尾
if (n == -1) {
System.out.println("读取结束!");
break;
}
//遍历数组, 操作本次读取的内容
System.out.println("本次读取长度: "+n);
for (byte b : bs) {
System.out.print(b+" ");
}
}
字节输出流
- OutputStream: 抽象父类
- FileOutputStream: 字节输出节点流, OutputStream的子类
创建
FileOutputStream fos=new FileOutputStream("接收数据的文件路径",true|false);
- 对路径的要求与输入流一致
- true|false: 设置数据在文件中的存储方式
- true: 追加
- false: 覆盖, 默认为覆盖
- 当文件不存在时,会自动创建
- 无法创建文件夹
常用方法
- flush(): 强制刷新缓冲区. 所有输出流都具备该方法
- write(int n): 一次写入一个字节
- write(byte[] b): 一次写入一个数组的数据
public static void main(String[] args)throws Exception {
//创建一个字节输出节点流- 向file/b.txt中写入内容-数据追加
FileOutputStream fos = new FileOutputStream("file/b.txt",true);
//写入ABCD
fos.write(65);
fos.write(66);
fos.write('C');
fos.write('D');
//以数组的方式写入ABCD
String str = "ABCD";
byte[] bs = str.getBytes();
fos.write(bs);
/* String str = "abcdefg";
fos.write(str.getBytes());
*/
//关流
fos.close();
System.out.println("操作正常!");
}
标准异常处理-自动关流
try(
需要自动关流的内容创建代码
){
其他的操作代码
}catch(...){
...
}...
- 原理: JDK7.0之后, 所有流都默认实现了
AutoCloseable
接口, 该接口中提供了自动关联所需的close()
try (//创建一个字节输入节点流- file/a.txt
FileInputStream fis = new FileInputStream("file/c.txt");
){
//循环读取文件所有内容
while (true) {
//接收本次读取结果
int n = fis.read();
//判断读取是否到达末尾
if (n == -1) {
break;
}
System.out.println(n + " ");
}
} catch (FileNotFoundException e) {
System.out.println("文件路径不正确");
} catch (IOException e) {
System.out.println("读取失败!");
} catch (Exception e) {
System.out.println("发生异常!");
e.printStackTrace();
}
文件复制
原理
利用JVM, 先将文件A中的内容读取到JVM中, 再将读取内容通过JVM写入到文件B中
先读后写
package com.by.test.copy;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test_copy {
public static void main(String[] args) {
copy1();
}
//一次复制一个字节
public static void copy1(){
try(
//先建输出流- 被写入的文件路径
FileOutputStream fos=new FileOutputStream("file/c.txt");
//再建输入流 - 被复制的文件路径
FileInputStream fis=new FileInputStream("file/b.txt")
){
//先读
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("file/d.txt");
//再建输入流 - 被复制的文件路径
FileInputStream fis=new FileInputStream("file/b.txt")
){
//先读
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(){
long l1 = System.nanoTime();
try(
//先建节点流对象
//先建输出流- 被写入的文件路径
FileOutputStream fos=new FileOutputStream("D:\\206\\java基础\\笔记\\206一阶段笔记4.md");
//再建输入流 - 被复制的文件路径
FileInputStream fis=new FileInputStream("D:\\206\\java基础\\笔记\\206一阶段笔记.md");
//再添加过滤流
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();
}
long l2 = System.nanoTime();
System.out.println("字节复制+缓冲过滤流: "+(l2-l1)/1E9);
}
实际开发中, 字节复制+缓冲过滤流的执行效率已经足以满足开发需求
使用
-
当对同一文件同时进行
先写后读
的操作时, 必须在写入完成之后提前刷新缓冲区, 将内容写入到文件才能正常读取-
刷新缓冲区的方式:
bos.close(): 提前关流, 关流之前会自动刷新缓冲区 bos.flush(): 直接强刷缓冲区 (推荐)
-
-
大部分的流为了提高数据传输效率, 底层都嵌套着过滤流, 使用数据缓冲区
package com.by.test;
import java.io.*;
public class Test_buffered {
public static void main(String[] args) {
//先往file/a.txt中写入26个小写字母, 再读取输出
try(
//输出-写入
BufferedOutputStream bos=new Buffere dOutputStream(new FileOutputStream("file/a.txt"));
//输入-读取
BufferedInputStream bis=new BufferedInputStream(new FileInputStream("file/a.txt"))
){
//先写入26个小写字母
for (int i = 97; i <=122 ; i++) {
bos.write(i);
}
System.out.println("写入成功!");
//手动关流
// bos.close();
//强制刷新缓冲区
bos.flush();
//再对文件中的内容进行读取
while (true) {
int n = bis.read();
if (n == -1) {
break;
}
System.out.print((char) n+" ");
}
System.out.println("读取成功!");
// bos.write(65);
}catch (FileNotFoundException e) {
System.out.println("文件路径不正确");
} catch (IOException e) {
System.out.println("读写失败!");
} catch (Exception e) {
System.out.println("发生异常!");
e.printStackTrace();
}
}
}
对象过滤流
给节点流添加附加功能
- 读写基本类型
- 读写引用类型
- ObjectInputStream: 对象输入过滤流-ois
- ObjectOutputStream: 对象输出过滤流-oos
创建
ObjectInputStream ois=new ObjectInputStream(fis对象);
ObjectOutputStream oos=new ObjectOutputStream(fos对象);
读写基本类型
xxx ois.readXxx(): 读取基本类型
void oos.writeXxx(xxx): 写入基本类型
注意: xxx对应的为基本类型
使用
-
先写后读时需要在写入完成后强刷缓冲区
-
为了确保数据传输的安全性, 对象过滤流会在数据写入时对其按照魔数机制进行加密, 在读取时再进行解密.
package com.by.test;
import java.io.*;
public class Test_object {
public static void main(String[] args) {
//先往file/a.txt中写入一个小数, 再读取输出
try(
FileOutputStream fos=new FileOutputStream("file/a.txt");
FileInputStream fis=new FileInputStream("file/a.txt");
//添加对象过滤流
ObjectOutputStream oos=new ObjectOutputStream(fos);
ObjectInputStream ois=new ObjectInputStream(fis)
){
//先写
oos.writeDouble(10.5);
//强刷缓冲区
oos.flush();
//再读
System.out.println(ois.readDouble());
}catch...
}
}
读写引用类型-String
类库中的引用类型
Object ois.readObject(): 读取引用类型
void oos.writeObject(Object o): 写入引用类型
使用
- readObject()|readXxx()读取到达末尾会抛出
EOFException
- writeObject()自带缓冲区刷新, 先写后读时无需手动刷新缓冲区
package com.by.test;
import java.io.*;
public class Test_object_String {
public static void main(String[] args) {
//先往file/a.txt中写入一些String, 再读取输出
try(
FileOutputStream fos=new FileOutputStream("file/a.txt");
FileInputStream fis=new FileInputStream("file/a.txt");
//添加对象过滤流
ObjectOutputStream oos=new ObjectOutputStream(fos);
ObjectInputStream ois=new ObjectInputStream(fis)
){
//先写
oos.writeObject("一二三四五");
oos.writeObject("上山打老虎");
oos.writeObject("老虎没打着");
oos.writeObject("打着小松鼠");
System.out.println("写入成功!");
//读取输出
while (true) {
try {
String str = (String) ois.readObject();
System.out.println(str);
} catch (EOFException e) {
//读取到达末尾
break;
}
}
}catch...
}
}
读写自定义类型
自定义类型必须实现Serializable接口, 意味着允许被序列化(IO流可以读取类的完整信息)
可以通过给属性添加transient修饰符的方式防止某些属性参与序列化
public class Student implements Serializable {
private String name;
//防止被序列化
private transient int age;
private double score;
....
}
序列化: 将对象相关的所有内容进行读取的过程
反序列化: 读取相关信息组装成对象的过程
//先往file/a.txt中写入一个学生对象, 再读取输出
try(
FileOutputStream fos=new FileOutputStream("file/a.txt");
FileInputStream fis=new FileInputStream("file/a.txt");
//添加对象过滤流
ObjectOutputStream oos=new ObjectOutputStream(fos);
ObjectInputStream ois=new ObjectInputStream(fis)
){
//先写- 序列化对象信息
oos.writeObject(new Student("zhangsan", 20, 90));
//读取输出- 反序列化对象
Student stu =(Student) ois.readObject();
System.out.println(stu);
}catch...