19

我有一种情况,我需要知道String/encoding 对的大小(以字节为单位),但不能使用该getBytes()方法,因为 1)String非常大并且Stringbyte[]数组中复制会使用大量内存,但更多的是第 2 点)根据每个字符的最大可能字节的长度getBytes()分配一个byte[]数组。String因此,如果我有String1.5B 字符和 UTF-16 编码,getBytes()将尝试分配 3GB 数组并失败,因为数组限制为 2^32 - X 字节(X 是特定于 Java 版本的)。

那么 - 有没有办法String直接从String对象计算 /encoding 对的字节大小?

更新:

这是 jtahlborn 的答案的工作实现:

private class CountingOutputStream extends OutputStream {
    int total;

    @Override
    public void write(int i) {
        throw new RuntimeException("don't use");
    }
    @Override
    public void write(byte[] b) {
        total += b.length;
    }

    @Override public void write(byte[] b, int offset, int len) {
        total += len;
    }
}
4

5 回答 5

12

很简单,只需将其写入一个虚拟输出流:

class CountingOutputStream extends OutputStream {
  private int _total;

  @Override public void write(int b) {
    ++_total;
  }

  @Override public void write(byte[] b) {
    _total += b.length;
  }

  @Override public void write(byte[] b, int offset, int len) {
    _total += len;
  }

  public int getTotalSize(){
     _total;
  }
}

CountingOutputStream cos = new CountingOutputStream();
Writer writer = new OutputStreamWriter(cos, "my_encoding");
//writer.write(myString);

// UPDATE: OutputStreamWriter does a simple copy of the _entire_ input string, to avoid that use:
for(int i = 0; i < myString.length(); i+=8096) {
  int end = Math.min(myString.length(), i+8096);
  writer.write(myString, i, end - i);
}

writer.flush();

System.out.println("Total bytes: " + cos.getTotalSize());

它不仅简单,而且可能与其他“复杂”答案一样快。

于 2013-11-08T19:43:12.480 回答
2

使用 apache-commons 库也是如此:

public static long stringLength(String string, Charset charset) {

    try (NullOutputStream nul = new NullOutputStream();
         CountingOutputStream count = new CountingOutputStream(nul)) {

        IOUtils.write(string, count, charset.name());
        count.flush();
        return count.getCount();
    } catch (IOException e) {
        throw new IllegalStateException("Unexpected I/O.", e);
    }
}
于 2017-10-30T14:56:30.127 回答
1

这是一个明显有效的实现:

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

public class TestUnicode {

    private final static int ENCODE_CHUNK = 100;

    public static long bytesRequiredToEncode(final String s,
            final Charset encoding) {
        long count = 0;
        for (int i = 0; i < s.length(); ) {
            int end = i + ENCODE_CHUNK;
            if (end >= s.length()) {
                end = s.length();
            } else if (Character.isHighSurrogate(s.charAt(end))) {
                end++;
            }
            count += encoding.encode(s.substring(i, end)).remaining() + 1;
            i = end;
        }
        return count;
    }

    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 100; i++) {
            sb.appendCodePoint(11614);
            sb.appendCodePoint(1061122);
            sb.appendCodePoint(2065);
            sb.appendCodePoint(1064124);
        }
        Charset cs = StandardCharsets.UTF_8;

        System.out.println(bytesRequiredToEncode(new String(sb), cs));
        System.out.println(new String(sb).getBytes(cs).length);
    }
}

输出是:

1400
1400

在实践中,我会增加到ENCODE_CHUNK10MChars 左右。

可能比 brettw 的答案效率略低,但实现起来更简单。

于 2013-11-08T19:23:57.607 回答
1

Guava 根据这篇文章有一个实现:

Utf8.encodedLength()

于 2020-04-13T21:24:12.440 回答
-2

好吧,这太恶心了。我承认这一点,但是这些东西被 JVM 隐藏了,所以我们必须稍微挖掘一下。并且流了一点汗。

首先,我们想要实际的 char[] 支持 String 而不制作副本。为此,我们必须使用反射来获取“值”字段:

char[] chars = null;
for (Field field : String.class.getDeclaredFields()) {
    if ("value".equals(field.getName())) {
        field.setAccessible(true);
        chars = (char[]) field.get(string); // <--- got it!
        break;
    }
}

接下来,您需要实现java.nio.ByteBuffer. 就像是:

class MyByteBuffer extends ByteBuffer {
    int length;            
    // Your implementation here
};

忽略所有的getter,实现所有的put方法put(byte)putChar(char)等等。在类似的内部put(byte),将长度增加 1,在put(byte[])增加长度的内部增加数组长度。得到它?放置的所有内容,您将其大小添加到length。但是你没有在你的 中存储任何东西ByteBuffer,你只是在数数和扔掉,所以没有空间。如果您对put方法进行断点,您可能会找出您实际需要实现的方法。 putFloat(float)例如,可能不使用。

现在是大结局,把它们放在一起:

MyByteBuffer bbuf = new MyByteBuffer();         // your "counting" buffer
CharBuffer cbuf = CharBuffer.wrap(chars);       // wrap your char array
Charset charset = Charset.forName("UTF-8");     // your charset goes here
CharsetEncoder encoder = charset.newEncoder();  // make a new encoder
encoder.encode(cbuf, bbuf, true);               // do it!
System.out.printf("Length: %d\n", bbuf.length); // pay me US$1,000,000
于 2013-11-08T08:27:15.420 回答