簡體   English   中英

使用RandomAccessFile讀取單個UTF-8字符

[英]Reading a single UTF-8 character with RandomAccessFile

我已經設置了一個順序掃描器,其中指向我的文件的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()但是它將整行返回為String,這不是我想要的。

另外,我不能簡單地使用另一個流讀取器,因為我的程序需要一個seek(int)函數,允許我在文件中來回移動。

我不確定您要做什么,但是讓我給您一些可能有用的信息。

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字符,則只需應用上述算法即可找到該字符的第一個字節(如果不是指定位置的那個字節) ),然后讀取並解碼該值。

有關從源字節解碼UTF-8的方法,請參見Java Charset類。 也許有更簡單的方法,但是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。

從威利斯·布萊克本(Willis Blackburn)的答案出發,我可以簡單地進行一些整數檢查,以確保它們超過一定數量,以獲取需要提前檢查的字符數。

從下表判斷:

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';
}

java.io.DataInputStream.readUTF(DataInput)的case語句,您可以派生出類似

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字符串長度。

請注意,如DataInput的Javadoc中所述,此處使用的編碼是經過修改的UTF-8

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM