再谈java乱码:GBK和UTF-8互转尾部乱码问题分析

本文探讨了在Java中不同字符集间转换可能导致的数据丢失问题。通过具体示例展示了使用GBK作为UTF-8字节流的中转字符集时,可能会导致数据丢失的情况,尤其是在处理奇数个汉字时。

一直以为,java中任意unicode字符串,可以使用任意字符集转为byte[]再转回来,只要不抛出异常就不会丢失数据,事实证明这是错的。

经过这个实例,也明白了为什么 getBytes()需要捕获异常,虽然有时候它也没有捕获到异常。

言归正传,先看一个实例。

用ISO-8859-1中转UTF-8数据

设想一个场景:

用户A,有一个UTF-8编码的字节流,通过一个接口传递给用户B;

用户B并不知道是什么字符集,他用ISO-8859-1来接收,保存;

在一定的处理流程处理后,把这个字节流交给用户C或者交还给用户A,他们都知道这是UTF-8,他们解码得到的数据,不会丢失。

下面代码验证:

public static void main(String[] args) throws Exception {
  //这是一个unicode字符串,与字符集无关
  String str1 = "用户";

  System.out.println("unicode字符串:"+str1);

  //将str转为UTF-8字节流
  byte[] byteArray1=str1.getBytes("UTF-8");//这个很安全,UTF-8不会造成数据丢失

  System.out.println(byteArray1.length);//打印6,没毛病

  //下面交给另外一个人,他不知道这是UTF-8字节流,因此他当做ISO-8859-1处理

  //将byteArray1当做一个普通的字节流,按照ISO-8859-1解码为一个unicode字符串
  String str2=new String(byteArray1,"ISO-8859-1");

  System.out.println("转成ISO-8859-1会乱码:"+str2);

  //将ISO-8859-1编码的unicode字符串转回为byte[]
  byte[] byteArray2=str2.getBytes("ISO-8859-1");//不会丢失数据

  //将字节流重新交回给用户A

  //重新用UTF-8解码
  String str3=new String(byteArray2,"UTF-8");

  System.out.println("数据没有丢失:"+str3);
}

输出:

unicode字符串:用户
6
转成ISO-8859-1会乱码:用户
数据没有丢失:用户

用GBK中转UTF-8数据

重复前面的流程,将ISO-8859-1 用GBK替换。

只把中间一段改掉:

    //将byteArray1当做一个普通的字节流,按照GBK解码为一个unicode字符串
        String str2=new String(byteArray1,"GBK");

        System.out.println("转成GBK会乱码:"+str2);

        //将GBK编码的unicode字符串转回为byte[]
        byte[] byteArray2=str2.getBytes("GBK");//数据会不会丢失呢?

运行结果:

unicode字符串:用户
6
转成GBK会乱码:鐢ㄦ埛
数据没有丢失:用户

好像没有问题,这就是一个误区。

修改原文字符串重新测试

将两个汉字 “用户” 修改为三个汉字 “用户名” 重新测试。

ISO-8859-1测试结果:

unicode字符串:用户名
9
转成GBK会乱码:用户名
数据没有丢失:用户名

GBK 测试结果:

unicode字符串:用户名
9
转成GBK会乱码:鐢ㄦ埛鍚�
数据没有丢失:用户�?

结论出来了

ISO-8859-1 可以作为中间编码,不会导致数据丢失;

GBK 如果汉字数量为偶数,不会丢失数据,如果汉字数量为奇数,必定会丢失数据。

why?

为什么奇数个汉字GBK会出错

直接对比两种字符集和奇偶字数的情形

重新封装一下前面的逻辑,写一段代码来分析:

public static void demo(String str) throws Exception {
  System.out.println("原文:" + str);

  byte[] utfByte = str.getBytes("UTF-8");
  System.out.print("utf Byte:");
  printHex(utfByte);
  String gbk = new String(utfByte, "GBK");//这里实际上把数据破坏了
  System.out.println("to GBK:" + gbk);

  byte[] gbkByte=gbk.getBytes("GBK");
  String utf = new String(gbkByte, "UTF-8");
  System.out.print("gbk Byte:");
  printHex(gbkByte);
  System.out.println("revert UTF8:" + utf);
  System.out.println("===");
//      如果gbk变成iso-8859-1就没问题
}

