有没有办法在Java中获取字符串的字节数大小?

4

我需要获取文件中每行的字节数,以便计算文件读取的百分比。我已经使用file.length()获得了文件的大小,但如何获取每行的大小?


你不需要字节,因为文件只包含字符(或者至少在你的问题中没有说明)。只需使用line.length()。 - Simon
不,他想要计算百分比。因此,他首先读取文件的字节长度,然后想要累加字符串字节以计算当前百分比。乍一看似乎很容易,但由于编码问题,实际上并不简单。 - AlexWien
啊,是的,咳咳,我明白了。(抱歉,有点迟钝) - Simon
5个回答

7
final String hello_str = "Hello World";

hello_str.getBytes().length is the "byte size", i.e. the number of bytes

6
你需要知道编码方式,否则问题毫无意义。例如,“foo”在UTF-16中占用6个字节,在ASCII中占用3个字节。假设你是逐行读取的(根据你的问题),你应该知道你正在使用的编码方式,因为你在开始读取时应该已经指定了它。
你可以调用String.getBytes(charset)来获取特定字符串的编码表示。
不要只调用String.getBytes(),因为那会使用平台默认的编码方式。
请注意,所有这些都有点麻烦...你已经读取了字节,将其解码为文本,然后又将其重新编码为字节...

5
您可能使用以下内容来读取文件:
FileInputStream fis = new FileInputStream(path);
BufferedReader br = new BufferedReader(new InputStreamReader(fis, "UTF-8"));
String line;
while ((line = br.readLine()) != null) {
   /* process line */
   /* report percentage */
}

你需要在开头指定编码方式。如果没有指定,Android将使用UTF-8。虽然它是默认值,但可以更改。我认为没有任何设备会这样做。
重复其他答案已经说明的:字符数并不总是等于字节数。特别是UTF编码比较棘手。目前有249,764个指定的Unicode字符和潜在的超过一百万个字符(来源:WP),UTF使用1到4个字节来编码所有这些字符。UTF-32是最简单的情况,因为它始终使用4个字节。UTF-8动态地使用1到4个字节。简单的ASCII字符只使用一个字节。(来源:UTF & BOM FAQ
要获取字节数,您可以使用例如line.getBytes("UTF-8").length()。一个很大的缺点是这非常低效,因为它每次都会创建字符串内部数组的副本,然后丢弃它。这就是Android | Performance Tips中提到的问题#1。
另外,从文件中实际读取的字节数也不是100%准确,原因如下:
  • 例如,UTF-16文本文件通常以特殊的2字节BOM(字节顺序标记)开头,以指示它们是小端还是大端解释。当您查看从读取器获取的String时,这2个(UTF-8:3,UTF-32:4)字节不会被报告。因此,在此处您已经少了一些字节。

  • 将文件的每一行转换为UTF-16 String 将为每行包括这些BOM字节。因此,getBytes 每行将报告多2个字节。

  • 行结束字符不是结果行String的一部分。更糟糕的是,您有不同的方式来表示行的结束。通常是Unix风格的'\n',它只有1个字符,或者是Windows风格的'\r''\n',它是两个字符。 BufferedReader 将简单地跳过它们。在这里,您的计算缺少非常可变数量的字节。从Unix / UTF-8的1个字节到Windows / UTF-32的8个字节。

最后两个原因如果你有Unix/UTF-16,则会互相抵消,但这可能不是典型情况。错误的影响也取决于行长度:如果每行有4个字节的错误,而总共只有10个字节,那么你的进度将会相当错误(如果我的数学没错的话,你的进度将在140%或60%,具体取决于你的计算是否假定每行-4或+4字节)。
这意味着无论你做什么,你都只能得到一个近似值。
获取实际字节数可能可以通过编写自己的特殊字节计数"Reader"来完成,但那需要相当多的工作。
另一种选择是使用自定义"InputStream"来计算从底层流中实际读取了多少字节。这并不难做,并且它不关心编码方式。
大的缺点是,它不会随着你读取的行数线性增加,因为BufferedReader会填充其内部缓冲区并从中读取行,然后从文件中读取下一个块,以此类推。如果缓冲区足够大,则您已经在第一行时就达到了100%。但我假设您的文件足够大,否则您就不会想了解进度。

例如,以下是这样的实现。它可以工作,但我不能保证它是完美的。如果流使用mark()reset(),它将无法正常工作。但文件读取不应该这样做。

static class CountingInputStream extends FilterInputStream {
    private long bytesRead;

    protected CountingInputStream(InputStream in) {
        super(in);
    }

    @Override
    public int read() throws IOException {
        int result = super.read();
        if (result != -1) bytesRead += 1;
        return result;
    }
    @Override
    public int read(byte[] b) throws IOException {
        int result = super.read(b);
        if (result != -1) bytesRead += result;
        return result;
    }
    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int result = super.read(b, off, len);
        if (result != -1) bytesRead += result;
        return result;
    }
    @Override
    public long skip(long n) throws IOException {
        long result = super.skip(n);
        if (result != -1) bytesRead += result;
        return result;
    }

    public long getBytesRead() {
        return bytesRead;
    }
}

