如何引用数组的一部分?

时间:2015-09-30 08:58:16

标签: java arrays protocol-buffers zero-copy

给定一个对象byte[],当我们想要使用这样的对象时,我们经常需要它的一部分。在我的特定示例中,我从导线获取byte[],其中前4个字节描述消息的长度,然后另外4个字节消息的类型(映射到具体的protobuf类的整数),然后剩余的byte[]是实际的消息的内容......像这样

length|type|content

为了解析这个消息,我必须将内容部分传递给知道如何从中解析实例的特定类...问题是通常没有提供任何方法,以便您可以指定从哪里到哪里解析器应阅读数组...

所以我们最终做的是复制该数组的剩余chuks,这是无效的......

据我所知,在java中,不可能创建另一个byte[]引用实际引用一些只有2个索引的原始较大byte[]数组(这是使用String导致内存的方法)泄漏)...

我想知道我们如何解决这样的情况?我认为放弃protobuf只是因为它没有提供一些parseFrom(byte[], int, int)没有做到这一点... protobuf只是一个例子任何事情都可能缺乏这种能力......

这是否会迫使我们编写效率低下的代码,或者有什么可以做的?(从添加该方法开始)......

3 个答案:

答案 0 :(得分:2)

通常情况下,你会用流解决这类问题。

流是一种抽象,用于读取处理当前数据块所需的内容。因此,您可以将正确的字节数读入字节数组并将其传递给您的解析函数。

你问,这是否会迫使我们编写效率低下的代码,或者有什么可以做的?'

通常,您会以流的形式获取数据,然后使用下面演示的技术将更加高效,因为您跳过制作一个副本。 (两个副本而不是三个;一个是OS,一个是你。在开始解析之前,你跳过制作总字节数组的副本。)如果你真的开始使用byte[]但它是由你自己构建的那么您可能希望更改为构建{ int length, int type, byte[] contentBytes }之类的对象,并将contentBytes传递给您的解析函数。

如果你真的,真的必须从byte[]开始,那么下面的技术只是解析它的一种更方便的方法,它不会更高效。

假设你从某处获得了一个字节缓冲区,你想要读取该缓冲区的内容。首先将它转换为流:

private static List<Content> read(byte[] buffer) {
    try {
        ByteArrayInputStream bytesStream = new ByteArrayInputStream(buffer);
        return read(bytesStream);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

上面的函数用流包装字节数组并将其传递给执行实际读取的函数。 如果你可以从一个流开始,那么显然你可以跳过上面的步骤,直接将该流传递给下面的函数:

private static List<Content> read(InputStream bytesStream) throws IOException {
    List<Content> results = new ArrayList<Content>();
    try {
        // read the content...
        Content content1 = readContent(bytesStream);
        results.add(content1);

        // I don't know if there's more than one content block but assuming
        // that there is, you can just continue reading the stream...
        //
        // If it's a fixed number of content blocks then just read them one
        // after the other... Otherwise make this a loop
        Content content2 = readContent(bytesStream);
        results.add(content2);
    } finally {
        bytesStream.close();
    }
    return results;
}

由于您的字节数组包含内容,因此您需要从流中读取内容块。由于你有一个长度和一个类型字段,我假设你有不同种类的内容块。下一个函数读取长度和类型,并根据读取类型将内容字节的处理传递给适当的类:

private static Content readContent(InputStream stream) throws IOException {
    final int CONTENT_TYPE_A = 10;
    final int CONTENT_TYPE_B = 11;

    // wrap the InputStream in a DataInputStream because the latter has
    // convenience functions to convert bytes to integers, etc.
    // Note that DataInputStream handles the stream in a BigEndian way,
    // so check that your bytes are in the same byte order. If not you'll
    // have to find another stream reader that can convert to ints from
    // LittleEndian byte order.
    DataInputStream data = new DataInputStream(stream);
    int length = data.readInt();
    int type = data.readInt();

    // I'm assuming that above length field was the number of bytes for the
    // content. So, read length number of bytes into a buffer and pass that 
    // to your `parseFrom(byte[])` function 
    byte[] contentBytes = new byte[length];
    int readCount = data.read(contentBytes, 0, contentBytes.length);
    if (readCount < contentBytes.length)
        throw new IOException("Unexpected end of stream");

    switch (type) {
        case CONTENT_TYPE_A:
            return ContentTypeA.parseFrom(contentBytes);
        case CONTENT_TYPE_B:
            return ContentTypeB.parseFrom(contentBytes);
        default:
            throw new UnsupportedOperationException();
    }
}

我已经编写了以下内容类。我不知道protobuf是什么,但它显然可以使用parseFrom(byte[])函数从字节数组转换为实际对象,因此将其作为伪代码:

class Content {
    // common functionality
}

class ContentTypeA extends Content {
    public static ContentTypeA parseFrom(byte[] contentBytes) {
        return null; // do the actual parsing of a type A content 
    }
}

class ContentTypeB extends Content {
    public static ContentTypeB parseFrom(byte[] contentBytes) {
        return null; // do the actual parsing of a type B content
    }
}

答案 1 :(得分:1)

在Java中,Array不仅仅是内存的一部分 - 它是一个对象,它有一些额外的字段(至少 - 长度)。所以你不能链接到数组的一部分 - 你应该:

  • 使用阵列复制功能或
  • 实现并使用一些仅使用部分字节数组的算法。

答案 2 :(得分:1)

关注似乎是无法在数组上创建视图(例如,等效于List#subList()的数组)。解决方法可能是使您的解析方法接受对整个数组的引用以及两个索引(或索引和长度)来指定方法应该处理的子数组。

这不会阻止方法读取或修改不应触及的数组部分。如果这是一个问题,也许可以使ByteArrayView类增加一些安全性:

public class ByteArrayView {
  private final byte[] array;
  private final int start;
  private final int length;

  public ByteArrayView(byte[] array, int start, int length) { ... }

  public byte[] get(int index) {
    if (index < 0 || index >= length) {
      throw new ArrayOutOfBoundsExceptionOrSomeOtherRelevantException();
    }
    return array[start + index];
  }
}

但另一方面,如果性能是一个问题,那么调用get()来获取每个字节的方法可能是不可取的。

代码用于说明;它没有经过测试或其他任何东西。

修改

在我自己的回答的第二次阅读中,我意识到我应该指出这一点:让ByteArrayView复制你从原始数组中读取的每个字节 - 只是逐字节而不是块。这对OP的担忧是不够的。