乱码一直就是个很麻烦的事情,项目有个功能需要从hive表获得用户的住址(住址有中文)
jdbc执行(查询select address from user_info_all limit 10)的结果直接插入到mysql表(utf8编码),再查看mysql全是乱码
首先怀疑是hive文件编码有问题,于是用CLI方式查询同一条语句,结果中文显示正常
这说明乱码与hive文件编码没有关系,为了排除SecureCRT编码因素,我写了个测试类——从hive表执行这条语句,输出到控制台的同时也原样输出到文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public class TestReadFromHive { public static void main(String[] args) { hql = "select address from user_info_all limit 10"; Connection con = null; Statement stmt = null; ResultSet rs = null; try { con = getHiveConnection(); stmt = con.createStatement(); rs = stmt.executeQuery(hql); Writer out =new BufferedWriter(new OutputStreamWriter(new FileOutputStream("/tmp/address.txt"))); while (rs.next()) { String address = rs.getString("address"); out.write(address+"\n"); System.out.println(address); } out.flush(); out.close(); } catch (SQLException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } |
编译后上传到服务器上,编写执行脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #!/bin/bash MY_LIB=./lib MY_JAR=\ $MY_LIB/hive-anttasks-0.7.0-CDH3B4.jar:\ $MY_LIB/hive-cli-0.7.0-CDH3B4.jar:\ #$MY_LIB/hive-jdbc-0.7.0-CDH3B4.jar:\ $MY_LIB/hive-jdbc-0.7.0-CDH3B4.jar:\ $MY_LIB/hive-common-0.7.0-CDH3B4.jar:\ $MY_LIB/hive-metastore-0.7.0-CDH3B4.jar:\ $MY_LIB/hive-contrib-0.7.0-CDH3B4.jar:\ $MY_LIB/hive-serde-0.7.0-CDH3B4.jar:\ $MY_LIB/hive-exec-0.7.0-CDH3B4.jar:\ $MY_LIB/hive-service-0.7.0-CDH3B4.jar:\ $MY_LIB/hive-hbase-handler-0.7.0-CDH3B4.jar:\ $MY_LIB/hive-shims-0.7.0-CDH3B4.jar:\ $MY_LIB/hive-hwi-0.7.0-CDH3B4.jar:\ $MY_LIB/libfb303.jar:\ $MY_LIB/hadoop-core-0.20.2-CDH3B4.jar:\ $MY_LIB/commons-logging-1.0.4.jar:\ $MY_LIB/commons-logging-api-1.0.4.jar:\ $MY_LIB/slf4j-api-1.4.3.jar:\ $MY_LIB/slf4j-log4j12-1.4.3.jar:\ $MY_LIB/log4j-1.2.15.jar CLASSPATH=\ .:\ $MY_JAR echo $CLASSPATH export CLASSPATH $JAVA_HOME/bin/java -Xms128m -Xmx128m -Xmn32m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+UseTLAB -XX:+CMSIncrementalMode -XX:+CMSIncrementalPacing -XX:CMSIncrementalDutyCycleMin=0 -XX:CMSIncrementalDutyCycle=10 -Dcrgw.module=testHive org.javali.mr.test.TestReadFromHive |
执行脚本后,控制台显示乱码,文件内容也乱码,将文件拷贝到本地($sz file)用不同的编辑器打开依旧乱码。
到这一步可以确定 jdbc读取出来的内容已经是乱码了,由于我的服务器系统编码是en_US , 很有可能是hive jdbc驱动没使用utf-8编码,而获取了系统编码进行转码。为了验证这个假设,我在执行脚本添加了export LANG=zh_CN.UTF-8 ,再次执行,中文显示正常了。
我们已经找到问题了,有一种解决办法就是将系统编码或者将运行时环境(Tomcat)的编码改为UTF-8,如果是个新开展的项目,做统一的编码设置无可厚非,如果因为要从hive表取一个中文字段,去改变现有系统运行环境的编码,决不是可取的解决办法,因为这个改动很有可能影响其他模块的可用性。
同事在apache 的jira 列表里恰好也有找了这个问题: https://issues.apache.org/jira/browse/HIVE-2137
| 附件的patch里有版本的对比 Index: jdbc/src/java/org/apache/hadoop/hive/jdbc/<span style="color: #ff0000;">HiveQueryResultSet.java</span> =================================================================== --- jdbc/src/java/org/apache/hadoop/hive/jdbc/HiveQueryResultSet.java (revision 1195103) +++ jdbc/src/java/org/apache/hadoop/hive/jdbc/HiveQueryResultSet.java (working copy) @@ -153,7 +153,7 @@ StructObjectInspector soi = (StructObjectInspector) serde.getObjectInspector(); List fieldRefs = soi.getAllStructFieldRefs(); - Object data = serde.deserialize(new BytesWritable(rowStr.getBytes())); + Object data = serde.deserialize(new BytesWritable(rowStr.getBytes("UTF-8"))); assert row.size() == fieldRefs.size() : row.size() + ", " + fieldRefs.size(); for (int i = 0; i < fieldRefs.size(); i++) { |
rowStr其实就是hive文件的一行数据,包装成ByteWritable没有指定编码,程序默认会使用系统的编码(en_US),这就是造成乱码的根源。
我们可以根据patch的变更来手动指定编码类型,不过这就得重新编译jdbc驱动了。
- 先获取源码,有两种方式:
1)从apache 的仓库checkout到本地 : http://svn.apache.org/repos/asf/hive/trunk
2)cd $HIVE_HOME/src下同样可以获取源码 - 新建java project——hive_jdbc,然后new package org.apache.hadoop.hive
- 从$HIVE_HOME/lib 目录找到以下jar包引入到工程CLASSPATH(hive-jdbc××.jar不需要,我们需要重新编译它)
- 把hive的jdbc包下源码拷贝到相应的package,有错误不用管,只要保证HiveQueryResultSet.java类能正常编译,定位到该类需要变更的位置将Object data = serde.deserialize(new BytesWritable(rowStr.getBytes()));替换为Object data = serde.deserialize(new BytesWritable(rowStr.getBytes(“UTF-8″)));
- 把编译好的HiveQueryResultSet.class 替换掉hive-jdbc***.jar包里的旧类,同时为了区分,把包重命名为hive-jdbc-0.7.0-CDH3B4_UTF8.jar
- 上传到服务器上,替换原来的jar包,再次执行测试脚本,此时无论系统编码是什么,只要SecureCRT客户端编码正确,输出到Console或者File都能正常显示中文了不过获取到address字段后并不能正常显示,需要重新用UTF-8构造一遍
String address = rs.getString(“address”); //直接system.out.println 在console会显示乱码
System.out.println(new String(address.getBytes(“UTF-8″)));
这样才能在console显示正常,直接insert到mysql是能正常显示的
借鉴mysql,我们要做的更好
虽然解决了问题,但出现了UTF-8硬编码,这就限定了只适用于UTF-8编码的场景,如果文件编码为GBK,问题又将来临
我们在使用mysql的jdbc连接时,会在jdbc url加上编码设置:
jdbc\:mysql\://localhost\:3306/mdmy?autoReconnect\=true&useUnicode\=true&characterEncoding\=UTF8
同样一种可配置、更通用的方案就是在hive jdbc url上附加上编码参数,将这个参数带到HiveQueryResultSet类使用它转码
这种方案需要对url连接的解析做下变更,但也不算困难,这个留到以后再实现
附件下载:hive-jdbc-0.7.0-CDH3B4_UTF8 下载后直接将扩展名改为jar即可
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请
点击举报。