Use a API JAVA para ler arquivos HDFS com caracteres ilegíveis e pisar em poços
Quero escrever uma interface para ler parte dos dados do arquivo no HFDS para visualização. Após implementá-la de acordo com o blog online, descobri que às vezes as informações de leitura aparecem truncadas, por exemplo, ao ler um csv, as strings são separados por vírgulas
A string em inglês aaa pode exibir normalmente a
string chinesa "Hello",
e a string mista chinês-inglês, como "aaaHello ", pode ser exibida normalmente e há
caracteres truncados . Depois de consultar muitos blogs, a solução provavelmente é: use o xxx conjunto de caracteres para decodificar. Com pensamentos de descrença, tentei um por um, mas realmente não funcionou.
Soluções
Como o HDFS suporta 6 codificações de conjuntos de caracteres, cada método de codificação de arquivo local provavelmente será diferente. Quando carregamos um arquivo local, na verdade codificamos o arquivo em um fluxo de bytes e o carregamos no sistema de arquivos para armazenamento. Portanto, quando GET arquivos de dados, enfrentando o fluxo de bytes de arquivos diferentes e codificação de conjunto de caracteres diferente, definitivamente não é uma decodificação de conjunto de caracteres fixa que pode ser decodificada corretamente.
Portanto, existem duas soluções
Conjunto de caracteres do codec HDFS corrigido. Por exemplo, se eu escolher UTF-8, ao enviar arquivos, a codificação é unificada, ou seja, os fluxos de bytes de diferentes arquivos são convertidos em codificação UTF-8 e, em seguida, armazenados. Desta forma, ao obter dados do arquivo, não há problema com a decodificação do conjunto de caracteres UTF-8. Mas fazer isso ainda terá muitos problemas na parte de transcodificação e é difícil de implementar.
Decodificação dinâmica. Selecione o conjunto de caracteres correspondente a decodificar de acordo com o conjunto de caracteres codificados do arquivo. Dessa forma, o fluxo de caracteres nativos do arquivo não será alterado e basicamente não haverá caracteres truncados.
Depois que escolhi a ideia de decodificação dinâmica, a dificuldade está em como determinar qual conjunto de caracteres usar para decodificação. Consulte o seguinte conteúdo para obter uma solução
Java detecta a codificação de texto (fluxo de bytes)
exigem:
Um determinado arquivo ou fluxo de bytes precisa detectar seu formato de codificação.
alcançar:
Baseado em jchardet
1
2
3
4
5
net.sourceforge.jchardet
jchardet
1.0
código é o seguinte:
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, 0, 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);
}
}
}
teste:
String file = "C:/3737001.xml";
String[] probableSet = DetectorUtils.detectChineseCharset(new FileInputStream(file));
for (String charset : probableSet) {
System.out.println(charset);
}
```
Google提供了检测字节流编码方式的包。那么方案就很明了了,先读一些文件字节流,用工具检测编码方式,再对应进行解码即可。
## 具体解决代码
```c
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;
}