字符流-写出数据
字符流写数据
步骤:
- 创建字符输出流对象
| 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对象,给出一个带有布尔值的文件名,表示是否附加写入的数据。 |
- 写数据
| 方法名 |
说明 |
| 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
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();
}
|
字符流-写出数据的注意事项
- 创建字符输出流对象
- 如果文件不存在,就创建,但是要保证父级路径存在
- 如果文件存在就先删除,再创建
- 写数据
- 写int类型的整数,实际写出的是整数在码表上对应的字母 (如果要写数字,用字符串的形式)
- 写字符串数据,是把字符串本身原样写出
- 释放资源
字符流-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();
}
|
- 普通字符流,本身就有缓冲区,缓冲区(8192)满了之后,才会进行写出,但是用flush可以不需要满,就可以输出
- 字符流自带的缓冲区用来将字节转为字符,因为无论字节流还是字符流读的都是字节,只不过,字符流在自己带的缓冲区里组装了一下,再输出
- 字节流没有这个用来组装的缓冲区,所以字节流不需要 flush
字符流-读取数据
字符流读数据的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流-小结
其他流
转换流-概念
转换流的本质是字符流,引出问题:不同编码读取时会乱码
不同编码读取出现乱码的问题
- 如果代码编码和被读取的文本文件的编码是一致的,使用字符流读取文本文件时不会出现乱码!
- 如果代码编码和被读取的文本文件的编码是不一致的,使用字符流读取文本文件时就会出现乱码!
IO流的体系
观察 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);
}
|
需要控制写出去的字符使用什么字符集编码,该咋整?
- 调用
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()
}
|
- 使用 “字符输出转换流” 实现
对象操作流-基本特点
下面这种方式存在一个问题,我保存的是用户名和密码,此时,无论是谁找到 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();
}
|
对象操作流的特点:可以把对象以字节的形式写到本地文件 (*把对象的整体以字节的形式写到本地文件),直接打开文件,是读不懂得,需要再次用对象操作流读到内存中
对象操作流-序列化
对象操作流分为两类:对象操作输入流和对象操作输出流
- 对象操作输出流 (对象序列化流):就是将对象写到本地文件中,或者在网络中传输对象,这种行为也称为序列化
- 对象操作输入流(对象反序列化流):把写到本地文件中的对象读到内存中,或者接收网络中传输的对象,这种行为也称为反序列化
| 构造方法 |
|
|
| Modifier |
Constructor |
描述 |
protected |
ObjectOutputStream() |
为完全重新实现ObjectOutputStream的子类提供一种方法,不必分配刚被ObjectOutputStream实现使用的私有数据。 |
|
ObjectOutputStream (OutputStream out) |
创建一个写入指定的OutputStream的ObjectOutputStream。 |
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异常