如何阅读与斯卡拉或Java混合编码的文本文件?(How to read a text file wi

2019-07-04 06:21发布

我试图解析一个CSV文件,最好使用weka.core.converters.CSVLoader。 但是我有一个文件是不是有效的UTF-8文件。 它基本上是一个UTF-8的文件,但一些字段值都在不同的编码,所以没有编码,其中整个文件是有效的,但我总有需要解析它。 除了使用Java库Weka的一样,我的主要工作在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中的InputStream? 这提供了核心Java解决方案。 在Scala中我还可以这样做编解码器隐含的默认行为。 我想我可以把它放在包对象的隐式编解码器定义,使之成为整个包的默认行为。

Answer 1:

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


Answer 2:

对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)
}


Answer 3:

Scala的编解码器,它返回一个解码器领域java.nio.charset.CharsetDecoder

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


Answer 4:

当他们再次是有效与无效忽略字节的问题,然后决定。 需要注意的是UTF-8允许的字符长度可变字节编码,因此,如果一个字节是无效的,你要明白,开始从阅读中再得到字符的有效流的字节。

总之,我不认为你会发现它能够“正确的”,因为它读取库。 我认为一个更富有成效的做法是尝试先清理这些数据了。



Answer 5:

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

为了实现该模式,我得到了灵感这个计算器等问题 。

我使用的编解码器的默认列表,并递归通过他们去。 如果他们都失败了,我打印出来吓人位:

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,所以代码可能不是最优的。



Answer 6:

一个简单的解决办法是解释数据流为ASCII,忽略所有的非文本字符。 但是,你会输的有效编码UTF8字符。 不知道这是否是你可以接受的。

编辑:如果你事先知道哪些列是有效的UTF-8,你可以写你自己的CSV解析器可以配置成什么柱使用的策略。



Answer 7:

使用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


文章来源: How to read a text file with mixed encodings in Scala or Java?