IO流
先决条件:掌握File类
File类是让Java虚拟机知道文件的抽象路径,包括对文件或文件夹本身的操作。IO流则是对文件中数据的读写操作,这篇文章则是详细介绍IO流
- I表示input,是数据从硬盘进内存的过程,称之为读
- O表示output,是数据从内存到硬盘的过程,称之为写
- 在数据传输的过程中,是谁在读?是谁在写?这个参照是谁
- IO的数据传输,可以看作是一条数据的流动,按照流动的方向,以内存为参照物进行读写操作
- 简单来说:内存在读,内存在写
IO流的分类:
什么是纯文本文件,用windows记事本打开能读的懂,那么这样的文件就是纯文本文件
- 1.按流向分
- 输入流
- 输出流
- 2.按数据类型分
- 字节流(操作非纯文本文件,如音频、视频和图片)
- 字符流 (操作纯文本文件,如java文件,txt文件等)
1 字节流
1.1 字节流写数据
创建字节输出流对象,
- 1.如果文件存在,会帮我们创建
- 2.如果文件不存在,会把文件清空
1.11 字节流的流程
- 创建字节输出流对象
- 调用字节输出流对象的写数据方法
- 释放资源
1.12 字节流写数据的三种方式
方法名 | 说明 |
---|---|
void write(int b) | 将指定的字节写入此文件输出流 一次写一个字节数据 |
void write(byte[] b) | 将 b.length字节从指定的字节数组写入此文件输出流 一次写一个字节数组数据 |
void write(byte[] b, int off, int len) | 将 len字节从指定的字节数组开始,从偏移量off开始写入此文件输出流 一次写一个字节数组的部分数据 |
1.13 字节流写数据的常见问题
字节流写数据如何实现换行
- windows:\r\n
- linux:\n
- mac:\r
字节流写数据如何实现追加写入
- public FileOutputStream(String name,boolean append)
- 创建文件输出流以指定的名称写入文件。如果第二个参数为true ,则字节将写入文件的末尾而不是开头
1.14 字节流写数据加异常处理
异常处理格式
-
try-catch-finally
try{ 可能出现异常的代码; }catch(异常类名 变量名){ 异常的处理代码; }finally{ 执行所有清除操作; }
-
finally特点
- 被finally控制的语句一定会执行,除非JVM退出
1.2 字节流读数据
字节输入流
- FileInputStream(String name):通过打开与实际文件的连接来创建一个FileInputStream,该文件由文件系统中的路径名name命名
字节输入流读取数据的步骤
- 创建字节输入流对象
- 调用字节输入流对象的读数据方法
- 释放资源
一次读一个字节数组的方法
- public int read(byte[] b):从输入流读取最多b.length个字节的数据
- 返回的是读入缓冲区的总字节数,也就是实际的读取字节个数
2 字节缓冲流
2.1 字节缓冲流构造方法
字节缓冲流介绍
- BufferOutputStream:该类实现缓冲输出流.通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用(减少硬盘到内存的次数)
- BufferedInputStream:创建BufferedInputStream将创建一个内部缓冲区数组.当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次很多字节
2.2 字节缓冲流例子
public class BufferStreamDemo1 {
public static void main(String[] args) throws IOException {
//字节缓冲输出流
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("C:\\b.txt"));
//写数据
bufferedOutputStream.write("hello\r\n".getBytes());
bufferedOutputStream.write("world\r\n".getBytes());
//释放资源
bufferedOutputStream.close();
//字节缓冲输入流
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("C:\\b.txt"));
//一次读取一个字节数组数据
byte[] bys = new byte[1024];
int len;
while ((len = bufferedInputStream.read(bys))!=-1){
System.out.println(new String(bys,0,len));
}
}
}
3 字符流
3.1 编码表
什么是字符集
- 是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等
- 计算机要准确的存储和识别各种字符集符号,就需要进行字符编码,一套字符集必然至少有一套编码。常见字符集有ASCII字符集、GBK字符集、Unicode字符集等
- GBK 一个文字两个字节
- Unicode码表中的UTF-8 编码格式一个文字三个字节
3.2 字符串中的编码问题
- 相关方法
方法名 | 说明 |
---|---|
byte[] getBytes() | 使用平台的默认字符集将该 String编码为一系列字节 |
byte[] getBytes(String charsetName) | 使用指定的字符集将该 String编码为一系列字节 |
String(byte[] bytes) | 使用平台的默认字符集解码指定的字节数组来创建字符串 |
String(byte[] bytes, String charsetName) | 通过指定的字符集解码指定的字节数组来创建字符串 |
- 代码
public class FileWriterDemo1 {
public static void main(String[] args) throws UnsupportedEncodingException {
//定义一个字符串
String s = "中国";
byte[] bytes = s.getBytes();
System.out.println(Arrays.toString(bytes));
byte[] bytes1 = s.getBytes("gbk");
System.out.println(Arrays.toString(bytes1));
//IDE默认的使用是Unicode码表的UTF-8编码格式
String ss = new String(bytes1);
System.out.println(ss);
String ss2 = new String(bytes1,"UTF-8");
System.out.println(ss2);
//出现乱码的原因是编码和解码格式不一致
String ss3 = new String(bytes1,"GBK");
System.out.println(ss3);
}
}
3.3 字符流写数据
字节流可以操作所有的文件,那么为什么还要使用字符流呢?
- 介绍
Writer: 用于写入字符流的抽象父类
FileWriter: 用于写入字符流的常用子类
简单理解:字符流=字节流+编码 - 构造方法
方法名 | 说明 |
---|---|
FileWriter(File file) | 根据给定的 File 对象构造一个 FileWriter 对象 |
FileWriter(File file, boolean append) | 根据给定的 File 对象构造一个 FileWriter 对象 |
FileWriter(String fileName) | 根据给定的文件名构造一个 FileWriter 对象 |
FileWriter(String fileName, boolean append) | 根据给定的文件名以及指示是否附加写入数据的 boolean 值来构造 FileWriter 对象 |
- 成员方法
方法名 | 说明 |
---|---|
void write(int c) | 写一个字符 |
void write(char[] cbuf) | 写入一个字符数组 |
void write(char[] cbuf, int off, int len) | 写入字符数组的一部分 |
void write(String str) | 写一个字符串 |
void write(String str, int off, int len) | 写一个字符串的一部分 |
- 刷新和关闭的方法
方法名 | 说明 |
---|---|
flush() | 刷新流,之后还可以继续写数据 |
close() | 关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据 |
3.4 字符读数据
- 介绍
Reader: 用于读取字符流的抽象父类
FileReader: 用于读取字符流的常用子类
构造方法
方法名 | 说明 |
---|---|
FileReader(File file) | 在给定从中读取数据的 File 的情况下创建一个新 FileReader |
FileReader(String fileName) | 在给定从中读取数据的文件名的情况下创建一个新 FileReader |
- 成员方法
方法名 | 说明 |
---|---|
int read() | 一次读一个字符数据 |
int read(char[] cbuf) | 一次读一个字符数组数据 |
- 代码演示
public class FileReaderDemo1 {
public static void main(String[] args) throws IOException {
FileReader fileReader = new FileReader("C:\\a.txt");
int len;
//一次读一个字符数据
while ((len = fileReader.read())!=-1){
System.out.println((char)len);
}
//一次读一个字符数组数据
char[] chs = new char[1024];
int len2;
while ((len2 = fileReader.read(chs))!=-1){
System.out.println(new String(chs,0,len));
}
//释放资源前,自动刷新流
fileReader.close();
}
}
3.5 字符流用户注册案例
-
案例需求
将键盘录入的用户名和密码保存到本地实现永久化存储
-
代码演示
public class FileWriteDemo2 {
public static void main(String[] args) throws IOException {
//需求: 将键盘录入的用户名和密码保存到本地实现永久化存储
//要求:用户名独占一行,密码独占一行
//分析:
//1,实现键盘录入,把用户名和密码录入进来
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名");
String username = sc.next();
System.out.println("请输入用户名");
String password = sc.next();
//2.分别把用户名和密码写到本地文件。
FileWriter fileWriter = new FileWriter("C:\\yonghu.txt");
//将用户名和密码写到文件中
fileWriter.write(username);
//表示写出一个回车换行符 windows \r\n
fileWriter.write("\r\n");
fileWriter.write(password);
//刷新流
fileWriter.flush();
//3.关流,释放资源
fileWriter.close();
}
}
4 字符缓冲流
4.1字节缓冲流介绍
- BufferedWriter:将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接受默认大小。默认值(8192个字符)足够大,可用于大多数用途
- BufferedReader:从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。 默认值(8192个字符)足够大,可用于大多数用途。
构造方法
方法名 | 说明 |
---|---|
BufferedWriter(Writer out) | 创建字符缓冲输出流对象 |
BufferedReader(Reader in) | 创建字符缓冲输入流对象 |
- 代码演示
public class BufferedWriterDemo1 {
public static void main(String[] args) throws IOException {
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("C:\\a.txt"));
bufferedWriter.write("hello\r\n");
bufferedWriter.write("world\r\n");
bufferedWriter.close();
BufferedReader reader = new BufferedReader(new FileReader("C:\\a.txt"));
//一次读一个字符数据
/* int ch;
while((ch = reader.read())!=-1){
System.out.println((char)ch);
}*/
//一次读一个字符数组
char[] chs = new char[1024];
int len;
while((len = reader.read(chs))!=-1){
System.out.println(new String(chs,0,len));
}
reader.close();
}
}
4.2 字符缓冲流特有功能
- 特有方法
方法名 | 说明 |
---|---|
void newLine() | 写一行行分隔符,行分隔符字符串由系统属性定义 |
String readLine() | 读一行文字。 结果包含行的内容的字符串,不包括任何行终止字符如果流的结尾已经到达,则为null |
- 代码演示
public class BufferedReaderDemo2 {
public static void main(String[] args) throws IOException {
//创建字符串缓冲流
BufferedWriter bw = new BufferedWriter(new FileWriter("C:\\a.txt"));
//写数据
for(int i=0;i<10;i++){
bw.write("hello" + i);
//字符缓冲流特有方法 换行
bw.newLine();
// bw.flush();
}
//释放资源
bw.close();
//创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("C:\\a.txt"));
String line;
while ((line= br.readLine())!=null){
System.out.println(line);
}
br.close();
}
}
4.3 字符缓冲流案例
- 案例需求
使用字符缓冲流读取文件中的数据,排序后再次写到本地文件。 - 代码演示
public class BufferedDemo3 {
public static void main(String[] args) throws IOException {
//需求:读取文件中的数据,排序后再次写到本地文件
//分析:
//1.要把文件中的数据读取进来。
BufferedReader br = new BufferedReader(new FileReader("C:\\sort.txt"));
String line = br.readLine();
System.out.println("读到的数据为:" + line);
br.close();
//2.按照空格进行切割
String[] split = line.split(" ");
int[] arr = new int[split.length];
for (int i = 0; i <split.length ; i++) {
String tem = split[i];
//类型转化
//3.把字符串类型的数组变成int类型
arr[i] = Integer.parseInt(tem);
}
//4。排序
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
BufferedWriter bw2 = new BufferedWriter(new FileWriter("C:\\sort.txt"));
//5.把排序之后结果写回到本地 1 2 3 4...
for (int i = 0; i < arr.length; i++) {
bw2.write(arr[i]+ " ");
}
//释放资源
bw2.close();
}
}
5 转化流
5.1 字符流中和编码解码问题相关的两个类
-
InputStreamReader:是从字节流到字符流的桥梁,父类是Reader
-
它读取字节,并使用指定的编码将其解码为字符
-
它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集-
-
-
OutputStreamWriter:是从字符流到字节流的桥梁,父类是Writer
-
是从字符流到字节流的桥梁,使用指定的编码将写入的字符编码为字节
-
它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集
-
5.2 转化流读写数据
构造方法
方法名 | 说明 |
---|---|
InputStreamReader(InputStream in) | 使用默认字符编码创建InputStreamReader对象 |
InputStreamReader(InputStream in,String chatset) | 使用指定的字符编码创建InputStreamReader对象 |
OutputStreamWriter(OutputStream out) | 使用默认字符编码创建OutputStreamWriter对象 |
OutputStreamWriter(OutputStream out,String charset) | 使用指定的字符编码创建OutputStreamWriter对象 |
- 代码演示
public class InputStreamDemo1 {
public static void main(String[] args) throws IOException {
//FileReader fileReader = new FileReader("C:\\a.txt");
//OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream("C:\\a.txt"));
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream("C:\\a.txt"));
outputStreamWriter.write("中国");
outputStreamWriter.close();
InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("C:\\a.txt"),"UTF-8");
int ch;
while((ch = inputStreamReader.read())!=-1){
System.out.println((char)ch);
}
inputStreamReader.close();
}
}
JDK11后推出字符流构造方法可以指定编码表,就不需要使用转化流让字符流与字节的之间进行转化了,减少了代码量。其中第二个参数为Charset对象,需要Charset.forName(String)方法来获取Charset对象。
6 对象操作流
6.1 对象序列化流
-
对象序列化介绍
- 对象序列化就是将对象保存到磁盘中,或者在网络中传输对象
- 这种机制就是使用一个字节序列表示一个对象,该字节序列化包含:对象的类型、对象的数据,对象的属性等信息
- 字节序列化写到文件之后,相当于文件中持久保存了一个对象的信息
- 反之,该字节序列化还可以从文件中读取回来,重构对象,对它进行反序列化
-
对象序列化流:ObjectOutputStream
- 将Java对象的原始数据类型和图形写入OutputStream。可以使用ObjectInputStream读取(重构)对象。可以通过使用流的文件来实现对象的持久存储。如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象。
- 构造方法
方法名 | 说明 |
---|---|
ObjectOutputStream(OutputStream out) | 创建一个写入指定的OutputStream的ObjectOutputStream |
- 序列化对象的方法
方法名 | 说明 |
---|---|
void writeObject(Object obj) | 将指定的对象写入ObjectOutputStream |
测试类
public static void main(String[] args) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("C:\\a.txt"));
Student student = new Student("张三",50);
objectOutputStream.writeObject(student);
objectOutputStream.close();
}
注意事项
- 一个对象要想被序列化,该对象所属的类必须必须实现Serializable 接口
- Serializable是一个标记接口,实现该接口,不需要重写任何方法
6.2 对象反序列化
- 对象反序列化流:ObjectInputStream
- ObjectInputStream反序列化先前使用ObjectOutputStream编写的原始数据和对象
- 构造方法
方法名 | 说明 |
---|---|
Object readObject() | 从ObjectInputStream读取一个对象 |
- 代码演示
public class ObjectInputStreamDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//ObjectInputStream(InputStream in):创建从指定的InputStream读取的ObjectInputStream
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("myOtherStream\\oos.txt"));
//Object readObject():从ObjectInputStream读取一个对象
Object obj = ois.readObject();
Student s = (Student) obj;
System.out.println(s.getName() + "," + s.getAge());
ois.close();
}
}
6.3 serialVersionUID&transient
serialVersionUID
-
用对象序列化流序列化了一个对象后,假如我们修改了对象所属的类文件,读取数据会不会出问题呢?
- 会出问题,会抛出InvalidClassException异常
-
如果出问题了,如何解决呢?
- 重新序列化
- 给对象所属的类加一个serialVersionUID
- private static final long serialVersionUID = 42L;
-
transient
-
如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
- 给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程
-
6.4 对象操作流练习
- 代码演示
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//序列化
//1.创建序列化流对象
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("C:\\c.txt"));
ArrayList<User> arrayList = new ArrayList<>();
//2.创建多个用户对象
User u1 = new User("张三",20);
User u2 = new User("李四",21);
User u3 = new User("王五",22);
//3.将学生对象添加到集合中
arrayList.add(u1);
arrayList.add(u2);
arrayList.add(u3);
//4.将集合对象序列化到文件中
objectOutputStream.writeObject(arrayList);
objectOutputStream.close();
//反序列化
//5.创建反序列化对象数据,读取到内存中
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("C:\\c.txt"));
Object obj = objectInputStream.readObject();
ArrayList<User> arrayList1 = (ArrayList<User>) obj;
objectInputStream.close();
for (User user:arrayList1
) {
System.out.println(user.getName()+","+user.getAge());
}
}
}
7 Properties集合
7.1 Properties作为Map集合的使用
Properties介绍
-
是一个Map体系的集合类
-
Properties可以保存到流中或从流中加载
-
属性列表中的每个键及其对应的值都是一个字符串
-
Properties基本使用
public class PPDemo1 {
public static void main(String[] args) {
//创建集合对象
Properties prop = new Properties();
prop.put("ditian001","陈先生");
prop.put("ditian002",";刘先生");
prop.put("ditian003",";和先生");
//遍历集合
Set<Object> keySet = prop.keySet();
for (Object key :
keySet) {
Object value = prop.get(key);
System.out.println(key+","+value);
}
}
}
7.2 Properties作为Map集合的特有方法
特有方法
方法名 | 说明 |
---|---|
Object setProperty(String key, String value) | 设置集合的键和值,都是String类型,底层调用 Hashtable方法 put |
String getProperty(String key) | 使用此属性列表中指定的键搜索属性 |
Set stringPropertyNames() | 从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串 |
- 代码演示
public static void main(String[] args) {
Properties prop = new Properties();
prop.setProperty("ditian001","陈先生");
prop.setProperty("ditian002","刘先生");
prop.setProperty("ditian003","和先生");
System.out.println(prop.getProperty("ditian001"));
System.out.println(prop.getProperty("ditian002"));
Set<String> setkey = prop.stringPropertyNames();
for (String key:setkey
) {
String value = prop.getProperty(key);
System.out.println(key + "," + value);
}
}
}
7.3Properties和IO流相结合的方法
- 和IO流结合的方法
方法名 | 说明 |
---|---|
void load(Reader reader) | 从输入字符流读取属性列表(键和元素对) |
void store(Writer writer, String comments) | 将此属性列表(键和元素对)写入此 Properties表中,以适合使用 load(Reader)方法的格式写入输出字符流 |
7.4 Properties集合练习
- 案例需求
生成一个a.txt文件中手动写上姓名和年龄将该数据封装成学生对象,写到本地文件、读取到集合中
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Properties prop = new Properties();
prop.setProperty("刘志忠","22");
prop.setProperty("高勤","23");
prop.setProperty("王五","24");
Set<String> names = prop.stringPropertyNames();
//int age = Integer.parseInt(prop.getProperty("age"));
ArrayList<Teacher> teachers = new ArrayList<>();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("C:\\a.txt"));
//对象操作流,在操作的时候,如果是多个对象,需要先存储在集合中,否则取出会抛异常
for (String name:names) {
String Sage = prop.getProperty(name);
int age = Integer.parseInt(Sage);
Teacher t = new Teacher(name,age);
teachers.add(t);
}
objectOutputStream.writeObject(teachers);
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("C:\\a.txt"));
ArrayList<Teacher >tea = (ArrayList<Teacher>)objectInputStream.readObject();
objectInputStream.close();
for (Teacher t:tea) {
System.out.println(t);
}
}
}