Java 流和編碼亂碼問題

最近一直在被編碼問題困擾。覺得這是我“職業(yè)生涯”里過不去的坎兒真慢,算是我的夢(mèng)魘潮尝。一想到只要我搬一天的磚榕吼,它就可能折磨我一次,我決定好好看一下勉失。于是我拿起了《Java核心技術(shù)》這本書羹蚣,看是翻起了第2章 輸入與輸出。結(jié)合網(wǎng)上的一些教程乱凿,然后以我的理解顽素,解決了我自己在編程中遇到的一個(gè)亂碼問題。不像之前是邊百度徒蟆,邊嘗試(有種神農(nóng)嘗百草的意思)各種帖子上寫的方法胁出,碰運(yùn)氣解決,這一次我是有點(diǎn)自我意識(shí)在改bug的(驕傲臉)段审。所以我打算在博采眾長(zhǎng)之后全蝶,把我這幾天學(xué)到的東西整理一下,可能中間還是有很多問題寺枉,或者是我理解不對(duì)的抑淫,還需要大家?guī)兔χ赋鰜?lái),我再改正型凳。來(lái)吧丈冬,開始打臉(委屈臉)嘱函。

什么是輸入流甘畅?什么是輸出流?

這是首先需要解決的問題往弓。其實(shí)就是明白自己的定位疏唾。我覺得網(wǎng)上很多的教程其實(shí)是有問題的,因?yàn)樗麄円簧蟻?lái)就是把《Java核心技術(shù)》這本書上的概念再念一遍函似,但是看不懂得還是看不懂槐脏。因?yàn)樗麄兒雎粤恕跋鄬?duì)”和“絕對(duì)”的概念,所以我覺得他們是在耍流氓撇寞。

在Java中顿天,“流”根據(jù)其流動(dòng)的方向是可以分為“輸入流”和“輸出流”的堂氯。那這個(gè)“方向”怎么定義。這是重點(diǎn)牌废,但是很多人都不提:)
今天我就要大聲告訴你咽白,
輸入流,輸出流是以程序?yàn)閰⒖键c(diǎn)來(lái)說(shuō)的鸟缕。
輸入流晶框,輸出流是以程序?yàn)閰⒖键c(diǎn)來(lái)說(shuō)的。
輸入流懂从,輸出流是以程序?yàn)閰⒖键c(diǎn)來(lái)說(shuō)的授段。
輸入流:就是給程序提供數(shù)據(jù)的流,程序可以從輸入流里獲取自己想要的數(shù)據(jù)番甩。
輸出流:是程序要向其寫入數(shù)據(jù)的流侵贵,也就是數(shù)據(jù)的目的地。
我覺得知道這一點(diǎn)对室,其實(shí)就知道是使用InputStream模燥,還是OutputStream了。比如掩宜,需要從文件A讀入數(shù)據(jù)蔫骂,那就new一個(gè)InputStream對(duì)象,然后調(diào)用read()方法牺汤。反之辽旋,要向文件B寫入數(shù)據(jù),就new一個(gè)OutputStream對(duì)象檐迟,然后調(diào)用write()方法补胚。
我講完了。
emmm追迟,是不是覺得我就是一個(gè)“水王”溶其。但是我覺得這是我今天學(xué)到的最有用的知識(shí)了。如果還需要補(bǔ)充一點(diǎn)的話敦间,就是“流”與“流”之間如何傳遞數(shù)據(jù)瓶逃,或者更確切一點(diǎn)說(shuō)就是,之間的“物質(zhì)”是什么廓块?

答案是:字節(jié)流厢绝。(心里默念一遍:一個(gè)字節(jié)等于8bit)

但是,這個(gè)字節(jié)流到底是怎么得到的带猴?我的問題是:我們程序白紙黑字寫的“Hello昔汉,程序媛!”是怎么變成字節(jié)的呢?字節(jié)流又是怎么變成我們認(rèn)識(shí)的文字的呢拴清?

自問自答:編碼 和 解碼

嗯靶病,應(yīng)該知道我接下去要說(shuō)的是什么了吧会通,就是亂碼問題了。

Java字符的編碼與亂碼問題

我覺得知乎上的這篇文章寫的超級(jí)好娄周。值得我們每一個(gè)被“亂碼”問題折磨的人渴语。https://zhuanlan.zhihu.com/p/25435644
雖然他寫了,但我還是想再?gòu)?fù)刻一遍昆咽。(人類的本質(zhì)是復(fù)讀機(jī))

