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异常