如何在Java中获取数组的子数组,而不复制数据?

53

我有一些类的库,可以处理读入缓冲区的数据。有没有办法避免在深层处理方法中一遍又一遍地复制数组并传递数据的部分呢?嗯,这听起来很奇怪,但在我的情况下,有一个特殊的编写器,将数据分成块并单独将它们写入不同的位置,所以它只执行System.arraycopy,获取它需要的内容并调用底层编写器,使用那个新的子数组。而且这种情况发生了很多次。重构这样的代码的最佳方法是什么?


在C++中进行一些JNI魔法怎么样?从GC的角度来看可能会是一场灾难。 - AlikElzin-kilaka
9个回答

68
Arrays.asList(array).subList(x, y).

这个方法不会返回一个数组,而是一个List,它更加灵活。


2
整个问题的重点在于避免复制。你认为上面的代码是做什么的? - Theo
80
返回由指定数组支持的固定大小列表。我接受你的道歉。 - Ricky Clarkson
Arrays.asList(new int[5000]) 不会复制吗? - dhardy
3
更正一下,使用原始类型是不起作用的:Arrays.asList(new int[]{...}) 的类型是 List<int[]>(所以不是我想要的)。 - dhardy
6
这个回答谈到了一个子列表,但是问题是关于子数组的,这是两个不同的东西。 - AlikElzin-kilaka

24

Java中的许多类可以接受数组的子集作为参数。例如,Writer.write(char cbuf[], int off, int len)。也许这已经足够满足您的用例。


这是最简单的方法,所以我会尝试它。 - Illarion Kovalchuk

14

在Java中,没有办法不复制数据而接收真正的数组。你不能在现有内存上创建新数组。基本上有两个选择:

  • 使用可以接受数组范围的方法。这已经被推荐过了。
  • 使用包装器,提供一些类似于数组的抽象,适用于许多应用程序。下面将进行描述。

您可以使用java.nio.Buffer类层次结构,特别是java.nio.ByteBuffer,它为整个数组或子范围提供缓冲区抽象。通常这就是人们所需的。这也提供了许多有趣的能力,如“零拷贝”翻转和灵活的字节区域表示。

下面是使用java.nio.ByteBuffer进行封装的示例。 这应该非常接近您所需的东西。至少对于某些操作来说是这样。

byte [] a1 = {0, 0, 1, 0};
ByteBuffer buf = ByteBuffer.wrap(a1,1,2);

然后您可以在 buf 上执行任何 ByteBuffer 操作。

只是一个警告, buf.array() 会返回原始的 a1 数组(后端)及其所有元素。


1
啊,所以即使我这样做 buf = ByteBuffer.wrap(a1, 1, 2)... buf.array() 仍然会返回 {0, 0, 1, 0}。所以这个想法不能真正用于获取子数组? - Burrito
在Java中,你无法获得真正的子数组而不进行复制。因此,使用包装器。列表是之前介绍过的一种,缓冲区抽象是另一种。我会说它在内存字节范围内更有用,但对于复杂对象数组,列表更常见。 - Roman Nikitchenko
1
这是一个知识渊博/深入的答案。 - WestCoastProjects

4

如果您使用内置的字节数组(如byte []),则无法在Java中声明子数组。原因是:数组的长度与数据一起存储,而不是在引用数组时声明。因此,不复制数据的子数组没有地方可以存储其长度!因此,对于基本类型,您可以使用上述高效的字节数组副本,对于更高级的类型(List),有可用的方法。


2
你可以采用与String类相同的方法; 创建一个不可变对象的类,该对象由数组、开始偏移量和结束偏移量构建,并提供对子数组的访问。这种对象的使用者不必知道整个数组或子数组之间的区别。构造函数不需要复制数组,只需存储数组引用及其边界即可。

1
你可以使用 (ArrayList).subList(value1, value2) 方法,我相信这可能有助于你的情况。当然,前提是你想使用 ArrayList。

当天回答!(哦...但是两年后...) - WestCoastProjects

1
也许你应该使用一种不同的类型来处理,它可以维护对原始数组切片的引用,而不是复制数据,类似于C#中的ArraySegment。这样做的另一个好处是,您还可以按需将切片移动到原始数组上,而无需创建新实例。 伪代码:
public class ArraySegment<T> implements Iterable<T> 
{
      private int from, to;
      private T[] original;
      
      public ArraySegment<T>(T[] original, int from, int to)
      {
          //constructor stuff
      }

      public T get(int index)
      {
           return original[index + from];
      }

      public int size()
      {
          return to - from + 1;
      }
      
      @Override
      public Iterator<T> iterator()
      {
          //Iterator that iterates over the slice
      }

      //Can support setters on from/to variables
}

0

Google的Guava库支持以ByteSource形式实现切片概念。

Google Guava是一个可随时使用的开源功能包,从头开始编写以遵循Google最佳实践,这依赖于重要的数组切片能力。


-3

请查看Arrays.copyOfRange(***)方法。


15
从javadoc中得知:“将指定数组的指定范围复制到一个新数组中”,但这并不是OP需要的内容。 - f1sh

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