JavaSE 进阶 (10) IO

字符流-写出数据

字符流写数据

步骤:

  1. 创建字符输出流对象
Constructor 描述
FileWriter(File file) 给一个File对象构造一个FileWriter对象。
FileWriter(FileDescriptor fd) 构造与文件描述符关联的FileWriter对象。
FileWriter(File file, boolean append) 给一个File对象构造一个FileWriter对象。
FileWriter(String fileName) 构造一个给定文件名的FileWriter对象。
FileWriter(String fileName, boolean append) 构造一个FileWriter对象,给出一个带有布尔值的文件名,表示是否附加写入的数据。
  1. 写数据
方法名 说明
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) 写一个字符串的一部分
  1. 释放资源

写一个字符

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public static void main(String[] args){
	//创建字符输出流的对象
	//FileWriter fw = new FileWriter(new File("charstream\\a.txt"));
	FileWriter fw = new FileWriter("charstream\\a.txt");

	//写出数据
	fw.write(97);	//a
	fw.write(98);	//b
	fw.write(99);	//c
	
	//释放资源
	fw.close();
}

写一个字符数组

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public static void main(String[] args){
	//创建字符输出流的对象
	//FileWriter fw = new FileWriter(new File("charstream\\a.txt"));
	FileWriter fw = new FileWriter("charstream\\a.txt");

	//写出数据
	char[] chars = {97, 98, 99, 100, 101};
	fw.write(chars);
	
	//释放资源
	fw.close();
}

写出字符数组的一部分

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public static void main(String[] args){
	//创建字符输出流的对象
	//FileWriter fw = new FileWriter(new File("charstream\\a.txt"));
	FileWriter fw = new FileWriter("charstream\\a.txt");

	//写出数据
	char[] chars = {97, 98, 99, 100, 101};
	fw.write(chars, 0, 3);  //abc
	
	//释放资源
	fw.close();
}

写一个字符串

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public static void main(String[] args){
	//创建字符输出流的对象
	//FileWriter fw = new FileWriter(new File("charstream\\a.txt"));
	FileWriter fw = new FileWriter("charstream\\a.txt");

	//写出数据
	String line = "程序员abc";
	fw.write(line);
	
	//释放资源
	fw.close();
}

写一个字符串的一部分

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public static void main(String[] args){
	//创建字符输出流的对象
	//FileWriter fw = new FileWriter(new File("charstream\\a.txt"));
	FileWriter fw = new FileWriter("charstream\\a.txt");

	//写出数据
	String line = "程序员abc";
	fw.write(line, 0, 2);	//程序
	
	//释放资源
	fw.close();
}

字符流-写出数据的注意事项

  1. 创建字符输出流对象
  • 如果文件不存在,就创建,但是要保证父级路径存在
  • 如果文件存在就先删除,再创建

  1. 写数据
  • 写int类型的整数,实际写出的是整数在码表上对应的字母 (如果要写数字,用字符串的形式)
  • 写字符串数据,是把字符串本身原样写出

  1. 释放资源
  • 每次使用完流,必须要释放资源

字符流-flush和close方法

方法名 说明
flush() 刷新流,还可以继续写数据
close() 关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public static void main(String[] args) throws IOException{
	//flush()刷新流。刷新完毕之后,还可以继续写数据
	//c1ose()关闭流。释放资源。一旦关闭,就不能写数据
	
	FileWriter fw = new Filewriter("charstream\\a.txt");
	
	fw.write("程序员"); //我们发现a.txt中什么都没有,现在还没有写到文件中
	fw.flush();  //刷新后发现a.txt中有了,flush有一个特点,刷新完后,还可以继续写数据,流并没有关闭
	fw.write("666");
	fw.flush();
	
	//fw.close();
}
  1. 普通字符流,本身就有缓冲区,缓冲区(8192)满了之后,才会进行写出,但是用flush可以不需要满,就可以输出
  2. 字符流自带的缓冲区用来将字节转为字符,因为无论字节流还是字符流读的都是字节,只不过,字符流在自己带的缓冲区里组装了一下,再输出
  3. 字节流没有这个用来组装的缓冲区,所以字节流不需要 flush
图1

字符流-读取数据

字符流读数据的2种方式

