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


181

我正在寻找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 POV可能会造成灾难吗?
2015年

它必须是原始字节数组吗?
MP Korstanje'2

Answers:


185

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

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

老实说,我觉得我的答案值得删除。@ 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);

10
这仍然会动态分配一个新的内存段,并将范围复制到该内存段中。

4
谢谢Dan-我忽略了OP不想创建新的数组并且我没有考虑到copyOfRange。如果它是开源的,它可能已经过去了。:)
David J. Liszewski 2011年

7
我认为很多人都希望从一个数组创建一个子数组,而不必担心它会使用更多的内存。他们遇到了这个问题并获得了想要的答案-因此请不要删除它,因为它很有用-我认为可以。
寂寞的编码者,2015年

2
实际上,copyOfRange仍然分配新的内存段
Kevingo Tsai

167

Arrays.asList(myArray)委托给new ArrayList(myArray),它不会复制数组,而只是存储引用。使用List.subList(start, end)after可以使a SubList仅引用原始列表(仍然仅引用数组)。无需复制数组或其内容,只需创建包装器,并且所涉及的所有列表均由原始数组支持。(我认为它会更重。)


9
为了澄清起见,它将委派给一个Arrays名为的私有类ArrayList,但该类确实是List围绕数组的,而不是将java.util.ArrayList其复制。没有(列表内容的)新分配,也没有第三方依赖性。我认为,这是最正确的答案。
dimo414

28
实际上,这不适用于OP想要的原始类型数组(byte[]在他的情况下)。您将得到的只是List<byte[]>。更改byte[] bigArrayByte[] bigArray可能会增加内存开销。
德米特里·阿夫托诺莫夫

2
真正实现期望的唯一方法是通过sun.misc.Unsafe课堂。
德米特里·阿夫托诺莫夫

39

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

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


3
是的,我希望使用某种指针方法,因为我不想动态分配内存。但看起来这就是我要做的。
jbu

1
正如@ unique72建议的那样,似乎存在通过利用各种Java列表/数组类型的实现中的细微之处来完成所需操作的方法。这似乎是有可能的,只是不是以一种明确的方式,这让我犹豫了太多的依赖……
Andrew

为什么要array*copy*()重用相同的内存?这不是呼叫者所期望的完全相反吗?
帕特里克·法夫尔

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()slice(),因为wrap()它本身仅影响相对的put / get函数,而不影响绝对的put / get函数。

ByteBuffer 理解起来可能有些棘手,但是很可能有效地实现了它,非常值得学习。


1
还值得注意的是,ByteBuffer对象可以很容易地解码:StandardCharsets.UTF_8.decode(ByteBuffer.wrap(buffer, 0, readBytes))
skeryl 2015年

@Soulman感谢您的解释,但是有一个问题比使用它更有效 Arrays.copyOfRange吗?
ucMedia

1
对于两个字节的数组,@ucMedia Arrays.copyOfRange可能更有效。通常,您必须针对您的特定用例进行度量。
灵魂人

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

我不认为有直接对数组执行此操作的好方法。



6

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

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

6

List小号让你与使用和工作subList的东西透明。基本数组将要求您跟踪某种偏移量-限制。ByteBuffer听说有类似的选择。

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

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

但是,尚不清楚您是否在处理数组元素本身,例如,您要计算一些东西并写回结果吗?


6

Java引用始终指向对象。该对象具有一个标头,该标头除其他外还标识了具体类型(因此强制类型转换会失败ClassCastException)。对于数组,对象的开始还包括长度,然后数据在内存中之后立即跟随(从技术上讲,实现是可以随意做的,但是做任何其他事情都是愚蠢的)。因此,您将无法获得指向数组某处的引用。

在C语言中,指针指向任何地方,任何地方,您可以指向数组的中间。但是您不能安全地强制转换或找出数组的长度。在D中,指针包含内存块和长度的偏移量(或等效于指向末尾的指针,我不记得实现实际上在做什么)。这允许D切片数组。在C ++中,您将有两个指向起点和终点的迭代器,但是C ++有点奇怪。

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


感谢您解释为什么这是不可能的。顺便说一句,String现在substring在HotSpot中复制(忘记哪个版本对此进行了更改)。为什么说JDK7比ByteBuffer允许更好的语法?
Aleksandr Dubinsky 2013年

@AleksandrDubinsky在撰写本文时,Java SE 7似乎将允许[]对用户定义类型(例如List和)使用数组表示法ByteBuffer。仍在等待...
Tom Hawtin-大头钉

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

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

(未经测试)


这将导致装箱-拆箱字节。可能会变慢。
MP Korstanje

@mpkorstanje:在Orable Java库中Byte,所有byte值的对象都被缓存。因此,拳击的开销应该很慢。

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

-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;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.