我试图解析一个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中我还可以这样做编解码器隐含的默认行为。 我想我可以把它放在包对象的隐式编解码器定义,使之成为整个包的默认行为。
这就是我如何设法用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)
对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)
}
Scala的编解码器,它返回一个解码器领域java.nio.charset.CharsetDecoder
:
val decoder = Codec.UTF8.decoder.onMalformedInput(CodingErrorAction.IGNORE)
Source.fromFile(filename)(decoder).getLines().toList
当他们再次是有效与无效忽略字节的问题,然后决定。 需要注意的是UTF-8允许的字符长度可变字节编码,因此,如果一个字节是无效的,你要明白,开始从阅读中再得到字符的有效流的字节。
总之,我不认为你会发现它能够“正确的”,因为它读取库。 我认为一个更富有成效的做法是尝试先清理这些数据了。
如果失败,我切换到不同的编解码器。
为了实现该模式,我得到了灵感这个计算器等问题 。
我使用的编解码器的默认列表,并递归通过他们去。 如果他们都失败了,我打印出来吓人位:
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,所以代码可能不是最优的。
一个简单的解决办法是解释数据流为ASCII,忽略所有的非文本字符。 但是,你会输的有效编码UTF8字符。 不知道这是否是你可以接受的。
编辑:如果你事先知道哪些列是有效的UTF-8,你可以写你自己的CSV解析器可以配置成什么柱使用的策略。
使用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