Ich versuche, eine CSV-Datei zu analysieren, idealerweise mit weka.core.converters.CSVLoader. Die Datei, die ich habe, ist jedoch keine gültige UTF-8-Datei. Es handelt sich meistens um eine UTF-8-Datei, aber einige der Feldwerte sind unterschiedlich codiert. Es gibt also keine Codierung, in der die gesamte Datei gültig ist, aber ich muss sie trotzdem analysieren. Abgesehen von der Verwendung von Java - Bibliotheken wie Weka arbeite ich hauptsächlich in Scala. Ich kann die Datei in scala.io nicht einmal lesen. Quelle: Zum Beispiel
Source.
fromFile(filename)("UTF-8").
foreach(print);
wirft:
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)
Ich bin vollkommen glücklich, alle ungültigen Zeichen wegzuwerfen oder sie durch eine Attrappe zu ersetzen. Ich werde eine Menge solcher Texte haben, um sie auf verschiedene Arten zu verarbeiten, und muss sie möglicherweise an verschiedene Bibliotheken von Drittanbietern weitergeben. Eine ideale Lösung wäre eine globale Einstellung, bei der alle Bibliotheken der unteren Ebene Java ungültige Bytes im Text ignorieren, sodass ich Bibliotheken von Drittanbietern für diese Daten ohne Änderung aufrufen kann.
LÖSUNG:
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)
Vielen Dank an + Esailija, die mich in die richtige Richtung gelenkt hat. Dies führte mich zu Wie erkenne ich ungültige UTF-8-Byte-Sequenzen, um sie im Java - Eingabestrom zu ersetzen? , der die Kernlösung Java bereitstellt. In Scala kann ich dies zum Standardverhalten machen, indem ich den Codec implizite. Ich denke, ich kann es zum Standardverhalten für das gesamte Paket machen, indem ich es in die implizite Codec-Definition des Paketobjekts setze.
So habe ich es mit Java geschafft:
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);
Die ungültige Datei wird mit Bytes erstellt:
0x68, 0x80, 0x65, 0x6C, 0x6C, 0xC3, 0xB6, 0xFE, 0x20, 0x77, 0xC3, 0xB6, 0x9C, 0x72, 0x6C, 0x64, 0x94
Welches ist hellö wörld
in UTF-8 mit 4 ungültigen Bytes gemischt.
Mit .REPLACE
Sie sehen das verwendete Standard-Unicode-Ersetzungszeichen:
//"h�ellö� wö�rld�"
Mit .IGNORE
, Sie sehen die ungültigen Bytes ignoriert:
//"hellö wörld"
Ohne Angabe von .onMalformedInput
, du erhältst
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)
Die Lösung für Scala Source (basierend auf der @ Esailija-Antwort):
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's Codec hat ein Decoderfeld, das ein Java.nio.charset.CharsetDecoder
:
val decoder = Codec.UTF8.decoder.onMalformedInput(CodingErrorAction.IGNORE)
Source.fromFile(filename)(decoder).getLines().toList
Ich wechsle zu einem anderen Codec, wenn einer ausfällt.
Um das Muster zu implementieren, habe ich mich von dieser anderen Stapelüberlauf-Frage inspirieren lassen.
Ich verwende eine Standard-Codec-Liste und gehe sie rekursiv durch. Wenn sie alle scheitern, drucke ich die gruseligen Teile aus:
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()
}
}
Ich lerne gerade Scala, daher ist der Code möglicherweise nicht optimal.
Das Problem beim Ignorieren ungültiger Bytes ist dann die Entscheidung, wann sie wieder gültig sind. Beachten Sie, dass UTF-8 Byte-Kodierungen mit variabler Länge für Zeichen zulässt. Wenn ein Byte also ungültig ist, müssen Sie wissen, von welchem Byte Sie lesen müssen, um wieder einen gültigen Zeichenstrom zu erhalten.
Kurz gesagt, ich glaube nicht, dass Sie eine Bibliothek finden, die beim Lesen "korrigieren" kann. Ich denke, ein viel produktiverer Ansatz ist es, zuerst zu versuchen, diese Daten zu bereinigen.
Eine einfache Lösung wäre, Ihren Datenstrom als ASCII zu interpretieren und alle Nicht-Text-Zeichen zu ignorieren. Sie würden jedoch auch gültige UTF8-Zeichen verlieren. Ich weiß nicht, ob das für dich akzeptabel ist.
BEARBEITEN: Wenn Sie im Voraus wissen, welche Spalten für UTF-8 gültig sind, können Sie einen eigenen CSV-Parser schreiben, der konfiguriert werden kann, welche Strategie für welche Spalte verwendet werden soll.
Verwenden Sie ISO-8859-1
Als Encoder. Dadurch erhalten Sie nur die in eine Zeichenfolge gepackten Bytewerte. Dies reicht aus, um CSV für die meisten Codierungen zu analysieren. (Wenn Sie 8-Bit- und 16-Bit-Blöcke gemischt haben, treten Probleme auf. Sie können die Zeilen in ISO-8859-1 weiterhin lesen, können die Zeile jedoch möglicherweise nicht als Block analysieren.)
Sobald Sie die einzelnen Felder als separate Zeichenfolgen haben, können Sie es versuchen
new String(oldstring.getBytes("ISO-8859-1"), "UTF-8")
um die Zeichenfolge mit der richtigen Codierung zu generieren (verwenden Sie den entsprechenden Codierungsnamen pro Feld, falls Sie ihn kennen).
Bearbeiten: Sie müssen Java.nio.charset.Charset.CharsetDecoder
Verwenden, um Fehler zu erkennen. Wenn Sie UTF-8 auf diese Weise zuordnen, erhalten Sie nur 0xFFFF in Ihrer Zeichenfolge, wenn ein Fehler auftritt.
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