我已经设置了一个顺序扫描程序,其中指向我的文件的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)
函数,允许我在文件中来回移动。
答案 0 :(得分:2)
我不完全确定你要做的是什么,但是让我给你一些可能有帮助的信息。
UTF-8编码将字符表示为1,2,3或4个字节,具体取决于字符的Unicode值。
现在这可能看起来非常拜占庭但是它的结果就是:你可以在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。
答案 1 :(得分: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';
}
答案 2 :(得分: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字符串长度的unsigned short。
请注意,此处使用的编码是 modified-UTF-8 ,如DataInput的Javadoc中所述。