目录
1. 为什么要用到转换流?
2. 字符输入转换流(重点掌握)
3. 字符转换输出流(理解即可)
4. 认识打印流
5. 打印流的作用
6. PrintStream(字节打印流)如何使用?
7. PrintStream 内部没有缓冲区
8. PrintWriter(字符打印流)如何使用?
9. PrintStream和PrintWriter的区别?
10. 打印流与常用的 System.out.println() 有什么关系?
我们都知道,字符集编码的格式多种多样,有UTF-8,UTF-16,GBK等等很多种,那么在读取的时候也会有差异,例如我们的IDEA编码格式为UTF-8,当我们去读取一个编码格式为GBK的文件时,即便是采用字节流全部读取出来,也会读取到一堆乱码。
如下所示,我在项目目录下创建一个名为 test02.txt 的文件,随便填写一些内容,将该文件的编码格式改为GBK。
然后我们去另一个编码为UTF-8 的类中编写一个 main 函数,读取该文件中的内容,代码如下所示
public static void main(String[] args) {
try (
// 创建 IO流 管道,得到原始字节流对象 is
Reader r = new FileReader("user-service/test02.txt");
// 这里我们可以多做一步,将is 对象加工成缓冲流对象提高读写效率
BufferedReader br = new BufferedReader(r);
){
// 定义一个字符串接收读取到的文件内容
String line;
// 将读取到每一行文件内容赋值给字符串 line,这里其实是在底层新建字符串
while ( (line = br.readLine()) != null){
System.out.println(line);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
运行上述方法,可以得到如下结果
在控制台,我们可以看到文件中的内容虽然被读取出来,却含有一部分乱码,原因就是因为我们读取的文件编码格式为 GBK,而我们 编写的main 方法编码格式为 UTF-8,编码格式不匹配,就会出现乱码问题。
这还仅仅只是向内存中读数据(即输入流编码格式的问题),如果从内存向硬盘写数据,想要编写的文件编码格式也不一样,该怎么办呢?有没有什么办法可以解决这个问题呢?这就需要用到我们的转换流。
字符转换输出流,在我们开发时还是较为常用的,因为我们在编码时,可能会遇到各种各样类型的文件,需要读入到内存进行处理,这个时候我们就需要使用字符输入转换流进行编码修改。
如图所示,字符输出转换流有两个构造器,我们常用的是第二个圈起来的,他的参数列表中第二个参数可以传入一个String 类型的编码字符串,这里就是要写我们所读取文件的编码格式。
再回到刚才的那个问题,我们使用字符转换输入流来试一下看看是否能够解决乱码问题,我已经写好了代码如下
public static void main(String[] args) {
try (
// 创建 IO流 管道,得到原始字节流对象 is
InputStream is = new FileInputStream("user-service/test02.txt");
// 将is 字节流转换成字符输入流对象
InputStreamReader isr = new InputStreamReader(is, "GBK");
// 这里我们可以多做一步,将isr 对象加工成缓冲流对象提高读写效率
BufferedReader br = new BufferedReader(isr);
){
// 定义一个字符串接收读取到的文件内容
String line;
// 将读取到每一行文件内容赋值给字符串 line,这里其实是在底层新建字符串
while ( (line = br.readLine()) != null){
System.out.println(line);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
运行此方法,我们到控制台查看输出结果
此时可以看到,刚才的乱码问题已经得到了解决。
这里需要提醒一点,在JDK11以后,这种方法就已经用的不多了。因为在JDK11以后,FileReader中又新增了一种构造器,可以额外添加一个字符集编码参数,所以FileReader也可以用作转换流了,但这是在JDK11以后,用JDK8的还要使用 InputStreamReader 转换流。
字符转换输出流相比于字符转换输入流,用的就没有那么多了,所以理解即可,但能熟练掌握最好。
刚才我们也他提到了,字符输入转换流可以控制改变写入到内存中的文件编码格式,那么字符转换输出流也是同样的道理,可有控制我们输出数据的编码格式。
如下图中即为字符转换输出流的构造器,我们常用的同样也是第二个可以传递编码格式参数的构造方法。
下面我简单做个小案例,将刚才 字符转换输入流中的test02.txt 文件中的内容复制一份,但编码格式仍然采用 GBK的形式
public static void main(String[] args) {
try (
// 创建 IO 输入流管道,得到原始字节流对象 is
InputStream is = new FileInputStream("user-service/test02.txt");
// 将is 字节流转换 成 字符转换输入流对象
InputStreamReader isr = new InputStreamReader(is, "GBK");
// 这里我们可以多做一步,将isr 对象加工成缓冲流对象提高 读的效率
BufferedReader br = new BufferedReader(isr);
// 创建 IO 输入流管道,得到原始字节流对象 os
OutputStream os = new FileOutputStream("user-service/test03.txt");
// 将os 字节流转换成字符转换输出流对象
OutputStreamWriter osw = new OutputStreamWriter(os,"GBK");
// 这里我们可以多做一步,将osw 对象加工成缓冲流对象提高 写的效率
BufferedWriter bw = new BufferedWriter(osw);
){
// 定义一个字符串接收读取到的文件内容
String line;
// 将读取到每一行文件内容赋值给字符串 line,这里其实是在底层新建字符串
while ( (line = br.readLine()) != null){
osw.write(line,0,line.length());
}
} catch (IOException e) {
e.printStackTrace();
}
}
运行上述代码,我们都可以在项目木块中找到已经被新建出来的test03.txt文件
这里需要提醒一点,在JDK11以后,这种方法就已经用的不多了。因为在JDK11以后,FileWriter中又新增了一种构造器,可以额外添加一个字符集编码参数,所以FileWriter也可以用作转换流了,但这是在JDK11以后,用JDK8的还要使用 OutputStreamWriter 转换流。
如下图所示,打印流一共有两个,一个是PrintStream,一个是PrintWriter,分别是OutputStream与Writer的子类。
打印流的作用其实很简单,我们知道,我们使用字节流在打印数据时,如果输出数字97,那么它实际打印的并不是97,而是97对应的ASCLL码表中的字母,就是a;
如果我们真的只是想打印数字97,就可以使用打印流,它可以实现我们写什么它就打印什么的功能。
打印流分为字节打印流与字符打印流两种;
此外,打印流是不操作数据源的,只能操作目的地。
下图即为PrintStream提供的构造器,字节打印流默认自动刷新
PrintStream的使用非常简单,我们只需要直接创建它的对象即可,如下代码所示
public static void main(String[] args) {
try (
PrintStream ps = new PrintStream("user-service/test04.txt");
){
ps.print(97);
ps.print("abcdefg");
ps.println("中国万岁");
ps.println(88.888);
ps.println(true);
} catch (IOException e) {
e.printStackTrace();
}
}
运行此方法,程序会自动创建名为 test04.txt 的文件,可以看到,这里可以添加诸多内容,证书,字符串,浮点数,布尔类型都可以加入,我们打开文件查看如下,可以看到添加成功
PrintStream 字节流底层没有缓冲区,开不开自动刷新都一样。
其实从使用上来讲,PrintStream与PrintWriter没有什么本质的区别,它们的功能都是为了打印数据,但PrintWriter毕竟是字符输出流,所以它可以打印字符串,这里用法就不再作演示了,几乎和PrintStream一摸一样。
但有一点,字符打印流自动刷新需要开启,不是默认开启的。
(1)从功能上来讲,PrintStream和PrintWriter都是一样的,都是使用方便,性能高效,这也是它们的核心功能。
(2)PrintStream继承字节输出流OutputStream,因此支持写字节数据的方法。
(3)PrintWriter继承字符输出流Writer,因此支持写字符数据。
我们知道,System.out.println() 是我们在编写代码时常常用到的一个打印语句,那么它与打印流有没有什么内在联系呢?
这里是有的哦!
其实 System 是我们Java中的一个类,我们打开它的源码看一下
往下翻阅,可以看到两个静态变量,err 和 out,其中 out 是我们最常用的,他就是 System.out.println 中的那个out
静态变量的调用方式也是 类名.静态变量;
System.out 就是获取了一个打印流的对象,这个流的对象时不需要我们自己去创建的,而是虚拟机在启动时自动帮我们创建的,而且这个打印流对象默认指向了控制台,而不是文件。
我们可以使用 PrintStream 创建一个对象接收这个对象,来试一下
从IDEA 的提示中就可以看到,接收的这个对象可以调用 PrintStream 的方法,我们随便打印一个字符串123,运行此方法,就会得到如下图所示结果,可以发现,此对象的默认指向就是我们的控制台。
其实这个流有一个专门的名称,叫做系统中的标准输出流。而且不能把它关闭,因为你一旦关闭,就无法继续在控制台打印内容了,如下所示,各位一看就懂了
因此要记住,System.out 是一个打印流对象,默认指向我们的控制台输出,而且不要关闭!
未完待续......