菜鸟举例理解字节流和字符流区别

菜鸟举例理解字节流和字符流区别

按照uft8编码方式存储文档

文档存储路径在D盘下

/**
* 按照utf8格式存储文档
*/
public static void storeDataByUTF8(){
	String path = "D:" + File.separator + "textutf8.txt";
	File file = new File(path);
	try {
		PrintWriter pw = new PrintWriter(file,"utf-8");
		for(int i=0;i<5;i++){
			pw.write(i+":"+"字节流与字符流!!!"+"\r\n");
		}
		pw.flush();
		pw.close();
	} catch (FileNotFoundException | UnsupportedEncodingException e) {
		e.printStackTrace();
	}
}

对比使用BufferedReader和FileInputStream按照byte读取文档(按照字节流方式读取)

使用BufferedReader按照byte读取文档代码如下:

public static void readDataWithArray(){
	String path = "D:" + File.separator + "textutf8.txt";
	File file = new File(path);
	try{
		FileInputStream fis = new FileInputStream(file);
  	    InputStreamReader isr = new InputStreamReader(fis,"utf-8");
		BufferedReader in = new BufferedReader(isr);
		byte[] b = new byte[2048];
		int temp = 0;
		int len = 0;
		
		while((temp=in.read())!=-1){  // -1是结束符
			b[len] = (byte)temp;
			if(len<2047)
				len++;
			else
				break;
		}
		in.close();
		System.out.println(new String(b,0,len,"utf-8"));
		
	}catch(Exception e){
		e.printStackTrace();
		System.out.println("While reading the data, the program threw out exceptions");
	}

}

输出结果发生了乱码:

0:W?AW&A
1:W?AW&A
2:W?AW&A
3:W?AW&A
4:W?AW&A

FileInputStream按照byte读取文档代码如下:

public static void readDataWithArray(){
	String path = "D:" + File.separator + "textutf8.txt";
	File file = new File(path);
	try{
		FileInputStream in = new FileInputStream(file);
		//InputStreamReader isr = new InputStreamReader(fis,"utf-8");
		//BufferedReader in = new BufferedReader(isr);
		
		byte[] b = new byte[2048];
		int temp = 0;
		int len = 0;
		
		while((temp=in.read())!=-1){  // -1是结束符
			b[len] = (byte)temp;
			if(len<2047)
				len++;
			else
				break;
		}
		in.close();
		System.out.println(new String(b,0,len,"utf-8"));
		
	}catch(Exception e){
		e.printStackTrace();
		System.out.println("While reading the data, the program threw out exceptions");
	}

}

输出结果正常:

0:字节流与字符流!!!
1:字节流与字符流!!!
2:字节流与字符流!!!
3:字节流与字符流!!!
4:字节流与字符流!!!

以上两段代码不同之处只是读取文本的流不同,其他代码相同。要回答这个问题,首先来理解一下为什么要编码?

为什么要编码

不知道大家有没有想过一个问题,那就是为什么要编码?我们能不能不编码?要回答这个问题必须要回到计算机是如何表示我们人类能够理解的符号的,这些符号也就是我们人类使用的语言。由于人类的语言有太多,因而表示这些语言的符号太多,无法用计算机中一个基本的存储单元—— byte 来表示,因而必须要经过拆分或一些翻译工作,才能让计算机能理解。我们可以把计算机能够理解的语言假定为英语,其它语言要能够在计算机中使用必须经过一次翻译,把它翻译成英语。这个翻译的过程就是编码。所以可以想象只要不是说英语的国家要能够使用计算机就必须要经过编码。
所以总的来说,编码的原因可以总结为:

  1. 计算机中存储信息的最小单元是一个字节即 8 个 bit,所以能表示的字符范围是 0~255 个
  2. 人类要表示的符号太多,无法用一个字节来完全表示
  3. 要解决这个矛盾必须需要一个新的数据结构 char,从 char 到 byte 必须编码

为什么两者的结果那么不同呢?

在Java中byte是1个字节(8个bit),char是2个字节;
最基础的流InputStream和OutputStream是按照字节读取的(byte)。

/**
 * Reads a byte of data from this input stream. This method blocks
 * if no input is yet available.
 *
 * @return     the next byte of data, or -1 if the end of the
 *             file is reached.
 * @exception  IOException  if an I/O error occurs.
 */
public int read() throws IOException {
    return read0();
}

但是BufferedReader类的read()方法返回的是char。所以没有处理好编码原因的第三个原因。

public class BufferedReader extends Reader {
    private char cb[];  // char数组的缓存
    private int nChars, nextChar;
    ...
        /**
     * Reads a single character.
     *
     * @return The character read, as an integer in the range
     *         0 to 65535 (0x00-0xffff), or -1 if the
     *         end of the stream has been reached
     * @exception  IOException  If an I/O error occurs
     */
    public int read() throws IOException {
        synchronized (lock) {
            ensureOpen();
            for (;;) {
                if (nextChar >= nChars) {
                    fill();
                    if (nextChar >= nChars)
                        return -1;
                }
                if (skipLF) {
                    skipLF = false;
                    if (cb[nextChar] == '\n') {
                        nextChar++;
                        continue;
                    }
                }
                return cb[nextChar++];
            }
        }
    }
    
}

举个详细的例子:
按照 ISO-8859-1 编码字符串“I am 君山”用 ISO-8859-1 编码,下面是编码结果:
菜鸟举例理解字节流和字符流区别_第1张图片

从上图看出7个char 字符经过ISO-8859-1 编码转变成7个byte 数组,ISO-8859-1是单字节编码,中文“君山”被转化成值是3f的byte。3f也就是“?”字符,所以经常会出现中文变成“?”很可能就是错误的使用了 ISO-8859-1这个编码导致的。中文字符经过 ISO-8859-1编码会丢失信息,通常我们称之为“黑洞”,它会把不认识的字符吸收掉。由于现在大部分基础的 Java 框架或系统默认的字符集编码都是 ISO-8859-1,所以很容易出现乱码问题。

以上总结只是举例了一个简单的例子,希望能通过这个例子使得你对Java字节流和字符流的区别有个感性的认识。如果需要了解两者的区别,强烈建议大家看看另一篇博客《深入分析 Java 中的中文编码问题》。

如果博客写得有什么问题,请各请各位大神指出。


你可能感兴趣的:(Java基础)