一例 jvm file.encoding 属性引起的 MapReduce/HBase 乱码问题

本文详细探讨了在Hadoop集群中遇到的中文乱码问题,通过排查和分析,发现是由于集群内两台机器的JVM参数(file.encoding)不一致导致。提出了两种解决方案:永久方案包括修改系统charset或通过启动参数指定编码;临时方案是在业务代码中自行指定编码。同时深入理解了JVM的-Dfile.encoding参数及其作用范围。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

文章转载自:http://my.oschina.net/leejun2005/blog/157338


1、题:

最近在往 HBase 写中文的时候,发现 hbase 查出来的数据会有部分中文乱码了,而部分中文又是正常的,按理来说,一般的乱码问题要么全乱,要么不乱。考虑到出现中文的地方都是来源于 hdfs 上的一个配置文件,而这个配置文件可以确定是 utf-8 编码的,那排除了原始文件导致的乱码,想想 MR 代码里也没有转码的逻辑,也排除了代码的问题,那就只有一种可能:Hadoop 集群的系统环境是异构的,这里面可能涉及到 linux 、java 的环境变量、配置的问题。

2、排查:

(1)打印了整个集群的 echo $LANG、echo $LC_ALL 等linux系统变量,发现都是一致的,排除了 os 环境的问题。

(2)剩下的重点放在了 java 环境上,在代码里加上如下两句,打印每条记录的 ip 和 jvm 编码,然后看看乱码的记录是那台机器产生的,并且当时 jvm child 的编码情况:

1 java.net.InetAddress test = java.net.InetAddress.getByName("localhost");
2 put.add(Bytes.toBytes("cf"), Bytes.toBytes("ip"), Bytes.toBytes(test.getLocalHost().getHostAddress()));
3 put.add(Bytes.toBytes("cf"), Bytes.toBytes("ec"), Bytes.toBytes(System.getProperty("file.encoding")));
同时也直接    System.out.println 出相应的中文字段,看是写进 hbase 之前还是之后乱掉的。

跑了一份测试数据后,发现 hbase 里的 ip、jvm 编码是没有规律的,然后查看 syso 打印的 log 发现,在写 hbase 之前已经就已经乱码了,然后想想 hbase 里的数据乱码之所以没有规律是因为 map 后要 shuffle、reduce 才能到 hbase。PS:sysout本身无编码概念,类似 linux 下的 cat、head、more 等。

然后再次把 ip、jvm编码 统计代码放到 map 阶段输出,果真发现了规律,集群中有两台机器的 jvm 编码不一致,不是 utf-8 的:

到这里我们可以知道原因了:由于集群中两台机器的 jvm 参数(file.encoding)不一致导致了部分中文结果的乱码。

3、解决方案:

知道原因了,那就看如何解决了,目的就是要改变 file.encoding 的值 。

(1)永久方案:

由于这个参数是 jvm 的启动参数,运行时不可被更改(你可以理解为这个参数是个全局参数,而且被缓存了,如果一旦运行时更改了, 可能会造成整个 jvm 里面的程序奔溃),你只能修改系统的charset, 或者jvm的启动参数里加上 -Dfile.encoding="UTF-8" 来指定,你运行时 setProperty("file.encoding","ISO-8859-1"); 这样是没用的,so,永久的解决办法是:啥时候把这两台机器offline 改编码后再online,然后再手动执行下 data balance。

(2)临时工方案:

不想这么大动干戈,想要临时解决方案,也行,那就需要在咱们自己的业务代码里绕开 jvm 提供的默认 file.encoding 编码,自己指定编码:

1 BufferedReader in = new BufferedReader(new FileReader(path.toString()));
2 换成:
3 BufferedReader in = new BufferedReader((new InputStreamReader(new FileInputStream(path.toString()),"utf-8")));
上面一句是我之前乱码的代码,如果你没有指定读取编码,那么 jvm 会使用自己的 file.encoding,这样就会造成在某些机器上读取文件就乱掉了。下面一句是自己指定编码,这样绕开了 jvm 的默认编码,与 jvm 从此形同陌路~ 

PS:FileReader 貌似没有提供指定编码的构造方法,所以换成了下面的类。

(3)疑问:

为什么之前一直都没乱码,而这次读文件却乱码了呢?

