使用JAVA api讀取HDFS文件亂碼踩坑
想寫一個讀取HFDS上的部分文件數據做預覽的接口,根據網上的博客實現后,發現有時讀取信息會出現亂碼,例如讀取一個csv時,字符串之間被逗號分割
-
英文字符串aaa,能正常顯示
-
中文字符串“你好”,能正常顯示
-
中英混合字符串如“aaa你好”,出現亂碼
查閱了眾多博客,解決方案大概都是:使用xxx字符集解碼。抱着不信的想法,我依次嘗試,果然沒用。
解決思路
因為HDFS支持6種字符集編碼,每個本地文件編碼方式又是極可能不一樣的,我們上傳本地文件的時候其實就是把文件編碼成字節流上傳到文件系統存儲。那么在GET文件數據時,面對不同文件、不同字符集編碼的字節流,肯定不是一種固定字符集解碼就能正確解碼的吧。
那么解決方案其實有兩種
-
固定HDFS的編解碼字符集。比如我選用UTF-8,那么在上傳文件時統一編碼,即把不同文件的字節流都轉化為UTF-8編碼再進行存儲。這樣的話在獲取文件數據的時候,采用UTF-8字符集解碼就沒什么問題了。但這樣做的話仍然會在轉碼部分存在諸多問題,且不好實現。
-
動態解碼。根據文件的編碼字符集選用對應的字符集對解碼,這樣的話並不會對文件的原生字符流進行改動,基本不會亂碼。
我選用動態解碼的思路后,其難點在於如何判斷使用哪種字符集解碼。
好在看到了一篇博客
https://blog.csdn.net/smallnetvisitor/article/details/84682867
Google提供了檢測字節流編碼方式的包。那么方案就很明了了,先讀一些文件字節流,用工具檢測編碼方式,再對應進行解碼即可。
具體代碼
pom
<dependency>
<groupId>net.sourceforge.jchardet</groupId>
<artifactId>jchardet</artifactId>
<version>1.0</version>
</dependency>
從HDFS讀取部分文件做預覽的邏輯
// 獲取文件的部分數據做預覽
public List<String> getFileDataWithLimitLines(String filePath, Integer limit) {
FSDataInputStream fileStream = openFile(filePath);
return readFileWithLimit(fileStream, limit);
}
// 獲取文件的數據流
private FSDataInputStream openFile(String filePath) {
FSDataInputStream fileStream = null;
try {
fileStream = fs.open(new Path(getHdfsPath(filePath)));
} catch (IOException e) {
logger.error("fail to open file:{}", filePath, e);
}
return fileStream;
}
// 讀取最多limit行文件數據
private List<String> readFileWithLimit(FSDataInputStream fileStream, Integer limit) {
byte[] bytes = readByteStream(fileStream);
String data = decodeByteStream(bytes);
if (data == null) {
return null;
}
List<String> rows = Arrays.asList(data.split("\\r\\n"));
return rows.stream().filter(StringUtils::isNotEmpty)
.limit(limit)
.collect(Collectors.toList());
}
// 從文件數據流中讀取字節流
private byte[] readByteStream(FSDataInputStream fileStream) {
byte[] bytes = new byte[1024*30];
int len;
ByteArrayOutputStream stream = new ByteArrayOutputStream();
try {
while ((len = fileStream.read(bytes)) != -1) {
stream.write(bytes, 0, len);
}
} catch (IOException e) {
logger.error("read file bytes stream failed.", e);
return null;
}
return stream.toByteArray();
}
// 解碼字節流
private String decodeByteStream(byte[] bytes) {
if (bytes == null) {
return null;
}
String encoding = guessEncoding(bytes);
String data = null;
try {
data = new String(bytes, encoding);
} catch (Exception e) {
logger.error("decode byte stream failed.", e);
}
return data;
}
// 根據Google的工具判別編碼
private String guessEncoding(byte[] bytes) {
UniversalDetector detector = new UniversalDetector(null);
detector.handleData(bytes, 0, bytes.length);
detector.dataEnd();
String encoding = detector.getDetectedCharset();
detector.reset();
if (StringUtils.isEmpty(encoding)) {
encoding = "UTF-8";
}
return encoding;
}