如何引用数组的一部分?

how to refer part of an array?

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

length|type|content

为了解析此消息,我必须将内容部分传递给特定的 class,它知道如何从中解析实例...问题是通常没有提供任何方法,因此您可以指定解析器应从何处读取数组...

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

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

我想知道我们如何解决这种情况?我想放弃 protobuf 只是因为它不提供一些 parseFrom(byte[], int, int) 没有意义... protobuf 只是一个例子,任何东西都可能缺少 api...

这会迫使我们编写低效的代码还是有什么可以做的?(除了添加该方法)...

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

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

问题似乎是无法在数组上创建视图(例如,相当于 List#subList() 的数组)。解决方法可能是让您的解析方法采用对整个数组和两个索引(或索引和长度)的引用来指定该方法应该处理的子数组。

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

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 的担忧。

通常你会用流处理这种事情。

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

你问'So does this force us to write inefficient code or there is something that can be done?'

通常您以流的形式获取数据,然后使用下面演示的技术会提高性能,因为您无需制作一份副本。 (两个副本而不是三个;一次由 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;
}

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

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();
    }
}

我编造了以下内容classes。我不知道 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
    }
}