1驾凶、一幅圖和四個(gè)概念

[圖片上傳失敗...(image-60d448-1563776264791)]

字符有三種形態(tài):形狀(顯示在顯示設(shè)備上)、數(shù)字(運(yùn)行于JVM中掷酗,Java統(tǒng)一為unicode編碼)和字節(jié)數(shù)組(不同的字符集有不同的映射方案)调违。
字符集合(Character set) :是一組形狀的集合。例如所有漢字的集合泻轰,發(fā)明于公元前技肩,發(fā)明者是倉(cāng)頡。它體現(xiàn)了字符的“形狀”浮声,它與計(jì)算機(jī)虚婿、編碼等無(wú)關(guān)。
編碼字符集(Coded character set) :是一組字符對(duì)應(yīng)的編碼(即數(shù)字)泳挥,為字符集合中的每一個(gè)字符給予一個(gè)數(shù)字然痊。例如最早的編碼字符集ASCII,發(fā)明于1967年屉符。再例如Java使用的unicode剧浸,發(fā)明于1994年(持續(xù)更新中)。由于編碼字符集為每一個(gè)字符賦予一個(gè)數(shù)字矗钟,因此在java內(nèi)部唆香,字符可以認(rèn)為就是一個(gè)16位的數(shù)字,因此以下方式都可以給字符賦值:

char c =‘中’
char c = 0x4e2d
char c = 20013

字符編碼方案(Character-encoding schema) :將字符編碼(數(shù)字)映射到一個(gè)字節(jié)數(shù)組的方案吨艇,因?yàn)樵诖疟P里躬它,所有信息都是以字節(jié)的方式存儲(chǔ)的。因此Java的16位字符必須轉(zhuǎn)換為一個(gè)字節(jié)數(shù)組才能夠存儲(chǔ)东涡。例如UTF-8字符編碼方案冯吓,它可以將一個(gè)字符轉(zhuǎn)換為1、2软啼、3或者4個(gè)字節(jié)桑谍。
一般認(rèn)為延柠,編碼字符集和字符編碼方案合起來(lái)被稱之為 字符集(Charset) 祸挪,這是一個(gè)術(shù)語(yǔ),要和前面的字符集合(Character set)區(qū)分開贞间。

2贿条、類型之間的轉(zhuǎn)化

2.1 從數(shù)字到形狀

就是說(shuō)從JVM中的數(shù)字雹仿,變?yōu)槠聊簧巷@示的文字,這一轉(zhuǎn)化過程是在字體庫(kù)的幫助下完成的整以,所以無(wú)需我們操心胧辽,也不會(huì)出錯(cuò),只要你給的數(shù)字是對(duì)的公黑,你就能得到你想要的數(shù)據(jù)邑商,所以這一轉(zhuǎn)化知道就行。

2.2 從數(shù)字到字節(jié)組——編碼

這是我們今天的重點(diǎn)凡蚜。
如圖所示人断,從JVM中的數(shù)字轉(zhuǎn)化為字節(jié)數(shù)組,也就是我們心心念念的“物質(zhì)”朝蜘,這個(gè)過程就是“編碼”恶迈。經(jīng)過“編碼”,我們就能得到可以傳輸谱醇,或者便于存儲(chǔ)的字節(jié)流暇仲。JVM上的同一個(gè)數(shù)字,比如0x4e2d副渴,采用不同的字符集進(jìn)行編碼奈附,能得到不同的字節(jié)數(shù)組。就如圖中可以看出煮剧,采用UTF-8的編碼得到的結(jié)果是e4 b8 ad桅狠;采用GBK編碼得到的結(jié)果是d6 d0;采用UTF-16編碼得到的是fe ff 4e 2d轿秧。有興趣的同學(xué)中跌,其實(shí)還是可以想想,這些數(shù)字是怎么得到的菇篡。而我就是這樣一個(gè)好奇且好學(xué)的寶寶漩符,我想知道他有沒有騙我,所以我查了一下資料驱还。其中這篇文章嗜暴,我覺得還是挺良心的:http://www.reibang.com/p/35f5f7d07732
比如就UTF-8這種編碼方式來(lái)舉個(gè)吧:

