如何在Scala或Java中读取带有混合编码的文本文件?

时间:2012-11-29 11:41:41

标签: java scala utf-8 character-encoding weka

我正在尝试解析CSV文件,理想情况下使用weka.core.converters.CSVLoader。 但是我的文件不是有效的UTF-8文件。 它主要是一个UTF-8文件,但是一些字段值采用不同的编码, 所以没有整个文件有效的编码, 但无论如何我需要解析它。 除了使用像Weka这样的java库之外,我主要在Scala中工作。 我甚至无法使用scala.io.Source读取文件: 例如

Source.
  fromFile(filename)("UTF-8").
  foreach(print);

抛出:

    java.nio.charset.MalformedInputException: Input length = 1
at java.nio.charset.CoderResult.throwException(CoderResult.java:277)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:337)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:176)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:153)
at java.io.BufferedReader.read(BufferedReader.java:174)
at scala.io.BufferedSource$$anonfun$iter$1$$anonfun$apply$mcI$sp$1.apply$mcI$sp(BufferedSource.scala:38)
at scala.io.Codec.wrap(Codec.scala:64)
at scala.io.BufferedSource$$anonfun$iter$1.apply(BufferedSource.scala:38)
at scala.io.BufferedSource$$anonfun$iter$1.apply(BufferedSource.scala:38)
at scala.collection.Iterator$$anon$14.next(Iterator.scala:150)
at scala.collection.Iterator$$anon$25.hasNext(Iterator.scala:562)
at scala.collection.Iterator$$anon$19.hasNext(Iterator.scala:400)
at scala.io.Source.hasNext(Source.scala:238)
at scala.collection.Iterator$class.foreach(Iterator.scala:772)
at scala.io.Source.foreach(Source.scala:181)

我很高兴将所有无效字符丢弃或用虚拟替换它们。 我将有很多这样的文字以各种方式处理 并且可能需要将数据传递给各种第三方库。 一个理想的解决方案是某种全球环境 导致所有低级java库忽略文本中的无效字节, 这样我就可以在没有修改的情况下调用这些数据的第三方库。

解决方案:

import java.nio.charset.CodingErrorAction
import scala.io.Codec

implicit val codec = Codec("UTF-8")
codec.onMalformedInput(CodingErrorAction.REPLACE)
codec.onUnmappableCharacter(CodingErrorAction.REPLACE)

val src = Source.
  fromFile(filename).
  foreach(print)

感谢+ Esailija指出我正确的方向。 这导致我How to detect illegal UTF-8 byte sequences to replace them in java inputstream? 它提供了核心java解决方案。在Scala中,我可以通过隐式编解码器使其成为默认行为。我想通过在包对象中加入隐式编解码器定义,我可以使它成为整个包的默认行为。

7 个答案:

答案 0 :(得分:22)