public static void printHex(byte[] byteArray) {
  StringBuffer sb = new StringBuffer();
  for (byte b : byteArray) {
    sb.append(Integer.toHexString((b >> 4) & 0xF));
    sb.append(Integer.toHexString(b & 0xF));
    sb.append(" ");
  }
  System.out.println(sb.toString());
};

public static void main(String[] args) throws Exception {
  String str1 = "姓名";
  String str2 = "用户名";
  demo(str1,"UTF-8","ISO-8859-1");
  demo(str2,"UTF-8","ISO-8859-1");

  demo(str1,"UTF-8","GBK");
  demo(str2,"UTF-8","GBK");
}

输出结果:

原文:姓名
UTF-8 Byte:e5 a7 93 e5 90 8d
to ISO-8859-1:姓名
ISO-8859-1 Byte:e5 a7 93 e5 90 8d
revert UTF-8:姓名
===
原文:用户名
UTF-8 Byte:e7 94 a8 e6 88 b7 e5 90 8d
to ISO-8859-1:用户名
ISO-8859-1 Byte:e7 94 a8 e6 88 b7 e5 90 8d
revert UTF-8:用户名
===
原文:姓名
UTF-8 Byte:e5 a7 93 e5 90 8d
to GBK:濮撳悕
GBK Byte:e5 a7 93 e5 90 8d
revert UTF-8:姓名
===
原文:用户名
UTF-8 Byte:e7 94 a8 e6 88 b7 e5 90 8d
to GBK:鐢ㄦ埛鍚�
GBK Byte:e7 94 a8 e6 88 b7 e5 90 3f
revert UTF-8:用户�?
===

为什么GBK会出错

前三段都没问题,最后一段,奇数个汉字的utf-8字节流转成GBK字符串,再转回来,前面一切正常,最后一个字节,变成了 “0x3f”,即”?”

我们使用”用户名” 三个字来分析,它的UTF-8 的字节流为:

[e7 94 a8] [e6 88 b7] [e5 90 8d]

我们按照三个字节一组分组,他被用户A当做一个整体交给用户B。

用户B由于不知道是什么字符集,他当做GBK处理,因为GBK是双字节编码,如下按照两两一组进行分组:

[e7 94] [a8 e6] [88 b7] [e5 90] [8d ?]

不够了,怎么办?它把 0x8d当做一个未知字符,用一个半角Ascii字符的 “?” 代替,变成了:

[e7 94] [a8 e6] [88 b7] [e5 90] 3f

数据被破坏了。

为什么 ISO-8859-1 没问题

因为 ISO-8859-1 是单字节编码,因此它的分组方案是:

[e7] [94] [a8] [e6] [88] [b7] [e5] [90] [8d]

因此中间不做任何操作,交回个用户A的时候,数据没有变化。

关于Unicode编码

因为UTF-16 区分大小端,严格讲:unicode==UTF16BE。

public static void main(String[] args) throws Exception {
  String str="测试";
  printHex(str.getBytes("UNICODE"));
  printHex(str.getBytes("UTF-16LE"));
  printHex(str.getBytes("UTF-16BE"));
}

运行结果:

fe ff 6d 4b 8b d5
4b 6d d5 8b
6d 4b 8b d5

其中 “fe ff” 为大端消息头,同理,小端消息头为 “ff fe”。

小结

作为中间转存方案,ISO-8859-1 是安全的。

UTF-8 字节流,用GBK字符集中转是不安全的;反过来也是同样的道理。

byte[] utfByte = str.getBytes("UTF-8");
String gbk = new String(utfByte, "GBK");
这是错误的用法,虽然在ISO-8859-1时并没报错。

首先,byte[] utfByte = str.getBytes("UTF-8");
执行完成之后,utfByte 已经很明确,这是utf-8格式的字节流;

然后,gbk = new String(utfByte, "GBK"),
对utf-8的字节流使用gbk解码,这是不合规矩的。

