IO流概述
IO是Input和Output的缩写。
两个单词一个表示输入,一个表示输出。表示的是数据的输入和数据的输出。
相对于运行内存来说,【数据从其他地方进入运行内存,就是输入】
【数据从运行内存到其他地方,就是输出】
IO:表示的不是一个特定的类型,表示的是数据的输入和输出的概念
IO流分类:
- 按照功能分类
- 字节流:控制字节信息输入和输出
- 字符流:控制字符信息的输入和输出
- 按照流向分类
- 输入流:控制信息从其他设备到运行内存
- 输出流:控制信息从运行内存到其他设备
具体分类:
- 字节输入流:InputStream
- 字节输出流:OutputStream
- 字符输入流:Reader
- 字符输出流:Writer
字节流
概述:用来操作字节信息输入内存和输出内存
字节输入流 InputStream
字节输出流 OutputStream
InputStream
概述:字节输入流,本类是一个抽象类,不能直接创建对象,需要通过子类创建
方法;
-
read() :从流中读取一个字节信息,将读取的信息进行返回。
-
该方法的返回值是int值:因为默认将读取的每个byte字节强制转为了int类型。
-
如果不转为int类型,而是使用byte类型直接接收,那么byte表示的范围为:-128—127,如果读取了一个信息为-1.-1是到达文件末尾还是读取的字节数据,不能区别,所以就将读取的每个字节统统转为int,一旦转为int类型之后,不管读取的信息是负数还是整数,结果都是为正数,当在读取到一个-1时,表示肯定时文件到达末尾。
-
-
read(byte[] b) :从流对象中一次读取b.length个字节
将读取的信息保存在数组中,返回的是读取的个数
-
read(byte[] b, int off, int len) :从流中读取指定长度的数据到数组中将读取的信息保存在数组中,返回的是读取的个数
-
available() :将流中还没有读取的字节个数进行返回
-
close() :关闭流资源
FileInputStream
概述:属于字节输入流的子类,该类的对象可以将磁盘上的文件数据读取到运行内存中
构造方法:
- FileInputStream(File file):将file文件对象封装为文件字节输入流对象
- FileInputStream(String str) :将str字符串所描述的文件封装为字节输入流对象
注意:
- 指定路径时,文件的路径要存在
- 给定的路径一定要是文件的路径,不能时文件夹,因为文件中才可以保存数据
代码:
//chen.txt中的文件
一个一个返回:
//创建文件路径
File f = new File("18day/src/chen.txt");
//造输入流
FileInputStream fis = new FileInputStream(f);
int i;
//循环输出,强转为char类型
while ((i = fis.read()) != -1) {
System.out.print((char) i + " ");
}
//关闭系统资源
fis.close();
//c h e n s h u a n g
返回数组,一组一组返回
//创建文件路径
File f = new File("18day/src/chen.txt");
//造输入流
FileInputStream fis = new FileInputStream(f);
byte[] c = new byte[13];
int i= fis.read(c);
System.out.println(Arrays.toString(c));
//关闭系统资源
fis.close();
//[99, 104, 101, 110, 115, 104, 117, 97, 110, 103, 0, 0, 0]
OutputStream
概述:字节输出流,将字节信息从内存中写出到其他设备。也是一个抽象类,需要通过 子类创建对象。
方法:
- write(int b) :将一个字节信息写出内存
- write(byte[] b) :将一个数组中的信息写出内存
- write(byte[] b, int off, int len) :将数组中的一部分信息写出内容
- close() :关闭流资源
FileOutputStream
概述:文件字节输出流,将字节信息从内存中写出到目标磁盘文件中。
构造方法;
- FileOutputStream(File file) :将file对象封装为一个字节输出流对象
- FileOutputStream(String name) :将name字符串对应的文件封装为字节输出流对像
- FileOutputStream((File file,boolean b) :将name字符串对应的文件封装为字节输出流对 象,如果后面的值为false,表示替换写入信息
- FileOutputStream(String name,boolean b) :将name字符串对应的文件封装为字节输出流对象,如果后面的值为true,表示追加写入信息
注意:在写出信息时,写出的路径一定是文件的路径不能是文件夹路径
public static void main(String[] args) throws IOException {
//造文件,写入的指定文件
File f = new File("18day/src/c.txt");
//造流
FileOutputStream fos = new FileOutputStream(f);
//一个字符一个字符的写入
fos.write(97);
//fos.write('c'); //char - int - byte
//直接写入一个数组
byte[] by = {1,2,4,63,34,};
fos.write(by);
//只写入数组中的前三个字节
fos.write(by,0,3);
//关闭系统资源
fos.close();
}
文件的拷贝
概述:将一个文件中的内容,全部复制到另外一个文件中。
思路:先读取源文件中的数据,然后再将读取的数据写出到另外一个文件中
图示原理:
//要赋值的文件路径
File f1 = new File("18day/src/chen.txt");
//目的路径
File f2 = new File("18day/src/c.txt");
//造输入输出流
FileInputStream fis = new FileInputStream(f1);
FileOutputStream fos = new FileOutputStream(f2);
//循环写入
int i ;
while ((i=fis.read())!=-1){
fos.write(i);
}
//关闭系统资源
fis.close();
fos.close();
拷贝效率的提升
-
为什么需要提升效率:
-
当需要拷贝一个文件,该文件的大小过大,拷贝的时间就非常久。
因为一个字节需要IO两次
-
-
提升的方式:
-
每次可以多读入几个字节,多写出几个字节。
-
可以准备一个数组,数组的大小为:文件的字节大小
使用数组的方式,一次读取完毕,一次写出完毕即可
-
优点:一次读取一次写出,时间短,效率高
-
缺点:如果需要创建的数组大小过大,对运行内存的要求久比较高,一旦数组的大小超 过了或者临近运行内存,那么久可能出先一些内存不够的问题。
-
-
最终方案:使用小数组拷贝
- 使用数组来进行读取和写出,但是数组空间不需要创建那么大,一次虽然拷贝不完,但 是也比一个个拷贝要快。
- 使用小数组注意:每次读取几个字节,就应该写出几个字节,避免数据写出重复
代码:
private static void test02() throws IOException {
//要赋值的文件路径
File f1 = new File("18day/src/chen.txt");
//目的路径
File f2 = new File("18day/src/c.txt");
//造输入输出流
FileInputStream fis = new FileInputStream(f1);
FileOutputStream fos = new FileOutputStream(f2);
//利用数组循环写入,提高效率
byte[] by = new byte[10];
int i ;
while ((i=fis.read(by))!=-1){
fos.write(by,0,i);
}
//关闭系统资源
fis.close();
fos.close();
}
高效缓冲字节流
高效缓冲字节输入流:BufferedInputStream
高效缓冲字节输出流:BufferedOutPutStream
-
构造方法
- BufferedInputStream(InputStream in) :将基础的字节输入流对象进行包装成为一个高效 字节输入流
- BufferedOutputStream(OutputStream out) :将基础的字节输出流对象进行包装
-
三种拷贝方式原理对比
private static void test02() throws IOException {
//要赋值的文件路径
File f1 = new File("18day/src/chen.txt");
//目的路径
File f2 = new File("18day/src/c.txt");
//造输入输出流--加入高校缓冲字节流
FileInputStream fis = new FileInputStream(f1);
FileOutputStream fos = new FileOutputStream(f2);
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
//利用数组循环写入,提高效率
byte[] by = new byte[10];
int i ;
while ((i=bis.read(by))!=-1){
bos.write(by,0,i);
}
//关闭系统资源,关闭缓冲字节流资源的时候,字节流的也被关闭
bis.close();
bos.close();
}
close和flush方法的区别
- 两个方法都可以将数据从缓冲区刷新到目标文件中
- 不同点:
- close不仅可以刷新数据进入目标文件,还可以关闭系统资源
- flush只能用来刷新数据进入目标文件
字符流
使用字符流的原因
- 如果想要读取文件中的信息,这个文件中信息全部英文,可以使用字节流一次读取一个 字节,进行转码展示。
- 如果文件中的信息,是全中文,可以每次读取三个或者两个,将读取的三个或者两次字 节也可以转成字符串展示。
- 如果文件中的信息,是中英文混杂,每次读取几个字节就不确定,就有可能出现乱码。
- 如果要写出一些字符串,没法直接通过字节流写出,需要先将这个字符串转为字节数组 才可以写出,每次都进行转换,浪费时间,也浪费空间。
概述:
-
字符流:可以以字符为单位操作数据的输入和输出的对象所属的类型
-
分类:
字符输入流 Reader
字符输出流 Writer
Reader
字符输入流:也是一个抽象父类,不能直接创建对象
方法:
- read() :读取一个字符信息,将读到的信息进行返回
- read(char[] c) :读取c.length个字符到数组中,返回的是读取的字符个数
- read(char[] c,in t offset,int len):读取一部分字符到数组中
- close() :关闭流资源
使用子类创建对象:FileReader
- FileReader(File file)
- FileReader(String fileName)
//创建文件路径
File f = new File("18day/src/chen.txt");
//造字符输入流
FileReader fr = new FileReader(f);
char[] c = new char[10];
int len = fr.read(c);
System.out.println(c);
//关闭系统资源
fr.close();
Writer
字符输出流,是一个抽象父类,不能直接创建对象
方法:
- write(int c) :写出一个字符
- write(String str) :写出一个字符串
- write(char[] cbuf) :写出一个数组中的字符
- write(char[] cbuf, int off, int len) :写出数组的一部分到目标文件
- write(String str, int off, int len) :写出字符串的一部分
- close()
FileWriter:文件字符输出流
- FileWriter(File file)
- FileWriter(String fileName)
public static void main(String[] args) throws IOException {
//创建文件路径
File f = new File("18day/src/chen.txt");
//造流----true:表示可以在文件上继续追加内容
FileWriter fw = new FileWriter(f,true);
fw.write('c');
fw.write("chen");
fw.write(new char[]{'a','b','c'});
fw.write(new char[]{'a','b','c'},0,2);
fw.write("我还好!",0,2);
//关闭系统资源
fw.close();
}
//chen.txt文件
cchenabcab我还
使用字符流拷贝文件
- 使用字符流一样可以拷贝文本文件
- 原理
- 区别使用:
- 如果以后仅仅是为了拷贝文件,使用字节流来拷贝
- 如果后续需要查看文件的内容,或者要写出内容,使用字符流来完成
字符流不可以拷贝非纯文本文件
-
纯文本文件:文件中的信息全都是通过字符组成
-
非纯文本文件:比如:视频 音频 图片
-
为什么字符流不能拷贝非纯文本文件:
因为非纯文本文件中的字节可能在编码表中没有对应字符信息,如果读取一个字节,没 有字符与之对应,在解码时,就会将这个没有对应字符的字节通过?字符来代替,再写 出的时候,写出的是?字符对应的字节,这一步对信息发生了篡改,就导致文件的数据 拷贝错误,源文件的内容可能再拷贝之后就无法使用。
高效缓冲字符流
类型:BufferedReader和BufferedWriter
概述:都是包装类型,需要对基础的字符流对象进行包装。包装之后,一次也可以读取 多个字 符,可以写出多个字符。
构造:BufferedReader(Reader in)
-
方法:readLine() :
一次可以从输入流中读取一行信息
返回值就是读取的这一行数据,如果到达文件的末尾,返回null
构造:BufferedWriter(Writer out)
- 方法:newLine() :表示在文件中进行换行
转换流
转换输入流和转换输出流
OutputStreamWriter:转换输出流,在输出数据时,可以指定编码格式
- 构造方法:OutputStreamWriter(OutputStream in, Charset cs) 使用基础的输出流对象,指定以cs 编码格式来写出信息
public static void main(String[] args) throws IOException {
//创建转换输入流
InputStreamReader isr = new InputStreamReader(new FileInputStream("19day/src/note/zhuanhuan/c.txt"),"UTF-8");
int i;
while ((i= isr.read())!=-1){
System.out.println(i);
}
isr.close();
}
InputStreamReade:转换输入流,在输入数据时,可以指定编码格式
- 构造方法:InputStreamReader(InputStream in, Charset cs) :使用基础的输入流对象,指定以cs编码 格式读取信息
public static void main(String[] args) throws IOException {
//创建转换输出流
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("19day/src/note/zhuanhuan/c.txt"),"UTF-8");
osw.write("aaabbbccc");
osw.close();
}
注意:
- 当读取信息时,应该以源文件的编码格式读取信息(解析信息)
- 当写出信息时,应该以目标文件的编码格式写出信息
- 不管是转换输入流还是转换输出流,都是字符流
- 转换流也是字节流到字符流的桥梁
public static void main(String[] args) throws IOException {
//创建转换输入流 被复制文件编码格式为 GBK
InputStreamReader isr = new InputStreamReader(new FileInputStream("19day/src/note/zhuanhuan/a.txt"),"GBK");
//创建转换输出流 目标文件编码格式为 UTF-8
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("19day/src/note/zhuanhuan/c.txt"),"UTF-8");
int i;
while ((i= isr.read())!=-1){
osw.write(i);
}
//关流
isr.close();
osw.close();
}
标准输入流输出流
标准输入流
概述:System.in
- 该流的类型为InputStream,属于是字节流
- 默认关联的设备为键盘,即数据源为键盘
- 一般该输入流和Scanner类型一起用更方便
//本身为一个字节输入流
InputStream is =System.in;
int i = is.read();
System.out.println(i);
/*
a
97
public static void main(String[] args) throws IOException {
InputStream is = System.in;
//可以将字节输入流加强为字符流
InputStreamReader isr = new InputStreamReader(is);
//可以将字符流加强为高效流
BufferedReader br = new BufferedReader(isr);
//使用高效流可以从控制台输入字符串
String s = br.readLine();
System.out.println(s);
br.close();
}
/*
aaabbb
aaabbb
- 通过**System.setIn(InputStream in)**方法,可以更改标准输入流关联的设备
//System.in 默认为从键盘录入的数据
//使用setIn方法后可以更改 读取的数据源
System.setIn(new FileInputStream("19day/src/c.txt"));
InputStream is = System.in;
int i = is.read();
System.out.println(i);
/*
65(c.txt中内容为A)
标准输出流
概述:System.out
- 该流的类型为PrintStream,其父类为OutputStream是一个字节输出流
- 默认关联的设备为控制台,即数据目的地为控制台
//System.out本身也是一个字节输出流
OutputStream os = System.out;
os.write(2);
os.write('c');
os.write('我');
os.close();
//可以对该输出流进行加强,加强为字符流
//加强之后可以在控制台输出字符串
OutputStream os = System.out;
OutputStreamWriter osw = new OutputStreamWriter(System.out);
osw.write("aaabbbc");
osw.close();
//aaabbbc
- 通过System.setOut(PrintStreamout)方法,可以更改标准输出流关联的设备
//System.out默认将数据打印在控制台
//可以通过 setOut方法修改
//修改之后,后续在使用这个流写出信息时不会写出到控制台,二十指定的位置
OutputStream os = System.out;
System.setOut(new PrintStream("19day/src/c.txt"));
OutputStreamWriter osw = new OutputStreamWriter(System.out);
osw.write("aaabbbc");
osw.close();
//c.txt中写入aaabbbc
打印流
打印流介绍:
-
打印流分为打印字节流和打印字符流:
PrintStream 打印字节流 字节输出流
PrintWriter 打印字符流 字符输出流
-
打印流属于输出流。
打印字节流
PrintStream:是OutPutStream的间接子类,是一个输出流,也是一个字节流
构造方法:
- PrintStream(File file) :创建具有指定文件的新打印流。
PrintStream ps = new PrintStream("19day/src/c.txt");
- PrintStream(String fileName) :创建指定文件路径的新打印流。
PrintStream ps = new PrintStream("19day/src/c.txt");
- PrintStream(OutputStream out):将一个字节流对象,封装为一个打印流
常用方法:
-
从父类中继承的方法:write()
注意:只可以写出一个字节信息
-
自己特有的方法:print println
不仅可以输出字节信息,还可以输出各种数据类型的信息
比如:整数、小数、字符、字符串、对象
PrintStream ps =new PrintStream(new FileOutputStream("19day/src/c.txt"));
//可以使用普通的字节输出流写出信息
ps.write(1);
ps.write('a');
//也可以使用特有的方法来写出信息 println print方法【可以写出各种类型的数据】
ps.println(true);
ps.println('c');
ps.println(88);
ps.println("aaa");
ps.println(100.2);
ps.println(new Person("aaa",22));
ps.close();
/*c.txt内容为:
atrue
c
88
aaa
100.2
Person{name='aaa', age=22}
打印字符流
PrintWriter:是Writer的间接子类,是一个输出流,也是一个字符流
构造方法:
- PrintWriter (File file) :创建具有指定文件的新打印流。
- PrintWriter (String fileName) :创建指定文件路径的新打印流。
PrintWriter pw = new PrintWriter("19day/src/c.txt");
- PrintWriter(OutputStream out):将一个字节流封装为一个打印字符流
- PrintWriter(Writer out) :将一个字符流封装为一个打印流
- PrintWriter(OutputStream out,boolean b):将一个字节流封装为一个打印字符流【可以实现自动刷新功能】
- PrintWriter(Writer out,boolean b) :将一个字符流封装为一个打印流【可以实现自动刷新功能】
常用方法:
-
从父类中继承的方法:write()
注意:可以写出一个字符,也可以写出一个字符串
-
自己特有的方法:print println
//打印流在创建对象时,可以传递一个布尔值来实现数据的自动刷新
PrintWriter pw = new PrintWriter(new FileWriter("19day/src/c.txt"));
pw.println("aaa");
pw.println('c');
pw.println(88);
pw.println(new Person("aaa",23));
pw.close();
/*c.txt内容为:
aaa
c
88
Person{name='aaa', age=23}
对象序列化流
相关概念:
数据的状态:
- 游离态:在运行内存中的运行的数据。随着程序的结束,数据也会被回收
- 持久态:在磁盘中进行保存的数据。随着程序的结束,随着计算机的关机和开机,数据并不会消失。
序列化:
-
将数据从运行内存中 ===> 到磁盘中
数据从游离态===>持久态 (输出)
反序列化:
-
将数据从磁盘中 ===> 到运行内存中
持久态===>游离态 (输入)
【对象序列化流的使用】
构造方法:
- ObjectOutputStream(OutputStream out):将一个输出流,封装为一个对象序列化流
序列化对象方法:
-
writeObject(Object obj):将指定对象写出到流中传输
-
将对象实现序列化之后,文件的内容看不懂,是正常情况,因为保存的是一个个对象数据,而不是字符串。
-
如果需要将一个类型的对象实现序列化,那么该类型就需要实现一个接口:
Serializable:序列化接口(标记型接口)
该接口没有任何方法需要重写,只需要让类型实现接口,就可以实现序列化和反序列化。
//学生类实现Serializable接口
public class Student implements Serializable {
private String name;
private int age;
//创建序列化输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("19day/src/c.txt"));
//造对象
Student s1 = new Student("aaa", 23);
oos.writeObject(s1);
oos.close();
多个对象的时候放在集合中
//造对象
Person p = new Person("陈爽",23);
Person p1 = new Person("陈爽",23);
//创建person类型的集合
ArrayList<Person> list = new ArrayList<>();
//创建对象序列化流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("19day/src/xuliehua/chen.txt"));
//把对象先保存在集合对象中
list.add(p);
list.add(p1);
//使用特殊方法将集合对象写入到指定文件中
oos.writeObject(list);
oos.close();
【反序列化的使用】
构造方法:
ObjectInputStream(InputStream inp):将一个输入流,封装为一个对象序列化流
反序列化对象方法:readObject():将对象从文件中读取到内存中
注意事项:
- 读取文件中的对象时,注意如果文件中数据读取完毕,继续读取出现:EOFException
- 当需要序列化多个对象时,一般将这多个对象先保存在集合对象中,只需要序列化一个集合对象即可。后续在反序列化的时候,也只需要读取一次,读取的是集 合对象,然后可以遍历读取的集合依次获取每一个保存的对象。
- 当类型需要参与序列化和反序列化,一般都给定一个固定的序列化ID给定之后,后续就可以随意对类型进行修改,即使修改了也不影响对象的序列化或者反序列化
//创建反序列化输入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("19day/src/c.txt"));
Object o = ois.readObject();
System.out.println(o);
//Student{name='aaa', age=23}
多个对象的时候遍历集合获取
//创建反序列化流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("19day/src/xuliehua/chen.txt"));
//使用方法,把集合对象读取到程序中
ArrayList<Student> list = (ArrayList<Student>)ois.readObject();
//循环遍历
for (Student p:list){
System.out.println(p);
}
ois.close();
//Student{name='陈爽', age=23}
//Student{name='陈爽', age=23}
transient关键字
1、在实现对象的序列化和反序列化时,如果某些特定的属性不需要参与,就可以通过关键 字:transient来修饰该属性,一旦属性被关键字修饰之后,就不参与读取和写出。
public class Student implements Serializable {
private transient String name;
Properties
是一个双列集合,是一个Map体系的集合类,是Hashtable的子类
properties特殊方法
- setProperty(String key, String value):添加键值对
- getProperty(String key):根据指定的键获取对应的值
- stringPropertyNames():将集合中的键获取到一个单列集合Set中进行存储
Properties和IO流相结合的方法
- store(Writer writer, String comments):将此属性列表(键和元素对)写入Properties文件
- store(OutputStream out, String comments) :将此属性列表(键和元素对)写入Properties
//创建一个Properties的集合类
Properties pro = new Properties();
pro.setProperty("aaa","23");
pro.setProperty("bbb","24");
pro.setProperty("ccc","25");
//把该集合中的每个键值对数据,输出保存在一个文件中
//参数一:给定一个输出流
//参数二:写出信息时,给出注释,不能不屑,但是可以为空
pro.store(new FileWriter("19day/src/c.txt"),"null");
//c.txt中内容
#null
#Tue Mar 08 21:27:50 CST 2022
bbb=24
aaa=23
ccc=25
- load(Reader reader):从输入字符流读取属性列表(键和元素对)
- load(InputStream in):从输入字节流读取属性列表(键和元素对)
//创建一个集合
Properties pro = new Properties();
//将某个文件中的键值对保存到集合中使用
pro.load(new FileReader("19day/src/c.txt"));
System.out.println(pro);
//{bbb=24, aaa=23, ccc=25}
案例1:
(1) 在Properties文件中有如下信息:
name=张三
age=18
hobby=玩游戏
(2)将数据读取到集合中
(3)将读取的数据封装成一个学生对象,将该对象输出为本地文件中永久保存
private static void ex01() throws IOException, ClassNotFoundException {
Properties pro = new Properties();
//把键值对下载到集合
pro.load(new FileReader("19day/src/properties/Properties.txt"));
//把键值对的信息创建对象--提前写好学生类并实现Serializable接口
Student stu = new Student(pro.getProperty("name"), pro.getProperty("age"), pro.getProperty("hobby") );
//创建序列化流
ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream("19day/src/properties/chen.txt"));
//把集合键值对写入到
oos.writeObject(stu);
//关流
oos.close();
}
案例二:
在Properties文件中有如下信息:
name=张三
age=18
hobby=玩游戏
(2)将该文件中的name=张三,改为name=李四
(3)要求:使用代码完成内容的修改
private static void test02() throws IOException {
Properties pro = new Properties();
pro.load(new FileReader("19day/src/properties/Properties.txt"));
pro.setProperty("name","李四");
pro.store(new FileWriter("19day/src/properties/Properties.txt"),"null");
}
关流方式
JDK1.7版本之前的关流方式
public static void main(String[] args) throws IOException {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("19day/src/c.txt");
fos = new FileOutputStream("19day/src/c.txt");
int i;
while ((i = fis.read()) != -1) {
fos.write(i);
}
} finally {
try {
if (fis != null) {
fis.close();
}
} finally {
if (fos != null) {
fos.close();
}
}
}
}
}
JDK1.8之后的关流方式
try(
FileInputStream fis =new FileInputStream("19day/src/c.txt");
FileOutputStream fos = new FileOutputStream("19day/src/c.txt");
) {
int i;
while ((i = fis.read()) != -1) {
fos.write(i);
}
}