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

184

我正在寻找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是长度。


1
在C++中使用JNI魔法怎么样?从GC的角度来看可能会是一场灾难? - AlikElzin-kilaka
它必须是原始字节数组吗? - M.P. Korstanje
15个回答

187

声明:此答案不符合问题的限制条件:

我不想为此在堆内存中创建一个新的字节数组。

(老实说,我认为我的回答应该被删除。@unique72的答案是正确的。我会让这个编辑一段时间,然后删除这个答案。)


我不知道是否可以直接使用数组来完成这个任务而不需要额外的堆分配,但是其他答案使用了子列表包装器,只为包装器分配了额外的空间,而不是数组本身,在处理大型数组时会很有用。

话虽如此,如果想要简洁,Java 6(2006年末)引入了实用方法Arrays.copyOfRange()

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

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

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

12
这仍然会动态分配一个新的内存段,并将范围复制到其中。 - Dan
4
谢谢Dan - 我没注意到OP不想创建新数组,也没有看copyOfRange的实现。如果这是闭源的,也许就可以通过了。 :) - David J. Liszewski
7
我认为很多人想从数组中创建一个子数组,并且不太担心它会使用更多的内存。他们遇到了这个问题并得到了他们想要的答案 - 所以请不要删除,因为它很有用 - 我认为这是可以的。 - The Lonely Coder
2
实际上,copyOfRange 仍然会分配新的内存段。 - Kevingo Tsai
1
@DavidJ.Liszewski 我想你的修改已经快6年了 :) - Gurwinder Singh
@GurwinderSingh 哦,天啊,你说得对。不管好坏,我现在几乎不再关注StackOverflow了。@ https://stackoverflow.com/users/1201038/the-lonely-coder . . 请创建一个新问题,并将Arrays.copyOfRange作为您的解决方案。 - David J. Liszewski

172

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


10
需要澄清的是,它委托给一个在Arrays中被令人困惑地称为ArrayList的私有类,但实际上它是一个围绕着数组的List,而不是会创建副本的java.util.ArrayList。没有对列表内容的新分配,也没有第三方依赖。我认为这是最正确的答案。 - dimo414
32
实际上,这对于基本类型数组将不起作用,正如原帖所需(在他的情况下是 byte[])。你得到的只会是 List<byte[]>。而将 byte[] bigArray 更改为 Byte[] bigArray 可能会带来显着的内存开销。 - Dmitry Avtonomov
2
唯一真正实现所需功能的方法是通过 sun.misc.Unsafe 类。 - Dmitry Avtonomov
可以创建一个直接引用MemorySegment(原生缓冲区)的ArrayList或者类似方式的Java数组吗?能否直接引用MemorySegment(原生缓冲区)创建ArrayList,或者相同方式创建Java数组? - peterk

40

如果你正在寻求指针风格的别名方法,以便你甚至不需要分配空间和复制数据,那么我认为你会感到很困难。

System.arraycopy()将从源复制到目标,并声称此实用程序具有高效性。你确实需要分配目标数组。


3
是的,我希望有一种指针方法,因为我不想动态分配内存。但看起来这是我必须要做的。 - jbu
1
正如@unique72所建议的那样,似乎有一些方法可以通过利用各种Java列表/数组类型的实现细节来实现您想要的功能。这似乎是可能的,只是不是以明确的方式,这让我不太敢过于依赖它... - Andrew
为什么array*copy*()应该重复使用相同的内存?这不是调用者所期望的完全相反的吗? - Patrick

24

一种方法是将数组包装在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可能有点棘手,但很可能实现高效,并且值得学习。

1
值得注意的是,ByteBuffer对象可以相当容易地解码:StandardCharsets.UTF_8.decode(ByteBuffer.wrap(buffer, 0, readBytes)) - skeryl
@Soulman 谢谢你的解释,但是有一个问题,这个方法比使用 Arrays.copyOfRange 更有效吗? - ucMedia
1
对于一个双字节数组,Arrays.copyOfRange 可能更有效率。通常情况下,你需要根据你的具体使用情况进行测量。 - Soulman

20

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

如果你的字节来自一个流,NIO缓冲区可以使用“直接模式”,这会创建一个由本地资源支持的缓冲区。在很多情况下,这可以提高性能。


14

你可以使用Apache Commons中的ArrayUtils.subarray,它比System.arraycopy更直观一些,但并非完美。不过,缺点是会在你的代码中引入另一个依赖项。


23
这句话的意思是:“它和 Java 1.6 中的 Arrays.copyOfRange() 方法是一样的。” - newacct

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

我认为没有一种直接使用数组的好方法来实现这个,然而。


9
List.subList(int startIndex, int endIndex)

9
您首先需要将数组包装为列表:Arrays.asList(...) .sublist(...); - camickr

8
List可以让您透明地使用和处理subList。原始数组需要您跟踪某种偏移量-限制。据我所知,ByteBuffer有类似的选项。 编辑: 如果您负责有用的方法,可以像Java本身中的许多与数组相关的方法一样定义它的边界:
doUseful(byte[] arr, int start, int len) {
    // implementation here
}
doUseful(byte[] arr) {
    doUseful(arr, 0, arr.length);
}

然而,如果您正在处理数组元素本身,例如计算某些内容并写回结果,则情况并不清楚。


7

一种选择是传递整个数组以及起始和结束索引,而不是迭代传递的整个数组。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接