Spark 处理中文乱码问题(UTF-8编码)

Spark 处理中文乱码问题(UTF-8编码)

2015年12月03日 16:14:24 amber_amber 阅读数:19878 标签: spark编码汉字UTF-8 更多

个人分类: spark学习分享

问题场景

要用spark处理一大堆微信日志数据,日志存放在HDFS上,是xml格式,里面有大量的中文。用scala + java实现了xml的处理逻辑,其中有一步是要获取xml中的一个title字段,中文。不管怎么抓取,最终得到的中文都会变成一堆“?????”,乱码了。从xml中获取非中文字段,没有任何问题。也就是说,代码的逻辑是没什么问题的。

 

问题解析

直接用hadoop fs -text或者hadoop fs -cat查看HDFS上的文件,是可以正常显示的,也就是说HDFS上存放的原数据是好的。那么就肯定是读取数据或者处理数据的过程中出了问题。spark on yarn的数据处理,同时涉及了HDFS,App driver, App excutor之间的交互,所以还真没法一下就判断出是哪一步传输中出了问题。抽丝剥茧,先梳理一遍spark的处理逻辑:

(1) 从HDFS把xml读取到每个NM上的executor中(spark on yarn环境)

(2) 在executor中对xml进行处理,获取中文字段。这里我实现了一个java方法,调用dom来解析xml。

(3) 把解析后的字段collect到driver中,做存储或者输出打印等。

(4) 或者把解析后的字段重新存入HDFS

进入Spark-shell,依次验证这几个步骤。读入HDFS上的xml文件,然后直接写入HDFS,检查发现字符显示正常,排除步骤(1)(4)。读入HDFS上的xml文件,collect到driver中,然后println,字符显示正常,排除步骤(3)。说明问题出在executor对字段的解析处理过程中。

 

无论汉字还是英文字符,本质上还是一组字节流,所以出现乱码,只能是编码解析出了问题。查看发现,代码中只有一个地方对xml文件中的字符做了解析,就是这里: 

 

 
  1. DocumentBuilder dbBuilder = dbFactory.newDocumentBuilder();

  2. InputStream strm = new ByteArrayInputStream(xmlStream.getBytes());

  3. Document doc = dbBuilder.parse(strm);

把string转为inputStream的过程。 找到了出问题的位置,下一步就是检测。 

登录到executor所在的hadoop节点,进入spark-shell, 输入System.getProperty("file.encoding"),返回”ISO-8859-1“,说明它的默认编码方式是ISO-8859-1。另一种检测方法,是定义一个String变量等于一个汉字,然后a.getBytes().length。检查它的字节数,并推断对应的字符编码。UTF8汉字占3个字节,GBK汉字占2个字节。 

ISO-8895-1占1字节,用ISO-8895-1的方式把汉字转成字节流,然后转回的过程中,肯定会损失一部分数据,所以会乱码。

问题定位到后,解决就很简单了。 在所有涉及到字节转换时,一定要指定编码方式。类似这样:

String -> Byte: 

string.getBytes("UTF-8")

Byte -> String:

new String(bytes, "UTF-8")