构造方法
Constructor 描述
FileReader(File file) 创建一个新的 FileReader,给出 File读取。
FileReader(FileDescriptor fd) 创建一个新的 FileReader,给予 FileDescriptor从中读取。
FileReader(String fileName) 创建一个新的 FileReader,给定要读取的文件的名称。

一次读取一个字符

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public static void main(String[] args) throws IOException{
	//创建字符输入流的对象
	//FileReader fr = new FileReader(new File("charstream\\a.txt"));
	FileReader fr = new FileReader("charstream\\a.txt");
	
	//读取数据
	int ch;
	while((ch = fr.read()) != -1){
		System.out.println((char)ch);
	}

	//释放资源
	fr.close();
}

一次读取多个字符

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public static void main(string[] args) throws IOException{
	//一次读取多个字符。
	
	//创建对象
	FileReader fr = new FileReader("charstream\\a.txt");
	
	//创建一个数组
	char[] chars = new char[1024];
	int len;

	//read方法还是读取,但是是一次读取多个字符
	//他把读到的字符都存入到chars数组。
	//返回值:表示本次读到了多少个字符。
	while((len = fr.read(chars)) != -1){
		System.out.println(new string(chars, 0, len));
	}
	
	fr.close();
}

字符流-练习

案例:保存键盘录入的数据

需求:将用户键盘录入的用户名和密码保存到本地实现永久化存储。要求用户名独占一行,密码独占一行。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 fw = new FileWriter("charstream\\a.txt");
	//将用户名和密码写到文件中
	fw.write(username);
	//表示写出一个回车换行符 windows: \r\n   Macos: \r    Linux: \n
	fw.write(str:"\r\n");
	fw.write(password);
	//刷新流
	fw.flush();
	//释放资源
	fw.close();
}

字符缓冲输入流-读取数据

字符缓冲流

  • BufferedWriter: 可以将数据高效的写出 (内部会创建一个8k的数组)
  • BufferedReader: 可以将数据高效的读取到内存 (内部会创建一个8k的数组)

