IO
字符集
ASCII字符集
标准ASCII使用1个字节存储一个字符,首位是0,总共可表示128个字符,对欧美来说完全够用
GBK
汉字内码扩展规范
包含了2w多个汉字等字符,GBK中一个中文字符编码由2个字节的形式存储
GBK兼容了ASCII字符集
GBK规定:汉字的第一个字节的第一位必须是1,因此以0开头的就是对ASCII字符集的兼容
Unicode字符集(统一码,万国码)
unicode是国际组织制定的,可以容纳世界上所有的文字、符号的字符集
UTF-32
4个字节表示一个字符
UTF-8
是Unicode字符集的一种编码方案,采取可变长编码方案,共分4个长度区:1个字节、2个字节、3个字节、4个字节
英文字符、数字等只占1个字节(兼容标准ASCII编码),汉字字符占用3个字节
0xxxxxxx
xxxxxxxx xxxxxxxx xxxxxxxx
0xxxxxxx
那怎么区分是1字节长度还是2字节长度3字节长度4字节长度呢?
1字节长度: 0xxxxxxx,以0
开头
2字节长度:110xxxxx 10xxxxxx 以110
开头,第二个以10
开头
3字节长度:1110xxxx 10xxxxxx 10xxxxxx 以1110
开头,第二个以10
开头,第三个以10
开头
4字节长度:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 以11110
开头,第二个以10
开头,第三个以10
开头,第三个以10
开头
编码与解码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
String str = "我爱学习java!";
// 1、编码
byte[] bytes = str.getBytes(); // 默认用平台编码UTF-8
System.out.println(Arrays.toString(bytes));
byte[] bytes2 = str.getBytes("GBK"); // 指定GBK编码
System.out.println(Arrays.toString(bytes2));
// 2、解码
String rs = new String(bytes); // 默认用平台编码UTF-8解码
System.out.println(rs);
String rs2 = new String(bytes,"GBK"); // 指定用GBK编码解码
System.out.println(rs2);
|
概述
IO流用于读写数据的(可以读写文件、网络中的数据…)
I指Input,称为输入流,负责把数据读到内存中去
O指Output,称为输出流,负责写数据出去
分类
按流的方向
输入流与输出流
按流中数据的最小单位
字节流:适合操作所有类型的文件,如:音频、视频、图片、文本文件的复制、转移等
字符流:只适合操作纯文本文件,如:读写txt、java文件等
因此,IO流总统可以分为4大流
字节输入流,字节输出流,字符输入流,字符输出流
字节输入流
抽象类:InputStream
实现类:FileInputStream(文件字节输入流)
FileInputStream:以内存为基准,可以把磁盘文件中的数据以字节的形式读入到内存中去
每次读取一个字节
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// 1、创建文件字节输入流管道与目标文件接通
// InputStream is = new FileInputStream(new File("cn.snailsir.study/src/aa.txt")); // 完整写法
InputStream is = new FileInputStream("cn.snailsir.study/src/aa.txt"); // 简洁写法,实际就是对完整方法的封装
// 2、每次读取一个字节返回
// public int read(): 每次读取一个字节返回,如果没有字节可读,返回-1
int b1 = is.read();
System.out.println(b1);
int b2 = is.read();
System.out.println((char)b2);
// 3、使用循环读取
int b;
while((b=is.read()) != -1){
System.out.println((char)b);
}
|
扩展:
- 性能差,每次一个一个字节读取,性能很差
- 无法避免读取汉字输出乱码问题,会截断汉字的字节
每次读取多个字节
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
|
// 1、创建文件字节输入流管道与目标文件接通
InputStream is = new FileInputStream("cn.snailsir.study/src/aa.txt"); // 简洁写法,实际就是对完整方法的封装
// 2、每次读取一个字节数组的字节,会返回读取的字节个数,没有字节可读返回-1
// public int read(byte[] buffer)
byte[] buffer = new byte[3];
int len1 = is.read(buffer);
System.out.println("内容"+new String(buffer));
System.out.println("个数"+len1);
int len2 = is.read(buffer);
// 读取多少就倒出多少
System.out.println("内容"+new String(buffer,0,len2));// 如果是new String(buffer)写法,而只能取出两个字节,那么返回的内容也是3个字节,只不过最后一个字节内容是上一个桶内容剩下的
System.out.println("个数"+len2);
// 3、使用循环读取
byte[] buffer = new byte[3];
int len;
while((len = is.read(buffer)) != -1){
String rs = new String(buffer,0,len);
System.out.print(rs);
}
|
扩展:
性能比较好
缺点:无法避免读取汉字输出乱码的问题
一次读取完全部字节
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// 1、创建文件字节输入流管道与目标文件接通
InputStream is = new FileInputStream("cn.snailsir.study/src/aa.txt"); // 简洁写法,实际就是对完整方法的封装
// 2、定义一个字节数组与源文件一模一样大小
File f = new File("cn.snailsir.study/src/aa.txt");
long size = f.length();
System.out.println("文件大小是:"+size);
byte[] buffer = new byte[(int)size];
int len = is.read(buffer);
System.out.println("读取的字节"+len);
System.out.println(new String(buffer));
// 以上方法jdk9之前都要这样写
// 以下方法jdk9开始使用
byte[] buffer = is.readAllBytes();
Syste.out.println(new String(buffer));
|
缺点:不能读取大文件内容,会内存泄漏
字节输出流
抽象类:OutputStream
实现类:FileOutputStream(文件字节输出流)
作用:以内存为基准,把内存中的数据以字节的形式写出到文件中去
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
|
// 1、创建一个文件字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream("cn.snailsir.study/src/aa.txt"); // 覆盖管道
OutputStream os = new FileOutputStream("cn.snailsir.study/src/aa.txt",true); // 追加管道
// 2、开始写字节数据出去
// public void write(int a) 每次写一个字节
os.write('a');
os.write(97);
os.write('中'); // 乱码 因为一个中文占3个字节,而这里只写入一个字节
os.wtite("\r\n".getBytes()); // 换行
// 3、每次写一个字节数组的数据
// public void write(byte[] buffer)
byte[] bytes = "abc我爱中国".getBytes();
os.write(bytes);
// 4、写每一个字节数组的一部分
// public void write(byte[] buffer,int pos,int len)
// params1: 字节数组
// params2: 写出去的第一个字节的索引
// params3: 总共写出去多少个字节
os.write(bytes,3,12);// 我爱中国
// io流管道属于系统资源,会占用内存和响应的IO资源(总线)
// 用完之后必须关闭管道,以释放占用的系统资源
// os.flush(); // 刷新缓存中的数据到磁盘文件中去(因为内存写入的快,硬盘写入的慢,没有引入缓存,硬盘会拖累内存的效率)
// 字节输出流:没有提供缓存区
os.close(); // 关闭包含刷新了
|
字符输入流
抽象类:Reader
实现类:FileReader
以内存为基准,可以把文件中的数据以字符的形式读入到内存中去
每次读取一个字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
// 1、创建文件字符输入流管道与源文件接通
// public FileReader(File file)
// public FileReader(String filePath)
Reader fr = new FileReader("cn.snailsir.study/src/aa.txt");
// 2、每次读取一个字符返回,没有字符可读返回-1
// public int read(): 每次读取一个字节返回,如果没有字节可读,返回-1
int c1 = fr.read();
System.out.println(c1);
int c2 = fr.read();
System.out.println((char)c2);
// 3、使用循环读取
int b;
while((b=fr.read()) != -1){
System.out.println((char)b);
}
|
解决了中文乱码问题
性能较差(工作中不用)
每次读取多个字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
try(
// 1、创建文件字符输入流管道与目标文件接通
Reader fr = new FileReader("cn.snailsir.study/src/aa.txt");
){
// 2、定义一个字符数组用于读取多个字符
char[] buffer = new char[3];
int len; // 记住每次读取多少个字符
while((len = fr.read(buffer)) != -1){
String rs = new String(buffer,0,len);
System.out.print(rs);
}
}catch(Exception e){
e.printStackTrace();
}
|
拓展
避免了中文乱码
性能可以
字符输出流
抽象类:Writer
实现类:FileWriter
以内存为基准,把内存中的数据以字符的形式写出到文件中去
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
|
try(
// 1、创建一个文件字符输出流管道与目标文件接通
// Write fw = new FileWrite("cn.snailsir.study/src/aa.txt"); // 覆盖管道
Write fw = new FileWrite("cn.snailsir.study/src/aa.txt",true); // 追加管道
){
// 2、开始写字符数据出去
// 2.1 写一个字符出去: public void write(int a)
fw.write('a');
fw.write(97);
fw.write('中');
fw.wtite("\r\n".getBytes()); // 换行
// 2.2 写一个字符串出去
fw.write("我爱你中国");
// 2.3 写字符串一部分出去 public void write(String s,int pos,int len)
fw.write("我爱你中国",0,3);// 我爱你
// 2.4 写一个字符数组出去:public void write(char[] chars)
char[] chars = "我爱你中国".toCharArray();
fw.write(chars);
// 2.5 写每一个字符数组的一部分 public void write(char[] chars,int pos,int len)
fw.write(chars,6,2);
//fw.flush();// 刷新内存缓存数据到硬盘上
}catch(Exception e){
e.printStackTrace();
}
|
资源释放方式
try-catch- finally
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
OutputStream os = null;
try{
...
...
}catch(IOException e){
e.printStackTrace();
}finally{
// 释放资源
try{
if(os != null) os.close(); // != null判断是因为排除try第一句就出现异常,这个时候os还是null
}catch(Exception e){
e.printStackTrace();
}
}
|
Finally代码区特点:无论try中的程序是正常执行了,还是出现异常,最后都一定会执行finally区,除非JVM终止
Finally代码区作用:一般用于在程序执行完成后进行资源的释放操作
try-with-resource
jdk7开始提供了更简单的资源释放方案
1
2
3
4
5
|
try(定义资源1;定义资源2;...){
可能出现异常的代码;
} catch(异常类名 变量名){
异常的处理代码;
}
|
该资源(try内部)使用完毕后,会自动调用其close()方法,完成对资源的释放
1
2
3
4
5
6
7
8
|
try(
// 资源:一般指的是最终实现了`AutoCloseable`接口
OutputStream os = new FileOutputStrem("aa.txt");
){
}catch(Exception e){
e.printStackTrace();
}
|
IO缓冲流
字节缓冲流
对原始流进行包装,以提高原始流读写数据的性能
字节缓冲输入流
BufferedInputStream
原理
字节缓冲输入流自带了8KB缓冲池
字节缓冲输出流
BufferedOutputStream
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
try(
// 1、创建字节输入流管道与源文件接通
InputStream is = new FileInputStream("cn.snailsir.study/src/aa.txt");
// 使用高级缓存字节输入流包装低级的字节输入流
InputStream bis = new BufferedInputStream(is);
// 2、创建字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream("cn.snailsir.study/src/aa-bak.txt");
// 使用高级缓存字节输出流包装低级的字节输出流
OutputStream bos = new BufferedOutputStream(os);
){
// 3、准备一个字节数组
byte[] buffer = new byte[1024];
// 4、转移数据
int len;
while((len = bis.read(buffer)) != -1){
bos.writ(buffer,0,len);
}
}catch(Exception e){
e.printStackTrace();
}
|
原理
字节缓冲输出流自带了8KB缓冲池
字符缓冲输入流
BufferedReader
原理
字符缓冲输出流自带了8K(8192个字符)缓冲池
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
try(
// 1、创建字符输入流管道与源文件接通
Reader fr = new FileReader("cn.snailsir.study/src/aa.txt");
// 使用高级缓存字节输入流包装低级的字节输入流
BufferedReader br = new BufferedInputStream(fr);
){
// 2、准备一个字符数组用于读取多少个字符
char[] buffer = new char[1024];
// 3、转移数据
int len;
while((len = br.read(buffer)) != -1){
String rs = new String(buffer,0,len);
System.out.print(rs);
}
// 4、缓存字符输入流多了一个按照行读取内容的功能
String line;
while((line = br.readLine()) != NULL){
System.out.println(line);
}
}catch(Exception e){
e.printStackTrace();
}
|
注意:
缓存字符输入流多了一个按照行读取内容的功能readLine
字符缓冲输出流
BufferedWriter
原理
字符缓冲输出流自带了8K(8192个字符)缓冲池
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
|
try(
// 1、创建一个文件字符输出流管道与目标文件接通
// Write fw = new FileWrite("cn.snailsir.study/src/aa.txt"); // 覆盖管道
Write fwo = new FileWrite("cn.snailsir.study/src/aa.txt",true); // 追加管道
// 使用高级缓存字符输出流包装低级的字符输出流
BufferedWriter fw = new BUfferedWriter(fwo); // 追加管道
){
// 2、开始写字符数据出去
// 2.1 写一个字符出去: public void write(int a)
fw.write('a');
fw.write(97);
fw.write('中');
fw.wtite("\r\n".getBytes()); // 换行
fw.newLine(); // 换行新增
// 2.2 写一个字符串出去
fw.write("我爱你中国");
// 2.3 写字符串一部分出去 public void write(String s,int pos,int len)
fw.write("我爱你中国",0,3);// 我爱你
// 2.4 写一个字符数组出去:public void write(char[] chars)
char[] chars = "我爱你中国".toCharArray();
fw.write(chars)
// 2.5 写每一个字符数组的一部分 public void write(char[] chars,int pos,int len)
fw.write(chars,6,2);
//fw.flush();// 刷新内存缓存数据到硬盘上
}catch(Exception e){
e.printStackTrace();
}
|
注意:
缓存字符输出流多了一个换行的功能newLine
原始流、缓冲流的性能分析
1
2
3
4
5
6
|
// 使用低级的字节流按照一个个字节的形式复制文件:慢的无人让人忍受,直接淘汰,禁止使用
// 使用低级的字节流按照字节数组的形式复制文件:还可以,但是相对较慢
// 使用高级的缓冲字节流按照一个个字节的形式复制文件:还是特别慢,不推荐使用
// 使用高级的缓冲字节流按照字节数组的形式复制文件:极快,推荐使用
调节字节数组大小(默认8k),可以提高效率,但并不是越大越好
|
IO转换流
为了解决不同编码读取时会乱码的问题
字符输入转换流
InputStreamReader:解决不同编码时,字符流读取文件内容乱码问题
原理
先获取文件的原始字节流,再将其按真实的字符集编码转成字符输入流,这样字符输入流中的字符就不乱码了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
try(
// 1、得到GBK文件的原始字节输入流
InputStream is = new FileInputStream("aa.txt");
// 2、通过字符输入转换流把原始字节流按照指定编码转换成字符输入流
// public InputStreamReader(InputStream is,String charset) 把原始的字节输入流,按照指定字符集编码转成字符输入流(重点)
Reader isr = new InputStreamReader(is,"GBK");
// 3、把字符输入流包装成高级的缓冲字符输入流
BufferedReader br = new BufferedReader(isr);
){
// 4、按照行数读取
String line;
while((line = br.readLine()) != null){
System.out.println(line)
}
}catch(Exception e){
e.printStackTrace();
}
|
字符输出转换流
控制写出去的字符使用什么字符集编码
String data = "我爱你中国"; byte[] bytes = data.getBytes("GBK");
在进行写入操作。这种方式也可以
原理
获取字节输出流,再按照指定的字符集编码将其转换成字符输出流,以后写出去的字符就会用该字符集编码了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
try(
// 1、创建一个文件字节输出流通向目标文件
OutputStream is = new FileOutputStream("out.txt");
// 指定写出去的编码时GBK
// public OutputStreamWriter(OutputStream os,String charset) 把原始的字节输出流,按照指定编码转成字符输出流
Reader isr = osw OutputStreamWriter(is,"GBK");
// 2、把字符输出流包装成高级的缓冲流
BufferedWrite bw = new BufferedWriter(osw);
){
// 4、写入数据
bw.write("xxxx");
bw.newLine();
bw.write("yyyy");
bw.newLine();
bw.write("zzzz");
}catch(Exception e){
e.printStackTrace();
}
|
IO打印流
PrintStream字节输出流
PrintWriter 字符输出流
作用
可以实现更方便、更高效的打印数据出去,能实现打印啥出去就是啥出去。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
try(
// public PrintStream(OutputStream/File/String); 打印流直接通向字节输出流/文件/文件路径
// public PrintStream(String fileName,Charset charset); 可以指定写出去的字符编码
// public PrintStream(OutputStream out,boolean autoFlush); 可以指定实现自动刷新
// public PrintStream(OutputStream out,boolean autoFlush,String encoding); 可以指定实现自动刷新,并可指定字符的编码
PrintStream ps = new PrintStream("ps.txt");
// PrintWriter pw = new PrintWriter(new FileWriter("ps.txt"),true); // 追加 与PrintStream使用方法一模一样
){
// 以下打印的内容会原封不动的存储到ps.txt文件中
// public void println(Xxx xx) 打印任意类型的数据出去
// public void write(int/String/char[]/...) 可以支持写字符数据出去
ps.println(6666);
ps.println('a');
ps.println(true);
ps.println("我爱你中国");
}catch(Exception e){
e.printStackTrace();
}
|
PrintStream与PrintWriter区别
- 打印数据的功能上是一模一样的:都是使用方便,性能高效
- PrintStream继承自字节输出流OutputStream,因此支持写字节数据的方法
- PrintStream继承自字符输出流Writer,因此支持写字符数据出去
应用
输出语句的重定向:打印语句一般打印在控制台,我们可以让其打印到文件中去,这就是输出语句的重定向
1
2
3
4
5
6
7
|
System.out.println("xxx");
System.out.println("yyy");
// 重定向
PrintStream ps = new PrintStream(new FileOutputStream("out.txt",true));
System.setOut(ps); // 把系统类的打印流改成我的打印流
System.out.println("zzz");
|
IO数据流
常用于通讯领域
DataOutputStream(数据输出流)
允许把数据和其类型一并写出去
1
2
3
4
5
6
7
8
9
10
11
12
|
try(
DataOutputStream dos = new DataOutputStream(new FileOutputStream("out.txt"));
){
// 写数据和类型出去
dos.writeByte(97);
dos.writeBoolean(true);
dos.writeInt(1000);
dos.writeChar('a');
dos.writeUTF("我爱你中国");
}catch(Exception e){
e.printStackTrace();
}
|
用于读取数据输出流写出去的数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
try(
// 创建高级的特殊数据输入流管道包装低级的字节输入流管道
DataInputStream dis = new DataInputStream(new FileInputStream("out.txt"));
){
// 开始读取,顺序要与写入流顺序一致
byte b = dis.readByte();
System.out.println(b);
boolean b1 = dis.readBoolean();
System.out.println(b1);
int i = dis.readInt();
System.out.println(i);
char c = dis.readChar();
System.out.println(c);
String s = dis.readUTF();
System.out.println(s);
}catch(Exception e){
e.printStackTrace();
}
|
IO序列化流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import lombok.AllArgsConstructor; // 使用30版本,28版本有问题
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
// 注意:如果学生对象要参与序列化,必须实现序列化接口
public class Student implements Serializable{
private String name;
private int age;
// transient 修饰的成员变量将不参与序列化
private transient String password;
}
|
对象序列化(ObjectOutputStream)
把java对象写入到文件中去
1
2
3
4
5
6
7
8
|
// 准备一个对象
Student s = new Student("snailsir",27,"123456");
// 创建对象字节输出流管道与目标文件管道
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt"));
// 开始写对象出去
oos.writeObject(s);
// 关闭资源
oos.close();
|
把文件里的java对象读出来
1
2
3
4
5
6
|
// 创建对象字节输入流管道
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));
// 读取对象进来(对象反序列化)
Student s = (Student)ois.readObject();
// 关闭资源
ois.close();
|
IO框架
框架:解决某类问题,编写的一套类、接口等
好处:在框架的基础上开发,可以得到优秀的软件框架,并能提高开发效率
框架形式:一般是把类、接口等编译成class形式,再压缩成一个.jar结尾的文件发行出去
IO框架:封装了java提供的对文件、数据进行操作的代码,对外提供了更简单的方式来对文件进行操作,对数据进行读写等
案例
复制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// 1、创建字节输入流管道与源文件接通
InputStream is = new FileInputStream("aa.txt");
// 2、创建字节输出流管道与目标文件接通
InputStream is = new FileInputStream("aa-bak.txt");
// 3、准备一个字节数组
byte[] buffer = new byte[1024];
// 4、转移数据
int len;
while((len = is.read(buffer)) != -1){
os.write(buffer,0,len);
}
// 5、释放资源
os.close();
is.close();
|
任何文件的底层都是字节,字节流做复制,是一字不漏的转移完全部字节,只要复制后的文件格式一致就没问题