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

4

15 回答 15

186

免责声明:此答案不符合问题的约束:

我不想为了做到这一点而在堆内存中创建一个新的字节数组。

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


我不知道有一种方法可以在没有额外堆分配的情况下直接使用数组执行此操作,但使用子列表包装器的其他答案仅对包装器进行了额外分配 - 但不是数组 - 这在以下情况下很有用一个大数组。

也就是说,如果您正在寻求简洁,实用方法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);
于 2009-07-08T21:53:07.173 回答
172

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

于 2011-01-11T05:41:28.347 回答
39

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

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

于 2009-07-08T20:35:25.923 回答
23

一种方法是将数组包装在 中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()and slice(),因为wrap()它本身只影响相对的 put/get 函数,而不是绝对函数。

ByteBuffer可能有点难以理解,但很可能有效地实施,并且非常值得学习。

于 2011-11-08T17:49:38.893 回答
20

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

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

于 2009-07-08T20:52:03.830 回答
14

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

于 2009-07-08T20:42:13.383 回答
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));
    }
}

但是,我不相信有直接使用数组执行此操作的好方法。

于 2009-07-08T20:40:28.987 回答
9
List.subList(int startIndex, int endIndex)
于 2009-07-08T20:31:27.617 回答
7

s 允许您透明List地使用和使用某些东西。subList原始数组将要求您跟踪某种偏移量 - 限制。ByteBuffers 和我听说的有类似的选择。

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

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

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

于 2009-07-08T20:33:13.620 回答
6

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

void method1(byte[] array) {
    method2(array,4,5);
}
void method2(byte[] smallarray,int start,int end) {
    for ( int i = start; i <= end; i++ ) {
        ....
    }
}
于 2009-07-08T20:44:40.790 回答
6

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

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

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

于 2009-07-08T21:32:25.730 回答
2

@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();
}
于 2015-02-07T08:52:44.197 回答
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;
      }
   };
}

(未经测试)

于 2011-01-14T14:34:49.120 回答
1

我需要遍历数组的末尾并且不想复制数组。我的方法是在数组上创建一个 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");
        }
      };
    }
  };
}
于 2016-06-27T02:58:36.013 回答
-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;
}
于 2014-06-06T05:29:34.097 回答