欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java自学-I/O 中文问题

程序员文章站 2022-05-29 10:34:37
Java中的编码中文问题 步骤 1 : 编码概念 计算机存放数据只能存放数字,所有的字符都会被转换为不同的数字。 就像一个棋盘一样,不同的字,处于不同的位置,而不同的位置,有不同的数字编号。 有的棋盘很小,只能放数字和英文 有的大一点,还能放中文 有的“足够”大,能够放下世界人民所使用的所有文字和符 ......

java中的编码中文问题

步骤 1 : 编码概念

计算机存放数据只能存放数字,所有的字符都会被转换为不同的数字。
就像一个棋盘一样,不同的字,处于不同的位置,而不同的位置,有不同的数字编号。
有的棋盘很小,只能放数字和英文
有的大一点,还能放中文
有的“足够”大,能够放下世界人民所使用的所有文字和符号

如图所示,英文字符 a 能够放在所有的棋盘里,而且位置都差不多
中文字符, 中文字符 能够放在后两种棋盘里,并且位置不一样,而且在小的那个棋盘里,就放不下中文
Java自学-I/O 中文问题
步骤 2 : 常见编码

工作后经常接触的编码方式有如下几种:
iso-8859-1 ascii 数字和西欧字母
gbk gb2312 big5 中文
unicode (统一码,万国码)

其中
iso-8859-1 包含 ascii
gb2312 是简体中文,big5是繁体中文,gbk同时包含简体和繁体以及日文。
unicode 包括了所有的文字,无论中文,英文,藏文,法文,世界所有的文字都包含其中

步骤 3 : unicode和utf

根据前面的学习,我们了解到不同的编码方式对应不同的棋盘,而unicode因为要存放所有的数据,那么它的棋盘是最大的。
不仅如此,棋盘里每个数字都是很长的(4个字节),因为不仅要表示字母,还要表示汉字等。

如果完全按照unicode的方式来存储数据,就会有很大的浪费。
比如在iso-8859-1中,a 字符对应的数字是0x61
而unicode中对应的数字是 0x00000061,倘若一篇文章大部分都是英文字母,那么按照unicode的方式进行数据保存就会消耗很多空间

在这种情况下,就出现了unicode的各种减肥子编码, 比如utf-8对数字和字母就使用一个字节,而对汉字就使用3个字节,从而达到了减肥还能保证健康的效果

utf-8,utf-16和utf-32 针对不同类型的数据有不同的减肥效果,一般说来utf-8是比较常用的方式
Java自学-I/O 中文问题
步骤 4 : java采用的是unicode

写在.java源代码中的汉字,在执行之后,都会变成jvm中的字符。
而这些中文字符采用的编码方式,都是使用unicode. "中"字对应的unicode是4e2d,所以在内存中,实际保存的数据就是十六进制的0x4e2d, 也就是十进制的20013。

package stream;
 
public class teststream {
    public static void main(string[] args) {
        string str = "中";
    }
}

步骤 5 : 一个汉字使用不同编码方式的表现

以字符 为例,查看其在不同编码方式下的值是多少

也即在不同的棋盘上的位置
Java自学-I/O 中文问题

package stream;
 
import java.io.unsupportedencodingexception;
 
public class teststream {
 
    public static void main(string[] args) {
        string str = "中";
        showcode(str);
    }
 
    private static void showcode(string str) {
        string[] encodes = { "big5", "gbk", "gb2312", "utf-8", "utf-16", "utf-32" };
        for (string encode : encodes) {
            showcode(str, encode);
        }
 
    }
 
    private static void showcode(string str, string encode) {
        try {
            system.out.printf("字符: \"%s\" 的在编码方式%s下的十六进制值是%n", str, encode);
            byte[] bs = str.getbytes(encode);
 
            for (byte b : bs) {
                int i = b&0xff;
                system.out.print(integer.tohexstring(i) + "\t");
            }
            system.out.println();
            system.out.println();
        } catch (unsupportedencodingexception e) {
            system.out.printf("unsupportedencodingexception: %s编码方式无法解析字符%s\n", encode, str);
        }
    }
}

步骤 6 : 文件的编码方式-记事本

字符在文件中的保存肯定也是以数字形式保存的,即对应在不同的棋盘上的不同的数字
记事本打开任意文本文件,并且另存为,就能够在编码这里看到一个下拉。
ansi 这个不是ascii的意思,而是采用本地编码的意思。如果你是中文的操作系统,就会使gbk,如果是英文的就会是iso-8859-1
unicode unicode原生的编码方式
unicode big endian 另一个 unicode编码方式
utf-8 最常见的utf-8编码方式,数字和字母用一个字节, 汉字用3个字节。
Java自学-I/O 中文问题
步骤 7 : 文件的编码方式-eclipse

eclipse也有类似的编码方式,右键任意文本文件,点击最下面的"property"
就可以看到text file encoding
也有iso-8859-1,gbk,utf-8等等选项。
其他的us-ascii,utf-16,utf-16be,utf-16le不常用。
Java自学-I/O 中文问题
步骤 8 : 用fileinputstream 字节流正确读取中文

为了能够正确的读取中文内容

  1. 必须了解文本是以哪种编码方式保存字符的
  2. 使用字节流读取了文本后,再使用对应的编码方式去识别这些数字,得到正确的字符
    如本例,一个文件中的内容是字符,编码方式是gbk,那么读出来的数据一定是d6d0。
    再使用gbk编码方式识别d6d0,就能正确的得到字符