这就是我用java创建的方法:

    FileInputStream input;
    String result = null;
    try {
        input = new FileInputStream(new File("invalid.txt"));
        CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
        decoder.onMalformedInput(CodingErrorAction.IGNORE);
        InputStreamReader reader = new InputStreamReader(input, decoder);
        BufferedReader bufferedReader = new BufferedReader( reader );
        StringBuilder sb = new StringBuilder();
        String line = bufferedReader.readLine();
        while( line != null ) {
            sb.append( line );
            line = bufferedReader.readLine();
        }
        bufferedReader.close();
        result = sb.toString();

    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch( IOException e ) {
        e.printStackTrace();
    }

    System.out.println(result);

使用bytes:

创建无效文件
0x68, 0x80, 0x65, 0x6C, 0x6C, 0xC3, 0xB6, 0xFE, 0x20, 0x77, 0xC3, 0xB6, 0x9C, 0x72, 0x6C, 0x64, 0x94

在UTF-8中hellö wörld,其中混合了4个无效字节。

使用.REPLACE,您会看到正在使用的标准unicode替换字符:

//"h�ellö� wö�rld�"

使用.IGNORE,您会看到忽略无效字节:

//"hellö wörld"

未指定.onMalformedInput,即可获得

java.nio.charset.MalformedInputException: Input length = 1
    at java.nio.charset.CoderResult.throwException(Unknown Source)
    at sun.nio.cs.StreamDecoder.implRead(Unknown Source)
    at sun.nio.cs.StreamDecoder.read(Unknown Source)
    at java.io.InputStreamReader.read(Unknown Source)
    at java.io.BufferedReader.fill(Unknown Source)
    at java.io.BufferedReader.readLine(Unknown Source)
    at java.io.BufferedReader.readLine(Unknown Source)

答案 1 :(得分:14)

scala源代码的解决方案(基于@Esailija答案):

def toSource(inputStream:InputStream): scala.io.BufferedSource = {
    import java.nio.charset.Charset
    import java.nio.charset.CodingErrorAction
    val decoder = Charset.forName("UTF-8").newDecoder()
    decoder.onMalformedInput(CodingErrorAction.IGNORE)
    scala.io.Source.fromInputStream(inputStream)(decoder)
}

答案 2 :(得分:13)

Scala的编解码器有一个解码器字段,它返回java.nio.charset.CharsetDecoder

val decoder = Codec.UTF8.decoder.onMalformedInput(CodingErrorAction.IGNORE)
Source.fromFile(filename)(decoder).getLines().toList

答案 3 :(得分:2)

忽略无效字节的问题是决定它们何时再次有效。请注意,UTF-8允许对字符进行可变长度的字节编码,因此如果一个字节无效,则需要了解要从哪个字节开始读取以再次获取有效的字符流。

简而言之,我认为你找不到一个能够“正确”阅读的图书馆。我认为一种更有效的方法是首先尝试清理这些数据。

答案 4 :(得分:2)

如果失败,我会切换到不同的编解码器。

为了实现这种模式,我从this other stackoverflow question获得灵感。

我使用默认的编解码器列表,并递归地浏览它们。如果它们都失败了,我会打印出可怕的部分:

private val defaultCodecs = List(
  io.Codec("UTF-8"),
  io.Codec("ISO-8859-1")
)

def listLines(file: java.io.File, codecs:Iterable[io.Codec] = defaultCodecs): Iterable[String] = {
  val codec = codecs.head
  val fileHandle = scala.io.Source.fromFile(file)(codec)
  try {
    val txtArray = fileHandle.getLines().toList
    txtArray
  } catch {
    case ex: Exception => {
      if (codecs.tail.isEmpty) {
        println("Exception:  " + ex)
        println("Skipping file:  " + file.getPath)
        List()
      } else {
        listLines(file, codecs.tail)
      }
    }
  } finally {
    fileHandle.close()
  }
}

我刚刚学习Scala,因此代码可能不是最佳的。

答案 5 :(得分:0)

一个简单的解决方案是将您的数据流解释为ASCII,忽略所有非文本字符。但是,即使有效的编码UTF8字符也会丢失。不知道这对你是否可以接受。

编辑:如果您事先知道哪些列是有效的UTF-8,您可以编写自己的CSV解析器,可以配置在哪个列上使用哪种策略。

答案 6 :(得分:0)

使用ISO-8859-1作为编码器;这只会给你打包成字符串的字节值。对于大多数编码,这足以解析CSV。 (如果你有混合的8位和16位块,那么你就麻烦了;你仍然可以读取ISO-8859-1中的行,但是你可能无法将该行解析为块。)< / p>

将单个字段作为单独的字符串后,您可以尝试

new String(oldstring.getBytes("ISO-8859-1"), "UTF-8")

使用正确的编码生成字符串(如果你知道的话,每个字段使用适当的编码名称。)

编辑:如果要检测错误,则必须使用java.nio.charset.Charset.CharsetDecoder。以这种方式映射到UTF-8只会在出现错误时在字符串中给出0xFFFF。

val decoder = java.nio.charset.Charset.forName("UTF-8").newDecoder

// By default will throw a MalformedInputException if encoding fails
decoder.decode( java.nio.ByteBuffer.wrap(oldstring.getBytes("ISO-8859-1")) ).toString