<think>好的,用户在使用Apache Spark读取Excel文件时,遇到了输出乱码问题,具体表现为打印出来的表头显示为“��������������������������ʼ����ʱ����ֹ����ʱ����ֵ����״̬ÿ��������ÿ����ٱ�”这样的乱码。用户提供的代码中设置了系统属性`file.encoding`和`sun.jnu.encoding`为UTF-8,并且在Spark配置中也添加了Java选项来指定编码。现在需要分析为什么仍然出现乱码。 首先,我需要考虑可能的乱码原因。通常,字符编码问题可能出现在多个环节:文件本身的编码、读取文件时使用的编码、环境默认编码不一致,或者输出时的编码设置不当。 用户代码中,使用`DataFormatter.formatCellValue`来获取单元格的值。查阅Apache POI的文档,`DataFormatter`会尽量根据单元格的格式返回字符串,但可能依赖系统的默认编码,除非显式指定编码。不过,在用户代码中,并没有看到为`DataFormatter`设置编码的地方,这可能是一个问题点。 接下来,用户设置了系统属性`file.encoding`和`sun.jnu.encoding`,并且在Spark的配置中传入了Java选项。但是,这些设置是否在正确的时间点生效?例如,JVM启动后修改`file.encoding`可能不会影响所有部分的编码,特别是像Apache POI这样的库可能在初始化时就已经读取了默认编码,后续修改系统属性可能不会生效。因此,用户虽然在代码中设置了系统属性,但可能这些设置并未被POI正确识别。 另外,Excel文件本身的编码也需要确认。虽然XSSFWorkbook处理的是.xlsx文件,这种文件本质上是ZIP压缩的XML文件,通常使用UTF-8编码。但如果有特殊字符或者文件保存时使用了其他编码,也可能导致问题。不过,这种情况比较少见,因为.xlsx格式通常内部都是UTF-8。 输出时,用户使用`print`语句打印到控制台。这时候需要确认控制台的编码是否支持UTF-8。例如,Windows的CMD默认编码是GBK,如果IDEA或Eclipse等IDE的控制台没有设置为UTF-8,也会出现乱码。这可能是一个关键点,即使用户正确设置了程序内部的编码,但输出环境不支持UTF-8,仍然会显示乱码。 此外,用户在使用`map`处理单元格时,虽然调用了`dataFormatter.formatCellValue(x)`,但并没有将结果保存下来,而是直接打印。不过,代码中的`map`操作可能不会产生预期的效果,因为Scala的`map`函数在这里用于副作用(打印),但实际并没有收集这些字符串到headers变量中。这可能导致headers变量实际上是一个Unit类型的序列,但用户可能更关注打印的结果是否正确。 解决方案方面,可能需要以下几个步骤: 1. **确保Excel文件正确保存为UTF-8编码**:虽然.xlsx通常没问题,但确认文件内容是否包含特殊字符,或者是否在导出时选择了正确的编码。 2. **显式指定DataFormatter的编码**:检查Apache POI的API,是否有方法在创建DataFormatter时指定编码。如果DataFormatter依赖于系统属性,那么系统属性是否在POI初始化之前设置?用户虽然在代码开头设置了系统属性,但JVM启动时可能已经读取了默认编码,此时修改可能对某些库无效。因此,可能需要通过JVM参数在启动时指定这些编码,而不是在运行时设置。 3. **验证控制台输出的编码**:检查运行环境的控制台是否支持UTF-8。例如,在IntelliJ IDEA中,可以在Run/Debug Configurations的VM options中添加`-Dfile.encoding=UTF-8`,并确保IDE全局编码设置为UTF-8。同时,检查终端的编码设置是否为UTF-8。 4. **在读取单元格时显式处理编码**:可能需要使用不同的方法读取单元格内容,例如,对于字符串类型的单元格,直接获取字符串值,并指定编码转换。 另外,用户可能在代码中遗漏了对字符串进行正确的编码转换步骤。例如,即使DataFormatter返回了正确的字节,如果输出时没有以正确的编码解码,也会出现乱码。或者,DataFormatter本身没有使用正确的编码处理字节,导致字符串被错误解码。 可能的代码调整包括: - 在启动JVM时,通过`-Dfile.encoding=UTF-8`参数设置编码,而不是在代码中动态设置,因为后者可能无法影响所有库。 - 检查DataFormatter是否需要在构造时传入Locale或编码参数,确保其按UTF-8处理内容。 - 在输出时,确保控制台或日志系统使用UTF-8编码,可能需要配置IDE或终端。 此外,用户可能误用了`map`函数,导致headers变量并没有正确收集到格式化后的字符串,而是执行了打印操作。这可能不会直接导致乱码,但会影响后续的数据处理。不过,用户的主要问题是打印出来的内容乱码,因此问题更可能出在编码设置或输出环境上。 总结可能的原因: 1. 系统属性`file.encoding`在运行时设置太晚,Apache POI已经使用了默认编码(如GBK)。 2. 控制台输出环境不支持UTF-8编码,导致正确读取的UTF-8字符被错误显示。 3. Excel文件本身存在编码问题,但可能性较低。 接下来,建议用户采取的解决步骤: - 将JVM参数`-Dfile.encoding=UTF-8`和`-Dsun.jnu.encoding=UTF-8`添加到Spark的启动配置中,而不是在代码中设置系统属性。例如,在提交Spark应用时通过`--conf`指定,或者在IDE的运行配置中添加VM options。 - 检查并确保IDE或终端的编码设置为UTF-8- 在代码中显式处理字符串编码,例如使用`new String(bytes, "UTF-8")`,但需要确认是否有字节流被错误解码的情况。 - 替换DataFormatter的使用,尝试直接获取单元格的字符串值,并指定编码转换。 例如,修改读取单元格的部分代码: ```scala val headers = (0 until headerRow.getLastCellNum) .map(headerRow.getCell) .map { cell => val value = dataFormatter.formatCellValue(cell) new String(value.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8) } ``` 不过,这可能并不必要,因为`dataFormatter.formatCellValue`应该返回正确的字符串,前提是编码设置正确。 另外,用户可能在Windows环境下运行,而Windows的默认编码是GBK,导致即使设置了UTF-8,控制台仍用GBK解码,从而出现乱码。这种情况下,需要确保控制台使用UTF-8解码输出。例如,在CMD中使用`chcp 65001`切换代码页到UTF-8,或者在PowerShell中设置相应的编码。 综上,最可能的解决方案是确保JVM在启动时就已经使用UTF-8编码,而不是在运行时修改系统属性,并且确保输出环境(如控制台)支持UTF-8编码。</think>在Spark读取Excel文件时出现乱码问题,通常与编码设置、文件读取方式和输出环境有关。以下是逐步分析与解决方案: --- ### **一、乱码原因分析** 1. **JVM编码设置时机不当** - 使用`System.setProperty`动态修改编码可能**未生效**,因为部分库(如Apache POI)在JVM启动时已缓存默认编码(如GBK)。 - Spark的`extraJavaOptions`配置**仅在Executor进程启动时生效**,但Driver端的编码可能在代码运行时已被锁定。 2. **Excel读取方式依赖系统编码** - Apache POI的`DataFormatter`默认使用JVM的`file.encoding`属性,若未在**JVM启动前**设置UTF-8,会导致读取时使用错误编码。 3. **控制台输出环境不兼容UTF-8** - Windows系统终端(如CMD、PowerShell)默认编码为GBK,直接打印UTF-8字符会显示乱码--- ### **二、解决方案** #### **1. 通过JVM参数强制指定编码** 在**启动JVM时**(而非运行时)设置编码,确保所有库使用UTF-8- **IDEA中配置**: ```text Run → Edit Configurations → VM options: -Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8 ``` - **Spark提交时配置**: ```scala .config("spark.driver.extraJavaOptions", "-Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8") .config("spark.executor.extraJavaOptions", "-Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8") ``` #### **2. 显式指定Excel单元格编码** 使用Apache POI时,直接处理字符串编码: ```scala import org.apache.poi.ss.usermodel.Cell import java.nio.charset.StandardCharsets val headers = (0 until headerRow.getLastCellNum) .map(headerRow.getCell) .map { cell => val rawValue = dataFormatter.formatCellValue(cell) // 显式转换确保UTF-8编码 new String(rawValue.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8) } ``` #### **3. 配置终端支持UTF-8** - **Windows CMD**: ```bat chcp 65001 # 切换终端代码页为UTF-8 ``` - **PowerShell**: ```powershell [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 ``` #### **4. 验证Excel文件编码** - 确保Excel文件保存为标准的`.xlsx`格式(内部使用UTF-8)。 - 避免从其他系统导出的文件包含特殊字符集。 --- ### **三、完整修正代码** ```scala // 通过JVM参数设置编码,而非运行时修改 val spark = SparkSession.builder() .appName("ExcelReader") .master("local[*]") .config("spark.driver.extraJavaOptions", "-Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8") .config("spark.executor.extraJavaOptions", "-Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8") .getOrCreate() // 读取Excel文件(确保路径无中文) val file = new FileInputStream("C:\\Users\\Lenovo\\Desktop\\xtcek-mpdxh.xlsx") val workbook = new XSSFWorkbook(file) val sheet = workbook.getSheetAt(0) val dataFormatter = new DataFormatter() // 处理表头并显式转码 val headerRow = sheet.getRow(0) val headers = (0 until headerRow.getLastCellNum) .map(headerRow.getCell) .map { cell => val rawValue = dataFormatter.formatCellValue(cell) new String(rawValue.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8) } headers.foreach(println) // 确保控制台支持UTF-8 ``` --- ### **四、验证步骤** 1. 检查终端编码是否为UTF-8。 2. 使用简单字符(如纯英文)测试Excel读取。 3. 打印字节流确认是否UTF-8: ```scala headers.foreach(str => println(str.getBytes(StandardCharsets.UTF_8).mkString(","))) ``` 通过上述步骤,可系统性解决Spark读取Excel时的中文乱码问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值