构造方法:

  • BufferedWriter(Writer out)
  • BufferedReader(Reader in)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public static void main(String[] args) throws IOException{
	//字符缓冲输入流
	BufferedReader br = new BufferedReader(new FileReader("charstream\\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();
}
  • 字符流本身就有缓冲区,再带一个缓冲区没有意义,和普通字符流用着一样
  • 我们用缓冲流,是因为他有特有的好用的方法

字符缓冲输出流-输出数据

构造方法
Constructor 描述
BufferedWriter(Writer out) 创建使用默认大小的输出缓冲区的缓冲字符输出流。
BufferedWriter(Writer out, int sz) 创建一个新的缓冲字符输出流,使用给定大小的输出缓冲区。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static void main(String[] args) throws IOException{
	//字符缓冲输出流
	BufferedWriter bw = new Bufferedwriter(new Filewriter("charstream\\a.txt"));

	//写出数据
	bw.write(97);  //a
	bw.write("\r\n");
	
	char[] chars = {97, 98, 99, 100, 101};
	bw.write(chars);  //abcde
	bw.write("\r\n");
	
	bw.write(chars, 0, 3);  //abc
	bw.write("\r\n");
	
	bw.write("程序员");  //程序员
	bw.write("\r\n");
	
	String line = "abcdefg";
	bw.write(line, 0, 5);  //abcde
	bw.write("\r\n");
	
	bw.flush();
	
	bw.close();
}

缓冲流-特有方法

字符缓冲流特有功能

BufferedWriter

  • void newLine(): 写一行行分隔符,行分隔符字符串由系统属性定义

BufferedReader

  • public String readLine(): 读一行文字。结果包含行的内容的字符串,不包括任何行终止字符,如果流的结尾已经到达,则为null

newline

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) throws IOException{
	//创建对象
	BufferedWriter bw = new BufferedWriter(new Filewriter("charstream\\a.txt"));
	
	//写出数据
	bw.write("程序员666");
	//跨平台的回车换行
	bw.newLine();
	bw.write("abcdef");
	//跨平台的回车换行
	bw.newLine();
	bw.write("-------------");

	//刷新流
	bw.flush();
	//释放资源
	bw.close();
}

readLine

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void main(String[] args) throws IOException{
	//创建对象
	BufferedReader br = new BufferedReader(new FileReader("charstream\\a.txt"));
	
	//读取数据
	String line1 = br.readLine();
	String line2 = br.readLine();
	String line3 = br.readLine();

	//在之前,如果读不到数据,返回-1
	//但是readLine如果读不到数据返回null
	String line4 = br.readLine();
	String line5 = br.readLine();

	System.out.println(line1);  //程序员666
	System.out.println(line2);  //abcdef
	System.out.println(line3);  //-------------
	System.out.println(line4);  //null
	System.out.println(line5);  //null

	//释放资源
	br.close();
}

使用循环进行改进

可以读取一整行数据。一直读,读到回车换行为止 (读取回车换行符之前的数据)。但是他不会读取回车换行符

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public static void main(String[] args)throws IOException {
	//字符缓冲流的特有功能
	//字符缓冲输入流BufferedReader: readLine读一整行
	
	//创建对象
	BufferedReader br = new BufferedReader(new FileReader("charstream\\a.txt"));

	//使用循环来进行改进
	String line;
	while((line = br.readLine()) != null){
		System.out.println(line);
	}

	//释放资源
	br.close();
}

缓冲流-练习

案例:读取文件中的数据排序后再次写到本地

需求:读取文件中的数据,排序后再次写到本地文件

9 1 2 5 3 10 4 6 7 8

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static void main(String[] args)throws IOException {
	//1.要把文件中的数据读取进来。
	BufferedReader br = new BufferedReader(new FileReader("charstream\\sort.txt"));
	//输出流一定不能写在这里,因为会清空文件中的内容
	//Bufferedwriter bw = new Bufferedwriter(new Filewriter("charstream\\sort.txt"));
	
	String line = br.readLine();
	System.out.println("读取到的数据为" + line);
	br.close();
	
	//2.按照空格进行切分
	String[] split = line.split(" "); //9 1 2 5 3 10 4 6 7 8
	//3.把字符串类型的数组变成int类型
	int[] arr = new int[split.length];
	//遍历split数组,可以进行类型转换。
	for(int i = 0; i < split.length; i++){
		String smallstr = split[i];
		//类型转换
		int number = Integer.parseInt(smallstr);
		//把转换后的结果存入到arr中
		arr[i]=number;
	}

	//2.排序
	Arrays.sort(arr);
	System.out.println(Arrays.tostring(arr));

	//3.把排序之后结果写回到本地1234..·
}

IO流-小结

图2

其他流

  • 转换流
  • 对象操作流
  • Properties

转换流-概念

转换流的本质是字符流,引出问题:不同编码读取时会乱码

不同编码读取出现乱码的问题

  • 如果代码编码和被读取的文本文件的编码是一致的,使用字符流读取文本文件时不会出现乱码
  • 如果代码编码和被读取的文本文件的编码是不一致的,使用字符流读取文本文件时就会出现乱码

IO流的体系

图3

观察 InputStreamReader 的组成

  • InputStreamReader = InputStream + Reader 把字节输入流封装成一个字符输入流,还可以自己来处理编码

观察 OutputStreamWriter 的组成

  • OutputStreamWriter = OutputStream + Writer 把字节输出流封装成一个字符输出流,还可以指定编码

转换流-字符输入转换流

InputStreamReader(字符输入转换流)

  • 解决不同编码时,字符流读取文本内容乱码的问题。
  • 解决思路:先获取文件的原始字节流,再将其按真实的字符集编码转成字符输入流,这样字符输入流中的字符就不乱码了。

字符流的本质:字节流 + 编码表

我们最终读数据、写数据采用的都是字节流,但是我们现在的文本文件的编码是GBK,我们拿 字节流 + GBK,他俩加在一起,我们就可称为转换流(字符流)

字符流的底层是字节流 + UTF-8,且编码格式无法更改,所以在读取GBK编码格式的文件时,是不行的,我们采用转换流就可以指定编码格式了,我们可以理解为就是可以指定编码格式的字符流

构造器 说明
public InputStreamReader(InputStream is) 把原始的字节输入流,按照代码默认编码转成字符输入流(与直接用FileReader的效果一样)
public InputStreamReader(InputStream is, String charset) 把原始的字节输入流,按照指定字符集编码转成字符输入流(重点)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public static void main(String[] args){
	//1 创建字符输入转换流对象
	//public InputStreamReader(InputStream is, String charset) 把原始的字节输入流,按照指定字符集编码转成字符输入流(重点)
	InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"), "utf-8");

	//2 读数据
	char[] chs = new char[1024];
	int len;
	while ((len = isr.read(chs)) !=- 1){
		System.out.print(new String(chs, 0, len));
	}

	//3 释放资源
	isr.close();
}

转换流-字符输出转换流

OutputStreamWriter (字符输出转换流)

  • 作用:可以控制写出去的字符使用什么字符集编码。
  • 解决思路:获取字节输出流,再按照指定的字符集编码将其转换成字符输出流,以后写出去的字符就会用该字符集编码了。
构造器 说明
public OutputStreamWriter(OutputStream os) 可以把原始的字节输出流,按照代码默认编码转换成字符输出流
public OutputStreamWriter(OutputStream os, String charset) 可以把原始的字节输出流,按照指定编码转换成字符输出流(重点)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public static void main(String[] args){
	//1 创建字符输出转换流对象,转换流的本质就是字符流
	//public OutputStreamWriter(OutputStream os, String charset)可以把原始的字节输出流,按照指定编码转换成字符输出流(重点)
	OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("a.txt"), "GBK");
	
	//2 写数据
	osw.write("hello转换流");
	osw.flush();
	
	//3 释放资源
	osw.close()
}

