在Java中获取数组的一部分而不在堆上创建新数组

时间:2009-07-08 20:28:07

标签: java arrays slice

我正在寻找一种Java方法,它将返回一个数组的一部分。一个例子是获取包含字节数组的第4和第5个字节的字节数组。我不想在堆内存中创建一个新的字节数组来做到这一点。现在我有以下代码:

doSomethingWithTwoBytes(byte[] twoByteArray);

void someMethod(byte[] bigArray)
{
      byte[] x = {bigArray[4], bigArray[5]};
      doSomethingWithTwoBytes(x);
}

我想知道是否有办法只做doSomething(bigArray.getSubArray(4, 2)),其中4是偏移量,2是长度,例如。

15 个答案:

答案 0 :(得分:183)

免责声明:此答案不符合问题的限制:

  

我不想在堆内存中创建一个新的字节数组来执行此操作。

老实说,我觉得我的答案值得删除。@ unique72的答案是正确的.Imma让这个编辑稍等一下然后我会删除这个答案。


我不知道如何直接对没有额外堆分配的数组执行此操作,但使用子列表包装器的其他答案仅对包装器进行额外分配 - 但不包括数组 - 这对于大阵列的情况。

那就是说,如果一个人正在寻求简洁,那么实用工具方法Arrays.copyOfRange()是在Java 6(2006年末?)中引入的:

byte [] a = new byte [] {0, 1, 2, 3, 4, 5, 6, 7};

// get a[4], a[5]

byte [] subArray = Arrays.copyOfRange(a, 4, 6);

答案 1 :(得分:165)

Arrays.asList(myArray)委托给新的ArrayList(myArray),它不复制数组但只存储引用。之后使用List.subList(start, end)创建一个SubList,它只引用原始列表(它仍然只引用数组)。不复制数组或其内容,只创建包装器,所有涉及的列表都由原始数组支持。 (我以为它会更重。)

答案 2 :(得分:39)

如果您正在寻找指针式样式别名方法,那么您甚至不需要分配空间并复制数据,那么我相信您运气不佳。

System.arraycopy()将从您的来源复制到目的地,并声明此实用程序的效率。您需要分配目标数组。

答案 3 :(得分:21)

一种方法是将数组包装在java.nio.ByteBuffer中,使用绝对put / get函数,并将缓冲区切片以处理子数组。

例如:

doSomething(ByteBuffer twoBytes) {
    byte b1 = twoBytes.get(0);
    byte b2 = twoBytes.get(1);
    ...
}

void someMethod(byte[] bigArray) {
      int offset = 4;
      int length = 2;
      doSomething(ByteBuffer.wrap(bigArray, offset, length).slice());
}

请注意,您必须同时调用wrap()slice(),因为wrap()本身只会影响相对的put / get函数,而不会影响绝对函数。

ByteBuffer理解起来可能有点棘手,但很可能有效实施,非常值得学习。

答案 4 :(得分:20)

使用java.nio.Buffer。它是各种原始类型缓冲区的轻量级包装器,有助于管理切片,位置,转换,字节排序等。

如果您的字节来自Stream,则NIO缓冲区可以使用“直接模式”,这将创建由本机资源支持的缓冲区。这可以在很多情况下提高性能。

答案 5 :(得分:14)

您可以在apache commons中使用ArrayUtils.subarray。不完美但比System.arraycopy.更直观。缺点是它确实在你的代码中引入了另一个依赖。

答案 6 :(得分:10)

我看到subList的答案已经在这里,但是这里的代码证明它是一个真正的子列表,而不是副本:

public class SubListTest extends TestCase {
    public void testSubarray() throws Exception {
        Integer[] array = {1, 2, 3, 4, 5};
        List<Integer> list = Arrays.asList(array);
        List<Integer> subList = list.subList(2, 4);
        assertEquals(2, subList.size());
        assertEquals((Integer) 3, subList.get(0));
        list.set(2, 7);
        assertEquals((Integer) 7, subList.get(0));
    }
}

我不相信有一种直接用数组做这个的好方法。

答案 7 :(得分:9)

List.subList(int startIndex, int endIndex)

答案 8 :(得分:7)

一个选项是传递整个数组以及开始和结束索引,并在这些索引之间进行迭代,而不是遍历传递的整个数组。

void method1(byte[] array) {
    method2(array,4,5);
}
void method2(byte[] smallarray,int start,int end) {
    for ( int i = start; i <= end; i++ ) {
        ....
    }
}

答案 9 :(得分:6)

List允许您透明地使用和使用subList内容。原始数组需要您跟踪某种偏移 - 限制。 ByteBuffer有类似我听到的选项。

修改 如果你负责有用的方法,你可以用bounds定义它(就像在java本身的许多数组相关的方法中所做的那样:

doUseful(byte[] arr, int start, int len) {
    // implementation here
}
doUseful(byte[] arr) {
    doUseful(arr, 0, arr.length);
}

但是,如果您自己处理数组元素,则不清楚,例如你计算一些东西并回写结果?

答案 10 :(得分:6)

Java引用始终指向一个对象。该对象有一个标题,其中包括标识具体类型(因此强制转换可能会失败ClassCastException)。对于数组,对象的开始还包括长度,然后数据紧跟在内存中(技术上,一个实现可以自由地做它喜欢的事情,但是做其他任何事情都是愚蠢的)。所以,你可以有一个指向某个数组的引用。

在C指针指向任何地方和任何地方,你可以指向数组的中间。但你不能安全地投射或找出阵列有多长。在D中,指针包含内存块和长度的偏移量(或者等效于指向结尾的指针,我不记得实现实际上做了什么)。这允许D切片数组。在C ++中,你会有两个指向开头和结尾的迭代器,但是C ++有点奇怪。

所以回到Java,不,你不能。如上所述,NIO ByteBuffer允许您包装数组然后对其进行切片,但是会给出一个尴尬的界面。你当然可以复制,这可能比你想象的要快得多。您可以引入自己的String - 类似抽象,允许您对数组进行切片(String的当前Sun实现具有char[]引用加上起始偏移和长度,更高性能实现拥有char[])。 byte[]是低级别的,但是你提出的任何基于类的抽象都会使语法变得非常混乱,直到JDK7(可能)。

答案 11 :(得分:2)

@ unique72回答作为一个简单的函数或行,你可能需要用你想要的相应类类型替换对象&#39; slice&#39;。提供两种变体以满足各种需求。

/// Extract out array from starting position onwards
public static Object[] sliceArray( Object[] inArr, int startPos ) {
    return Arrays.asList(inArr).subList(startPos, inArr.length).toArray();
}

/// Extract out array from starting position to ending position
public static Object[] sliceArray( Object[] inArr, int startPos, int endPos ) {
    return Arrays.asList(inArr).subList(startPos, endPos).toArray();
}

答案 12 :(得分:1)

List包装器怎么样?

List<Byte> getSubArrayList(byte[] array, int offset, int size) {
   return new AbstractList<Byte>() {
      Byte get(int index) {
         if (index < 0 || index >= size) 
           throw new IndexOutOfBoundsException();
         return array[offset+index];
      }
      int size() {
         return size;
      }
   };
}

(未测试的)

答案 13 :(得分:1)

我需要遍历数组的末尾,并且不想复制数组。我的方法是在数组上进行Iterable。

{{1}}

答案 14 :(得分:-1)

这比Arrays.copyOfRange稍微轻一点 - 没有范围或负面

public static final byte[] copy(byte[] data, int pos, int length )
{
    byte[] transplant = new byte[length];

    System.arraycopy(data, pos, transplant, 0, length);

    return transplant;
}