UTF-8的編碼規(guī)則很簡(jiǎn)單,只有二條:
1议蟆、對(duì)于單字節(jié)的符號(hào)闷沥,字節(jié)的第一位設(shè)為0,后面7位為這個(gè)符號(hào)的unicode碼咐容。因此對(duì)于英語(yǔ)字母舆逃,UTF-8編碼和ASCII碼是相同的。
2、對(duì)于n字節(jié)的符號(hào)(n>1)路狮,第一個(gè)字節(jié)的前n位都設(shè)為1虫啥,第n+1位設(shè)為0,后面字節(jié)的前兩位一律設(shè)為10奄妨。剩下的沒有提及的二進(jìn)制位涂籽,全部為這個(gè)符號(hào)的unicode碼。

看文字很費(fèi)解砸抛,上圖:
【圖略】
就拿我們的“中”字而言评雌,它在JVM的數(shù)字是0x 4e 2d,屬于上面Unicode字符中的第三種情況,所以就可以把轉(zhuǎn)換的16個(gè)二進(jìn)制依次放入上述的x中直焙。我利用在線的二進(jìn)制轉(zhuǎn)化武器柳骄,可以得到e4 b8 ad的結(jié)果,這就可以看出這位作者是真的很良心箕般,糟老頭也不都是壞的耐薯。
至于其他的編碼方式,想驗(yàn)證的可以去看看規(guī)則然后動(dòng)手試一下丝里。

上面那么多看似很高端的東西曲初,其實(shí)看不懂也可以不用看懂,我提一下就是為了zhuangbility杯聚,因?yàn)槲覀兤綍r(shí)寫代碼完全是無(wú)感知的臼婆。了解了最多就是心里踏實(shí)一點(diǎn),不了解知道怎么用就好幌绍。但是颁褂,你要確保你真的會(huì)用,不然你的老板會(huì)不高興的傀广。
編碼的例子代碼如下:
第一種方法颁独,使用String的getBytes方法:

private static byte[] encoding1(String str, String charset) throws UnsupportedEncodingException {
    return str.getBytes(charset);
}

第二種方法,使用Charset的encode方法:

private static byte[] encoding2(String str, String charset) {
        Charset cset = Charset.forName(charset);
        ByteBuffer byteBuffer = cset.encode(str);
        byte[] bytes = new byte[byteBuffer.remaining()];
        byteBuffer.get(bytes);
        return bytes;
}

實(shí)現(xiàn)的方式千千萬(wàn)伪冰,但是我們一定要抓到重點(diǎn):編碼得到的什么結(jié)果誓酒。就是這玩意兒: byte[] 。對(duì)贮聂,就是我們需要的字節(jié)流靠柑,就是我們需要的“物質(zhì)”。

2.3 從字節(jié)數(shù)組到數(shù)字——解碼

在完成了一系列操作以后吓懈,你還是需要讓別人知道你在想什么歼冰,最好的方式就是文字,我們大家能看得到的東西耻警,而字節(jié)數(shù)組這東西隔嫡,太過于抽象甸怕,所以我們需要把它變?yōu)橐粋€(gè)數(shù)字,這個(gè)轉(zhuǎn)化過程就是解碼畔勤。解碼就是把從磁盤或者網(wǎng)絡(luò)上得到的信息,轉(zhuǎn)換為字符或字符串扒磁。
解碼與編碼最大的區(qū)別是庆揪,解碼難。難在哪里妨托。就是你不知道或者你沒有意識(shí)去了解缸榛,你拿到的字節(jié)之前是怎么編碼的。就好像你不知道你現(xiàn)在身邊的人之前遇到過誰(shuí)兰伤。所以解碼時(shí)一定要指定字符集内颗,否則將會(huì)使用默認(rèn)的字符集進(jìn)行解碼。如果使用了錯(cuò)誤的字符集敦腔,則會(huì)出現(xiàn)亂碼均澳。
解碼的例子代碼如下:
第一種方法,使用String的構(gòu)造函數(shù):

private static String decoding1(byte[] bytes,String charset) throws UnsupportedEncodingException {
        String str = new String(bytes, charset);
        return str;
    }

第二種方法符衔,使用Charset的decode方法:

 private static String decoding2(byte[] bytes, String charset) {
        Charset cset = Charset.forName(charset);
        ByteBuffer buffer = ByteBuffer.wrap(bytes);
        CharBuffer charBuffer = cset.decode(buffer);
        return charBuffer.toString();
    }

3找前、 默認(rèn)的字符集