JDK11 以后,字符流新推出了一个构造,普通字符流也可以指定编码格式

1
2
3
4
5
6
FileReader fr = new FileReader("MyTest\\test.txt", Charset.forName("GBK"));
int ch;
while((ch = isr.read()) != -1){
	System.out.println((char)ch);
}
fr.close();

1
2
3
4
5
* @since 11
*/
public FileReader(String fileName, Charset charset) throws IOException {
    super(new FileInputStream(fileName), charset);
}

需要控制写出去的字符使用什么字符集编码,该咋整?

  1. 调用String提供的getBytes方法解决?
1
2
3
String data = "我爱你中国abc";

byte[] bytes = data.getBytes("GBK");
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public static void main(String[] args){
	//1 创建字符输出转换流对象,转换流的本质就是字符流
	//public OutputStreamWriter(OutputStream os, String charset)可以把原始的字节输出流,按照指定编码转换成字符输出流(重点)
	//OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("a.txt"), "GBK");
	FileWriter osw = new FileWriter("a.txt");
	
	//2 写数据
	byte[] bytes = "hello 转换流".getBytes("GBK"); //编码
	//这里会出问题,因为String默认是UTF-8编码。String s = new String(bytes,"UTF-8");在这里解码了,会产生乱码,只能用字节流
	String s = new String(bytes);
	osw.write(s);
	osw.flush();
	
	//3 释放资源
	osw.close()
}

上面的字符流不行,我们用下面的字节流尝试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public static void main(String[] args){
	FileOutputStream osw = new FileOutputStream("a.txt");
	
	//2 写数据
	osw.write("hello 转换流".getBytes("GBK"));  //指定GBK编码,将GBK编码的数据写到文件中
	osw.flush();
	
	//3 释放资源
	osw.close()
}
  1. 使用 “字符输出转换流” 实现

对象操作流-基本特点

下面这种方式存在一个问题,我保存的是用户名和密码,此时,无论是谁找到 a.txt 都能看到账户名和密码,此时,数据是不安全的,因此需要用到对象操作流

1
2
3
4
5
6
7
8
9
public static void main(String[] args) throws IOException{
	User user = new User("zhangsan", "qwer");
	//需求:把这个用户信息保存到本地文件去
	Bufferedwriter bw = new Bufferedwriter(new FileWriter("a.txt"));
	bw.write(user.getusername());
	bw.newLine();
	bw.write(user.getpassword());
	bw.close();
}

对象操作流的特点:可以把对象以字节的形式写到本地文件 (*把对象的整体以字节的形式写到本地文件),直接打开文件,是读不懂得,需要再次用对象操作流读到内存中

对象操作流-序列化

图4

对象操作流分为两类:对象操作输入流对象操作输出流

  1. 对象操作输出流 (对象序列化流):就是将对象写到本地文件中,或者在网络中传输对象,这种行为也称为序列化
  2. 对象操作输入流(对象反序列化流):把写到本地文件中的对象读到内存中,或者接收网络中传输的对象,这种行为也称为反序列化
