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

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

222647 次浏览
List.subList(int startIndex, int endIndex)

List允许您透明地使用和处理某些事物的 subList。原始数组需要您跟踪某种偏移限制。我听说 ByteBuffer也有类似的选择。

编辑: 如果你是这个有用的方法的负责人,你可以用边界来定义它(就像 Java 本身中许多与数组相关的方法所做的那样:

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

但是,如果你处理数组元素本身,例如,你计算一些东西并写回结果,这是不清楚的?

如果你正在寻找一个指针风格的别名方法,这样你甚至不需要分配空间和复制数据,那么我相信你是不走运的。

System.arraycopy()将从您的源复制到目的地,并声称这个实用工具的效率。您确实需要分配目标数组。

我看到 subList 的答案已经在这里了,但这里的代码证明了它是一个真正的 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));
}
}

然而,我认为直接使用数组并没有什么好方法。

您可以在 apache commons 中使用 子数组。不完美,但比 System.arraycopy.更直观一些。缺点是它确实在代码中引入了另一个依赖项。

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

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

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

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

Java 引用总是指向一个对象。这个对象有一个头,其中标识了具体的类型(因此使用 ClassCastException进行强制转换可能会失败)。对于数组,对象的开始也包括长度,然后数据紧接着在内存中(从技术上讲,实现可以自由地做它想做的事情,但是做其他事情是愚蠢的)。因此,不能有指向数组某处的引用。

在 C 语言中,指针指向任何地方和任何东西,你可以指向数组的中间。但是您不能安全地强制转换或查找数组的长度。在 D 中,指针包含内存块和长度的偏移量(或者等价于指向末尾的指针,我不记得实现实际做了什么)。这允许 D 对数组进行切片。在 C + + 中,有两个迭代器指向开始和结束,但是 C + + 有点奇怪。

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

免责声明: 本答案不符合问题的约束条件:

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

(说实话,我觉得我的回答值得删除。@ unique72给出的答案是正确的。我让这个编辑坐一会儿,然后我会删除这个答案。)


我不知道有什么方法可以直接在没有额外堆分配的情况下对数组进行分配,但是使用子列表包装器的其他答案只对包装器有额外的分配,而对数组没有,这在大型数组的情况下会很有用。

也就是说,如果想要简洁,实用方法 Arrays.copyOfRange()是在 Java6中引入的(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);

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

薄的 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;
}
};
}

(未经测试)

一种方法是将数组包装在 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可能有点难以理解,但很可能是有效实现的,值得学习。

这比 Arrays.copOfRange 轻量级一些——没有范围或负值

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

@ unique72作为一个简单的函数或行,您可能需要替换 Object,使用您希望“切片”的相应类类型。为了适应不同的需要,提供了两种不同的版本。

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

我需要遍历数组的末尾,而不想复制数组。我的方法是在数组上创建一个 Iterable。

public static Iterable<String> sliceArray(final String[] array,
final int start) {
return new Iterable<String>() {
String[] values = array;
int posn = start;


@Override
public Iterator<String> iterator() {
return new Iterator<String>() {
@Override
public boolean hasNext() {
return posn < values.length;
}


@Override
public String next() {
return values[posn++];
}


@Override
public void remove() {
throw new UnsupportedOperationException("No remove");
}
};
}
};
}