我们使用字节流虽然可以操作所有类型的文件, 但也会遇到一些问题:
- 把文本文件中的中文内容读取到内存(Java程序)时, 可能会出现乱码
- 将中文写入到文本文件中, 可能会出现乱码
那为什么会导致这种现象发生呢?
- 其实是编码和解码的编码表不一致导致的
编码表
基础知识:
- 计算机中储存的信息都是用二进制数据表示的; 我们在屏幕上看到的英文 汉字等字符是二进制数转换之后的结果
- 按照编码表规则, 将字符存储到计算机中, 称为编码
- 按照同样的编码表规则,将存储在计算机中的二进制数据解析显示出来,称为解码
- 编码和解码使用的码表必须一致,否则会导致乱码
- 简单理解:
- 存储一个字符a,首先需在码表中查到对应的数字是97,然后按照转换成二进制的规则进行存储。称为编码
- 读取的时候,先把二进制解析出来,再转成97,通过97查找码表中对应的字符是a。称为解码
- 简单理解:
编码表的种类:
ASCII码表
- ASCII(American Standard Code for Information Interchange, 美国信息交换标准码表): 包括了数字字符, 英文大小写字符和一些常见的标点符号字符
GBK码表
- window系统默认的码表; 兼容ASCII码表, 也包含了21003个汉字, 并支持繁体汉字以及部分日韩文字
- GBK是中国的码表, 一个中文以两个字节的形式存储; 但不包含世界上所有国家的文字
Unicode码表
- 由国际组织ISO制定, 是统一的万国码表, 计算机科学领域里的一项业界标准, 容纳世界上大多数国家的所有常见文字和符号
- 但是因为表示的字符太多, 所以Unicode码表中的数字不是直接以二进制的形式存储到计算机的,会先通过UTF-7, UTF-7.5, UTF-8, UTF-16, 以及UTF-32的编码方式再存储到计算机, 其中最为常见的就是UTF-8
- Unicode是万国码表, 以UTF-8编码后一个中文以三个字节的形式存储
字符流读取中文的过程
字符流 = 字节流 + 编码表
- 不管是在哪张码表中,中文的第一个字节一定是负数
上图采用的码表为Unicode码表, 采用的编码方式为UTF-8
使用字符输入流一次读一个字符
使用字符输入流一次读一个字符数组
public class ReaderDemo1 {
public static void main(String[] args) {
try (FileReader fr = new FileReader("C:\\Users\\11946\\Desktop\\old.txt");
FileWriter fw = new FileWriter("day11\\new.txt")) {
// 一次读写一个字符
/*int ch = -1;
while ((ch = fr.read()) != -1) {
fw.write(ch);
fw.flush();
}*/
// 一次读写一个字符数组
char[] chs = new char[1024];
int len = -1;
while ((len = fr.read(chs)) != -1) {
fw.write(chs, 0, len);
fw.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
字符缓冲流特有功能
BufferedWriter:
- void newLine():写一个行分隔符(换行),会根据操作系统的不同,写入不同的行分隔符
BufferedReader:
- public String readLine() :读取文件一行数据, 不包含换行符号 , 读到文件的末尾返回null
转换流
转换流就是来进行字节流和字符流之间转换的
- InputStreamReader 是从字节流到字符流的桥梁
- OutputStreamWriter 是从字符流到字节流的桥梁
public class ConversionDemo1 {
public static void main(String[] args) throws IOException {
// method1();
// InputStreamReader : 从字节流到字符流的桥梁
// public InputStreamReader(InputStream in, String charsetName) : 创建指定编码的 InputStream
InputStreamReader isr = new InputStreamReader(new FileInputStream("day11\\GBK编码文件.txt"), "GBK");
char[] chs = new char[1024];
int len;
while ((len = isr.read(chs)) != -1) {
System.out.println(new String(chs, 0, len));
}
isr.close();
}
// 以GBK编码表为基准, 将内容写入到文件中
private static void method1() throws IOException {
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("day11\\GBK编码文件.txt"), "GBK");
osw.write("初随林霭动\n");
osw.write("稍共夜凉分\n");
osw.write("窗迥侵灯冷\n");
osw.write("庭虚近水闻\n");
osw.flush();
osw.close();
}
}
对象操作流
对象操作流分为两类 :对象操作输入流 和 对象操作输出流
- 对象操作输出流(对象序列化流) :就是将对象写到本地文件中,或者在网络中传输对象
- 对象操作输入流(对象反序列化流):把写到本地文件中的对象读到内存中,或者接收网络中传输的对象
- 特点 :
- 可以把对象以字节的形式写到本地文件,直接打开文件,是读不懂的,需要再次用对象操作流读到内存中
- 注意 :
- 如果一个类对象想要被序列化 , 那么此类需要实现Serializable接口
- Serializable接口的含义 :
- 是一个标记性接口 , 里面没有任何抽象方法
- 只要一个类实现了此接口 , 表示此类的对象可以被序列化
在一个文件中读写一个对象
public class ObjectStreamDemo1 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
method1(); // 将对象写入到本地文件中
method2(); // 从本地文件中读取对象
}
private static void method1() throws IOException {
// 1 创建对象操作输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("day11\\user.txt"));
User user = new User("xiaobo", "123456");
// 2 把对象写入文件中
oos.writeObject(user);
// 3 释放资源
oos.close();
}
private static void method2() throws IOException, ClassNotFoundException {
// 1 创建对象操作输入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("day11\\user.txt"));
// 2 读数据
User user = (User)ois.readObject(); // 向下转型
// 3 打印对象
System.out.println(user);
// 4 释放资源
ois.close();
}
}
在一个文件中读写多个对象
public class ObjectStreamTest1 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
method1(); // 将对象写入到本地文件中
method2(); // 从本地文件中读取对象
}
private static void method2() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("day11\\user.txt"));
while(true) {
try {
Student s = (Student) ois.readObject();
System.out.println(s);
} catch(EOFException e) { // 如果对象已经读取完毕还继续读取的话会抛出此异常
e.printStackTrace();
break;
}
}
ois.close();
}
private static void method1() throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("day11\\user.txt"));
Student s1 = new Student("刘备", 30);
Student s2 = new Student("关羽", 28);
Student s3 = new Student("张飞", 26);
oos.writeObject(s1);
oos.writeObject(s2);
oos.writeObject(s3);
oos.flush();
oos.close();
}
}
在一个文件中读写一个对象列表
public class ObjectStreamTest2 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
method1();
method2();
}
private static void method2() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("day11\\user.txt"));
ArrayList<Student> stuList = (ArrayList<Student>) ois.readObject();
for (Student stu : stuList) {
System.out.println(stu);
}
ois.close();
}
private static void method1() throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("day11\\user.txt"));
ArrayList<Student> stuList = new ArrayList<>();
stuList.add(new Student("刘备", 30));
stuList.add(new Student("关羽", 28));
stuList.add(new Student("张飞", 26));
oos.writeObject(stuList);
oos.close();
}
}
- 用对象序列化流序列化了一个对象后,假如我们修改了对象所属的Javabean类,读取数据会不会出问题呢?
- 会出问题,会抛出InvalidClassException异常
- 如果出问题了,如何解决呢?
- 给对象所属的类加一个serialVersionUID
- private static final long serialVersionUID = 42L;
- 如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
- 给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程
commons-io
自己用IO流水线文件的复制等操作比较繁琐, 推荐第三方工具包 commons-io
public class homework11 {
public static void main(String[] args) throws IOException {
// 复制文件
FileUtils.copyFile(new File("C:\\Users\\11946\\Desktop\\test.mp4"), new File("F:\\copy.mp4"));
// 将java文件夹里的内容复制到copy文件夹中
FileUtils.copyDirectory(new File("C:\\Users\\11946\\Desktop\\java"), new File("F:\\copy"));
}
}