【问题标题】:Converting a byte array to string without using new operator in Java在 Java 中不使用 new 运算符将字节数组转换为字符串
【发布时间】:2010-07-10 00:22:01
【问题描述】:

除了使用new String(bytearray)之外,还有其他方法可以将字节数组转换为字符串吗?确切的问题是我通过 UDP 连接在网络上传输一个 json 格式的字符串。在另一端,我在一个固定大小的字节数组中接收它(因为我不知道数组大小)并从字节数组中创建一个新字符串。如果我这样做,我分配的整个内存都是不必要的。

为了避免这种情况,我将字节数组转换为字符串,将字符串截断到最后一个有效字符,然后将其转换为字节数组并从中创建一个新字符串。如果我这样做,它只会用完所需的内存,但垃圾收集频率会变得如此之高,因为它涉及更多数量的分配。做这个的最好方式是什么?

【问题讨论】:

  • 字节数组的编码是什么?
  • 目的地是什么?您可能希望立即将其写入目的地,而不是在byte[]String 中进行中介。

标签: java string performance bytearray


【解决方案1】:

会是这样的:

String s = new String( bytearray, 0, lenOfValidData, "US-ASCII");

做你想做的(将字符集更改为任何合适的编码)?


更新:

根据您的 cmets,您可能想尝试:

socket.receive(packet);
String strPacket = new String( packet.getData(), 0, packet.getLength(), "US-ASCII");
receiver.onReceive( strPacket);

我对 Java 的数据报支持不够熟悉,无法知道 packet.getLength() 是否返回数据报的截断长度或原始长度(在截断以适合接收缓冲区之前)。像这样创建字符串可能更安全:

String strPacket = new String( packet.getData(), 
                               0, 
                               Math.min( packet.getLength(), packet.getData().length),
                               "US-ASCII");

再说一遍,这可能是不必要的。

【讨论】:

  • 这里的问题是数据包中字符串的长度不同。所以我不知道这里的 lenOfValidData 。有没有办法在不知道的情况下做到这一点?此外,使用 new String 会导致大量 GC,因为我的字符串大小通常为 8k-10k。
  • 你现在如何确定有效数据的长度(不知何故在第一次转换为字符串之后)?至于避免new String - 如果您需要字符串中的数据,则必须在某个时候发生(即使它隐藏在返回字符串对象的方法中)。您应该能够做的是避免创建中间字符串和/或字节数组对象。
  • 另外 - 在我看来,无论从 UDP 数据包中填充字节数组的内容,都应该让您知道它放入缓冲区/数组中的数据量。您应该将该信息用作lenOfValidData 参数。
  • 我知道我的字符串的最后一个字符,因为它是 json 格式的。所以我可以在 String 上使用 substring() 方法来只获取所需的字符。
  • 这是我试过的两个sn-ps。 #1 socket.receive(数据包);字符串 strPacket = new String(packet.getData()); receiver.onReceive(strPacket.substring(0,strPacket.indexOf('}')+1)));这个保留了不必要的内存。 #2 socket.receive(数据包);字符串 strPacket = new String(packet.getData()); receiver.onReceive(new String(strPacket.substring(0,strPacket.indexOf('}')+1)).getBytes());这个导致 GC 的频率是第一个的两倍。
【解决方案2】:

最简单、最可靠的方法是使用从 UDP 套接字读取的数据包长度。 DatagramSocket.receive(...) 的 javadoc 是这样说的:

从这个套接字接收一个数据报包。当此方法返回时,DatagramPacket 的缓冲区被接收到的数据填充。数据报包还包含发送者的 IP 地址,以及发送者机器上的端口号。

此方法阻塞,直到收到数据报。 数据报数据包对象的长度字段包含接收到的消息的长度。如果消息比数据包的长度长,则消息被截断。

如果你不能这样做,那么下面将分配一个最小大小的字符串,而不会分配不必要的临时对象。

  byte[] buff = ... // read from socket.

  // Find byte offset of first 'non-character' in buff
  int i;
  for (i = 0; i < buff.length && /* buff[i] represents a character */; i++) { /**/ }

  // Allocate String
  String res = new String(buff, 0, i, charsetName);

请注意,确定非字符的标准是字符集和应用程序特定的。但可能测试零字节就足够了。

编辑

javadoc 究竟是什么意思 “新字符串的长度是字符集的函数,因此可能不等于子数组的长度。”

这表明对于某些字符编码(例如 UTF-8、UTF-16、JIS 等),某些字符由两个或多个字节表示。例如,10 个字节的 UTF-8 可能表示少于 10 个字符。

【讨论】:

  • +1 javadoc 中的“新字符串的长度是字符集的函数,因此可能不等于子数组的长度”究竟是什么意思。 (这是属于您建议的构造函数的注释。)
  • socket.receive(packet); String strPacket = new String( packet.getData(), 0, packet.getLength(), "US-ASCII");接收器.onReceive(strPacket);这对我来说很好。你能解释一下我什么时候应该检查零字节吗?
  • UNICODE 的问题在于,无论您使用什么 UTF,都不能保证它会是那个长度。我不确定即使是 UTF-32 是否也能保证这一点。例如,即使 UTF-16 默认使用 16 位,您最终也可以在 UTF-16 字符中使用 32 位。他们不得不延长它。
【解决方案3】:

您可以通过使用StringBuilder 来避免创建第二个字符串。我想你的数据接收过程是这样的:

  1. 在客户端获取(固定大小)字节数组。
  2. 创建一个 StringBuilder 对象。
  3. 只要您读取有效字符并将它们附加到 StringBuilder 对象,就循环遍历数组。
  4. 现在可以丢弃字节数组。 (我宁愿保留它,下次您通过网络收到一些东西,以避免不必要的内存分配。)
编辑

我按照 Tofubeer 的建议使用 StringBuilder 而不是 StringBuffer

【讨论】:

  • StringBuilder 反而更快,因为它不同步。您也可以创建一个 StringBuilder 并在最后将长度设置为零。
  • 其实,如果你看看 StringBuilder 和 StringBuffer 是如何管理它们内部的字符数组的,你会发现它们也可能会过度分配。
  • 字符串太长。大概有 8k-10k 个字符长。循环它并创建一个 StringBuilder 是个好主意吗?如果我理解错误,请告诉我。
  • 另外,最后的StringBuilder.toString() 调用将需要再次复制字符数组。
  • @rajaramyadhav 由于确定字符串长度的唯一方法是查看其字符,我无法想象您如何避免循环并同时创建最小长度的字符串。从 Stephen C 的回答来看,String(byte[] bytes, int offset, int length, String charsetName) 构造函数似乎没有任何隐藏分配,所以我建议你试试他的方法。
【解决方案4】:

您能否先将输入流写入ByteArrayOutputStream,然后在输出流上调用toString?所以是这样的:

ByteArrayOutputStream os = new ByteArrayOutputStream();
while (!socket.isClosed()) {
    InputStream is = socket.getInputStream();
    byte[] buffer = new byte[1024]; // some tmp buffer.  Define the appropriate size here
    int bytesRead;
    while ((bytesRead = is.read(buffer)) != -1) {
        baos.write(buffer, 0, bytesRead);
        if (is.available() <= 0) {
            break;
        }
    }
    System.out.println(baos.toString());
    baos.reset();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-06-03
    • 2011-08-06
    • 2013-12-19
    • 2011-07-26
    • 1970-01-01
    • 1970-01-01
    • 2015-01-14
    相关资源
    最近更新 更多