那是因为 hbase 的 Bytes、map 的 fileinputformat key/value、mapreduce 的 context.write 默认都是自己硬编码了 utf-8,做到了 和 jvm 编码无关,所以不会遇到上述问题。

4、深入理解 jvm 的 -Dfile.encoding 参数

上面说了这么多,可能有同学还是不大明白:jvm 的这参数有毛用啊?为毛之前都没听过这玩意呢?

恩,没听过正常,之前我也没听过哈~

(1)从源码开始追踪

在JDK 1.6.0_20的src.zip文件中,查找包含file.encoding字眼的文件.
共找到4个, 分别是:
(a)先上重头戏 java.nio.Charset类:

01 public static Charset defaultCharset() {
02         if (defaultCharset == null) {
03             synchronized (Charset.class) {
04                 java.security.PrivilegedAction pa = new GetPropertyAction("file.encoding");
05                 String csn = (String) AccessController.doPrivileged(pa);
06                 Charset cs = lookup(csn);
07                 if (cs != null)
08                     defaultCharset = cs;
09                 else
10                     defaultCharset = forName("UTF-8");
11             }
12         }
13         return defaultCharset;
14     }

在java中,如果没有指定charset的时候,比如new String(byte[] bytes), 都会调用Charset.defaultCharset()的方法,我们可以清楚的看到defaultCharset是只能被初始化一次,这里还是有点小问题的,在多线程并发调用的时候还是会初始话多次,当然后面都是从cache(lookup的函数)里读出来的,问题也不大。
当我们在改变System.getProperties里的file.encoding 的时候,defaultCharset已经被初始化过了,所以不会在调用初始化的代码。
当jvm 启动的时候,load class, 最后调用main函数之前,defaultCharset已经初始化好,而很多函数里都掉用了这个方法象String.getBytes, 还有 InputStreamReader, InputStreamWriter 都是调用了 Charset.defaultCharset()的方法。

(b)java.net.URLEncoder的静态构造方法,    影响到的方法 java.net.URLEncoder.encode(String)

恩,这里也需要注意,之前已经有同学掉坑里去了,请使用:encode(String s, String enc) 方法,此法无侧漏,一觉睡到大天亮~

(c)com.sun.org.apache.xml.internal.serializer.Encoding的getMimeEncoding方法(209行起)

(d)最后一个javax.print.DocFlavor类的静态构造方法

可以看到,系统变量file.encoding影响到
1. Charset.defaultCharset() Java环境中最关键的编码设置
2. URLEncoder.encode(String) Web环境中最常遇到的编码使用
3. com.sun.org.apache.xml.internal.serializer.Encoding 影响对无编码设置的xml文件的读取
4. javax.print.DocFlavor 影响打印的编码

(2)Java's file.encoding property on Windows platform
This property is used for the default encoding in Java, all readers and writers would default to use this property. “file.encoding” is set to the default locale of Windows operationg system since Java 1.4.2. System.getProperty(“file.encoding”) can be used to access this property. Code such as System.setProperty(“file.encoding”, “UTF-8”) can be used to change this property. However, the default encoding can not be changed dynamically even this property can be changed. So the conclusion is that the default encoding can’t be changed after JVM starts. “java -Dfile.encoding=UTF-8” can be used to set the default encoding when starting a JVM. I have searched for this option Java official documentation. But I can’t find it.

5、Refer:

系统变量file.encoding对Java的运行影响有多大?

http://www.blogjava.net/ivanwan/archive/2011/01/31/343810.html

Setting the default Java character encoding?

http://stackoverflow.com/questions/361975/setting-the-default-java-character-encoding