就好比一个美国人说一段英语,让一个不懂英文又不会学舌的日本人听,然后传递消息给另一个美国人。

为什么ISO-8859-1 没问题呢?

因为它只认识一个一个的字节,就相当于是一个录音机。我管你说的什么鬼话连篇,过去直接播放就可以了。

getBytes() 是会丢失数据的操作,而且不一定会抛异常。

unicode是安全的,因为他是java使用的标准类型,跨平台无差异。

Java中进行UTF-8GBK码,如果使用不当会导致乱码问题。一种错误的用法是将UTF-8格式的字节流直接使用GBK进行解码,这是不合规矩的。因为UTF-8GBK是不同的字符集,它们的编码方式不同。就好比一个美国人说英语,让一个不懂英文又不会学舌的日本人听,然后传递消息给另一个美国人,这样会导致消息的失真。因此,在解决字符集乱码问题时,一般需要显示指定编码解码字符集。 正确的UTF-8GBK码操作应该是这样的: ``` byte[] utfByte = str.getBytes("UTF-8"); // 将字符串以UTF-8编码换为字节流 String gbk = new String(utfByte, "GBK"); // 将UTF-8的字节流以GBK解码为字符串 ``` 这样可以确保字符集的正确换,避免出现乱码问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [再java乱码GBKUTF-8互转尾部乱码问题分析](https://blog.csdn.net/weixin_34072637/article/details/91697486)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Java使用UTF-8GBK编码后还是乱码问题的解决办法](https://blog.csdn.net/phker/article/details/131533428)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
<think> 我们正在讨论的是在VSCode中如何设置缩进空格数为4文件编码为UTF-8。这涉及到两个主要设置:缩进(indent)文件编码(file encoding)。 在VSCode中,可以通过以下几种方式设置: 1. **通过状态栏快速设置**: - 打开一个文件(例如Shell脚本文件),在状态栏的最右侧可以看到当前的缩进设置(如“Spaces: 4”或“Tab Size: 4”)编码(如“UTF-8”)。 - 点击状态栏上的缩进信息(例如“Spaces: 4”),会弹出一个菜单,可以选择“使用空格缩进”或“使用制表符缩进”,同时也可以设置缩进空格数(例如2、4、8等)。 - 点击状态栏上的编码信息(如“UTF-8”),可以选择“通过编码保存”来更改文件的编码,或者选择“通过编码重新打开”以另一种编码方式重新打开当前文件。 2. **通过用户设置(User Settings)进行全局设置**: - 打开VSCode的设置:使用快捷键`Ctrl + ,`(Windows/Linux)或`Cmd + ,`(Mac),或者通过菜单“文件”>“首选项”>“设置”打开。 - 在设置中搜索“editor.tabSize”,将其值设置为4。 - 搜索“editor.insertSpaces”,将其设置为`true`,这样就会使用空格代替制表符。 - 搜索“files.encoding”,将其设置为“utf8”,这样新创建的文件将默认使用UTF-8编码。 3. **通过工作区设置(Workspace Settings)进行项目级设置**: - 在设置界面,可以看到用户设置工作区设置两个标签页。在工作区设置中,可以针对当前项目进行设置,方法与用户设置相同。 4. **通过文件尾部的设置(针对特定语言)**: - VSCode允许为特定的文件类型设置缩进编码。例如,可以为Shell脚本文件设置特定的缩进规则。这可以通过在设置中搜索“语言特定设置”来实现,或者直接在设置中配置: ```json "[shellscript]": { "editor.tabSize": 4, "editor.insertSpaces": true } ``` - 同样,也可以设置特定语言的编码,但通常编码设置是全局的,也可以在特定文件类型中设置。 5. **通过.editorconfig文件**: - 在项目根目录下创建`.editorconfig`文件,可以统一团队中的代码风格。例如: ``` root = true [*] indent_style = space indent_size = 4 charset = utf-8 ``` - 为了使用.editorconfig,需要安装VSCode的EditorConfig插件。 注意:编码设置除了默认编码外,还可以在打开文件时选择编码。如果遇到文件编码显示不正确(如乱码),可以点击状态栏的编码,然后选择“通过编码重新打开”并选择正确的编码(例如GBKUTF-8等)。 下面是一个配置示例,展示如何在VSCode的用户设置中设置: ```json { "editor.tabSize": 4, "editor.insertSpaces": true, "files.encoding": "utf8" } ``` 这样设置后,新建的文件都将默认使用4个空格的缩进UTF-8编码。 另外,对于已经打开的文件,如果临时更改缩进,可以通过状态栏的缩进设置切换,但这只会影响当前文件。同样,编码也可以通过状态栏的编码设置临时更改。 如果你想让所有Shell脚本文件都遵循这个规则,可以添加语言特定设置: ```json { "[shellscript]": { "editor.tabSize": 4, "editor.insertSpaces": true }, "files.encoding": "utf8" } ``` 这样,所有的Shell脚本文件都会使用4个空格缩进,而其他文件类型则使用全局设置(如果没有单独设置的话)。 最后,关于状态栏的显示,VSCode默认会显示当前文件的缩进编码信息。如果状态栏没有显示,可以检查设置中是否禁用了状态栏,或者通过右键点击状态栏进行定制。 注意:在用户设置中,`files.encoding`的值可以使用小写`utf8`,也可以使用大写`UTF8`(不区分大小写),但官方文档推荐使用小写。 参考: [^1]: VSCode状态栏设置的相关文档。 现在,我们来生成相关问题: §§相关问题§§ 1. 如何在VSCode中为不同的编程语言设置不同的缩进规则? 2. VSCode中如何解决打开文件时出现乱码问题? 3. 如何在VSCode中创建一个.editorconfig文件来统一项目代码风格? 4. VSCode的状态栏如何自定义显示内容? </think> 在 VSCode 中设置缩进空格数为 4 文件编码为 UTF-8 的方法如下(包含全局设置语言特定设置): ### 一、设置缩进为 4 个空格 #### 方法 1:通过状态栏快速设置 1. 打开任意文件 2. 点击编辑器**底部状态栏右側**的缩进指示器(显示如 `Spaces: 2` 或 `Tab Size: 4`) 3. 在弹出的菜单中选择: - **使用空格缩进**(确保非 Tab 缩进) - **选择缩进大小** → **4** #### 方法 2:全局用户设置(推荐) 1. 打开设置:`Ctrl + ,`(Windows/Linux)或 `Cmd + ,`(Mac) 2. 搜索 `tab size` → 设置值为 **4** 3. 搜索 `insert spaces` → 勾选 ✅ ```json { "editor.tabSize": 4, "editor.insertSpaces": true } ``` #### 方法 3:语言特定设置(如 Shell 脚本) 1. 打开设置 → 搜索 `@lang:shellscript` 2. 添加配置: ```json "[shellscript]": { "editor.tabSize": 4, "editor.insertSpaces": true } ``` --- ### 二、设置文件编码为 UTF-8 #### 方法 1:状态栏快速切换 1. 点击状态栏右下角的编码指示器(如 `UTF-8`) 2. 选择 **通过编码保存** → **UTF-8** #### 方法 2:全局默认编码设置 1. 打开设置 → 搜索 `files.encoding` 2. 选择 **utf8** 作为默认编码: ```json { "files.encoding": "utf8" } ``` #### 方法 3:语言特定编码(如 Shell 脚本) ```json "[shellscript]": { "files.encoding": "utf8" } ``` --- ### 三、验证设置效果 1. **缩进验证**: - 新建文件输入代码 → 按 `Tab` 键应插入 4 个空格 - 状态栏显示 `Spaces: 4` 2. **编码验证**: - 状态栏显示 `UTF-8` - 保存文件后,用十六进制编辑器查看 → 文件开头应为 `EF BB BF`(UTF-8 BOM 签名,可选) > 💡 提示:通过 `.editorconfig` 文件可统一团队规范(需安装 EditorConfig 插件): > ``` > root = true > [*] > indent_style = space > indent_size = 4 > charset = utf-8 > ``` > [^1] ---
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值