【问题标题】:How to aggregate two Strings and a List<byte[]> into a single byte[] and then extract them如何将两个字符串和一个 List<byte[]> 聚合成一个 byte[] 然后提取它们
【发布时间】:2011-12-13 19:37:34
【问题描述】:

我需要将 2 个字符串和一个列表聚合成一个 byte[] 以便通过网络发送(使用具有函数 send(byte[]) 的特殊库。

然后,在另一端,我需要取回 3 个不同的对象。

我做了一个丑陋的实现,但它很慢。基本上,我所做的是

        public byte[] myserializer(String dataA, String dataB, List<byte[]> info) {

        byte[] header = (dataA +";" + dataB + ";").getBytes();

        int numOfBytes = 0;
        for (byte[] bs : info) {
            numOfBytes += bs.length;
        }

        ByteArrayOutputStream b = new ByteArrayOutputStream();
        ObjectOutputStream o;
        try {
            o = new ObjectOutputStream(b);
            o.writeObject(info);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        byte[] data = b.toByteArray();

        int length = header.length + data.length;
        byte[] headerLength = (new Integer(header.length)).toString()
                .getBytes();
        byte[] pattern = ";".getBytes();
        int finalLength = headerLength.length + pattern.length + length;

        byte[] total = new byte[finalLength];enter code here
        total = // Copy headerLength, header and total into byte[] total

        return return;

本质上,我正在创建一种看起来像这样的框架

      HEADER                    INFO

(---------------------------------------------- -)(----------------------------------) HEADER_LENGHT;DATA_A;DATA_B;SERIALIZED_LIST_OBJECT

然后,在接收方,我执行逆过程,这就是“全部”。这行得通,但效率低下且丑陋。

建议?最佳实践?想法?

哦...再多提一点:这也适用于 J2SE 和 Android

非常感谢!

【问题讨论】:

  • 可能,序列化是性能成本的重要组成部分。所以你可能想自己序列化列表。一个明显的问题是字节数​​组之间的分隔符。如果您知道有任何字节不会在数组中,则可以简化此操作。否则,您必须使用某种转义。
  • 是的,这就是我想要做的,但它有点复杂。我不能对 INFO 块使用任何分隔符,因为它可以是二进制数据。我正在考虑将列表元素的数量及其长度作为标题的一部分添加......这样你就不需要分隔符了。我尝试了这种方法,但它以意大利面条代码结束。我选择的答案,它试图做到这一点,但以一种更简洁的方式!

标签: java android sockets io bytearray


【解决方案1】:

将其全部写入ByteArrayOutputStream,并在其周围使用ObjectOutputStream 以序列化Strings 和List,然后调用将BAOS 转换为byte[] 数组的方法。在另一端,做相反的事情。

或者定义一个包含{String, String, List} 元组的可序列化对象,然后使用ObjectOutputStream 对其进行序列化,然后使用ObjectInputStream 对其进行反序列化。简单得多。

或者只发送三个。 TCP是一个字节流,消息之间没有界限,字节都是按顺序到达的。如果您想节省对网络的写入,请插入 BufferedOutputStream 并在写入 List 后刷新它。

【讨论】:

  • 将参数包装在内部对象中会更灵活,更易于维护
【解决方案2】:

这是一种序列化字节数组并在另一端反序列化的简单方法。请注意,该方法只接受List&lt;byte[]&gt; 类型的一个参数,并且由于您的参数dataAdataB 的类型为String,您可以简单地假设列表中的前两个byte[] 元素就是这两个参数.我相信这比通过ObjectOutputStream 进行对象序列化要快得多,并且在另一端反序列化也会更快。

public class ByteListSerializer {
static private final int INT_SIZE = Integer.SIZE / 8;

    static public void main(String...args) {
        ByteListSerializer bls = new ByteListSerializer();

        // ============== variable declaration =================
        String dataA = "hello";
        String dataB = "world";
        List<byte[]> info = new ArrayList<byte[]>();
        info.add(new byte[] {'s','o','m','e'});
        info.add(new byte[] {'d','a','t','a'});
        // ============= end variable declaration ==============

        // ======== serialization =========
        info.add(0, dataA.getBytes());
        info.add(1, dataB.getBytes());
        byte[] result = bls.dataSerializer(info);

        System.out.println(Arrays.toString(result));

        // ======== deserialization ========
        List<byte[]> back = bls.dataDeserializer(result);

        String backDataA = new String(back.get(0));
        String backDataB = new String(back.get(1));
        back.remove(0);
        back.remove(0);

        // ============ print end result ============
        System.out.println(backDataA);
        System.out.println(backDataB);
        for (byte[] b : back) {
            System.out.println(new String(b));
        }
    }

    public byte[] dataSerializer(List<byte[]> data) {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ByteBuffer lenBuffer = ByteBuffer.allocate(4);

        try {
            for (byte[] d : data) {
                lenBuffer.putInt(0, d.length);
                out.write(lenBuffer.array());
                out.write(d);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        // wrap this
        byte[] dataBuffer = new byte[out.size() + 4];
        lenBuffer.putInt(0, out.size());
        System.arraycopy(lenBuffer.array(), 0, dataBuffer, 0, 4);
        System.arraycopy(out.toByteArray(), 0, dataBuffer, 4, out.size());

        return dataBuffer;
    }

    public List<byte[]> dataDeserializer(byte[] data) {
        if (data.length < INT_SIZE) {
            throw new IllegalArgumentException("incomplete data");
        }

        ByteBuffer dataBuffer = ByteBuffer.wrap(data);
        int packetSize = dataBuffer.getInt();

        if (packetSize > data.length - INT_SIZE) {
            throw new IllegalArgumentException("incomplete data");
        }

        List<byte[]> dataList = new ArrayList<byte[]>();
        int len, pos = dataBuffer.position(), nextPos;

        while (dataBuffer.hasRemaining() && (packetSize > 0)) {
            len = dataBuffer.getInt();
            pos += INT_SIZE;
            nextPos = pos + len;
            dataList.add(Arrays.copyOfRange(data, pos, nextPos));

            dataBuffer.position(pos = nextPos);
            packetSize -= len;
        }

        return dataList;
    }
}

框架构造为

     - 4 bytes: the total bytes to read (frame size = [nnnn] + 4 bytes header)
    |      - 4 bytes: the first chunk size in bytes
    |     |          - x bytes: the first chunk data
    |     |         |          
    |     |         |           - 4 bytes: the n chunk size in byte
    |     |         |          |         - x bytes: the n chunk data
    |     |         |          |        |
    |     |         |          |        |
[nnnn][iiii][dddd....][...][iiii][dddd...]

上面的例子会输出

[0, 0, 0, 34, 0, 0, 0, 5, 104, 101, 108, 108, 111, 0, 0, 0, 5, 119, 111, 114, 108, 100, 0, 0, 0, 4, 115, 111, 109, 101, 0, 0, 0, 4, 100, 97, 116, 97]
hello
world
some
data

请注意帧格式由byte[] 块组成,因此只要您知道块的顺序,您就可以对几乎任何数据集使用这些方法。

【讨论】:

  • DataOutputStreamDataInputStream 可以更“简单”地完成所有工作。
  • @EJP,我相信它会产生与 DataOutputStream 几乎相同的代码,它不会“序列化”而是简单地将数据输出为字节(就像在我的示例中一样),并且您仍然必须预先添加返回结果字节之前的 4 字节帧长度。
  • 只是一个问题:ByteArrayOutputStream 和 ByteBuffer 对象的效率如何?认为我为我发送的每一帧创建一个实例。
  • 取决于您调用这些方法的频率。对象创建通常具有成本效益,但是拥有类级别的实例会导致您的类不是线程安全的。如果你确定你的类不会有并发访问,你可以拥有一个 ByteBuffer 的实例并使用 ByteArrayOutputStream.reset() 重用 ByteArrayOutputStream 来重新初始化它。如果您的班级将具有并发访问权限,则您可以拥有这些对象的同步池......但这超出了问题范围。我建议您按原样进行测试,如果速度变得更加关键,请进行改进。
猜你喜欢
  • 1970-01-01
  • 2010-12-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多