构造方法
Modifier Constructor 描述
protected ObjectOutputStream() 为完全重新实现ObjectOutputStream的子类提供一种方法,不必分配刚被ObjectOutputStream实现使用的私有数据。
ObjectOutputStream (OutputStream out) 创建一个写入指定的OutputStreamObjectOutputStream
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class User implements Serializable{
	private string username;
	private string password;
	
	public User(){
	}
	
	public User(String username, String password){
		this.username = username;
		this.password = password;
	}
	
	//set/get...
}

public static void main(String[] args) throws IOException {
	User user = new User("zhangsan", "qwer");
	
	ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt"));
	oos.writeObject(user);
	oos.close();
}

注意:如果想把一个对象写到文件上,则该对象所在的类必须实现Serializable接口


Serializable接口

概念:Serializable接口是一个"标记接口"

特点:

  • 标记接口没有抽象方法,不需要重写,
  • 标记接口仅作为一个标记,不具有任何实际意义,当一个类实现了Serializable接口,那么就表示这个类的对象可以被序列化

对象操作流-反序列化

构造方法
Modifier Constructor 描述
protected ObjectInputStream() 为完全重新实现ObjectInputStream的子类提供一种方法,不必分配刚刚被ObjectInputStream实现使用的私有数据。
ObjectInputStream (InputStream in) 创建从指定的InputStream读取的ObjectInputStream。
1
2
3
4
5
6
public static void main(String[] args )throws IOException, ClassNotFoundException {
	ObjectInputStream ois = new ObjectInputStream(new FileInputstream("a.txt"));
	User o = (User)ois.readObject();
	System.out.println(o);
	ois.close();
}

对象操作流的-两个注意点 1

(1) 用对象序列化流序列化了一个对象后,假如我们修改了对象所属的Javabean类,读取数据会不会出问题呢?

会出问题,会抛出InvalidClassException异常


当序列化运行时检测到类中的以下问题之一时抛出

  • 类的串行版本与从流中读取的类描述符的类型不匹配
  • 该类包含未知的数据类型
  • 该类没有可访问的无参数构造函数

serialVersionUID 序列号

如果我们自己没有定义,那么虚拟机会根据类中的信息自动的计算出一个序列号

如果我们修改了类中的信息,那么虚拟机会再次计算出一个序列号


问题出现的原因?

第一步:把User对象序列化到本地。—-5824992206458892149

第二步:修改了javabean类,导致类中的序列号自动变成 ——4900133124572371851

第三步:把文件中的对象读到内存,本地中的序列号和类中的序列号不一致了


解决? 不让虚拟机帮我们自动计算,我们自己手动给出。而且这个值不要变

1
private static final long serialVersionUID = 100L;

也就是说,我自己定义一个序列号,在将对象序列化到本地时,这个序列号也会序列化到本地,然后,我们修改javabean类(注意:不要修改自己定义的序列号),把文件中的对象读到内存中时,序列号的值没变

(2) 如果出问题了,如何解决呢?

给对象所属的类加一个serialVersionUID

1
private static final long serialVersionUID = 42L;

(3) 如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?

给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程

1
private transient string password;

对象操作流的-两个注意点 2

序列号的定义格式

1
private static final long serialVersionUID = 1L;

值随便写,只要不超过 long 的取值范围就可以

对象操作流-练习

案例:用对象操作流读写多个对象

需求:创建多个Javabean类对象写到文件中,再次读取到内存。


第一种方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class Student implements Serializable {
	private static final long serialversionUID = 1L;
	private string name;
	private int age;
	//...
}

public static void main(String[] args) throws IOException {
	Student s1 = new Student("杜子腾"16);
	Student s2 = new student("张三"23);
	Student s3 = new Student("李四"24);
	
	ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt"));
	oos.writeObject(s1);
	oos.writeObject(s2);
	oos.writeObject(s3);
	
	oos.close();
	
	ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));
	Object obj;
	/**
	读到末尾既不是-1也不是null
	while((obj = ois.readObject()) != null){

		System.out.println(obj);
	}
	*/
	while(true){
		try{
			Object o = ois.readObject();
		}catch (EOFException e){
			break;  //EOFException代表文件读到末尾,用break进行终止
		}
	}
	ois.close()
}

其他的流读到末尾要不是 -1,要不是 null

而对象操作流读到末尾是 EOFException


