在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个回答

6

Java中的引用始终指向一个对象。该对象具有标头,其中包括识别具体类型的信息(因此强制转换可能会失败并导致ClassCastException)。对于数组,对象的开头还包括长度,然后数据紧随其后存储在内存中(从技术上讲,实现可以自由地做任何事情,但这样做是愚蠢的)。因此,您不能拥有指向数组某个位置的引用。

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

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


感谢您解释为什么这是不可能的。顺便说一下,在HotSpot中,String现在会在substring上进行复制(忘记哪个版本更改了这个)。您为什么说JDK7将允许比ByteBuffer更好的语法? - Aleksandr Dubinsky
在撰写本文时,Java SE 7似乎将允许在用户定义的类型(例如List和ByteBuffer)上使用数组[]表示法。仍在等待中... - Tom Hawtin - tackline

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

(未经测试)


这将导致字节的装箱和拆箱。可能会很慢。 - M.P. Korstanje
在Oracle Java库中,所有byte值的Byte对象都被缓存。因此,装箱开销应该相当慢。 - Lii

1

我需要遍历数组的末尾,但不想复制该数组。我的方法是将数组转换为可迭代对象。

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

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