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

Fer*_*ago 2 java android

我需要文件中每行的大小(以字节为单位),因此我可以获得读取文件的百分比.我已经获得了文件的大小file.length(),但是如何获得每行的大小?

Jon*_*eet 6

你需要知道编码 - 否则这是一个毫无意义的问题.例如,"foo"是UTF-16中的6个字节,但是ASCII中是3个字节.假设你一次读一行(给出你的问题),你应该知道你正在使用哪种编码,因为你应该在开始阅读时指定它.

您可以调用String.getBytes(charset)以获取特定字符串的编码表示.

不要只是调用String.getBytes()如将使用平台默认的编码.

请注意,所有这些都是有点工作...你已经读取了字节,将它们解码为文本,然后你将它们重新编码为字节......


小智 6

final String hello_str = "Hello World";

hello_str.getBytes().length is the "byte size", i.e. the number of bytes
Run Code Online (Sandbox Code Playgroud)


zap*_*apl 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 */
}
Run Code Online (Sandbox Code Playgroud)

您需要在开始时指定编码。如果不这样做,您应该在 Android 上获取 UTF-8。这是默认值,但可以更改。我认为没有设备可以做到这一点。

重复其他答案已经说过的内容:字符数并不总是与字节数相同。尤其是 UTF 编码很棘手。目前分配的 Unicode 字符有 249,764 个,可能超过一百万个 ( WP ),而 UTF 使用 1 到 4 个字节来对所有这些字符进行编码。UTF-32 是最简单的情况,因为它始终使用 4 个字节。UTF-8 动态地执行此操作并使用 1 到 4 个字节。简单的 ASCII 字符仅使用 1 个字节。(来源:UTF & BOM 常见问题解答

要获取字节数,您可以使用例如line.getBytes("UTF-8").length()。一个很大的缺点是,这是非常低效的,因为它每次都会创建 String 内部数组的副本,然后将其丢弃。这是 Android 上的#1 | 性能技巧

由于以下原因,从文件读取的实际字节数也不是 100% 准确:

  • 例如,UTF-16 文本文件通常以特殊的 2 字节 BOM(字节顺序标记)开头,以表明它们是否必须解释小端或大端。String当您仅查看从阅读器获得的信息时,不会报告这 2 个(UTF-8: 3、UTF-32: 4)字节。所以你已经落后了一些字节。

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

  • 行结束字符不是结果行的一部分String。更糟糕的是,您有不同的方式来表示线路结束。通常,Unix 风格'\n'只有 1 个字符,Windows 风格'\r''\n'则有 2 个字符。将BufferedReader简单地跳过这些。这里你的计算丢失了非常可变的字节数。从 Unix/UTF-8 的 1 字节到 Windows/UTF-32 的 8 字节。

如果您使用 Unix/UTF-16,最后两个原因会相互抵消,但这可能不是典型情况。错误的影响还取决于行长度:如果每行有 4 个字节的错误,而总共只有 10 个字节长,那么您的进度将出现相当大的错误(如果我的数学很好,您的进度将是 140%)或最后一行之后的 60%,具体取决于您的计算是否假设每行 -4 或 +4 字节)

这意味着到目前为止,无论您做什么,您都只能得到一个近似值。

如果您编写自己的特殊字节计数,则可能可以获得实际的字节计数,Reader但这将是相当大量的工作。

另一种方法是使用自定义的方法InputStream来计算实际从底层流中读取的字节数。这并不难做到,而且它不关心编码。

最大的缺点是它不会随着您读取的行数线性增加,因为BufferedReader它将填充其内部缓冲区并从那里读取行,然后从文件中读取下一个块,依此类推。如果缓冲区足够大,则第一行已经达到 100%。但我假设您的文件足够大,否则您不会想了解进度。

例如,这就是这样的实现。它有效,但我不能保证它是完美的。mark()如果流使用and ,它将不起作用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;
    }
}
Run Code Online (Sandbox Code Playgroud)

使用以下代码

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();
Run Code Online (Sandbox Code Playgroud)

我得到的输出像

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
Run Code Online (Sandbox Code Playgroud)

或者如果是相同文件 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
Run Code Online (Sandbox Code Playgroud)

您可以更新进度,而不是打印它。

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

  • 如果您知道您有简单的 ASCII 文本,其编码仅使用 1 个字节来表示这些字符:只需使用String#length()(并且可能添加 +1 或 +2 作为行结尾) String#length()即可快速且简单,并且只要您知道要读取的文件有的话应该没有问题。
  • 如果您有国际文本,而简单的方法不起作用:
    • 对于处理每一行需要相当长的时间的较小文件:String#getBytes(),处理 1 行花费的时间越长,临时数组及其垃圾收集的影响越小。误差应在可接受的范围内。只要确保进度最后 > 100% 或 < 100% 时不会崩溃。
    • 对于较大的文件,上述方法。文件越大越好。以 0.001% 的步长更新进度只会减慢速度。减小读取器的缓冲区大小会提高准确性,但也会降低读取性能。
  • 如果您有足够的时间:编写您自己的 Reader 来告诉您确切的字节位置。InputStreamReader可能是和的组合,BufferedReader因为 Reader 已经对字符进行了操作。Android 的 实现可能有助于作为起点。