黑马程序员技术交流社区
标题:
IO流相关知识点
[打印本页]
作者:
易智办
时间:
2017-12-27 17:33
标题:
IO流相关知识点
## 标准输入输出流概述
*
类字段: 静态成员变量
*
`System`
类的类字段
*
`err`
:
* 标准错误输出流. 命令行输出错误信息
* 是`OutputStream`的子类, PrintStream, 属于字节输出流
*
`in`
:
* 标准输入流. 键盘输入
* 是`InputStream`类, 属于字节输入流
*
`out`
:
* 标准输出流. 将数据输出到控制台
* 是`OutputStream`的子类, PrintStream, 属于字节输出流
*
String类的
`byte[] getBytes()`
方法, 可以获取字符串的byte数组, 即将字符串转为byte数组
## OutputStreamWriter概述和使用
*
刚才发生了什么?
* 第1次代码:
* 使用BufferedReader字符输入流, 一次读取一行字符串
* 输出到控制台, 所以使用System.out, 它是OutputStream字节输出流
* 由于字节输出流只能写byte或byte数组, 但读入的是一个字符串, 所以要将字符串转换为byte数组, 并添加换行
* 效果: BufferedReader -> String -> byte数组 -> System.out
* 这样不优雅: 能不能让代码以字符流输出的方式, 从System.out这种字节输出流写出去呢?
* 第2次的代码:
* 我们发现, OutputStreamWriter可以把字符流转换为字节流进行输出, 这样我们就可以用操作字符输出流的方式写代码, 而数据却以字节流输出
* 所以将OutputStream改为使用OutputStreamWriter
* 仍然是一次读取一行字符串, 但写的时候, 由于OutputStreamWriter属于Writer, 所以直接可以写字符串, 不用转换byte数组了, 很方便
* 效果: BufferedReader -> String -> OutputStreamWriter -> System.out
* 还是不优雅: 换行符写死了, 如果程序运行在其他系统上怎么办, 能否用BufferedWriter自带的newLine()方法换行?
* 第3次代码
* BufferedWriter自带换行方法, OutputStreamWriter可以写到字节流, 两者如何结合?
* BufferedWriter构造方法中传入Writer即可, 而OutputStreamWriter就是Writer子类, 所以将转换流传入即可
* 且可以使用newLine()方法写入换行符
* 效果: BufferedReader -> String -> BufferedWriter -> OutputStreamWriter -> System.out
* 这很优雅!
* 这个案例的目的: 知道字符流可以转换为字节流写出
```java
public class OutputStreamWriterDemo {
public static void main(String[] args) throws IOException {
//method1();
//method2();
method3();
}
// 第三次方法: 使用BufferedWriter
private static void method3() {
// 使用BufferedReader字符输入流, 一次读取一行字符串
BufferedReader br = new BufferedReader(new FileReader("SystemInOutDemo.java"));
// 用OutputStreamWriter, 按照字符输出流的操作方式, 以字节流写出
Writer w = new OutputStreamWriter(System.out);
// 想使用缓冲流的特有方法, 将OutputStreamWriter对象传入
BufferedWriter bw = new BufferedWriter(w);
// 一次读写一行
String line;
while ((line = br.readline()) != null) {
// 使用了转换流后, 可以直接写字符串, 很方便
bw.write(line);
// 也可以使用缓冲流特有换行方法
bw.newLine();
}
br.close();
bw.close();
}
// 第二次方法: 使用OutputStreamWriter转换
private static void method2() {
// 使用BufferedReader字符输入流, 一次读取一行字符串
BufferedReader br = new BufferedReader(new FileReader("SystemInOutDemo.java"));
// 用OutputStreamWriter, 按照字符输出流的操作方式, 以字节流写出
Writer w = new OutputStreamWriter(System.out);
// 一次读写一行
String line;
while ((line = br.readline()) != null) {
// 使用了转换流后, 可以直接写字符串, 很方便
w.write(line);
w.write("\r\n");
}
br.close();
w.close();
}
// 第一次方法: 将字符串转byte数组
private static void method1() {
// 使用BufferedReader字符输入流, 一次读取一行字符串
BufferedReader br = new BufferedReader(new FileReader("SystemInOutDemo.java"));
// 输出到控制台, 所以使用System.out, 它是OutputStream字节输出流
OutputStream os = System.out;
// 一次读写一行
String line;
while ((line = br.readline()) != null) {
// 要写一行字符串, 而os是字节流, 只能写byte数组, 所以把字符串转为byte数组
os.write(line.getBytes());
// 问题来了, readLine()读取一行字符串是不带换行的, 怎么换行呢? 所以写换行
os.write("\r\n".getBytes());
}
br.close();
os.close();
}
}
```
*
`OutputStreamWriter`
类:
* 是
`Writer`
的子类, 属于字符输出流. 是字符流通向字节流的桥梁(代码中以字符写出, 实际操作文件时以字节写出)
* 作用: 将字符输出流转换为字节输出流写出
* 如何使用
* 创建对象: `new OutputStreamWriter(字节输出流对象)`, 传入一个字节输出流, 返回一个字符输出流
* 转换为常用的BufferedWriter: `new BufferedWriter(OutputStreamWriter对象);`
*
5分钟练习: OutputStreamWriter, 按照字符读取, 按照字节写出
* 创建项目s2-day10, 建包com.itheima.practice_01
* 建类Test
* 用BufferedReader读取文件
* 用BufferedReader使用OutputStreamWriter转换System.out写出到控制台
* 读写时用字符串
```java
/*
利用BufferedReader,BufferedWriter
OutputStreamWrtier 将项目根目录下的
SystemInOutDemo.java 输出到控制台上
*/
public class Test {
public static void main(String[] args) throws IOException {
// 创建缓冲字符输入流对象
BufferedReader br = new BufferedReader(new FileReader("Test.java"));
// 声明标准输出流, 用于输出到控制台
OutputStream os = System.out;
// 将字节输出流转换为字符输出流
Writer w = new OutputStreamWriter(os);
// 创建缓冲字符输出流对象封装字符输出流
BufferedWriter bw = new BufferedWriter(w);
// 以上可以简化为匿名对象方式
// BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
// 这样就可以使用操作字符的方式实现读写
String line;
while ((line = br.readLine()) != null) {
// 以字符流的操作方式写出
bw.write(line);
bw.newLine();
}
// 释放资源
bw.close();
br.close();
}
}
```
---
## InputStreamReader的概述和使用
*
刚才又发生了什么?
* 第1次代码:
* 键盘录入字符串, 写入文件中. 键盘录入是System.in, 字节输入流, 只能读取byte或byte数组. 写文件用字符串最方便, 用FileWriter
* 字节输入流读取的byte数组, 怎么以String写出呢? 可以将byte数组转为String
* 效果: System.in -> byte数组 -> String -> FileWriter
* 不优雅: 写byte数组时还要转字符串操作, 不方便, 能不能读进来就是字符?
* 第2次代码:
* 我们发现InputStreamReader, 可以以字节输入流读入, 但操作时可以转换为字符输入流操作, 这样写代码就可以按字符直接操作, 看起来很方便!
* 将System.in放入InputStreamReader, 转换为字符输入流来操作, 可以直接读char数组
* 效果: System.in -> char数组 -> FileWriter
```java
public class InputStreamReaderDemo {
public static void main(String[] args) {
//method1();
method2();
}
// 第二次代码: 使用InputStreamReader转换
private static void method2() {
// 从键盘读取, 要使用System.in, 他属于InputStream字节输入流
InputStream is = System.in;
// 字节输入流要操作byte数组, 我想操作String, 所以使用转换流
Reader r = new InputStreamReader(is);
// 输出字符串到文件中, 字符流最方便
FileWriter fw = new FileWriter("a.txt");
// 转换后, 可以直接读取字符数组
char[] chs = new char[1024];
int len;
while ((len = r.read(chs)) != -1) {
// 直接写字符数组, 减少了创建String的步骤
fw.write(chs, 0, len);
// 刷新写入文件
fw.flush();
}
is.close();
fw.close();
}
// 第一次代码: byte数组转String写出
private static void method1() {
// 从键盘读取, 要使用System.in, 他属于InputStream字节输入流
InputStream is = System.in;
// 输出字符串到文件中, 字符流最方便
FileWriter fw = new FileWriter("a.txt");
// 字节输入流不能读字符串, 所以读byte数组
byte[] bys = new byte[1024];
int len;
while ((len = is.read(bys)) != -1) {
// 字符输出流以字符串输出更方便, 所以将byte数组转为String
fw.write(new String(bys, 0, len));
// 刷新写入文件
fw.flush();
}
is.close();
fw.close();
}
}
```
*
`InputStreamReader`
类
* 是
`Reader`
的子类, 属于字符输入流. 是字节流通向字符流的桥梁(实际操作文件时以字节读入, 代码中以字符读入)
* 作用: 将字节输入流转换为字符输入流读入
* 如何使用
* 创建对象: `new InputStreamReader(字节输入流对象)`, 传入一个字节输入流, 返回一个字符输入流
* 转换为常用的BufferedReader: `new BufferedReader(InputStreamReader对象);`
*
5分钟练习: 键盘输入数据, 写入文件中
* 继续使用项目s2-day10, 建包com.itheima.practice_02
* 建类Test
* 使用InputStreamReader将System.in转换
* 使用FileWriter写出到a.txt
* 一次读写一个char数组
```java
/*
利用InputStreamReader和FileWriter流
读取键盘录入的数据
并输出到项目根目录下的a.txt
*/
public class Test {
public static void main(String[] args) throws IOException {
// 创建文件字符输出流
FileWriter fw = new FileWriter("a.txt");
// 创建标准输入流
InputStream is = System.in;
// 多态转换为字符输入流
Reader r = new InputStreamReader(is);
// 转换后可以直接使用字符数组接收读写文件
char[] chs = new char[1024];
int len;
while ((len = r.read(chs)) != -1) {
// 写字符数组
fw.write(chs, 0, len);
// 刷新
fw.flush();
// 这个案例中, 接收的是键盘录入, 所以读取不到-1, 不能停止程序, 所以需要刷新才能在文件中看见
// 结束时点击红色方块
}
// 释放资源
r.close();
fw.close();
}
}
```
## 打印流概述
*
打印流分类
*
`PrintStream`
: 字节打印流
*
`PrintWriter`
: 字符打印流
* 只有输出流, 因为打印是输出操作
*
PrintWriter特点
* 可以自动换行,
`println()`
. 会根据系统自动确定换行符
* 不能输出byte字节, 可以输出其他任意类型(要输出字节可以使用PrintStream)
* 通过构造方法的配置可以实现自动刷新(flush)(只在调用
`println`
,
`printf`
,
`format`
时有用)
* 也是包装流, 自身没有写出功能
* 可以把字节输出流转换为字符输出流
* 关流不会抛出异常(此类中的方法不会抛出 I/O 异常,尽管其某些构造方法可能抛出异常)
## 打印流特有功能
*
PrintWriter类
* 构造方法
* `PrintWriter PrintWriter(String filepath)`
* `PrintWriter PrintWriter(Writer out, boolean autoFlush)`: 创建对象, 同时设置是否自动刷新
* `PrintWriter(OutputStream out, boolean autoFlush)`: 创建对象, 同时设置是否自动刷新
* 成员方法
* `void write(String s)`: 写一个字符串
* `void print(String s)`: 输出字符串, 没有换行
* `void println(String s)`: 输出字符串并换行. 如果启动了自动刷新, 则会执行自动刷新写入数据
* `void printf(Locale l, String format, Object... args)`: 使用指定格式字符串和参数将格式化的字符串写入输出流. 如果启动了自动刷新, 则会执行自动刷新写入数据
* `void format(Locale l, String format, Object... args)`: 使用指定格式字符串和参数将格式化的字符串写入输出流. 如果启动了自动刷新, 则会执行自动刷新写入数据
---
## 练习: 使用打印流复制文本文件
*
5分钟练习: 打印流复制文本文件
* 继续使用项目s2-day10, 建包com.itheima.practice_03
* 建类Test
* 读取: BufferedReader
* 写出: PrintWriter, 自动刷新
* 一次读写一行字符串, 调用println()自带换行
```java
/*
利用打印流和高效流将根目录下的SystemInOutDemo.java
拷贝到D盘下
*/
public class Test {
public static void main(String[] args) throws IOException {
// 创建缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("Test.java"));
// 创建打印输出流
PrintWriter pw = new PrintWriter(new FileWriter("Copy.java"), true);
// 一次读写一行
String line;
while ((line = br.readLine()) != null) {
// 打印流输出并换行, 同时会自动刷新
pw.println(line);
}
// 释放资源
pw.close(); // 注意该方法的声明, 并不会抛出异常
br.close(); // 而我们以前使用的流会抛出异常
}
}
```
## 对象操作流概述
*
对象操作流分类(都属于字节流)
*
`ObjectInputStream`
*
`ObjectOutputStream`
*
作用: 读写任意类型的对象
*
注意:
* 使用对象输出流写出对象到文件, 则该文件只能使用对象输入流读取对象, 其他流不行
* 只能将实现了
`java.io.Serializable`
接口的类的对象写入流中, 对象内部有自定义类型的成员变量, 也要实现该接口
*
ObjectOutputStream类
* 构造方法
* `ObjectOutputStream ObjectOutputStream(OutputStream out)`: 创建对象输出流
* 成员方法
* `void writeObject(Object o)`: 将对象写入对象输出流
*
ObjectInputStream类
* 构造方法
* `ObjectInputStream ObjectInputStream(InputStream in)`: 创建对象输入流
* 成员方法
* `Object readObject()`: 从对象输入流读取对象, 一次只读取一个对象. 当读不到时抛出`EOFException`.
*
`java.io.Serializable`
接口:
* 没有成员, 是一个标识接口
* 用于序列化
* 序列化: 将对象的信息转换为可存储的信息的过程
## 使用对象操作流读写对象
*
`EOFException`
: End of File Exception, 文件末尾异常, 当输入过程中意外达到文件或流的末尾时, 抛出此异常
*
5分钟练习: 读写对象
* 继续使用项目s2-day10, 建包com.itheima.practice_04
* 建类Student
* 实现Serializable接口
* 成员变量
* 私有姓名, 年龄
* 空参, 有参构造
* get/set
* toString
* 建类Test
* 创建2个学生对象
* 创建对象输出流对象, 目标a.txt
* 创建对象输入流对象, 目标a.txt
* 先写2个对象, writeObject()
* 然后使用while循环读取readObject(), 通过捕获EOFException来结束
```java
/*
新建一个学生类,学生中有姓名和年龄两个属性
重写toString()方法
创建两个学生对象,分别是:
zhangsan 18
lisi 19
将这两个对象写入到a.txt中,然后读出这两个学生
对象并打印
*/
public class Student implements Serializable {
private String name;
private int age;
public Student() {
super();
}
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 写学生对象到文件
writeStudent();
// 从文件中读取学生对象
readStudent();
}
private static void readStudent() throws IOException, ClassNotFoundException {
// 创建对象输入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));
// 通过捕获EOFException异常的方式判断读取完毕
try {
// 不知道有多少个对象, 所以循环读取
while (true) {
Object object = ois.readObject();
System.out.println(object);
}
} catch (EOFException e) { // End of File
System.out.println("读完了");
}
// 释放资源
ois.close();
}
private static void writeStudent() throws IOException, FileNotFoundException {
// 创建2个学生对象
Student s1 = new Student("zhangsan", 18);
Student s2 = new Student("lisi", 19);
// 创建对象输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt"));
// 直接输出对象
oos.writeObject(s1);
oos.writeObject(s2);
// 释放资源
oos.close();
}
}
```
## 解决对象输入流读取对象异常的问题
*
捕获
`EOFException`
很不优雅, 有没有更好的方法?
* 一个文件只保存一个对象, 只读取一次
* 对于多个对象, 可以将多个对象放入一个集合对象中, 就可以实现
* 注意: 集合中的对象类型, 也需要实现Serializable接口, 所有相关自定义类型的成员变量也要
*
5分钟练习: 使用集合改进
* 继续使用项目s2-day10, 建包com.itheima.practice_05
* 复制上一个练习的代码
* 修改写入和读取
* 先将2个学生对象放入一个List集合中, 然后写这个集合对象
* 读取时只读一次, 不捕获EOFException异常, 强转为List, 获取元素打印
```java
/*
新建一个学生类,学生中有姓名和年龄两个属性
重写toString()方法
创建两个学生对象,分别是:
zhangsan 18
lisi 19
将这两个对象写入到a.txt中,然后读出这两个学生
对象并打印
*/
public class Student implements Serializable {
private String name;
private int age;
public Student() {
super();
}
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 写学生对象到文件
writeStudent();
// 从文件中读取学生对象
readStudent();
}
private static void readStudent() throws IOException, ClassNotFoundException {
// 创建对象输入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("b.txt"));
// 直接读取一次对象, 因为只有一个集合对象
Object object = ois.readObject();
// 强转为集合对象
ArrayList<Student> list = (ArrayList<Student>) object;
// 遍历集合
for (Student student : list) {
System.out.println(student);
}
// 释放资源
ois.close();
}
private static void writeStudent() throws IOException, FileNotFoundException {
// 创建2个学生对象
Student s1 = new Student("zhangsan", 18);
Student s2 = new Student("lisi", 19);
// 创建集合对象
ArrayList<Student> list = new ArrayList<>();
// 将学生对象装入集合
list.add(s1);
list.add(s2);
// 创建对象输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("b.txt"));
// 直接输出集合对象
oos.writeObject(list);
// 释放资源
oos.close();
}
}
```
## 实体类中序列号的作用
*
`private static final long serialVersionUID`
: 序列版本号
* 作用:
* 相当于根据类的成员信息生成的身份证号, 通过序列化流写对象到文件中时, 会将该序列号写入文件. 如果类有改动, 那么代码中序列版本号也会变化. 可以根据代码中的序列号和文件中的序列号是否一致来判断对象与类是否对应
* 默认不定义序列号时, 会自动生成随机的
* 如果不想因为类的改动导致序列号变化, 则要在定义类时手动定义序列号, 使其固定为一个值
*
`InvalidClassException`
用对象读取流时发生该异常的原因:
1.
该类的序列版本号与从流中读取的类描述符的版本号不匹配
2.
该类包含未知数据类型(即可能添加了新的成员变量或方法)
3.
该类没有可访问的无参数构造方法
*
注意:
* 如果固定了序列号, 当读取文件中没有属性时, 返回值就是null
* 序列号的影响只与成员变量有关, 与方法无关
* 如果类中比文件中增多了属性, 则该属性为null
* 如果类中比文件中减少了属性, 则无法获取该属性
---=
## Properties概述
*
继承了
`Hashtable<K, V>`
, 实现
`Map<K,V>`
接口, 以键值对方式存储数据
*
作用: 对文件读写键值对信息.
*
应用场景: 用于保存程序的配置信息
*
配置文件的格式:
* 在配置文件中的保存形式是每行一个
`键=值`
*
`#`
是注释
*
注意:
* 不能存
`null`
的键和值
* 只能保存英文字符和符号, 不能存中文
* 键和值都是字符串
*
Properties类
* 构造方法
* `Properties Properties()`: 创建对象
* 成员方法
* 可以使用Map接口的方法
* `String getProperty(String key)`: 根据键获取值. 如果找不到该键, 则返回null
* `String getProperty(String key, String defaultValue)`: 根据键获取值, 如果值不存在, 则使用指定的默认值
* `void setProperty(String key, String value)`: 设置键值对
## Properties和IO流结合的功能
*
Properties类常用方法
*
`void load(InputStream in)`
: 从字节输入流中读取Properties
*
`void load(Reader reader)`
: 同上, 只是流不同
*
`void list(PrintStream out)`
: 将Properties输出到指定的字节打印输出流. 会自动加一个
`-- listing properties --`
文件头
*
`void list(PrintWriter writer)`
: 同上, 只是流不同
*
`void store(Writer writer, String comments)`
: 将Properties输出到指定的输出流, 并添加一个注释. 如不想增加注释可填写null. 无论是否有注释, 都会自动添加时间字符串
*
`void store(OutputStream os, String comments)`
: 同上, 只是流不同
*
`list()`
和
`store()`
的区别
* list只能接收打印流(PrintStream, PrintWriter)
* store可以接收任何输出流(OutputStream, Writer)
*
常用Propertis的读写操作步骤
* 写出到文件
* 创建Properties对象, 添加键值对, 使用`list()`或`store()`保存到文件中
* 从文件读取
* 创建Properties对象, 使用`load()`从文件加载数据, 使用`getProperties()`根据指定键获取值, 或使用遍历Map的方式遍历所有键和值
*
5分钟练习: 将键值对存入Properties并写入文件
* 继续使用项目s2-day10, 建包com.itheima.practice_06
* 建类Test
* 创建Properties对象
* 添加3个键值对
* 分别使用list()和store()保存到文件中
```java
```
---
## 编码表
*
编码表的作用: 将计算机二进制数据转换为不同语言的字符
*
常见编码表种类
*
`ASCII`
: 美国标准码. 包含26个英文字母的大写和小写, 数字, 符号
*
`ISO-8859-1`
: 西方语言编码
*
`GB2312`
: 国标码
*
`GBK`
: 国标扩展码
*
`Unicode`
: 万国码. 支持多国语言字符.
*
`UTF-8`
: Unicode的一种实现方式, 长度可变的码表, 一个字符占用1个或2个字节
*
`ANSI`
: 本地编码表. 根据系统设置决定编码表
## Java中字符串的编码
*
编码是字节转换为字符过程中的操作
* 读二进制文件, 需要将其编码为可阅读的字符
* 写字符为二进制文件, 也需要查询编码表找到对应的字节数值
*
Java中的字符串默认使用
`ANSI`
编码
*
String类中常用方法
*
`byte[] getBytes()`
: 获取字符串的byte数组, 使用默认编码
*
`byte[] getBytes(String charsetName)`
: 获取字符串的byte数组, 使用指定编码
*
`String String(byte[] bytes)`
: 将byte数组转化为字符串, 使用默认编码
*
`String(byte[] bytes, String charsetName)`
: 将byte数组转换为字符串, 使用指定编码
*
`String(byte[] bytes, int offset, int len, String charsetName)`
: 将byte数组的一部分转换为字符串, 使用指定编码
*
乱码
* 什么是乱码? 查找编码表找不到对应的字符, 显示为错误字符.
* 举例, `哈`在GBK编码表中的值是11(假设), 我们把11写入到文件, 然后读取文件, 读的时候使用UTF-8编码, 11在UTF-8编码表中对应的字符为`?`(假设), 就显示乱码了
* 原因: 读的编码与写的编码不一致
* 解决方法: 保证读和写的编码一致, 即可解决
*
编码涉及的异常
*
`UnsupportedEncodingException`
: 不支持的编码类型
*
5分钟练习: 使用UTF-8编码读写文件
* 继续使用项目s2-day10, 建包com.itheima.practice_07
* 建类Test
* 定义字符串, 调用getBytes(String charset)方法获取utf-8的字节数组
* 创建FileOutputStream将字节数组写入到文件
* 创建FileInputStream将字节读取出来, 转换为以utf-8编码的String打印
```java
```
---
## 字符流中的编码
*
处理乱码的2种方式:
1.
String通过指定编码转为byte数组, 然后再创建String: (GBK字符串转UTF-8字符串写入文件)
* 先将String通过目标编码转为byte数组: `byte[] bys = "月薪过万".getBytes("UTF-8");`
* 再将byte数组转换为String: `String str = new String(bys);`
* 写入到文件: `fw.write(str);`
2.
OutputStreamWriter可以指定编码写入文件, 免去使用String通过编码转换为byte数组的步骤
* `OutputStreamWriter OutputStreamWriter(OutputStream out, String charsetName)`: 创建转换流对象, 并指定编码
*
5分钟练习: 使用字符流读取文件并以另一种编码写入新文件
* 继续使用项目s2-day10, 建包com.itheima.practice_08
* 创建aa.txt文件, 设置编码UTF-8, 填写字符串"键盘敲烂,月薪过万"
* 建类Test
* 创建转换输入流, 将文件内容使用UTF-8编码读取, 并打印到控制台
* 创建转换输出流, 将读取到的内容以GBK编码, 写入到cc.txt文件中
```java
```
------------------------
## 今日扩展
*
IO体系结构(终结版)
```
字节流
|_ InputStream # 字节输入流
|_ FileInputStream # 专门操作文件的字节输入流
|_ BufferedInputStream # 带有缓冲区的字节输入流, 效率高
|_ ObjectInputStream # 对象输入流
|_ OutputStream # 字节输出流
|_ FileOutputStream # 专门操作文件的字节输出流
|_ BufferedOutputStream # 带有缓冲区的字节输出流, 效率高
|_ PrintStream # 字节打印流
|_ ObjectOutputStream # 对象输出流
字符流
|_ Reader # 字符输入流
|_ BufferedReader # 带有缓冲区的字符输入流, 效率高
|_ InputStreamReader # 将字节流转换为字符流的转换输入流
|_ FileReader # 专门操作文件的字符输入流
|_ Writer # 字符输出流
|_ BufferedWriter # 带有缓冲区的字符输出流, 效率高
|_ OutputStreamWriter # 将字节流转换为字符流的转换输出流
|_ FileWriter # 专门操作文件的字符输出流
|_ PrintWriter # 字符打印流
```
## 今日总结
*
标准流
*
`System.err`
: 标准错误流. (命令行输出错误信息)
*
`System.in`
: 标准输入流. (键盘输入)
* 是`InputStream`类, 属于字节输入流
*
`System.out`
: 标准输出流. (控制台输出)
* 是`OutputStream`的子类PrintStream, 属于字节输出流
*
转换流
*
`InputStreamReader`
: 字节输入流转字符输入流
* 是`Reader`的子类, 属于字符输入流
*
`OutputStreamWriter`
: 字符输出流转字节输出流
* 是`Writer`的子类, 属于字符输出流
* 特点
* 转换流也是包装类, 需要传入实际的输入输出流对象
*
打印流
*
`PrintStream`
: 字节打印流
*
`PrintWriter`
: 字符打印流
* 注意
* 打印是输出操作, 所以打印流只有输出, 没有输入
* PrintWriter打印流的特点
* 可以自动换行, println(). 会根据系统自动确定换行符
* 不能输出字节, 可以输出其他任意类型(要输出字节需要使用PrintStream)
* 通过构造方法的配置可以实现自动刷新(flush)(只在调用println, printf, format时有用)
* 也是包装流, 自身没有写出功能
* 可以把字节输出流转换为字符输出流
* 关流不会抛出异常(此类中的方法不会抛出 I/O 异常,尽管其某些构造方法可能抛出异常)
* 方法
* 构造方法
* `PrintWriter PrintWriter(String filepath)`
* `PrintWriter PrintWriter(Writer out, boolean autoFlush)`: 创建对象, 同时设置是否自动刷新
* `PrintWriter(OutputStream out, boolean autoFlush)`: 创建对象, 同时设置是否自动刷新
* 成员方法
* `void write(String s)`: 写一个字符串
* `void print(String s)`: 输出字符串, 没有换行
* `void println(String s)`: 输出字符串并换行. 如果启动了自动刷新, 则会执行自动刷新写入数据
* `void printf(Locale l, String format, Object... args)`: 使用指定格式字符串和参数将格式化的字符串写入输出流. 如果启动了自动刷新, 则会执行自动刷新写入数据
* `void format(Locale l, String format, Object... args)`: 使用指定格式字符串和参数将格式化的字符串写入输出流. 如果启动了自动刷新, 则会执行自动刷新写入数据
*
对象操作流
* 作用: 读写对象到文件
*
`ObjectInputStream`
: 对象输入流
*
`ObjectOutputStream`
: 对象输出流
* 注意
* 使用对象输出流写出对象到文件, 该文件只能使用对象输入流读取对象
* 只能将实现了`java.io.Serializable`接口的对象写入流中
* Serializable接口
* 标记接口, 用于标记该类可以序列化
* `private static final long serialVersionUID`: 序列版本号. 用于确定对象和类定义是否一致
* `InvalidClassException`用对象读取流时发生该异常的原因:
1.
该类的序列版本号与从流中读取的类描述符的版本号不匹配
2.
该类包含未知数据类型(即可能添加了新的成员变量或方法)
3.
该类没有可访问的无参数构造方法
* 方法
* 构造方法:
* `ObjectOutputStream ObjectOutputStream(OutputStream out)`: 创建对象输出流
* `ObjectInputStream ObjectInputStream(InputStream in)`: 创建对象输入流
* 成员方法
* `void writeObject(Object o)`: 将对象写入对象输出流
* `Object readObject()`: 从对象输入流读取对象, 一次只读取一个对象. 当读不到时抛出`EOFException`.
* 读取对象异常的优化操作
* 在对象流中只保存一个对象, 通过该对象保存其他对象
* 比如用集合存储多个同类型的对象
* 定义一个类, 其中包含不同类型的其他类型对象
*
Properties
* 继承
`Hashtable<K, V>`
, 实现
`Map<K, V>`
* 作用: 以键值对方式保存信息到文本文件
* 应用场景: 作为程序的配置文件
* 注意
* 不能存`null`的键和值
* 只能保存英文字符和符号
* 键和值都是字符串
* 方法
* 构造方法
* `Properties Properties()`
* 成员方法
* 可以使用Map的方法
* `String getProperty(String key)`: 根据键获取值. 如果找不到该键, 则返回null
* `String getProperty(String key, String defaultValue)`: 根据键获取值, 如果值不存在, 则使用指定的默认值
* `void setProperty(String key, String value)`: 设置键值对
* `void load(InputStream in)`: 从字节输入流中读取Properties
* `void load(Reader reader)`: 从字符输入流中读取Properties
* `void list(PrintStream out)`: 将Properties输出到指定的字节打印输出流. 会自动加一个`-- listing properties --`文件头
* `void list(PrintWriter out)`: 将Properties输出到指定的字符打印输出流. 会自动加一个`-- listing properties --`文件头
* `void store(Writer writer, String comments)`: 将Properties输出到指定的输出流, 并添加一个注释. 如果不想要注释可传null. 无论有没有注释, 都会添加时间字符串的注释
* list方法和store方法的区别
* list只能接收打印流(PrintStream, PrintWriter)
* store可以接收任何输出流(OutputStream, Writer)
* Properties读写操作步骤
* 写出到文件
* 创建Properties对象, 添加键值对, 使用list()或store()保存到文件中
* 从文件读取
* 创建Properties对象, 使用load()从文件加载数据, 使用getProperty()根据指定键获取值, 或使用遍历Map的方式遍历所有键和值
*
编码表
* 作用: 将计算机二进制数据转换为不同语言的字符
* 常见编码表
* `ASCII`: 美国标准码. 包含26个英文字母的大写和小写, 数字, 符号
* `ISO-8859-1`: 西方语言编码
* `GB2312`: 国标码
* `GBK`: 国标扩展码
* `Unicode`: 万国码. 支持多国语言字符.
* `UTF-8`: Unicode的一种实现方式, 长度可变的码表, 一个字符占用1个或2个字节
* `ANSI`: 本地编码表. 根据系统设置决定编码表
* Java String类对于字节和编码的操作
* `byte[] getBytes()`: 获取字符串的byte数组, 使用默认编码
* `byte[] getBytes(String charsetName)`: 获取字符串的byte数组, 使用指定编码
* `String String(byte[] bytes)`: 将byte数组转化为字符串, 使用默认编码
* `String(byte[] bytes, String charsetName)`: 将byte数组转换为字符串, 使用指定编码
* `String(byte[] bytes, int offset, int len, String charsetName)`: 将byte数组的一部分转换为字符串, 使用指定编码
* 乱码
* 原因: 读的编码与写的编码不一致
* 解决方法: 保证读和写的编码一致, 即可解决
* 处理乱码的2种方式:
1.
String通过指定编码转为byte数组, 然后再创建String: (GBK字符串转UTF-8字符串写入文件)
* 先将String通过目标编码转为byte数组: `byte[] bys = "月薪过万".getBytes("UTF-8");`
* 再将byte数组转换为String: `String str = new String(bys);`
* 写入到文件: `fw.write(str);`
2.
OutputStreamWriter可以指定编码写入文件, 免去使用String通过编码转换为byte数组的步骤
* `OutputStreamWriter OutputStreamWriter(OutputStream out, String charsetName)`: 创建转换流对象, 并指定编码
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/)
黑马程序员IT技术论坛 X3.2