2017-02-17 14 views
2

Я настроил последовательный сканер, где RandomAccessFile указывая на мой файл может читать один символ, с помощью приведенной ниже способом:Чтение одного UTF-8 символов с RandomAccessFile

public char nextChar() { 
    try { 
     seekPointer++; 
     int i = source.read(); 
     return i > -1 ? (char) i : '\0'; // INFO: EOF character is -1. 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 
    return '\0'; 
} 

seekPointer является лишь ссылкой для моей программы, но метод хранит source.read() в int, а затем возвращает его в char, если это не конец файла. Но эти символы, которые я получаю, находятся в формате ASCII, вызывают его так плохо, что я даже не могу использовать такой символ, как ç.

Есть ли способ получить символ , то есть в формате UTF-8 или по крайней мере что-то стандартизованное, что позволяет использовать не только набор символов ASCII?

Я знаю, что могу использовать readUTF(), но это возвращает целую строку в виде строки, которая не является тем, чем я занимаюсь.

Кроме того, я не могу просто использовать другой считыватель потоков, потому что для моей программы требуется функция seek(int), позволяющая мне перемещаться вперед и назад в файле.

+1

InputStreamReader? –

+0

@TamasHegedus Обновлен вопрос. Мне нужна функция поиска. – finnrayment

+0

Как @WillisBlackburn указывает в своем подробном ответе ниже, вы не можете выбрать случайное смещение байта в файле UTF-8 и гарантированно получить «символ». Возможно, вам придется выполнить резервное копирование, чтобы найти начало многобайтовой последовательности. Это то, что вы имели в виду? –

ответ

2

Я не совсем уверен, что вы пытаетесь сделать, но позвольте мне дать вам некоторую информацию, которая может помочь.

Кодировка UTF-8 представляет символы как 1, 2, 3 или 4 байта в зависимости от значения символа Unicode.

  • Для символов 0x00-0x7F UTF-8 кодирует символ как один байт. Это очень полезное свойство, потому что если вы используете только 7-битные символы ASCII, кодировки UTF-8 и ASCII идентичны.
  • Для символов 0x80-0x7FF UTF-8 использует 2 байта: первый байт является бинарным 110, за которым следуют 5 старших бит символа, а второй - двоичный 10, за которым следуют 6 младших бит символа.
  • 3- и 4-байтовые кодировки аналогичны 2-байтовому кодированию, за исключением того, что первый байт 3-байтового кодирования начинается с 1110, а первый байт 4-байтового кодирования начинается с 11110.
  • Для получения подробной информации см. Wikipedia.

Сейчас это может показаться довольно византийский, но результат этого заключается в следующем: вы можете прочитать любой байт в файле UTF-8, и знаете ли вы, глядя на автономный характер, первый байт многобайтовый символ или один из других байтов многобайтового символа.

Если байт, который вы читаете, начинается с двоичного кода 0, вы смотрите на однобайтовый символ. Если он начинается с 110, 1110 или 11110, то у вас есть первый байт многобайтового символа из 2, 3 или 4 байта, соответственно. Если он начинается с 10, то это один из последующих байтов многобайтового символа; разверните назад, чтобы найти его начало.

Так что если вы хотите, чтобы ваш вызывающий абонент искал любую случайную позицию в файле и читал символ UTF-8, вы можете просто применить алгоритм выше, чтобы найти первый байт этого символа (если это не тот в указанной позиции), а затем считывать и декодировать значение.

См. Класс Java Charset для метода декодирования UTF-8 из исходных байтов. Там могут быть более простые способы, но Charset будет работать.

Обновление: Этот код должен обрабатывать 1- и 2-байтовые случаи UTF-8. Не тестировалось вообще, YMMV.

for (;;) { 
    int b = source.read(); 
    // Single byte character starting with binary 0. 
    if ((b & 0x80) == 0) 
     return (char) b; 
    // 2-byte character starting with binary 110. 
    if ((b & 0xE0) == 0xC0) 
     return (char) ((b & 0x1F) << 6 | source.read() & 0x3F); 
    // 3 and 4 byte encodings left as an exercise... 
    // 2nd, 3rd, or 4th byte of a multibyte char starting with 10. 
    // Back up and loop. 
    if ((b & 0xC0) == 0xF0) 
     source.seek(source.getFilePosition() - 2); 
} 

Я бы не стал искать seekPointer. RandomAccessFile знает, что это такое; просто позвоните getFilePosition, когда вам это нужно.

+0

Не могли бы вы привести мне пример? Я пытаюсь сделать «алгоритм» с байтовыми проверками, но он никуда не денется ... – finnrayment

+0

Ну, похоже, мне удалось создать алгоритм, я просто сделаю некоторые проверки и посмотрю, работает ли он полностью. – finnrayment

+0

Да, 'seekPointer' для других вещей, которые я использую, я включил его только потому, что использую его в методе. Я использую его для поиска между символами и строками файла, поэтому я могу ссылаться на * где * символы на самом деле находятся в строке/позиции файла. – finnrayment

0

Из сазе в java.io.DataInputStream.readUTF(DataInput) вы можете получить что-то вроде

public static char readUtf8Char(final DataInput dataInput) throws IOException { 
    int char1, char2, char3; 

    char1 = dataInput.readByte() & 0xff; 
    switch (char1 >> 4) { 
     case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: 
      /* 0xxxxxxx*/ 
      return (char)char1; 
     case 12: case 13: 
      /* 110x xxxx 10xx xxxx*/ 
      char2 = dataInput.readByte() & 0xff; 
      if ((char2 & 0xC0) != 0x80) { 
       throw new UTFDataFormatException("malformed input"); 
      } 
      return (char)(((char1 & 0x1F) << 6) | (char2 & 0x3F)); 
     case 14: 
      /* 1110 xxxx 10xx xxxx 10xx xxxx */ 
      char2 = dataInput.readByte() & 0xff; 
      char3 = dataInput.readByte() & 0xff; 
      if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80)) { 
       throw new UTFDataFormatException("malformed input"); 
      } 
      return (char)(((char1 & 0x0F) << 12) | ((char2 & 0x3F) << 6) | ((char3 & 0x3F) << 0)); 
     default: 
      /* 10xx xxxx, 1111 xxxx */ 
      throw new UTFDataFormatException("malformed input"); 
    } 
} 

