Как читать текстовый файл со смешанными кодировками в Scala или Java?
Я пытаюсь разобрать CSV файл, в идеале используя weka.core.converters.CSVLoader.
Однако файл, который у меня есть, не является допустимым файлом UTF-8.
Это в основном файл UTF-8, но некоторые из значений поля находятся в разных кодировках,
поэтому нет кодировки, в которой весь файл действителен,
но мне все равно нужно разбирать его.
Помимо использования java-библиотек, таких как Weka, я в основном работаю в Scala.
Я даже не могу прочитать файл usin 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 я могу сделать это по умолчанию, сделав кодек неявным. Я думаю, что могу сделать это по умолчанию для всего пакета, поставив его в неявное определение кодека в объекте пакета.
Ответы
Ответ 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
вы видите используемый стандартный символ замены юникода:
//"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)
Ответ 2
Решение для scala Source (на основе ответа @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)
}
Ответ 3
Scala Codec имеет поле декодера, которое возвращает java.nio.charset.CharsetDecoder
:
val decoder = Codec.UTF8.decoder.onMalformedInput(CodingErrorAction.IGNORE)
Source.fromFile(filename)(decoder).getLines().toList
Ответ 4
Проблема с игнорированием неверных байтов затем решает, когда они снова действительны. Обратите внимание, что UTF-8 позволяет кодировать байты переменной длины для символов, поэтому, если байт недействителен, вам нужно понять, какой байт начать читать, чтобы снова получить действительный поток символов.
Короче говоря, я не думаю, что вы найдете библиотеку, которая может "исправить", как она читает. Я думаю, что гораздо более продуктивный подход - сначала попытаться очистить эти данные.
Ответ 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, поэтому код может быть не оптимальным.
Ответ 6
Простым решением будет интерпретировать ваш поток данных как ASCII, игнорировать все нетекстовые символы. Однако вы потеряете даже допустимые кодированные символы UTF8. Не знаю, приемлемо ли это для вас.
EDIT: Если вы заранее знаете, какие столбцы являются допустимыми UTF-8, вы можете написать свой собственный синтаксический анализатор CSV, который можно настроить, какую стратегию использовать в каком столбце.
Ответ 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