1. 程式人生 > 程式設計 >JAVA讀取HDFS的檔案資料出現亂碼的解決方案

JAVA讀取HDFS的檔案資料出現亂碼的解決方案

使用JAVA api讀取HDFS檔案亂碼踩坑

想寫一個讀取HFDS上的部分檔案資料做預覽的介面,根據網上的部落格實現後,發現有時讀取資訊會出現亂碼,例如讀取一個csv時,字串之間被逗號分割

  • 英文字串aaa,能正常顯示
  • 中文字串“你好”,能正常顯示
  • 中英混合字串如“aaa你好”,出現亂碼

查閱了眾多部落格,解決方案大概都是:使用xxx字符集解碼。抱著不信的想法,我依次嘗試,果然沒用。

解決思路

因為HDFS支援6種字符集編碼,每個本地檔案編碼方式又是極可能不一樣的,我們上傳本地檔案的時候其實就是把檔案編碼成位元組流上傳到檔案系統儲存。那麼在GET檔案資料時,面對不同檔案、不同字符集編碼的位元組流,肯定不是一種固定字符集解碼就能正確解碼的吧。

那麼解決方案其實有兩種

  • 固定HDFS的編解碼字符集。比如我選用UTF-8,那麼在上傳檔案時統一編碼,即把不同檔案的位元組流都轉化為UTF-8編碼再進行儲存。這樣的話在獲取檔案資料的時候,採用UTF-8字符集解碼就沒什麼問題了。但這樣做的話仍然會在轉碼部分存在諸多問題,且不好實現。
  • 動態解碼。根據檔案的編碼字符集選用對應的字符集對解碼,這樣的話並不會對檔案的原生字元流進行改動,基本不會亂碼。

我選用動態解碼的思路後,其難點在於如何判斷使用哪種字符集解碼。參考下面的內容,獲得瞭解決方案

java檢測文字(位元組流)的編碼方式

需求:

某檔案或者某位元組流要檢測他的編碼格式。

實現:

基於jchardet

<dependency>
	<groupId>net.sourceforge.jchardet</groupId>
	<artifactId>jchardet</artifactId>
	<version>1.0</version>
</dependency>

程式碼如下:

public class DetectorUtils {
	private DetectorUtils() {
	}
 
	static class ChineseCharsetDetectionObserver implements
			nsICharsetDetectionObserver {
		private boolean found = false;
		private String result;
 
		public void Notify(String charset) {
			found = true;
			result = charset;
		}
 
		public ChineseCharsetDetectionObserver(boolean found,String result) {
			super();
			this.found = found;
			this.result = result;
		}
 
		public boolean isFound() {
			return found;
		}
 
		public String getResult() {
			return result;
		}
 
	}
 
	public static String[] detectChineseCharset(InputStream in)
			throws Exception {
		String[] prob=null;
		BufferedInputStream imp = null;
		try {
			boolean found = false;
			String result = Charsets.UTF_8.toString();
			int lang = nsPSMDetector.CHINESE;
			nsDetector det = new nsDetector(lang);
			ChineseCharsetDetectionObserver detectionObserver = new ChineseCharsetDetectionObserver(
					found,result);
			det.Init(detectionObserver);
			imp = new BufferedInputStream(in);
			byte[] buf = new byte[1024];
			int len;
			boolean isAscii = true;
			while ((len = imp.read(buf,buf.length)) != -1) {
				if (isAscii)
					isAscii = det.isAscii(buf,len);
				if (!isAscii) {
					if (det.DoIt(buf,len,false))
						break;
				}
			}
 
			det.DataEnd();
			boolean isFound = detectionObserver.isFound();
			if (isAscii) {
				isFound = true;
				prob = new String[] { "ASCII" };
			} else if (isFound) {
				prob = new String[] { detectionObserver.getResult() };
			} else {
				prob = det.getProbableCharsets();
			}
			return prob;
		} finally {
			IOUtils.closeQuietly(imp);
			IOUtils.closeQuietly(in);
		}
	}
}

測試:

		String file = "C:/3737001.xml";
		String[] probableSet = DetectorUtils.detectChineseCharset(new FileInputStream(file));
		for (String charset : probableSet) {
			System.out.println(charset);
		}

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,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,bytes.length);
  detector.dataEnd();
  String encoding = detector.getDetectedCharset();
  detector.reset();

  if (StringUtils.isEmpty(encoding)) {
   encoding = "UTF-8";
  }
  return encoding;
 }

以上就是JAVA讀取HDFS的檔案資料出現亂碼的解決方案的詳細內容,更多關於JAVA讀取HDFS的檔案亂碼的資料請關注我們其它相關文章!