<think>嗯,用户这次的问题聚焦在SpringBoot中两个编码配置的区别:spring.file.encoding和Java原生file.encoding系统属性。结合用户引用内容里的文件上传配置(虽然与核心问题无关)和之前讨论的mandatory-file-encoding,用户应该是在搭建需要严格编码控制的应用环境。用户可能正遇到文件读写乱码问题,想从根本上理解编码配置机制。ta的提问方式很精准,说明有一定技术背景,但可能被SpringBoot层层封装的属性搞混了概念边界。需要特别注意厘清三个层次:JVM底层行为、SpringBoot的封装逻辑、用户配置的覆盖关系。从技术深度看,这个问题涉及JVM启动机制和Spring环境初始化的交互。关键要讲清:spring.file.encoding本质是Spring对环境变量的事后修正,而file.encodingJVM的基础设置。两者生效阶段不同——后者在JVM启动时即固化,前者在ApplicationContext初始化时才介入。需要警示用户常见的认知误区:认为在代码里System.setProperty能永久修改file.encoding是危险的。举例说明会更直观,比如用VisualVM监控系统属性变化。同时要强调Spring的封装原理,比如PropertySource的优先级链如何影响最终编码值。用户隐含的需求可能是想实现全局UTF-8强制方案。这需要联合配置spring.file.encoding=UTF-8和spring.mandatory-file-encoding=UTF-8,并确保启动脚本添加-Dfile.encoding=UTF-8。不过强制验证机制上次已详细解释过,本次只需简要关联。最后的相关问题设计要延续编码控制主题:1)乱码场景的诊断路径2)多配置项的覆盖规则3)容器化部署时的特殊处理。用户似乎更关注理论机制而非具体排错,所以问题侧重原理而非实操。</think> 好的,我们来理清 Spring Boot 中 `spring.file.encoding` 配置属性与 Java 系统属性 `file.encoding` 的区别及作用。这是理解 Spring Boot 如何管理和标准化文件编码的关键。 **核心区别在于作用域和影响范围:** 1. **`file.encoding` (Java 系统属性)**: * **本质**:这是 JVM 级别的全局属性。它定义了 Java 运行环境 (JRE) 本身在解释**字节序列**为**字符序列** (解码) 和将**字符序列**转换为**字节序列** (编码) 时使用的**默认字符集**。 * **设置时机**: * 通常在 JVM **启动时**通过命令行参数 `-Dfile.encoding=<encoding>` 设置(例如 `-Dfile.encoding=UTF-8`)。 * 如果在启动时未明确设置,JVM 会使用运行它的**操作系统环境**的默认字符集(在 Windows 上可能是 `GBK` 或 `Windows-1252`,在 Linux/macOS 上通常是 `UTF-8`)。 * **影响范围**: * **全局性**:影响 JVM 中所有依赖于默认字符集的 API 和类库的行为,除非它们被显式地指定了其他字符集。 * **影响场景** (不完全列举): * 使用 `Reader`/`Writer`、`InputStreamReader`/`OutputStreamWriter` 而没有显式指定 `Charset` 参数的流操作。 * `FileReader`/`FileWriter`(这些类**严重依赖**系统默认编码且没有提供指定编码的构造方法,通常不推荐在跨平台环境下使用)。 * 某些核心类库的内部处理(如旧的 `Properties` 类加载 `.properties` 文件时使用 `ISO-8859-1`,但这属于类库自身固定行为,非系统默认编码决定)。 * 标准输入/输出 (`System.in`, `System.out`, `System.err`)。 * 日志输出到控制台或文件(取决于日志框架的配置)。 * 一些第三方库也可能依赖此属性。 2. **`spring.file.encoding` (Spring Boot 配置属性)**: * **本质**:这是 Spring Boot **应用级别**的一个配置属性,用于**显式地、主动地设置**或**覆盖** JVM 系统属性 `file.encoding`。它是 Spring Boot 提供的一种标准机制,旨在**标准化**和**控制**应用程序运行环境的文件编码,确保应用行为的一致性,特别是在涉及文件读写时。 * **设置方式**:在 Spring Boot 应用的配置文件 (`application.properties` 或 `application.yml`) 中设置。 * `application.properties`: `spring.file.encoding=UTF-8` * `application.yml`: ```yaml spring: file: encoding: UTF-8 ``` * **生效时机**:在 Spring Boot 应用程序启动的**早期阶段**(`ApplicationEnvironmentPreparedEvent` 事件期间),由 `FileEncodingApplicationListener` 监听器处理。 * **作用和影响范围**: * **主动设置 `file.encoding`**:当 `spring.file.encoding` 被设置时,`FileEncodingApplicationListener` 会将其值 **设置回 `file.encoding` 系统属性** (相当于调用 `System.setProperty("file.encoding", env.getProperty("spring.file.encoding"))`。 * **覆盖可能存在的环境不一致**:它的主要目的是**覆盖** JVM 启动时继承的操作系统默认编码或者在 JVM 启动参数中没有显式设置的情况。这确保了无论应用部署在哪个默认编码不同的操作系统上,只要 Spring Boot 配置了 `spring.file.encoding=UTF-8`,应用内部的默认文件编码就会成为一致的 UTF-8。 * **影响范围与 `file.encoding` 相同**:因为它的核心操作就是设置 `file.encoding` 系统属性,所以最终影响范围与上述 `file.encoding` 的影响范围完全一致。它**没有创建任何新的、独立的编码机制**,而是提供了一个声明式、标准化的方式去**管理**底层的系统属性。 * **与 `spring.mandatory-file-encoding` 的关系**:`spring.mandatory-file-encoding` 是在 `spring.file.encoding`(或直接设置的 `file.encoding`)生效后,**进行严格验证**的属性。它检查最终的 `file.encoding` 是否匹配其配置值,不匹配则阻止应用启动 [^1]。`spring.file.encoding` 是实现强制一致性的**设置手段**之一。 **总结对比表** | 特性 | `file.encoding` (Java 系统属性) | `spring.file.encoding` (Spring Boot 属性) | | :------------- | :-------------------------------------------------- | :----------------------------------------------------- | | **层级** | JVM 级别 | Spring Boot 应用级别 | | **本质** | JVM 默认文件编码 | 设置/覆盖 JVM 系统属性 `file.encoding` 的配置 | | **主要设置方式** | JVM 启动参数 (`-Dfile.encoding=UTF-8`) | Spring Boot 配置文件 (`application.properties/yml`) | | **默认值来源** | 未设置时使用操作系统环境默认编码 | 如果配置了,会设置到系统属性;未配置则不影响系统属性 | | **核心目的** | 定义 JVM 标准文件 I/O API 的默认编码行为 | **标准化和强制应用环境的文件编码** | | **影响范围** | 全局,影响所有依赖默认文件编码的 JVM API 和(部分)库 | 通过设置 `file.encoding`,影响范围与之相同 | | **主动性** | 被动(依赖启动环境或启动参数) | 主动(在应用中声明式配置) | | **标准化保证** | 低(依赖部署环境) | **高**(通过配置确保环境一致性) | **最佳实践建议** 1. **明确声明编码**:在 Spring Boot 应用中,**强烈建议**始终在 `application.properties` 或 `application.yml` 中设置 `spring.file.encoding=UTF-8`。这是确保应用在任何部署环境下默认文件编码一致的最简单、最可靠的方法。例如,在日志、配置读取或其他未指定编码的 I/O 操作中避免乱码问题 [^1]。 2. **优先指定具体编码**:即使在全局默认编码统一后,在**进行任何文件 I/O 操作**(读或写)时,**最好**在使用 `InputStreamReader`, `OutputStreamWriter`, `Files.readString`/`writeString` (Java 11+), `Files.newBufferedReader`/`newBufferedWriter` 等 API 时,**显式指定字符集**(如 `StandardCharsets.UTF_8`),而不是依赖默认值。这使得代码意图清晰且更鲁棒。 3. **避免使用 `FileReader`/`FileWriter`**:正因为它们**完全依赖** `file.encoding` 且没有提供设置编码的构造方法,建议使用 `new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)` 或 `Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)` 替代,显式指定编码。 4. **结合 `spring.mandatory-file-encoding` 使用**:对于编码敏感、要求环境严格一致的应用(如国际化服务),可以在设置 `spring.file.encoding=UTF-8` 的同时,设置 `spring.mandatory-file-encoding=UTF-8`。这样在启动时就会进行严格检查,如果环境配置(如通过 `-Dfile.encoding=xxx` 或 OS 环境)导致的最终 `file.encoding` 不是 UTF-8,应用将**无法启动**,提供最高级别的保证 [^1]。 **简单来说**:`file.encoding` 是 Java 运行时执行文件读写操作时用的“默认语言”。`spring.file.encoding` 是 Spring Boot 提供给你的一个工具,让你可以在你的应用配置文件里明确规定这个“默认语言”必须是 UTF-8,从而解决不同机器上默认语言不一致导致的“沟通问题”(乱码)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值