第二种方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void main(String[] args) throws IOException {
	Student s1 = new Student("杜子腾"16);
	Student s2 = new student("张三"23);
	Student s3 = new Student("李四"24);
	
	ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt"));
	ArrayList<Student> list = new ArrayList<>();  //ArayList也实现了Serializable接口,也有serialVersionUID
	list.add(s1);
	list.add(s2);
	list.add(s3);
	
	//我们往本地文件中写的就是一个集合
	oos.writeObject(list);
	
	oos.close();
	
	ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));
	ArrayList<student> list2 = (ArrayList<student>) ois.readObject();
	for (Student student : list2){
		System.out.println(student);
	}
	ois.close();
	
}

无论有多少对象,我放入到一个容器中,将容器写入到文件中,这样读的时候只需要读一次,读一个容器

Properties-概述

Properties概述:

  1. 是集合体系中的一员,是Map集合的子类,是无序的
图5
  1. Properties中有跟IO相关的方法
方法签名 功能描述
load(InputStream inStream) 从输入字节流读取属性列表(键和元素对)
store(OutputStream out, String comments) 将此 Properties 表中的属性列表(键和元素对)以适合于使用 load(InputStream) 方法加载到 Properties 表格的格式写入输出流
  1. 键值对的数据类型基本都定义为字符串 (没有泛型限制,什么都可以存,但我们一般只存字符串)

Properties没有泛型,创建对象的时候,不需要在后面写<>,也就是说我可以向Properties添加任意数据类型,但一般我们只存字符串

Properties-作为map集合的基本使用

练习:Properties作为Map集合的使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class PropertiesDemo1 {
	public static void main(String[] args){
		Properties prop = new Properties();
		
		//增
		prop.put("小龙女""尹志平");
		prop.put("郭襄""杨过");
		prop.put("黄蓉""欧阳克");
		
		//删
		//根据键删除对应的键值对
		prop.remove("郭襄");

		//改
		//如果键不存在,那么就添加,如果键存在,那么就覆盖
		prop.put("小龙女""杨过");

		//查
		object value = prop.get("黄蓉");
		
		//遍历
		//方式一
		Set<object> keys = prop.keyset();
		for (object key : keys) {
			Object value = prop.get(key);
			System.out.println(key + "=" value);
		}
		
		//方式二
		//装的是所有的键值对对象,
		Set<Map.Entry<Object,Object>> entries = prop.entryset();
		for (Map.Entry<Object,Object> entry : entries){
			Object key = entry.getkey();
			Object value = entry.getvalue();
			System.out.println(key + "=" + value);
		}
	}
		
}

Properties-特有的方法

Properties作为集合的特有方法:

方法名 说明
Object setProperty(String key, String value) 设置集合的键和值,都是String类型,底层调用Hashtable方法put
String getProperty(String key) 使用此属性列表中指定的键搜索属性
Set stringPropertyNames() 从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class PropertiesDemo1 {
	public static void main(String[] args){
		//Object setProperty (string key,string value)---put
		//设置集合的键和值,都是String类型,底层调用Hashtable方法put
		Properties prop = new Properties();
		prop.setProperty("江苏""南京");
		prop,setProperty("安徽""南京");
		prop.setProperty("山东""济南");
		
		//String getProperty(string key)---get
		//使用此属性列表中指定的键搜索属性
		String value = prop.getProperty("江苏");
		System.out.println(value);
		
		//Set<String> stringPropertyNames()---keySet
		//从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串
		Set<string> keys = prop.stringPropertyNames();
		for(String key : keys){
			String value = prop.getProperty(key);
			System.out.println(key + "=" + value);
		}
		
	}
}

Properties-load、store

Properties和IO流结合的方法:

方法名 说明
void load(InputStream inStream) 从输入字节流读取属性列表(键和元素对)
void load(Reader reader) 从输入字符流读取属性列表(键和元素对)
void store(OutputStream out, String comments) 将此属性列表(键和元素对)写入此Properties表中,以适合于使用load(InputStream)方法的格式写入输出字节流
void store(Writer writer, String comments) 将此属性列表(键和元素对)写入此Properties表中,以适合使用load(Reader)方法的格式写入输出字符流

1
2
public void load(Reader r); //把字符流所指向的 "配置文件" 中的键值对数据,读取到Properties集合中
public void store(Writer w, String comments); //把Properties集合中的键值对数据写到输出流指向的文件中

