其实,一切都是字节流,没有字符流这个东西。字符只是根据编码集对字节流翻译之后的产物。
代码分析
看下面这段代码:
public class JavaEncode {
public static void main(String[] args) throws IOException {
// test 1
InputStream inputStream = new FileInputStream(new File("demo.txt"));
byte[] bytes = new byte[6];
inputStream.read(bytes);
System.out.println(HexUtil.encodeHex(bytes));
String utf8Str = new String(bytes, "gbk");
System.out.println(utf8Str);
// test 2
inputStream = new FileInputStream(new File("demo.txt"));
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "GBK");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String cs = bufferedReader.readLine();
System.out.println(cs);
}
}
其中demo.txt中存储的文本为:
另外文件编码格式是UTF-8。
输出的结果为:
e4b8ade59bbd
涓浗
涓浗
其中e4b8ade59bbd为“中国”两个汉字的UTF-8编码,第二行和第三行为乱码,两次输出的乱码是一样的。
对于test 1
- 首先创建了一个demo.txt文件的输入流(类似于一个管道)
- 然后从输入流中读取文件的字节流。根据输出的结果,可以看出,test 1是以UTF-8编码格式,将“中国”两个汉字的字节读入字节数组的
-
【String utf8Str = new String(bytes, "gbk");】debug一下,看看utf8Str中存的字符数组是什么
它想将字符以UTF-8编码格式获取的字节数组再以GBK编码的形式转化为字符。
GBK是定长编码,UTF-8是不定长编码,这一转肯定是要出问题的。“中国”两个字符以UTF-8编码,是长度为6的字节数组,GBK字符字节长度为2,所以变成了三个字符,但是在JVM中,都是Unicode字符编码来表示字符的,所以JVM又将三个GBK字符编码转换成了Unicode编码,存在JVM内存中,才有了utf8Str上图中的情况。
对于test 2,
- 【 inputStream = new FileInputStream(new File("demo.txt"));】建立字节输入流
- 【 InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "GBK");】InputStreamReader是字节流向字符流转换的桥梁,可以指定编码格式。
- 【 BufferedReader bufferedReader = new BufferedReader(inputStreamReader);】 BufferedReader是字符流的一种实现方式,后面会介绍。
-
【String cs = bufferedReader.readLine();】按行读取文本。字符串cs中的字符数组存储的是什么呢?
这行代码在读取文本中的信息的时候,应该也是经历了如下步骤
- 获取UTF-8编码格式字节数组 bs
- 根据GBK编码,将bs转化为GBK字符
- 将GBK字符转化为其对应的Unicode编码,于是有了上图cs的情况。
从上面的两个test可以看出, test 1可以操作到字节级别,这个就叫做字节流,test 2操作最小的也是一个Unicode字符,这个叫做字符流。从我们创建字符流对象的过程,可以知道,字符流其实是对字节流的封装,只不过字符流一次会操作一个Unicode字符。
另外,字节流处理的范围更加大,例如图片、音频等,我们可以获取他们的字节流,但是它们有自己的编码规则,无法转化为我们的字符。
Java I/O编码系统
- 面向字节流的InputStream和OutputStream
- 面向字符流的Reader 和 Writer
字节流的InputStream和OutputStream是一切的基础,实际总线中流动的只有字节流。Java中负责从字节流向字符流解码的桥梁是:
InputStreamReader和OutputStreamWriter,可以指定以什么编码格式读取或写入。
Java中的字符流处理的最基本的单元是Unicode码元(大小2字节),它通常用来处理文本数据。所谓Unicode码元,也就是一个Unicode代码单元,范围是0x0000~0xFFFF。在以上范围内的每个数字都与一个字符相对应,Java中的String类型默认就把字符以Unicode规则编码而后存储在内存中。然而与存储在内存中不同,存储在磁盘上的数据通常有着各种各样的编码方式。使用不同的编码方式,相同的字符会有不同的二进制表示。实际上字符流是这样工作的:
- 输出字符流:把要写入文件的字符序列(实际上是Unicode码元序列)转为指定编码方式下的字节序列(OutputStreamWriter指定的编码,如果没有指定,则按照操作系统默认),然后再写入到文件中;
- 输入字符流:把要读取的字节序列按指定编码方式解码为相应字符序列(按照InputStreamReader指定的编码,先编码,再转码为Unicode码元序列)从而可以存在JVM内存中。