我正在尝试解析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中,我可以通过隐式编解码器使其成为默认行为。我想通过在包对象中加入隐式编解码器定义,我可以使它成为整个包的默认行为。
答案 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