Java IO之字符流
一、字符集与字符编码
- 为什么要有字符集
我们在计算机屏幕上看到的是实体化的文字,而在计算机存储介质中存放的实际是二进制的比特流。那 么在这两者之间的转换规则就需要一个统一的标准,否则就会出现乱码了现象;小伙伴QQ上传过来的文件,在我们本地打开又乱码了。 于是为了实现转换标准,各种字符集标准就出现了。
- 什么是字符集
简单的说字符集就规定了某个文字对应的二进制数字存放方式(编码)和某串二进制数值代表了哪个文字(解 码)的转换关系。
- 什么是字符编码
计算机只能存储0,1之类2进制数字,怎么样让它表示那么多各种各样的字符呢?就需要对各种字符指定一个数值的编码代号,这就是字符编码。如:a这个字符,在ascii字符集编码表中对应的编号是97,而“中”在gb2312字符集中对应的编号是:16进制是D6D0 10进制是54992 。通过编号就可以找到计算机对应字符。不用将那么复杂的字符保存在计算机中,只需要保存它代号就好。字符集只是指定了一个集合中有哪些字符,而字符编码,是为这个集合中所有字符定义个编号,这就是字符集与编码区别所在。
此外计算机中只保存字符在某字符集中对应的字符编号值,计算机只需要维持一份字符集清单,当读到这种编号值(编码),就在对应字符清单中找出该字符显示出来即可。字符大小颜色都是程序按照字符样式绘制而成的。如图:
计算机并不区分二进制文件与文本文件。所有的文件都是以二进制形式来存储的,因此,从本质上说,所有的文件都是二进制文件。所以字符流是建立在字节流之上的,它能够提供字符层次的编码和解码。列如,在写入一个字符时,Java虚拟机会将字符转为文件指定的编码(默认是系统默认编码),在读取字符时,再将文件指定的编码转化为字符。
常见的码表如下:
ASCII:美国标准信息交换码。用一个字节的7位可以表示。
ISO8859-1:拉丁码表。欧洲码表,用一个字节的8位表示。又称Latin-1(拉丁编码)或“西欧语言”。ASCII码是包含的仅仅是英文字母,并且没有完全占满256个编码位置,所以它以ASCII为基础,在空置的0xA0-0xFF的范围内,加入192个字母及符号藉以供使用变音符号的拉丁字母语言使用。从而支持德文,法文等。因而它依然是一个单字节编码,只是比ASCII更全面。
GB2312:英文占一个字节,中文占两个字节.中国的中文编码表。
GBK:中国的中文编码表升级,融合了更多的中文文字符号。
Unicode: 国际标准码规范,融合了多种文字。所有文字都用两个字节来表示,Java语言使用的就是unicode。
UTF-8:最多用三个字节来表示一个字符。
我们以后接触最多的是iso8859-1、gbk、utf-8
查看上述码表后,很显然中文的‘中’在iso8859-1中是没有对映的编码的。或者一个字符在2中码表中对应的编码不同,例如有一些字在不同的编码中是有交集的,例如bjg5 和gbk 中的汉字简体和繁体可能是一样的,就是有交集,但是在各自码表中的数字不一样。
例如使用gbk 将中文保存在计算机中,中国对应gbk编码为100 200如果使用big5 打开,可能 ? ... 不同的编码对映的是不一样的。很显然,我们使用什么样的编码写数据,就需要使用什么样的编码来读数据。
ISO8859-1:一个字节
GBK: 两个字节包含了英文字符和扩展的中文 ISO8859-1+中文字符
UTF-8 万国码,推行的。是1~3个字节不等长。英文存的是1个字节,中文存的是3个字节,是为了节省空间。
- 乱码
如果一个字符按照一个字符集存储在计算机中(编码),而使用另一种字符集读取该字符(解码)那就会出现乱码现象。应为计算机中存储使用的字码表与读取使用的字码表不匹配。
二、java中字符的编码与解码
计算机中存储和传输的基本单位是字节,而我们看到的信息是字符(人能看懂的)。
编码:编码就是真实字符与二进制串的对应关系,真实字符转换成二进制串。编码是信息从一种形式或格式转换为另一种形式的过程也称为计算机编程语言的代码简称编码。用预先规定的方法将文字、数字或其它对象编成数码,或将信息、数据转换成规定的电脉冲信号。
java中的编码:
在byte[] buffer=string.getBytes();中,如果没有给.getBytes();指定字符集,那么在编码过程中,就会按照系统默认的编码格式进行编码。
String str="中";
byte [] b_gbk=str.getBytes("GBK");
byte [] b_utf=str.getBytes("UTF-8");
byte [] b_unic=str.getBytes("UTF-16");
byte [] b_Iso=str.getBytes("ISO8859-1");
解码:解码就是二进制串与真实字符的对应关系,二进制串转换成真实字符。将信息从已经编码的形式恢复到编码前原状的过程。也就是用特定方法把数码还原成它所代表的内容或将电脉冲信号、光信号、无线电波等转换成它所代表的信息、数据等的过程。
java中的解码:
//str按照 GBK编码 后 ,再按照GBK解码
String str_1=new String(str.getBytes("GBK"), "GBK");
String str_2=new String(str.getBytes("UTF-8"), "UTF-8");
String str_3=new String(str.getBytes("UTF-32"), "UTF-32");
String str_4=new String(str.getBytes("ISO8859-1"), "ISO8859-1");
三、关于字符流的一些探讨:
字符流为何存在
既然字节流提供了能够处理任何类型的输入/输出操作的功能,那为什么还要存在字符流呢?容我慢慢道来,字节流不能直接操作Unicode字符,因为一个字符有两个字节,字节流一次只能操作一个字节。如果JAVA不能直接操作字符,我会感到JAVA对这个世界满满的恶意,所以提供对直接的字符输入/输出的支持是很有必要的,因为我们的口号是:一次编写,到处运行。
字符流的概念
输出字符流:把要写入文件的字符序列(实际是unicode码元序列)转为指定编码方式下的字节序列,然后在写入文件中。
输入字符流:把要读取的字节序列按照指定编码方式转为相应的字符序列(实际是unicode码元序列),从而写入内存中。
字符流的层次关系
字符流层次结构的顶层是Reader和Writer抽象类,与字节流中的InputStream、OutputStream相对应。常用类继承结构图如下:
四、字符流
字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串。字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的。字符流操作的是缓冲区(当我们对文件进行读写操作时如果不调用close() 或 flush()方法时不能看到数据的变化)。
java的文本(char)是16位无符号整数,是字符的unicode编码(双字节编码),文件是byte byte byte ...的数据序列,文本文件是文本(char)序列按照某种编码方案(utf-8,utf-16be,gbk)序列化为byte的存储结果
字符流(Reader Writer)---->操作的是文本文本文件,字符的处理,一次处理一个字符,字符的底层任然是基本的字节序列
(一)、字符输入流
将磁盘(文件)中的数据读入内存中
Reader 是所有的输入字符流的父类,它是一个抽象类。
InputStreamReader:将字节输入流转换为字符输入流。是字节流通向字符流的桥梁,可以指定字节流转换为字符流的字符集。完成byte流解析为char流,按照编码解析
FileReader:该类从InputStreamReader类继承而来,可以关联源文件,是字符流的过滤器。
BufferedReader(缓冲流):字节输入缓冲流,一次只能读一行,从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。BufferedReader 由Reader类扩展而来,提供通用的缓冲方式文本读取,而且提供了很实用的readLine。
LineNumberReader(关于行号缓冲流):它是BufferedReader的子类,LineNumberReader比BufferedReader多了个功能,就是可以获取当前行号getLineNumber()与设置起始行号setLineNumber(int lineNumber)。默认情况下,行编号从 0 开始。该行号随数据读取在每个行结束符处递增,并且可以通过调用 setLineNumber(int)
更改行号。但要注意是,setLineNumber(int)
不会实际更改流中的当前位置;它只更改将由 getLineNumber() 返回的值。
(二)、字符输出流
将内存中的数据按照字符形式写入磁盘(文件)中
Writer:是所有的输出字符流的父类,它是一个抽象类。
OutputStreamWriter:将字节输出流转为字符输出流,是字符流通向字节流的桥梁,可使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。即OutputStreamWriter提供char流到byte流,按照编码处理
FileWriter:类从OutputStreamWriter类继承而来。该类按字符向流中写入数据,可以关联源文件,是字符流的过滤器
BufferedWriter(缓冲流):字节输出缓冲流,将文本写入字符输出流,一次只能写一行,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。 可以指定缓冲区的大小,或者接受默认的大小。在大多数情况下,默认值就足够大了。 该类提供了 newLine() 方法,它使用平台自己的行分隔符概念,此概念由系统属性 line.separator 定义。该类写入文本的方法就是父类Writer提供的write()系列方法,在后面的示例中我么将演示他特有的newLine( )方法。
五、示例分析
(一)、InputStreamReader和OutStreamWriter示例
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package me.io.chars; 2 3 import java.io.FileInputStream; 4 import java.io.FileOutputStream; 5 import java.io.IOException; 6 import java.io.InputStreamReader; 7 import java.io.OutputStreamWriter; 8 9 /** 10 * InputStreamReader和OutStreamWriter示例 11 * 12 * @author Administrator 13 * 14 */ 15 public class IsrAndOswDemo { 16 public static void main(String[] args) throws IOException { 17 String file = "src/me/io/IOUtils.java"; 18 //默认项目的编码,将来操作时要写文件本身的编码格式,不然会乱码 19 InputStreamReader isr = new InputStreamReader(new FileInputStream(file)); 20 OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("test.java"), "UTF-8"); 21 /* 22 int num = 0; 23 while((num = isr.read()) != -1) { 24 System.out.print((char)num); 25 }*/ 26 int c = 0; 27 char[] buf = new char[8*1024]; 28 StringBuilder sb = new StringBuilder(); 29 /** 30 * 批量读取,放入到buf这个字符数组,从第零个位置开始放,最多放buf.length个 31 * 返回的是读取到的字符个数 32 */ 33 while((c = isr.read(buf, 0, buf.length)) != -1) { 34 sb.append(buf, 0, c); 35 osw.write(buf, 0, c); 36 osw.flush(); 37 } 38 System.out.print(sb); 39 isr.close(); 40 osw.close(); 41 } 42 }
(二)、FileReader和FileWriter示例
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package me.io.chars; 2 3 import java.io.FileReader; 4 import java.io.FileWriter; 5 import java.io.IOException; 6 7 /** 8 * FileReader和FileWriter测试类 9 * @author Administrator 10 * 11 */ 12 public class FrAndFwDemo { 13 public static void main(String[] args) throws IOException { 14 15 FileReader fr = new FileReader("src/me/io/IOUtils.java"); 16 //参数true代表可以追加内容 17 FileWriter fw = new FileWriter("demo/test.java", true); 18 char[] buf = new char[8*1024]; 19 int c = 0; 20 while((c = fr.read(buf, 0, buf.length)) != -1) { 21 fw.write(buf, 0, c); 22 fw.flush(); 23 } 24 fr.close(); 25 fw.close(); 26 } 27 }
(三)、BufferedReader,BufferedWriter,PrintWriter示例
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package me.io.chars; 2 3 import java.io.BufferedReader; 4 import java.io.BufferedWriter; 5 import java.io.FileInputStream; 6 import java.io.FileOutputStream; 7 import java.io.IOException; 8 import java.io.InputStreamReader; 9 import java.io.OutputStreamWriter; 10 import java.io.PrintWriter; 11 12 /** 13 * 过滤流 14 * BufferedReader,BufferedWriter,PrintWriter示例 15 * @author Administrator 16 * 17 */ 18 public class BrAndBwOrPwDemo { 19 20 public static void main(String[] args) throws IOException { 21 BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("src/me/io/IOUtils.java"))); 22 //BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("demo/test.java"))); 23 PrintWriter pw = new PrintWriter("demo/test.java"); 24 String line = null; 25 while((line = br.readLine()) != null) { 26 //不能读取换行 27 System.out.println(line); 28 // bw.write(line); 29 // //单独写出换行操作 30 // bw.newLine(); 31 // bw.flush(); 32 pw.println(line); 33 pw.flush(); 34 } 35 br.close(); 36 //bw.close(); 37 pw.close(); 38 } 39 }
参考文章:
https://www.cnblogs.com/jalja/p/6030137.html