1
2
3
//写properties文件时不要有空格和符号,就写 键=值 就好了
username=zhangsan
password=123

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void main(String[] args){
	//void load (Reader reader)                     将本地文件中的键值对数据读取到集合中
	//void store (Writer writer, String comments)   将集合中的数据以键值对形式保存在本地
	
	//读取-load
	Properties prop = new Properties();
	FileReader fr = new FileReader("prop.properties");
	//调用完了load方法之后,文件中的键值对数据已经在集合prop中了
	prop.load(fr);
	fr.close();
	System.out.println(prop);
	
	//写-store
	Properties prop = new Properties();
	prop.put("zhangsan","123");
	prop.put("lisi","456");
	prop.put("wangwu","789");
	
	FileWriter fw = new FileWriter("prop.properties");
	prop.store(fw, null);  //第二个参数是注释
	fw.close();
}
图6
1
2
3
4
5
6
7
\u4E3A\u4EC0\u4E48\u8981\u4FEE\u6539\u624B\u673A u53F7\u5462? 就是我们写中文注释

\u代表utf-8,4E3A代表那个字

properties文件是一个配置文件,该文件的默认编码是ISO-8859-1,是为了与国外接轨

通过file—setting----搜索encoding------file encodings-------可以让我们看懂
图7

对于 Properties 文件,使用字节流还是字符流读取

在Java中,读取 properties 配置文件时,使用字符流(如 BufferedReaderInputStreamReader )通常更为合适。原因主要有以下几点:

  • 编码问题:properties 文件通常包含文本数据,这些文本数据以特定的字符编码(如UTF-8)存储。字符流可以处理这些编码问题,确保正确地读取和解析文件内容。
  • 可读性:字符流以字符为单位读取数据,这使得解析 properties 文件中的键值对更为直观和方便。字节流以字节为单位读取数据,这可能需要额外的处理才能将字节转换为字符。
  • Java标准库支持:Java标准库提供了 java.util.Properties 类,该类提供了方便的方法来加载和解析 properties 文件。这个类通常与字符流一起使用,例如通过 Properties.load(InputStream) 方法加载 properties 文件,其中 InputStream 可以是任何字符输入流(如 BufferedReader 或 InputStreamReader )。

当然,如果你确定 properties 文件的内容完全是ASCII字符,并且不关心编码问题,你也可以使用字节流来读取文件。但是,在大多数情况下,使用字符流是更安全和更方便的选择。


使用字符流读取properties配置文件之所以更“方便”,主要体现在以下几个方面:

  • 自动处理编码:字符流可以自动处理字符编码的转换,这意味着你不需要关心文件是以什么编码保存的。对于properties文件,这通常是UTF-8或其他常见的文本编码。使用字符流时,Java运行时环境会根据系统的默认编码或你指定的编码来自动解码字节为字符,这简化了编码处理的复杂性。
  • 直观的API:字符流API通常更直观,更容易理解。例如,BufferedReader类提供了按行读取文本文件的简单方法,如readLine()。这使得解析键值对变得相对简单,因为你可以按行读取并解析每一行。
  • 与Properties类的集成:Java的Properties类与字符流有很好的集成。你可以直接使用Properties.load(Reader)方法将字符流中的数据加载到Properties对象中。这使得读取和解析properties文件变得非常简单。
  • 文本处理的便利性:字符流以字符为单位进行操作,这使得处理文本数据(如字符串操作、正则表达式匹配等)变得更加容易。相比之下,字节流处理的是原始字节,对于文本处理来说,可能需要更多的转换和编码处理。
  • 可读性:字符流使得代码更加易读。当你看到BufferedReader或InputStreamReader时,你会很自然地想到这是在处理文本数据。这使得代码维护和其他开发人员理解你的代码变得更加容易。
  • 错误处理:字符流通常提供更具体的异常信息,这有助于在出现问题时更快地定位和解决问题。例如,如果文件编码与预期不符,字符流可能会抛出与编码相关的异常,这使得调试和问题解决更加直观。

综上所述,使用字符流读取 properties 配置文件不仅更安全,而且在多个方面都更加方便。它简化了编码处理、提供了直观的API、与Properties类集成良好、便于文本处理、提高代码可读性,并提供了更具体的错误处理信息。