亂碼問題是因?yàn)槲覀冊(cè)诰幋a和解碼的過程中,采用了不一樣的字符集判族。有時(shí)候如果我們沒有指明編碼和解碼的方式就會(huì)采用默認(rèn)的字符集躺盛,如果我們不知道什么是默認(rèn)的字符集,就會(huì)有可能出現(xiàn)亂碼的問題形帮。Java的默認(rèn)字符集槽惫,可以在兩個(gè)地方設(shè)定,一是執(zhí)行java程序時(shí)使用-D file.encoding參數(shù)指定辩撑,例如 -D file.encoding=UTF-8 就指定默認(rèn)字符集是UTF-8界斜。二是在程序執(zhí)行時(shí)使用Properties進(jìn)行指定,如下:

private static void setEncoding(String charset) {
    Properties properties = System.getProperties();
    properties.put("file.encoding",charset);
    System.out.println(properties.get("file.encoding"));
}

注意合冀,這兩種方法如果同時(shí)使用锄蹂,則程序開始時(shí)使用參數(shù)指定的字符集,在Properties方法后使用Properties指定的字符集水慨。
如果這兩種方法都沒有使用得糜,則使用操作系統(tǒng)默認(rèn)的字符集。例如中文版windows 7的默認(rèn)字符集是GBK晰洒。
默認(rèn)字符集的優(yōu)先級(jí)如下:
1.程序執(zhí)行時(shí)使用Properties指定的字符集朝抖;
2.java命令的-Dfile.encoding參數(shù)指定的字符集;
3.操作系統(tǒng)默認(rèn)的字符集谍珊;
4.JDK中默認(rèn)的字符集治宣,我跟蹤了JDK1.8的源代碼,發(fā)現(xiàn)其默認(rèn)字符集指定為ISO-8859-1

4、 亂碼

從上述章節(jié)可知侮邀,字符的形態(tài)有三種坏怪,分別是“形狀”、“數(shù)字”和“字節(jié)”绊茧。字符的三種形態(tài)之間的轉(zhuǎn)換也有三類:從數(shù)字到形狀铝宵,從數(shù)字到字節(jié)(編碼),從字節(jié)到數(shù)字(解碼)华畏。
從數(shù)字到形狀不會(huì)產(chǎn)生亂碼鹏秋,亂碼就產(chǎn)生在編碼和解碼的時(shí)候。仔細(xì)想來(lái)亡笑,編碼也是不會(huì)產(chǎn)生亂碼的侣夷,因?yàn)閺臄?shù)字到字節(jié)(指定某個(gè)字符集)一定能夠轉(zhuǎn)換成功,即使某字符集中不包含該數(shù)字仑乌,它也會(huì)用指定的字節(jié)來(lái)代替百拓,并在轉(zhuǎn)換時(shí)給出指示。
如此一來(lái)晰甚,亂碼只會(huì)產(chǎn)生在解碼時(shí):例如使用某字符集A編碼的字節(jié)耐版,使用字符集B來(lái)進(jìn)行解碼,而A和B并不兼容压汪。這樣一來(lái)粪牲,解碼產(chǎn)生的數(shù)字(字符編碼)就是錯(cuò)誤的止剖,那么它顯示出來(lái)也是錯(cuò)誤的腺阳。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末穿香,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子皮获,更是在濱河造成了極大的恐慌焙蚓,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件购公,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡宏浩,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門靠瞎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)比庄,“玉大人求妹,你說(shuō)我怎么就攤上這事佳窑。” “怎么了神凑?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)强挫。 經(jīng)常有香客問我岔霸,道長(zhǎng),這世上最難降的妖魔是什么呆细? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任絮爷,我火速辦了婚禮趴酣,結(jié)果婚禮上坑夯,老公的妹妹穿的比我還像新娘。我一直安慰自己柜蜈,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布隶垮。 她就那樣靜靜地躺著秘噪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪指煎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天暖侨,我揣著相機(jī)與錄音,去河邊找鬼崇渗。 笑死京郑,一個(gè)胖子當(dāng)著我的面吹牛葫掉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播俭厚,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼叼丑!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起鸠信,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤论寨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后葬凳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡劲装,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了占业。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片江场。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖址否,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情樊诺,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布音同,位于F島的核電站,受9級(jí)特大地震影響权均,放射性物質(zhì)發(fā)生泄漏锅锨。R本人自食惡果不足惜恋沃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望恕洲。 院中可真熱鬧梅割,春花似錦霜第、人聲如沸户辞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)书蚪。三九已至,卻和暖如春殊校,著一層夾襖步出監(jiān)牢的瞬間读存,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工让簿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人莲祸。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓椭迎,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親畜号。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容