使用以下代码。
File file = new File("mytestfile.txt");
int linesRead = 0;
long progress = 0;
long fileLength = file.length();
String line;

CountingInputStream cis = new CountingInputStream(new FileInputStream(file));
BufferedReader br = new BufferedReader(new InputStreamReader(cis, "UTF-8"), 8192);
while ((line = br.readLine()) != null) {
    long newProgress = cis.getBytesRead();
    if (progress != newProgress) {
        progress = newProgress;
        int percent = (int) ((progress * 100) / fileLength);
        System.out.println(String.format("At line: %4d, bytes: %6d = %3d%%", linesRead, progress, percent));
    }
    linesRead++;
}
System.out.println("Total lines: " + linesRead);
System.out.println("Total bytes: " + fileLength);
br.close();

我得到的输出如下:
At line:    0, bytes:   8192 =   5%
At line:   82, bytes:  16384 =  10%
At line:  178, bytes:  24576 =  15%
....
At line: 1621, bytes: 155648 =  97%
At line: 1687, bytes: 159805 = 100%
Total lines: 1756
Total bytes: 159805

或者在同一文件UTF-16编码的情况下。
At line:    0, bytes:  24576 =   7%
At line:   82, bytes:  40960 =  12%
At line:  178, bytes:  57344 =  17%
.....
At line: 1529, bytes: 303104 =  94%
At line: 1621, bytes: 319488 =  99%
At line: 1687, bytes: 319612 = 100%
Total lines: 1756
Total bytes: 319612

不要打印,你可以更新自己的进度。

那么,最好的方法是什么?

  • 如果您知道您有使用仅为这些字符使用1个字节的编码的简单ASCII文本:只需使用String#length()(可能还要添加+1或+2以获取行尾) String#length()快速简单,只要您知道您拥有的文件,您就不应该有任何问题。
  • 如果您有国际文本,简单方法无法解决:
    • 对于需要处理每行时间较长的较小文件:String#getBytes(),处理1行所需的时间越长,则临时数组及其垃圾收集的影响越小。不准确度应在可接受范围内。只需确保在结束时不会崩溃,如果进度> 100%或<100%。
    • 对于较大的文件,请使用上述方法。文件越大,效果越好。以0.001%的步骤更新进度只会减慢事情。减少读取器的缓冲区大小会增加准确性,但也会降低读取性能。
  • 如果您有足够的时间:编写自己的Reader,告诉您确切的字节位置。也许是InputStreamReaderBufferedReader的组合,因为Reader已经在字符上操作。Android的 实现可以作为起点。

0

假设您有一个名为hello_str的字符串变量。

final String hello_str = "Hello World";

 //Check Character length
 hello_str.length() //output will be 11
 // Check encoded sizes
 final byte[] utf8Bytes = hello_str.getBytes("UTF-8");
 utf8Bytes.length  //output will be 11

 final byte[] utf16Bytes= hello_str.getBytes("UTF-16");
 utf16Bytes.length // output will be "24"

  final byte[] utf32Bytes = hello_str.getBytes("UTF-32");
  utf32Bytes.length // output will be "44"

0
如果文件是ASCII文件,则可以使用String.length();否则就会变得更加复杂。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接