Java编码解码学习笔记
1 什么是编码与解码
电脑是由电路板组成,电路板里面集成了无数的电阻和电容,交流电经过电容的时候,电压比较低,记为低电平,用0表示;
交流电经过电阻的时候,电压比较高,记为高电平,用1来表示;所以,每一个1和0在计算机中被称为位,也就是bit位。
然而,如果使用一个位来表示计算机中的最小存储单元,那么我们存储单元只能存储0或者1,存储范围太小了,
所以我们规定用8个bit位作为一组来表示计算机的最小存储单元。8个位每个位上能存储0或1,
则byte的存储范围是00000000-11111111(换算成整数即0-255)。这个最小存储单元就是byte字节。
计算机的底层只能存储0和1,如果是日常生活中遇到的数字比如127,这个可以通过10进制和二进制的转换从而让计算机存储
011111111,但是如果计算机存储类似汉子、英文字符、符号字符等内容,是如何存储的呢?
根据上图解释说明,计算机提供了很多的编码表记录了字符和数字的一一对应关系,
编码就是把字符对应编码表中的码值存储在电脑中,
而解码则是把码值在编码表中的对应的字符展现出来。
注意:计算机中存储一个数,是用二进制来表示的,比如存储127,那么计算机的底层是0111 1111,人看这些二进制的数值通常都是眼花缭乱的,如何方便而规整的表示这些二进制数呢,不妨引入十六进制。二进制换算成十六进制,则是每四位为一组转换为16进制即可,比如0111 1111 这个数前4位 0111 转换为7,后4位转换为F,则最终的16进制数是7F,一般繁琐的二进制数使用十六进制来表示会比较方便规整,所以人们习惯用十六进制数来表示码值。
计算机提供了哪些编码表呢?
2 常见的编码表
2.1 ASCII
世界上虽然有各种各样的字符,但计算机发明之初没有考虑那么多,基本上只考虑了美国的需求,美国大概只需要128个字符,美国就规定了这128个字符的二进制表示方法,这个方法是一个标准,称为ASCII编码,全程是American Standard Code for Information interchange,美国信息互换标准代码。128个字符用7个位刚好可以表示,计算机存储的最小单元是byte,即8位,ASCII码中最高位设置为0,用剩下的7位表示字符。这7位可以看做数字0到127,ASCII码规定了从0到127个,每个数字代表什么含义。我们先来看数字32到126的含义,如下图所示,除了中文之外,我们平常用的字符基本都涵盖了,键盘上的字符大部分也都涵盖了。
ASCII值 | 控制字符 | ASCII值 | 控制字符 | ASCII值 | 控制字符 | ASCII值 | 控制字符 |
---|---|---|---|---|---|---|---|
0 | NUT | 32 | (space) | 64 | @ | 96 | 、 |
1 | SOH | 33 | ! | 65 | A | 97 | a |
2 | STX | 34 | " | 66 | B | 98 | b |
3 | ETX | 35 | # | 67 | C | 99 | c |
4 | EOT | 36 | $ | 68 | D | 100 | d |
5 | ENQ | 37 | % | 69 | E | 101 | e |
6 | ACK | 38 | & | 70 | F | 102 | f |
7 | BEL | 39 | , | 71 | G | 103 | g |
8 | BS | 40 | ( | 72 | H | 104 | h |
9 | HT | 41 | ) | 73 | I | 105 | i |
10 | LF | 42 | * | 74 | J | 106 | j |
1 | VT | 43 | + | 75 | K | 107 | k |
12 | FF | 44 | , | 76 | L | 108 | l |
13 | CR | 45 | - | 77 | M | 109 | m |
14 | SO | 46 | . | 78 | N | 110 | n |
15 | SI | 47 | / | 79 | O | 111 | o |
16 | DLE | 48 | 0 | 80 | P | 112 | p |
17 | DCI | 49 | 1 | 81 | Q | 113 | q |
18 | DC2 | 50 | 2 | 82 | R | 114 | r |
19 | DC3 | 51 | 3 | 83 | S | 115 | s |
20 | DC4 | 52 | 4 | 84 | T | 116 | t |
21 | NAK | 53 | 5 | 85 | U | 117 | u |
22 | SYN | 54 | 6 | 86 | V | 118 | v |
23 | TB | 55 | 7 | 87 | W | 119 | w |
24 | CAN | 56 | 8 | 88 | X | 120 | x |
25 | EM | 57 | 9 | 89 | Y | 121 | y |
26 | SUB | 58 | : | 90 | Z | 122 | z |
27 | ESC | 59 | ; | 91 | [ | 123 | { |
28 | FS | 60 | < | 92 | / | 124 | | |
29 | GS | 61 | = | 93 | ] | 125 | } |
30 | RS | 62 | > | 94 | ^ | 126 | ` |
数字32到126表示的这些字符都是可打印字符,0到31和127表示一些不可以打印的字符,这些字符一般用于控制目的,这些字符中大部分都是不常用的。
2.2 ISO-8859-1
扩展的ASCII表,西欧编码 一个字节存储一个字符
扩展的是拉丁文,不是中文汉字。主要是给拉丁文语言用。
西欧 是拉丁文,字符也是比较少的。
ISO-8859-1又称Latin-1,它也是使用了一个字节表示一个字符,因为西欧的文字也都是字母拼接,只不过不是26个英文字母罢了,
其中0到127与ASCII一样,128到255规定了不同的含义。
在128到255中,128到159不表示一些控制字符,这些字符也不常用,这里不介绍了,
160到255表示一些西欧字符,如下图所示:
2.3 GB2312
中国编码格式 7000多个,不包含罕见字,不包含繁体字。
美国和西欧字符用一个字节就够了,但中文显然是不够用的。中文第一个标准是GB2312。GB2312标准主要针对的是简体中文常见字符,包括约7000个汉字,不包括一些罕见词,不包括繁体字。GB2312固定使用两个字节表示汉字,在这两个字节中,最高位都是1,如果是0,就认为是ASCII字符。
在这两个字节中,其中第一个字节范围是1010 0001(十进制161) -1111 0111(十进制247),
第二个字节范围是1010 0001(十进制161)-1111 1110(十进制254)。
比如:“贤哥”的GB2312编码是
贤 | 哥 |
---|---|
CF,CD | B8,E7 |
2.4 GBK
中国编码格式 21000汉字,其中包括繁体字,不含少数民族。
用2个字节表示。 Windows简体中文系统 用的是GBK编码。
GBK建立在GB2312的基础上,向下兼容GB2312,也就是说,GB2312编码的字符的二进制表示,
在GBK编码里是完全一样的。GBK增加了一万四千多个汉字,共计21000汉字,其中包括繁体字。
GBK同样使用固定的两个字节表示,其中第一个字节范围是1000 0001(十进制129)-1111 1110(十进制254),
第二个字节范围是0100 1111(十进制64) - 0111 1110(十进制126)和1000 0000(十进制128) - 1111 1110(十进制254)。
需要注意的是,第二个字节是从64位开始的(64属于byte正数范围,和ASCII的编码重合了),也就是说,第二个字节最高位可能为0,
那怎么会知道它是汉子的一部分,还是一个ASCII字符呢?
其实很简单,因为汉子是用固定两个字节表示的,在解析二进制流的时候,如果第一个字节的最高位是1,那么就将下一个字节读进来一起解析为一个汉子,而不用考虑它的最高位,解析完后,跳到第一个字节继续解析。
ANSI:系统默认编码
2.5 GB18030
中国编码格式 4个字节
GB18030向下兼容GBK,增加了五万五千多个字符,共七万六千多个字符。包含了很多少数民族字符,以及中日韩统一字符。用两个字节表示不了GB18030中的所有字符,GB18030使用变长编码,有的字符是两个字节,有的是四个字节。在两个字节编码中,字节表示范围与GBK一样。
在四个字节编码中:
第一个字节的值从1000 0001(十进制129)到1111 1110(十进制254),第二个字节的值从0011 0000(十进制48) 到 0011 1001(十进制57),
第三个字节的值从1000 0001(十进制129) 到1111 1110(十进制254),第第个字节的值从0011 0000(十进制48) 到0011 1001(十进制57)。
解析二进制时,如何知道是两个字节还是四个字节表示一个字符呢?很简单,看第二个字节的范围,如果是48到57就是四个字节表示,因为两个字节编码中的第二个字节都比这个。
所有这样综合说明GB18030兼容GBK,兼容GB2312,兼容ASCII,
但是GB18030,GBK,GB2312这些编码和ISO-8859-1是不兼容的。
2.6 Big5
Big5是针对繁体中文的,广泛用在台湾香港等地。Big5包括1万3千多个繁体字,和GB2312类似,一个字符同样固定两个字节表示。
在这两个字节中,
第一个字节范围1000 0001(十进制129)到1111 1110(十进制254),
第二个字节范围是0111 0000(十进制54) - 0111 1110(十进制126) 和 1010 0001(十进制161)-1111 1110(十进制254)。
Big5和GB18030,GBK,GB2312不兼容,如果已经理解了上文,其实你就能理解为什么Big5和GB的一个编码为什么不兼容了。
2.7 编码表汇总
我们简单汇总一下上面的内容。
ASCII码是基础,一个字节表示,最高位设为0,其他7位表示128个字节。其他编码都是兼容ASCII的,最高位使用1来进行区分。
西欧主要使用Windows1252,使用一个字节,增加了额外128个字符。
中文大陆地区的三个主要编码GB2312,GBK,GB18030,有时间先后关系,表示的字符数越来越多,且后面的兼容前面的,
GB2312和GBK都是用两个字节表示,而GB18030则使用两个或四个字节表示。
香港台湾地区的主要编码是Big5。
如果文本里的字符都是ASCII码字符,那么采用以上所说的,任一编码方式都是一样的,不会乱码。
但如果有高位为1的字符,出了GB2312/GBK/GB18030外,其他编码都是不兼容的,
比如,Windows-1252和中文的各种编码是不兼容的,
即使Big5和GB18030都能表示繁体字了,其表示方式也是不一样的,而这就会出现所谓的乱码。
2.8 乱码与兼容
兼容:GB2312/GBK/GB18030 ASCII是兼容的,比如我们文本里面的a字符,使用这四种码表任何一种都是可以正常显示的。
windows-1252和ISO-8859-1和ASCII是兼容的。 西欧
Big5和ASCII是兼容的。
但是西欧编码和Big5以及GB系列的编码,他们相互之间是不兼容的,也就是 同样的码值在三种编码表中显示的内容是不一样的。
兼容示意图:
乱码示意图:
2.9 Unicode
以上我们介绍了中文和西欧的字符与编码,但世界上还有很多的国家的字符,每个国家的各种计算机厂商都对自己常用的字符进行编码,在编码的时候基本忽略了别的国家的字符和编码,甚至忽略了同一个国家的其他计算机厂商,这样造成的结果就是出现了太多的编码,且互相不兼容。
世界上所有的字符能不能统一编码呢?可以,这就是Unicode.
Unicode做了一件事,就是给世界上所有字符都分配了一个唯一的数字编号,这个编号范围从0x000000到0x10FFFF,包括110多万。但大部分常用字符都在0x0000到0xFFFF之间,即65536个数字之内。每个字符都有一个Unicode编号,这个编号一般写成16进制,在前面加U+。大部分中文的编号范围在U+4E00到U+9FA5,例如,“贤”的unicode是U+8D24.
Unicode就做了这么一件事,就是给所有字符分配了唯一数字编号。它并没有规定这个编号怎么对应到二进制表示,这是与上面介绍的其他编码不同的,其他编码都既规定了能表示哪些字符,又规定了美国字符对应的二进制是什么,而Unicode本身只规定了每个字符的数字编号是多少。
那编号怎么对应到二进制表示呢?有多种方案,有UTF-8,UTF-16,UTF-32。
2.9.1 UTF-32
这个最简单,就是字符编号的整数二进制形式,四个字节。
但每个字符都用四个字节表示,非常浪费空间,实际采用的也比较少。
2.9.2 UTF-16
注意:UTF-8常用于系统内部编码,我们平常说的“Unicode编码是2个字节”这句话,其实是因为Windows系统默认的Unicode编码就是UTF-16,在常用基本字符2个字节的编码方式已经够用导致的误解。其实是可变长度的。在没有特殊说明的情况下,常说的Unicode编码可以理解为UTF-16编码。
UTF-8比UTF-32节省了很多空间,
但是任何一个字符都至少需要两个字节表示,对于美国和西欧国家而言,还是很浪费的,于是UTF-8出现了。
2.9.3 UTF-8
UTF-8就是使用变长字节表示,每个字符使用的字节个数与其Unicode编号的大小有关,编号小的使用的字节就少,编号大的使用的字节就多,使用的字节个数从1到4个不等。
具体来说,各个Unicode编号范围对应的二进制格式如下表所示:
Unicode(十六进制) | UTF-8字节流(二进制) |
---|---|
000000-00007F | 0xxxxxxx 128,就是ASCII一样 1个字节表示 |
000080-0007FF | 110xxxxx 10xxxxxx 2个字节表示 |
000800-00FFFF | 1110xxxx 10xxxxxx 10xxxxxx 中国汉字一般都落在这个范围 3个字节表示 |
010000-10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 4个字节表示 |
图中的x表示可以用的二进制位,而每个字节开头的1或0是固定的。
小于128位,编码与ASCII码一样,最高位是0。其他编号第一个字节有特殊含义,最高位有几个连续的1,表示一共用几个字节表示,
而其他字节都可以10开头。4字节模板有21个x,即可以容纳21位二进制数字。
Unicode的最大码位0x10FFFF也只有21位。
一个系统,要符合能被全球访问的要求,所以只能在UTF-8和UTF-32/UTF-16三种编码中选其中一种。
注意:UTF-8和UTF-32/UTF-16不同的地方是UTF-8是兼容ACSII的,对大部分中文而言,一个中文字符需要用三个字节表示,UTF-8的优势是网络上数据传输英文字符只需要1个字节,可以节省带宽资源。
所以当前大部分的网络应用都使用UTF-8编码,因为网络应用的代码编写全部都是使用的英文编写,占据空间小,网络传输速度快。
2.10 乱码的原因和可逆性
乱码产生的根源一般情况下可以归集为三个方面:
1.编码引起的乱码 —不可逆
2.解码引起的乱码 —可逆
3.缺少某种字体库引起的乱码(这种情况需要用户安装对应的字体库)。 —可逆
其中大部分乱码问题是由不合适的解码方式造成的。
乱码可逆情况:
其中缺少字体,只需要安装对应的字体库即可解决乱码。比如Windows系统在C:\Windows\Fonts目录下会有安装好的字体库列表。
安装字体库比较简单,下载后解压,然后复制到对应系统的Fonts目录下。
解码方式和编码方式不一致的情况,只需要解码方式和编码方式一致即可让乱码恢复。
乱码不可逆的情况:
GBK编码不支持这几个字符“吉”,如果在一个GBK编码的文件中,写入“吉”这些字符,那么他们就会变成??,对应的码值是3F,
这样的情况就没有办法恢复,因为“吉”的本来编码变成了两个3F(即两个问号),无论如何也不能恢复过来了。
3 char字符
在Java内部进行字符处理时,采用的是Unicode,具体编码格式是UTF-16BE。简单回顾一下,UTF-16使用两个或者四个字节表示一个字符。
Unicode编号范围在65536以内的占两个字节,超出范围的占四个字节。
char本质上是一个固定占用两个字节的无符号正整数,这个正整数对应Unicode编号,用来表示那个Unicode编码对应的字符。
由于固定占用两个字节,char只能表示Unicode编号在65536以内的字符,而不能表示超出范围的字符。
那超出范围的字符怎么表示呢?只能使用String类来表示,例如汉子“吉”的Unicode码点为0x20887,该码点显然超出了65535,所以只能用String表示,而当粘贴到代码中时,自动转换为两个字符“\uD842\uDFB7”。
char 本质 固定的两个字节
UTF-16 基本面 两个字节表示 如果是辅助码的话 用4个字节表示
char 表示不了辅助面的字符,于是String出来了。
4 String类
4.1 编码的方法
4.1.1 getBytes()方法
package com.tangguanlin.encoding;
/**
* 说明:不指定编码格式
* 作者:汤观林
* 日期:2022年01月09日 21时
*/
public class EncodingTest1 {
public static void main(String[] args) {
String str = "黑马";
byte[] bytes = str.getBytes();//进行编码 -23 -69 -111 -23 -87 -84 utf-8:一个汉子3个字节,共6个字节
for(byte ebyte:bytes){
System.out.println(ebyte);
int temp = ebyte;
String toBinaryString = Integer.toBinaryString(temp);
System.out.println(toBinaryString);
}
}
}
运行结果:
-23
11111111111111111111111111101001
-69
11111111111111111111111110111011
-111
11111111111111111111111110010001
-23
11111111111111111111111111101001
-87
11111111111111111111111110101001
-84
11111111111111111111111110101100
4.1.2 getBytes(String charsetName)方法
package com.tangguanlin.encoding;
import java.io.UnsupportedEncodingException;
/**
* 说明:指定编码格式
* 作者:汤观林
* 日期:2022年01月09日 21时
*/
public class EncodingTest1 {
public static void main(String[] args) throws UnsupportedEncodingException {
String str = "黑马";
//指定UTF-8编码
byte[] bytes = str.getBytes("UTF-8");//进行编码 -23 -69 -111 -23 -87 -84 utf-8:一个汉子3个字节,共6个字节
for(byte ebyte:bytes){
System.out.println(ebyte);
int temp = ebyte;
String toBinaryString = Integer.toBinaryString(temp);
System.out.println(toBinaryString);
}
System.out.println("===================================");
//指定GBK编码
byte[] bytes1 = str.getBytes("GBK"); //-70 -38 -62 -19 GBk编码一个汉子 2个字节存储
for(byte ebyte:bytes1){
System.out.println(ebyte);
int temp = ebyte;
String toBinaryString = Integer.toBinaryString(temp);
System.out.println(toBinaryString);
}
}
}
运行结果:
-23
11111111111111111111111111101001
-69
11111111111111111111111110111011
-111
11111111111111111111111110010001
-23
11111111111111111111111111101001
-87
11111111111111111111111110101001
-84
11111111111111111111111110101100
===================================
-70
11111111111111111111111110111010
-38
11111111111111111111111111011010
-62
11111111111111111111111111000010
-19
11111111111111111111111111101101
4.2 解码的方法
4.2.1 new String(byte[] bytes)
package com.tangguanlin.encoding;
import java.io.UnsupportedEncodingException;
/**
* 说明:不指定编码格式
* 作者:汤观林
* 日期:2022年01月09日 22时
*/
public class EncodingTest2 {
public static void main(String[] args) throws UnsupportedEncodingException {
byte[] bytes = {-23,-69,-111,-23,-87,-84}; //黑马:UTF-8 [ -23 -69 -111 -23 -87 -84]
String s = new String(bytes); //解码 用的UTF-8 编码
System.out.println(s); //黑马
}
}
运行结果:
黑马
4.2.2 new String(byte[] bytes,String charsetName)
package com.tangguanlin.encoding;
import java.io.UnsupportedEncodingException;
/**
* 说明:指定解码格式
* 作者:汤观林
* 日期:2022年01月09日 22时
*/
public class EncodingTest2 {
public static void main(String[] args) throws UnsupportedEncodingException {
byte[] bytes = {-23,-69,-111,-23,-87,-84}; //黑马:UTF-8 [ -23 -69 -111 -23 -87 -84]
String s = new String(bytes,"UTF-8"); //解码 用的UTF-8 编码
System.out.println(s); //黑马
//指定具体的解码格式
byte[] bytes1 = {-70,-38,-62,-19}; //黑马:GBK [-70 -38 -62 -19]
String s1 = new String(bytes1, "GBK"); //
System.out.println(s1);
}
}
运行结果:
黑马
黑马
4.3 乱码的可逆问题
package com.tangguanlin.encoding;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
/**
* 说明:乱码可逆的情况
* 作者:汤观林
* 日期:2022年01月09日 22时
*/
public class EncodingTest3 {
public static void main(String[] args) throws UnsupportedEncodingException {
java.lang.String str = "黑马"; //黑马:GBK [-70 -38 -62 -19]
byte[] gbks = str.getBytes("GBK");
System.out.println(Arrays.toString(gbks));
String s1 = new String(gbks, "UTF-8");
System.out.println(s1);
String s2 = new String(gbks, "GBK");
System.out.println(s2);
}
}
运行结果;
[-70, -38, -62, -19]
����
黑马
4.4 乱码的不可逆问题
package com.tangguanlin.encoding;
import java.util.Arrays;
/**
* 说明:乱码不可逆问题
* 作者:汤观林
* 日期:2022年01月09日 22时
*/
public class EncodingTest4 {
public static void main(String[] args) throws Exception {
String str = "黑马" ;
byte[] bytes = str.getBytes("ISO-8859-1"); //[63, 63]
System.out.println(Arrays.toString(bytes));
String s1 = new String(bytes, "ISO-8859-1"); //??
System.out.println(s1);
String s2 = new String(bytes, "GBK"); //??
System.out.println(s2);
String s3 = new String(bytes, "UTF-8"); //??
System.out.println(s3);
}
}
运行结果:
[63, 63]
??
??
??
4.5 ISO-8859-1编码的妙用
package com.tangguanlin.encoding;
import java.util.Arrays;
/**
* 说明:ISO-8859-1的妙用
* 作者:汤观林
* 日期:2022年01月09日 22时
*/
public class EncodingTest5 {
public static void main(String[] args) throws Exception {
String str = "黑马" ;
byte[] bytes = str.getBytes("GBK"); //黑马:GBK [-70 -38 -62 -19]
System.out.println(Arrays.toString(bytes));
String s1 = new String(bytes, "ISO-8859-1"); //一个字节,对应一个字符 任何字符都能找到对应
System.out.println(s1); //ºÚÂí
byte[] bytes1 = s1.getBytes("ISO-8859-1");
System.out.println(Arrays.toString(bytes1)); //[-70, -38, -62, -19]
String s2 = new String(bytes1, "GBK");
System.out.println(s2); //黑马
}
}
运行结果:
[-70, -38, -62, -19]
ºÚÂí
[-70, -38, -62, -19]
黑马
5 IO流-字符流
5.1 InputStreamReader
字符输入流的原理:
案例:
a.txt
代码:
package com.tangguanlin.encoding;
import java.io.*;
/**
* 说明:字符输入流乱码原理
* 作者:汤观林
* 日期:2022年01月09日 23时
*/
public class EncodingTest6 {
public static void main(String[] args) throws IOException {
InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("a.txt"), "GBK");
int ch;
while ((ch=inputStreamReader.read())!=-1){
System.out.println((char)ch);
}
inputStreamReader.close();
}
}
运行结果:
涓
浗
字符输入流乱码原理:
5.2 OutputStreamReader
字符输出流的原理:
代码:
package com.tangguanlin.encoding;
import java.io.*;
/**
* 说明:字符输出流乱码原理
* 作者:汤观林
* 日期:2022年01月09日 23时
*/
public class EncodingTest7 {
public static void main(String[] args) throws IOException {
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream("b.txt"), "GBK");
outputStreamWriter.write("中国");
outputStreamWriter.flush(); //刷新 写出去
outputStreamWriter.close();
}
}
运行结果:
字符输出流乱码原理:
5.3 字符流复制文件
字符流复制文本乱码因素
4个因素:
源文件编码 -----------------------编码要一致------------------------------Reader缓冲区编码
Writer缓冲区编码---------------编码要一致------------------------------目标文件编码
代码:
package com.tangguanlin.encoding;
import java.io.*;
/**
* 说明:字符流复制文本文件乱码原理
* 作者:汤观林
* 日期:2022年01月09日 23时
*/
public class EncodingTest7 {
public static void main(String[] args) throws IOException {
//编码改为GBK才可以
InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("a.txt"), "UTF-8");
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream("b.txt"), "UTF-8");
int ch;
while ((ch = inputStreamReader.read())!=-1){
outputStreamWriter.write(ch);
}
inputStreamReader.close();
outputStreamWriter.close();
}
}
运行结果:
�й�
字符流复制文本文件乱码原理:
5.4 字符流复制图片
代码:
package com.tangguanlin.encoding;
import java.io.*;
/**
* 说明:字符流复制图片乱码原理
* 作者:汤观林
* 日期:2022年01月09日 23时
*/
public class EncodingTest8 {
public static void main(String[] args) throws IOException {
//编码改成 ISO-8859-1 才能复制
InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("D:\\11.jpg"), "UTF-8");
//编码改成 ISO-8859-1 才能复制
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream("D:\\22.jpg"), "UTF-8");
int ch;
while ((ch = inputStreamReader.read())!=-1){
outputStreamWriter.write(ch);
}
inputStreamReader.close();
outputStreamWriter.close();
}
}
运行结果:
字符流复制图片乱码原理:
解决方案原理: