一、字符流
1.1 引入
public class Demo1 {
public static void main(String[] args) throws IOException {
FileInputStream fi = new FileInputStream("a.txt");
int res = -1;
while ((res = fi.read()) != -1) {
System.out.print((char)res); // ä½ å¥½world 出现乱码
// 中文UTF-8编码占用3个字节,read()每次读1个字节,所以有乱码
}
fi.close();
}
}
-
字符流的介绍
-
由于字节流操作中文不是特别的方便,所以
Java
就提供字符流字符流 = 字节流 + 编码表
-
中文的字节存储方式
- 用字节流复制文本文件时,文本文件也会有中文,但是没有问题,原因是最终底层操作会自动进行字节拼接成中文,如何识别是中文的呢?
- 汉字在存储的时候,无论选择哪种编码存储,第一个字节都是负数
1.2 编码表
-
什么是字符集
- 是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等
- 计算机要准确的存储和识别各种字符集符号,就需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有
ASCII
字符集、GBXXX
字符集、Unicode
字符集等
-
常见的字符集
-
ASCII
字符集:ASCII
:是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)
-
基本的
ASCII
字符集,使用7位表示一个字符,共128字符。ASCII
的扩展字符集使用8位表示一个字符,共256字符,方便支持欧洲常用字符。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等
-
GBXXX
字符集GBK
:最常用的中文码表。是在GB2312
标准基础上的扩展规范,使用了双字节编码方案,共收录了21003
个汉字,完全兼容GB2312
标准,同时支持繁体汉字以及日韩汉字等
-
Unicode
-
Unicode
,是计算机科学领域里的一项业界标准。为每种语言中的每个字符设定了统一并且唯一的整数值,把这个数值称为码点(Code Point)
。 -
但是它并不规定计算机如何存储和传输这个数值(以多少个字节存储,是定长还是变长,
Unicode
没有字节长度的概念)。 -
Unicode
定义了一个码点空间包含1,114,112
个码点,范围从0
到0x10FFFF
,也就是说,它仅仅是一个字符映射集。其中0x0000 ~ 0xFFFF
的字符表示常用字符集,称BMP
字符,0x10000 ~ 0x10FFFF
字符叫增补字符。 -
Unicode
目前规划的总空间是17个平面(平面0至16),0x0000 至 0x10FFFF
,每个平面有 65536 个码点。
例如,"AB中国"这个字符串,对应的Unicode
编码为:A -> \u0041 B -> \u0042 中 -> \u0049 国 -> \u56FD
-
UTF32
编码- 4个字节表示一个字符,总共
2^32=4294967296
- 其高位均为0,空间浪费比较严重,因此应用很少
- 4个字节表示一个字符,总共
-
常用的
UTF16
一般使用两个字节表示常用字符,对于不能表示的或不常用的字符才使用32位编码,这是Windows
程序默认的Unicode
编码方式- 使用 1 ~ 2 个
16bit
变长编码表示1,112,064
个Unicode
码点 - 它扩展于固定
16bit
长度的UCS-2
- 使用 1 ~ 2 个
-
UTF8
编码按照不同的国家文字的多少分别使用1个字节、2个字节、3个字节和4个字节表示,常用于网络传速。- 使用 1 ~ 4 个字节变长编码表示
1,112,064
个Unicode
码点 - 兼容
ASCII
- 码点数值越小,使用的字节数越少,出现的频率越高
- 使用 1 ~ 4 个字节变长编码表示
-
-
1.3 字符串中的编码解码问题
-
相关方法
方法名 说明 byte[] getBytes()
使用平台的默认字符集将该 String
编码为一系列字节byte[] getBytes(String charsetName)
使用指定的字符集将该 String
编码为一系列字节String(byte[] bytes)
使用平台的默认字符集解码指定的字节数组来创建字符串 String(byte[] bytes, String charsetName)
通过指定的字符集解码指定的字节数组来创建字符串 -
代码演示
public class Demo2 { public static void main(String[] args) throws UnsupportedEncodingException { // byte[] getBytes() 使用平台的默认字符集将该String编码为一系列字节 // byte[] getBytes(String charsetName) 使用指定的字符集将该String编码为一系列字节 String str = "字符流编码"; byte[] bytes1 = str.getBytes(); System.out.println(Arrays.toString(bytes1)); byte[] gbks = str.getBytes("GBK"); System.out.println(Arrays.toString(gbks)); // String(byte[] bytes) 使用平台的默认字符集解码指定的字节数组来创建字符串 // String(byte[] bytes, String charsetName)通过指定的字符集解码指定的字节数组来创建字符串 String s1 = new String(bytes1); String s2 = new String(gbks,"gbk"); System.out.println(s1); System.out.println(s2); } }
1.4 字符流写数据
-
介绍
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()
关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据 - 注:刷新流会立即将缓冲区的内容刷新到磁盘上
-
代码演示
public class Demo3 { public static void main(String[] args) throws IOException { FileWriter fw = new FileWriter("a.txt"); //void write(int c):写一个字符 fw.write(97); //void writ(char[] cbuf):写入一个字符数组 char[] chars = {97, 98, 99, 100}; fw.write(chars); //void write(char[] cbuf, int off, int len):写入字符数组的一部分 fw.write(chars, 0, 3); //void write(String str):写一个字符串 String str1 = "字符流写入字符串"; fw.write(str1); //void write(String str, int off, int len):写一个字符串的一部分 fw.write(str1, 1, 2); // a.txt内容:aabcdabc字符流写入字符串符流 //释放资源 fw.close(); } }
1.5 字符流读数据
- 介绍
Reader
:用于读取字符流的抽象父类FileReader
:用于读取字符流的常用子类
-
构造方法
方法名 说明 FileReader(File file)
在给定从中读取数据的 File
的情况下创建一个新FileReader
FileReader(String fileName)
在给定从中读取数据的文件名的情况下创建一个新 FileReader
-
成员方法
方法名 说明 int read()
一次读一个字符数据 int read(char[] cbuf)
一次读一个字符数组数据 -
代码演示
public class Demo4 { public static void main(String[] args) throws IOException { FileReader fr = new FileReader("a.txt"); System.out.println(fr.read()); // 一次读一个字符数据 // 一次读取一个字符数组 char[] chars = new char[1024]; int len; while ((len = fr.read(chars)) != -1) { System.out.println(new String(chars, 0, len)); } fr.close(); } }
1.6 字符流用户注册案例
-
案例需求
- 将键盘录入的用户名和密码保存到本地实现永久化存储
-
实现步骤
- 获取用户输入的用户名和密码
- 将用户输入的用户名和密码写入到本地文件中
- 关流,释放资源
-
代码实现
public class Demo5 { public static void main(String[] args) throws IOException { // 实现键盘录入,把用户名和密码录入进来 Scanner sc = new Scanner(System.in); System.out.println("请录入用户名"); String username = sc.next(); System.out.println("请录入密码"); String password = sc.next(); // 分别把用户名和密码写到本地文件。 FileWriter fw = new FileWriter("a.txt"); // 将用户名和密码写到文件中 fw.write(username); // 表示写出一个回车换行符 windows \r\n MacOS \r Linux \n fw.write("\r\n"); fw.write(password); // 关流,释放资源 fw.close(); } }
1.7 字符缓冲流
-
字符缓冲流介绍
-
BufferedWriter
:将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接受默认大小。默认值足够大(8192),可用于大多数用途 -
BufferedReader
:从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。 默认值足够大(8192),可用于大多数用途
-
-
构造方法
方法名 说明 BufferedWriter(Writer out)
创建字符缓冲输出流对象 BufferedReader(Reader in)
创建字符缓冲输入流对象 -
代码演示
public class Demo6 { public static void main(String[] args) throws IOException { // BufferedWriter(Writer out) BufferedWriter bw = new BufferedWriter(new FileWriter("a.txt")); // 存在默认清空 bw.write("line1"); bw.write("\r\n"); bw.write("line2"); bw.close(); // BufferedReader(Reader in) BufferedReader br = new BufferedReader(new FileReader("a.txt")); char[] chars = new char[1024]; int len; while ((len = br.read(chars)) != -1) { System.out.println(new String(chars, 0, len)); } br.close(); } }
1.8 字符缓冲流特有功能
-
方法介绍
BufferedWriter
类中:
方法名 说明 void newLine()
写一行 行分隔符,行分隔符字符串由系统属性定义 BufferedReader
类中:
方法名 说明 String readLine()
读一行文字。结果包含行的内容的字符串,不包括任何行终止字符如果流的结尾已经到达,则为 null
-
代码演示
public class Demo7 { public static void main(String[] args) throws IOException { // BufferedWriter(Writer out) BufferedWriter bw = new BufferedWriter(new FileWriter("a.txt")); // 存在默认清空 bw.write("line1"); bw.newLine(); bw.write("line2"); bw.close(); // BufferedReader(Reader in) BufferedReader br = new BufferedReader(new FileReader("a.txt")); System.out.println(br.readLine()); System.out.println(br.readLine()); br.close(); } }
1.9 字符缓冲流操作文件中数据排序案例
-
案例需求
使用字符缓冲流读取文件中的数据,排序后再次写到本地文件
-
实现步骤
- 将文件中的数据读取到程序中
- 对读取到的数据进行处理
- 将处理后的数据添加到集合中
- 对集合中的数据进行排序
- 将排序后的集合中的数据写入到文件中
-
代码实现
import java.io.*; import java.util.Arrays; public class Demo8 { public static void main(String[] args) throws IOException { BufferedWriter bw = new BufferedWriter(new FileWriter("a.txt")); bw.write("1 3 4 6 2 7 2 5 3 9"); bw.close(); // 读取文件中乱序的字符串 BufferedReader br = new BufferedReader(new FileReader("a.txt")); String s = br.readLine(); System.out.println("原内容:" + s); String[] strings = s.split(" "); int[] array = new int[strings.length]; for (int i = 0; i < strings.length; i++) { array[i] = Integer.parseInt(strings[i]); } // 排序 Arrays.sort(array); System.out.println("排序后:" + Arrays.toString(array)); // 清空源文件写入排序后的数组 BufferedWriter bw2 = new BufferedWriter(new FileWriter("a.txt")); for (int i = 0; i < array.length; i++) { bw2.write(array[i] + " "); } bw2.close(); } }
1.10 IO流小结
- IO流小结
二、转换流
2.1 字符流中和编码解码问题相关的两个类
-
InputStreamReader
:是从字节流到字符流的桥梁,父类是Reader
- 它读取字节,并使用指定的编码将其解码为字符
- 它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集
-
OutputStreamWriter
:是从字符流到字节流的桥梁,父类是Writer
- 是从字符流到字节流的桥梁,使用指定的编码将写入的字符编码为字节
- 它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集
JDK 11
以后可以直接使用FileReader(String fileName, Charset charset)
指定字符集读取文件
2.2 转换流读写数据
-
构造方法
方法名 说明 InputStreamReader(InputStream in)
使用默认字符编码创建 InputStreamReader
对象InputStreamReader(InputStream in,String chatset)
使用指定的字符编码创建 InputStreamReader
对象OutputStreamWriter(OutputStream out)
使用默认字符编码创建 OutputStreamWriter
对象OutputStreamWriter(OutputStream out,String charset)
使用指定的字符编码创建 OutputStreamWriter
对象 -
代码演示
public class Demo9 { public static void main(String[] args) throws IOException { outputStreamWriter(); // 写一个GBK编码的文件 inputStreamReader(); // 以GBK解码读取 InputStreamReader isr = new InputStreamReader(new FileInputStream("C:\\Users\\16085\\Desktop\\a.txt")); char[] chars = new char[1024]; int len; while ((len = isr.read(chars)) != -1) { // 读取GBK编码的文件出现乱码 System.out.println(new String(chars, 0, len)); // GBK�����ַ� } isr.close(); // JDK11 以后可以直接指定以何种方式解码 FileReader fileReader = new FileReader("C:\\Users\\16085\\Desktop\\a.txt",Charset.forName("GBK")); BufferedReader br = new BufferedReader(fileReader); System.out.println(br.readLine()); // GBK编码字符 } private static void outputStreamWriter() throws IOException { OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("C:\\Users\\16085\\Desktop\\a.txt"),"GBK"); osw.write("GBK编码字符"); osw.close(); } private static void inputStreamReader() throws IOException { InputStreamReader isr = new InputStreamReader(new FileInputStream("C:\\Users\\16085\\Desktop\\a.txt"),"GBK"); char[] chars = new char[1024]; int len; while ((len = isr.read(chars)) != -1) { // 指定GBK解码以后正常打印 System.out.println(new String(chars, 0, len)); // GBK编码字符 } isr.close(); } }
三、对象操作流
3.1 对象序列化流
-
对象序列化介绍
- 对象序列化:就是将对象保存到磁盘中,或者在网络中传输对象
- 这种机制就是使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据和对象中存储的属性等信息
- 字节序列写到文件之后,相当于文件中持久保存了一个对象的信息
- 反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化
-
对象序列化流:
ObjectOutputStream
- 将
Java
对象的原始数据类型和图形写入OutputStream
。 可以使用ObjectInputStream
读取(重构)对象。 可以通过使用流的文件来实现对象的持久存储。 如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象
- 将
-
构造方法
方法名 说明 ObjectOutputStream(OutputStream out)
创建一个写入指定的 OutputStream
的ObjectOutputStream
-
序列化对象的方法
方法名 说明 void writeObject(Object obj)
将指定的对象写入 ObjectOutputStream
-
示例代码
- 学生类
public class Student implements Serializable { private String name; private int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
测试类
public class Demo10 { public static void main(String[] args) throws IOException { Student s = new Student("zhangsan", 23); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt")); oos.writeObject(s); // 将对象s保存到文件中 oos.close(); } }
-
注意事项
- 一个对象要想被序列化,该对象所属的类必须必须实现
Serializable
接口 Serializable
是一个标记接口,实现该接口,不需要重写任何方法
- 一个对象要想被序列化,该对象所属的类必须必须实现
3.2 对象反序列化流
-
对象反序列化流:
ObjectInputStream
ObjectInputStream
反序列化先前使用ObjectOutputStream
编写的原始数据和对象
-
构造方法
方法名 说明 ObjectInputStream(InputStream in)
创建从指定的 InputStream
读取的ObjectInputStream
-
反序列化对象的方法
方法名 方法名 Object readObject()
从 ObjectInputStream
读取一个对象 -
几种读写结束符对比
方法名 | 说明 |
---|---|
read() | 读取到文件末尾返回值是 -1 |
readLine() | 读取到文件的末尾返回值 null |
readObject() | 读取到文件的末尾 直接抛出异常 |
-
如果要序列化的对象有多个,不建议直接将多个对象序列化到文件中,因为反序列化时容易出异常
- 建议:将要序列化的多个对象存储到集合中,然后将集合序列化到文件中
-
示例代码
public class Demo11 { public static void main(String[] args) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt")); //Object readObject():从ObjectInputStream读取一个对象 System.out.println(ois.readObject()); // Student{name='zhangsan', age=23} ois.close(); } }
3.3 serialVersionUID&transient
-
serialVersionUID
- 用对象序列化流序列化了一个对象后,假如我们修改了对象所属的类文件,读取数据会不会出问题呢?
- 会出问题,会抛出
InvalidClassException
异常
- 会出问题,会抛出
- 如果出问题了,如何解决呢?
- 重新序列化
- 给对象所属的类加一个
serialVersionUID
private static final long serialVersionUID = 42L;
- 用对象序列化流序列化了一个对象后,假如我们修改了对象所属的类文件,读取数据会不会出问题呢?
-
transient
- 如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
- 给该成员变量加
transient
关键字修饰,该关键字标记的成员变量不参与序列化过程
- 给该成员变量加
- 如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
-
示例代码
学生类
public class Student implements Serializable { private static final long serialVersionUID = 42L; private String name; // private int age; private transient int age; ...... }
测试类
public class Demo12 { public static void main(String[] args) throws IOException, ClassNotFoundException { write(); // age被transient修饰,存储int默认值0 read(); } //反序列化 private static void read() throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt")); //Object readObject():从ObjectInputStream读取一个对象 System.out.println(ois.readObject()); // Student{name='zhangsan', age=0} ois.close(); } //序列化 private static void write() throws IOException { Student s = new Student("zhangsan", 23); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt")); oos.writeObject(s); // 将对象s保存到文件中 oos.close(); } }
3.4 对象操作流练习
-
案例需求
- 创建多个学生类对象写到文件中,再次读取到内存中
-
实现步骤
- 创建序列化流对象
- 创建多个学生对象
- 将学生对象添加到集合中
- 将集合对象序列化到文件中
- 创建反序列化流对象
- 将文件中的对象数据,读取到内存中
-
代码实现
学生类(同上)
测试类
public class Demo13 { public static void main(String[] args) throws Exception { // 序列化 // 创建序列化流对象 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt")); ArrayList<Student> arrayList = new ArrayList<>(); // 创建多个学生对象 Student s1 = new Student("zhangsan", 23); Student s2 = new Student("lisi", 24); // 将学生对象添加到集合中 arrayList.add(s1); arrayList.add(s2); // 将集合对象序列化到文件中 oos.writeObject(arrayList); oos.close(); // 反序列化 // 创建反序列化流对象 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt")); // 将文件中的对象数据,读取到内存中 Object obj = ois.readObject(); ArrayList<Student> arrayList2 = (ArrayList<Student>) obj; ois.close(); // 遍历 for (Student s : arrayList2) { System.out.println(s); } } }
四、Properties集合
4.1 Properties作为Map集合的使用
-
Properties
介绍- 是一个
Map
体系的集合类 Properties
可以保存到流中或从流中加载- 属性列表中的每个键及其对应的值都是一个字符串
- 是一个
-
Properties
基本使用public class Demo14 { public static void main(String[] args) { Properties properties = new Properties(); // 增加键值 properties.put("id1", "zhangsan"); properties.put("id2", "lisi"); properties.put("id3", "wangwu"); // 删除键值 properties.remove("id3"); // 修改键值 properties.put("id2", "zhaoliu"); // 查询键值 System.out.println(properties.get("id1")); // 遍历 // 先获取键,再获取值 Set<Object> keySet = properties.keySet(); for (Object key : keySet) { Object value = properties.get(key); System.out.println(key + "=" + value); } System.out.println("--------------"); // 先获取entry集合,再分别获取键值 Set<Map.Entry<Object, Object>> entries = properties.entrySet(); for (Map.Entry<Object, Object> entry : entries) { Object key = entry.getKey(); Object value = entry.getValue(); System.out.println(key + "=" + value); } } }
4.2 Properties作为Map集合的特有方法
-
特有方法
方法名 说明 Object setProperty(String key, String value)
设置集合的键和值,都是 String
类型,底层调用Hashtable
方法put
String getProperty(String key)
使用此属性列表中指定的键搜索属性 Set<String> stringPropertyNames()
从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串 -
示例代码
public class Demo15 { public static void main(String[] args) { Properties prop = new Properties(); // Object setProperty(String key, String value):设置集合的键和值,都是String类型 prop.setProperty("id1", "zhangsan"); prop.setProperty("id2", "lisi"); prop.setProperty("id3", "wangwu"); // String getProperty(String key):使用此属性列表中指定的键搜索属性 System.out.println(prop.getProperty("id1")); // Set<String> stringPropertyNames():从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串 Set<String> names = prop.stringPropertyNames(); for (String key : names) { String value = prop.getProperty(key); System.out.println(key + "=" + value); } } }
4.3 Properties和IO流相结合的方法
-
和
IO
流结合的方法方法名 说明 void load(Reader reader)
从输入字符流读取属性列表(键和元素对) void store(Writer writer, String comments)
将此属性列表(键和元素对)写入此 Properties
表中,以适合使用load(Reader)
方法的格式写入输出字符流 -
示例代码
public class Demo16 {
public static void main(String[] args) throws IOException {
Properties prop = new Properties();
// void load(Reader reader):从输入字符流读取属性列表(键和元素对)
FileReader fr = new FileReader("prop.properties");
prop.load(fr);
fr.close();
System.out.println(prop);
// void store(Writer writer, String comments)
// 将此属性列表(键和元素对)写入此Properties表中,comments为开头注释
prop.setProperty("id3","wangwu");
FileWriter fw = new FileWriter("prop.properties");
prop.store(fw,"");
fw.close();
}
}
prop.properties
内容