55

我正在尝试解析 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 为我指明了正确的方向。这导致我如何检测非法 UTF-8 字节序列以在 java 输入流中替换它们? 它提供了核心的 java 解决方案。在 Scala 中,我可以通过使编解码器隐式来使其成为默认行为。我想我可以通过将隐式编解码器定义放在包对象中来使其成为整个包的默认行为。

4

7 回答 7

29

这就是我设法用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);

无效文件是用字节创建的:

0x68, 0x80, 0x65, 0x6C, 0x6C, 0xC3, 0xB6, 0xFE, 0x20, 0x77, 0xC3, 0xB6, 0x9C, 0x72, 0x6C, 0x64, 0x94

hellö wörld采用 UTF-8 格式,混入了 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)
于 2012-11-29T13:04:42.300 回答
17

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

val decoder = Codec.UTF8.decoder.onMalformedInput(CodingErrorAction.IGNORE)
Source.fromFile(filename)(decoder).getLines().toList
于 2015-08-31T14:40:40.460 回答
15

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)
}
于 2014-09-22T15:03:39.020 回答
2

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

简而言之,我认为您不会找到一个在阅读时可以“纠正”的库。我认为一个更有成效的方法是首先尝试清理这些数据。

于 2012-11-29T11:46:41.950 回答
2

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

为了实现该模式,我从另一个 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,所以代码可能不是最优的。

于 2014-10-31T13:21:16.563 回答
0

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

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

于 2012-11-29T11:51:11.133 回答
0

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

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

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
于 2012-11-29T12:10:17.493 回答