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

jbu*_*jbu 180 java arrays slice

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

doSomethingWithTwoBytes(byte[] twoByteArray);

void someMethod(byte[] bigArray)
{
      byte[] x = {bigArray[4], bigArray[5]};
      doSomethingWithTwoBytes(x);
}
Run Code Online (Sandbox Code Playgroud)

我想知道是否有一种方法可以做到doSomething(bigArray.getSubArray(4, 2))4是偏移量,2是长度,例如.

Dav*_*ski 184

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

我不想在堆内存中创建一个新的字节数组来做到这一点.

(老实说,我觉得我的答案值得删除.@ 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);
Run Code Online (Sandbox Code Playgroud)

  • 这仍然会动态分配一个新的内存段并将范围复制到其中. (10认同)
  • 我想很多人都想从数组中创建一个子数组,并不担心它会占用更多的内存.他们遇到了这个问题并得到他们想要的答案 - 所以请不要删除,因为它有用 - 我认为没关系. (7认同)
  • 谢谢Dan - 我忽略了OP不想创建新数组而且我没有看到`copyOfRange`的实现.如果它是封闭源可能它已经过去了.:) (4认同)
  • 实际上,copyOfRange仍然分配新的内存段 (2认同)

小智 165

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

  • 实际上,这不适用于原始类型数组,因为OP需要(在他的情况下为`byte []`).所有你得到的将是`List <byte []>`.将`byte [] bigArray`改为`Byte [] bigArray`可能会产生很大的内存开销. (28认同)
  • 为了澄清,它将委托给`Arrays`中的一个私有类,混淆地称为`ArrayList`,但它实际上是一个数组周围的`List`,而不是`java.util.ArrayList`,它可以复制.没有新的分配(列表的内容),也没有第三方依赖.我相信这是最正确的答案. (9认同)
  • 真正实现所需要的唯一方法是通过`sun.misc.Unsafe`类. (2认同)

djn*_*jna 39

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

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

  • 是的,我希望有一种指针方法,因为我不想动态分配内存.但看起来这就是我将要做的事情. (3认同)

Sou*_*man 22

一种方法是将数组包装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());
}
Run Code Online (Sandbox Code Playgroud)

请注意,你必须调用这两个wrap()slice()的,因为wrap()本身仅影响相对PUT/GET功能,而不是绝对的.

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


Jam*_*hek 20

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

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


set*_*eth 14

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

  • 它与Java 1.6中的Arrays.copyOfRange()相同 (22认同)

Car*_*ter 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));
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,我不相信有一种直接用数组做这个的好方法.


Man*_*lva 9

List.subList(int startIndex, int endIndex)
Run Code Online (Sandbox Code Playgroud)

  • 您首先需要将Array包装为List:Arrays.asList(...).sublist(...); (9认同)

Sam*_*ane 7

一种选择是传递整个数组以及开始和结束索引,并在它们之间进行迭代,而不是遍历传递的整个数组.

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


aka*_*okd 6

List小号允许您使用和处理的subList东西透明.原始数组需要您跟踪某种偏移 - 限制.ByteBuffer我有类似的选择.

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

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

但是,如果您自己处理数组元素,例如计算某些内容并回写结果,那么目前尚不清楚?


Tom*_*ine 6

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

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

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