注: 在gbk的棋盘上找到的字后,jvm会自动找到在unicode这个棋盘上对应的数字,并且以unicode上的数字保存在内存中。
Java自学-I/O 中文问题

package stream;
   
import java.io.file;
import java.io.fileinputstream;
import java.io.ioexception;
   
public class teststream {
   
    public static void main(string[] args) {
        file f = new file("e:\\project\\j2se\\src\\test.txt");
        try (fileinputstream fis = new fileinputstream(f);) {
            byte[] all = new byte[(int) f.length()];
            fis.read(all);
   
            //文件中读出来的数据是
            system.out.println("文件中读出来的数据是:");
            for (byte b : all)
            {
                int i = b&0x000000ff;  //只取16进制的后两位
                system.out.println(integer.tohexstring(i));
            }
            system.out.println("把这个数字,放在gbk的棋盘上去:");
            string str = new string(all,"gbk");
            system.out.println(str);
        } catch (ioexception e) {
            // todo auto-generated catch block
            e.printstacktrace();
        }
   
    }
}

步骤 9 : 用filereader 字符流正确读取中文

filereader得到的是字符,所以一定是已经把字节根据某种编码识别成了字符
而filereader使用的编码方式是charset.defaultcharset()的返回值,如果是中文的操作系统,就是gbk
filereader是不能手动设置编码方式的,为了使用其他的编码方式,只能使用inputstreamreader来代替,像这样:

new inputstreamreader(new fileinputstream(f),charset.forname("utf-8"));

在本例中,用记事本另存为utf-8格式,然后用utf-8就能识别对应的中文了。

解释: 为什么中字前面有一个?
如果是使用记事本另存为utf-8的格式,那么在第一个字节有一个标示符,叫做bom用来标志这个文件是用utf-8来编码的。
Java自学-I/O 中文问题

package stream;
 
import java.io.file;
import java.io.fileinputstream;
import java.io.filenotfoundexception;
import java.io.filereader;
import java.io.ioexception;
import java.io.inputstreamreader;
import java.io.unsupportedencodingexception;
import java.nio.charset.charset;
 
public class teststream {
 
    public static void main(string[] args) throws unsupportedencodingexception, filenotfoundexception {
        file f = new file("e:\\project\\j2se\\src\\test.txt");
        system.out.println("默认编码方式:"+charset.defaultcharset());
        //filereader得到的是字符,所以一定是已经把字节根据某种编码识别成了字符了
        //而filereader使用的编码方式是charset.defaultcharset()的返回值,如果是中文的操作系统,就是gbk
        try (filereader fr = new filereader(f)) {
            char[] cs = new char[(int) f.length()];
            fr.read(cs);
            system.out.printf("filereader会使用默认的编码方式%s,识别出来的字符是:%n",charset.defaultcharset());
            system.out.println(new string(cs));
        } catch (ioexception e) {
            // todo auto-generated catch block
            e.printstacktrace();
        }
        //filereader是不能手动设置编码方式的,为了使用其他的编码方式,只能使用inputstreamreader来代替
        //并且使用new inputstreamreader(new fileinputstream(f),charset.forname("utf-8")); 这样的形式
        try (inputstreamreader isr = new inputstreamreader(new fileinputstream(f),charset.forname("utf-8"))) {
            char[] cs = new char[(int) f.length()];
            isr.read(cs);
            system.out.printf("inputstreamreader 指定编码方式utf-8,识别出来的字符是:%n");
            system.out.println(new string(cs));
        } catch (ioexception e) {
            // todo auto-generated catch block
            e.printstacktrace();
        }
         
    }
}

练习移除bom

如果用记事本根据utf-8编码保存汉字就会在最前面生成一段标示符,这个标示符用于表示该文件是使用utf-8编码的。

找出这段标示符对应的十六进制,并且开发一个方法,自动去除这段标示符

答案 :

package stream;
   
import java.io.file;
import java.io.fileinputstream;
import java.io.ioexception;
import java.util.arrays;
   
public class teststream {
   
    public static void main(string[] args) {
        file f = new file("e:\\project\\j2se\\src\\test.txt");
        try (fileinputstream fis = new fileinputstream(f);) {
            byte[] all = new byte[(int) f.length()];
            fis.read(all);
            system.out.println("首先确认按照utf-8识别出来有?");
            string str = new string(all,"utf-8");
            system.out.println(str);
            system.out.println("根据前面的所学,知道'中'字对应的utf-8编码是:e4 b8 ad");
            system.out.println("打印出文件里所有的数据的16进制是:");
            for (byte b : all) {
                int i = b&0xff;
                system.out.print(integer.tohexstring(i)+ " ");
            }
            system.out.println();
            system.out.println("通过观察法得出 utf-8的 bom 是 ef bb bf");
            byte[] bom = new byte[3];
            bom[0] = (byte) 0xef;
            bom[1] = (byte) 0xbb;
            bom[2] = (byte) 0xbf;
            byte[] filecontentwithoutbom= removebom(all,bom);
            system.out.println("去掉了bom之后的数据的16进制是:");
            for (byte b : filecontentwithoutbom) {
                int i = b&0xff;
                system.out.print(integer.tohexstring(i)+ " ");
            }           
            system.out.println();
            system.out.println("对应的字符串就没有问号了:");
            string strwithoutbom=new string(filecontentwithoutbom,"utf-8");
            system.out.println(strwithoutbom);
        } catch (ioexception e) {
            // todo auto-generated catch block
            e.printstacktrace();
        }
   
    }
 
    private static byte[] removebom(byte[] all, byte[] bom) {
        return arrays.copyofrange(all, bom.length, all.length);
    }
}