Java读取HDFS的文件数据出现乱码的方案

使用JAVA api读取HDFS文件乱码踩坑

想写一个读取HFDS上的部分文件数据做预览的接口,根据网上的博客实现后,发现有时读取信息会出现乱码,例如读取一个csv时,字符串之间被逗号分割

英文字符串aaa,能正常显示

中文字符串“你好”,能正常显示

中英混合字符串如“aaa你好”,出现乱码

查阅了众多博客,解决方案大概都是:使用xxx字符集解码。抱着不信的想法,我依次尝试,果然没用。

解决思路

因为HDFS支持6种字符集编码,每个本地文件编码方式又是极可能不一样的,我们上传本地文件的时候其实就是把文件编码成字节流上传到文件系统存储。那么在GET文件数据时,面对不同文件、不同字符集编码的字节流,肯定不是一种固定字符集解码就能正确解码的吧。

那么解决方案其实有两种

固定HDFS的编解码字符集。比如我选用UTF-8,那么在上传文件时统一编码,即把不同文件的字节流都转化为UTF-8编码再进行存储。这样的话在获取文件数据的时候,采用UTF-8字符集解码就没什么问题了。但这样做的话仍然会在转码部分存在诸多问题,且不好实现。

动态解码。根据文件的编码字符集选用对应的字符集对解码,这样的话并不会对文件的原生字符流进行改动,基本不会乱码。

我选用动态解码的思路后,其难点在于如何判断使用哪种字符集解码。参考下面的内容,获得了解决方案

java检测文本(字节流)的编码方式

需求:

某文件或者某字节流要检测他的编码格式。

实现:

基于jchardetdependency>

    <groupId>net.sourceforge.jchardet</groupId>

    <artifactId>jchardet</artifactId>

    <version>1.0</version>

</dependency>

代码如下:

publicclassDetectorUtils {

    privateDetectorUtils() {

    }


    staticclassChineseCharsetDetectionObserver implements

            nsICharsetDetectionObserver {

        privatebooleanfound = false;

        privateString result;


        publicvoidNotify(String charset) {

            found = true;

            result = charset;

        }


        publicChineseCharsetDetectionObserver(booleanfound, String result) {

            super();

            this.found = found;

            this.result = result;

        }


        publicbooleanisFound() {

            returnfound;

        }


        publicString getResult() {

            returnresult;

        }


    }


    publicstaticString[] detectChineseCharset(InputStream in)

            throwsException {

        String[] prob=null;

        BufferedInputStream imp = null;

        try{

            booleanfound = false;

            String result = Charsets.UTF_8.toString();

            intlang = nsPSMDetector.CHINESE;

            nsDetector det = newnsDetector(lang);

            ChineseCharsetDetectionObserver detectionObserver = newChineseCharsetDetectionObserver(

                    found, result);

            det.Init(detectionObserver);

            imp = newBufferedInputStream(in);

            byte[] buf = newbyte[1024];

            intlen;

            booleanisAscii = true;

            while((len = imp.read(buf, 0, buf.length)) != -1) {

                if(isAscii)

                    isAscii = det.isAscii(buf, len);

                if(!isAscii) {

                    if(det.DoIt(buf, len, false))

                        break;

                }

            }


            det.DataEnd();

            booleanisFound = detectionObserver.isFound();

            if(isAscii) {

                isFound = true;

                prob = newString[] { "ASCII"};

            } elseif(isFound) {

                prob = newString[] { detectionObserver.getResult() };

            } else{

                prob = det.getProbableCharsets();

            }

            returnprob;

        } finally{

            IOUtils.closeQuietly(imp);

            IOUtils.closeQuietly(in);

        }

    }

测试: 

String file = "C:/3737001.xml";

String[] probableSet = DetectorUtils.detectChineseCharset(newFileInputStream(file));

for(String charset : probableSet) {

    System.out.println(charset);

}

从HDFS读取部分文件做预览的逻辑

// 获取文件的部分数据做预览

publicList<String> getFileDataWithLimitLines(String filePath, Integer limit) {

 FSDataInputStream fileStream = openFile(filePath);

 returnreadFileWithLimit(fileStream, limit);

}


// 获取文件的数据流

privateFSDataInputStream openFile(String filePath) {

 FSDataInputStream fileStream = null;

 try{

  fileStream = fs.open(newPath(getHdfsPath(filePath)));

 } catch(IOException e) {

  logger.error("fail to open file:{}", filePath, e);

 }

 returnfileStream;

}


// 读取最多limit行文件数据

privateList<String> readFileWithLimit(FSDataInputStream fileStream, Integer limit) {

 byte[] bytes = readByteStream(fileStream);

 String data = decodeByteStream(bytes);

 if(data == null) {

  returnnull;

 }


 List<String> rows = Arrays.asList(data.split("\\r\\n"));

 returnrows.stream().filter(StringUtils::isNotEmpty)

   .limit(limit)

   .collect(Collectors.toList());

}


// 从文件数据流中读取字节流

privatebyte[] readByteStream(FSDataInputStream fileStream) {

 byte[] bytes = newbyte[1024*30];

 intlen;

 ByteArrayOutputStream stream = newByteArrayOutputStream();

 try{

  while((len = fileStream.read(bytes)) != -1) {

   stream.write(bytes, 0, len);

  }

 } catch(IOException e) {

  logger.error("read file bytes stream failed.", e);

  returnnull;

 }

 returnstream.toByteArray();

}


// 解码字节流

privateString decodeByteStream(byte[] bytes) {

 if(bytes == null) {

  returnnull;

 }


 String encoding = guessEncoding(bytes);

 String data = null;

 try{

  data = newString(bytes, encoding);

 } catch(Exception e) {

  logger.error("decode byte stream failed.", e);

 }

 returndata;

}


// 根据Google的工具判别编码

privateString guessEncoding(byte[] bytes) {

 UniversalDetector detector = newUniversalDetector(null);

 detector.handleData(bytes, 0, bytes.length);

 detector.dataEnd();

 String encoding = detector.getDetectedCharset();

 detector.reset();


 if(StringUtils.isEmpty(encoding)) {

  encoding = "UTF-8";

 }

 returnencoding;

}

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容