Обратите внимание, что RandomAccessFile реализует DataInput, следовательно, вы можете передать его описанным выше способом. Прежде чем вызывать его для первого символа, вам нужно прочитать строковый символ без знака, который представляет длину строки UTF.

Обратите внимание, что используемая здесь кодировка изменена-UTF-8, как описано в Javadoc DataInput.

2

Построение из ответа Уиллиса Блэкберна, я могу просто выполнить некоторые целые проверки, чтобы убедиться, что они превышают определенное число, чтобы получить количество символов, которые мне нужно проверить заранее.

Судя по следующей таблице:

first byte starts with 0       1 byte char 
first byte starts with 10 >= 128 && <= 191 ? byte(s) char 
first byte starts with 11  >= 192   2 bytes char 
first byte starts with 111  >= 224   3 bytes char 
first byte starts with 1111  >= 240   4 bytes char 

Мы можем проверить целое число, считанное из RandomAccessFile.read() пути сравнения его с числами в средней колонке, которые буквально только целые представления одного байта. Это позволяет нам полностью пропускать преобразование байтов, экономя время.

Следующий код, будет считывать символ из RandomAccessFile, с байтовой длиной 1-4:

int seekPointer = 0; 
RandomAccessFile source; // initialise in your own way 

public void seek(int shift) { 
    seekPointer += shift; 
    if (seekPointer < 0) seekPointer = 0; 
    try { 
     source.seek(seekPointer); 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 
} 

private int byteCheck(int chr) { 
    if (chr == -1) return 1; // eof 
    int i = 1; // theres always atleast one byte 
    if (chr >= 192) i++; // 2 bytes 
    if (chr >= 224) i++; // 3 bytes 
    if (chr >= 240) i++; // 4 bytes 
    if (chr >= 128 && chr <= 191) i = -1; // woops, we're halfway through a char! 
    return i; 
} 

public char nextChar() { 
    try { 
     seekPointer++; 
     int i = source.read(); 

     if (byteCheck(i) == -1) { 
      boolean malformed = true; 
      for (int k = 0; k < 4; k++) { // Iterate 3 times. 
       // we only iterate 3 times because the maximum size of a utf-8 char is 4 bytes. 
       // any further and we may possibly interrupt the other chars. 
       seek(-1); 
       i = source.read(); 
       if (byteCheck(i) != -1) { 
        malformed = false; 
        break; 
       } 
      } 
      if (malformed) { 
       seek(3); 
       throw new UTFDataFormatException("Malformed UTF char at position: " + seekPointer); 
      } 
     } 

     byte[] chrs = new byte[byteCheck(i)]; 
     chrs[0] = (byte) i; 

     for (int j = 1; j < chrs.length; j++) { 
      seekPointer++; 
      chrs[j] = (byte) source.read(); 
     } 

     return i > -1 ? new String(chrs, Charset.forName("UTF-8")).charAt(0) : '\0'; // EOF character is -1. 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 
    return '\0'; 
} 
+0

Скорее всего, это правильно. Вы должны решить, что вы хотите сделать, если байт начинается с 10 (другими словами> = 128). В этом случае вы просматриваете байт в середине символа и должны либо выполнять резервное копирование, либо читать вперед, пока не найдете стартовый байт. –

+0

Как насчет голосования? :-) –

+0

@WillisBlackburn Хорошо, как я разработал свою программу, мне на самом деле это не понадобится, но она будет хорошей кривой обучения, поэтому я пойду сейчас! – finnrayment

 Смежные вопросы

  • Нет связанных вопросов^_^