构建一个更好的ArrayList

时间:2013-11-10 18:44:06

标签: java arrays arraylist big-o

当您在列表的开头或中间插入ArrayList时,您总是会有O(n)表现,但最后的插页可以维持O(1),因为您只是将它添加到列表的末尾。

我已经高度了解了如何在开头O(1)进行插入并平均O(n)插入O(n/2),但我不确定是否可能与底层数组结构。

我的想法

由于ArrayList实际上只是一个更大的数组,当它达到阈值时“调整”自身,为什么它不能在列表的前端和末尾调整自身,而不是仅在结尾?这将允许在开头插入O(1),同时还允许您在中间重写插入以采用最短路径插入自身。 (例如,如果您在索引1处插入,则移动0比移动2n更容易。

我认为为什么Sun最初没有这样做的答案是因为写Arrays.copyOf和扩展名System.arrayCopy的方式。两者都从列表的开头开始并复制到一个更大的数组中。我已经看过这两种方法的Sun源代码,但是我的Java技能水平有点先进。

问题

在我看来,这有两个主要问题:

1)首先,可以重写/重载Arrays.copyOfSystem.arrayCopy以将子数组插入更大数组的中间吗?

2)这样做的重大影响是什么?

这是一个好主意,还是我遗漏了一些明显不合理的东西?

提前致谢!

3 个答案:

答案 0 :(得分:2)

你的想法完全可行,对于 deque 实现很有意义,其中常见的情况是在两端添加和删除元素。它对ArrayList没有多大意义,因为99%的用例只会添加到最后。

至于你关于System.arraycopy性质的问题,我不确定我是否完全理解你

a)是的,您可以使用arraycopy 用另一个数据的内容覆盖一个数组的中间位置,并且

b)arraycopy没有插入到数组中,因为该操作对Java的固定大小数组没有意义。

最接近“插入数组”的想法是创建一个全新的,更大的数组,并arraycopy来自两个输入数组的适当范围。由此产生的性能是您可以获得的最佳性能。

答案 1 :(得分:1)

您描述的内容与ArrayList的概念不符。

ArrayList元素的顺序由它们的插入顺序定义(在这种情况下,除非你指定一个索引,否则你总是将它们添加到最后)。

如果订单是由对象成员的值而不是插入顺序定义的,那么您需要LinkedList

答案 2 :(得分:1)

是的,您可以在开头插入,与插入结束时一样高效。您只需要在中间而不是索引0处开始填充后备阵列。您不需要任何不同的System.arraycopy,请参阅下面的示例代码。

除了存储支持数组(data)和当前size之外,您还可以存储当前的start偏移量,并相应地考虑它。

BetterArrayList<T> extends AbstractList<T> {
  private int size;   // Used elements in data.
  private int start;  // Position of element 0 in data.
  private T[] data;   // data.length is the current total capacity.

  public BetterArrayList<T>(int initialCapacity) {
    data = new Object[initialCapacity];
    start = initialCapacity / 2;
    size = 0;
  }

  public void add(int index, T value) {
    if (index < size / 2) {  // Insert at the front or end?
      if (start == 0) {
        realloc(data.length * 2);
      }
      System.arraycopy(data, start, data, start - 1, index);
      start--;
    } else {
      if (size + start == data.length) {
        realloc(data.length * 2);
      }
      System.arraycopy(data, start + index, 
                       data, start + index + 1, size - index);
    }
    data[start + index] = value;
    size++;
  }    

  public T get(int index) {
    if (index < 0 || index >= size) {
      throw new IndexOutOfBoundsException();
    }
    return data[start + index];
  }

  private void realloc(int newCapacity) {
    T[] newData = new Object[newCapacity];
    int newStart = newCapacity - size / 2;
    System.arraycopy(data, start, newData, newStart, size);
    start = newStart;
    data = newData;
  }

  public T set(int index, T value) {
    Value old = get(index);
    data[size + index] = value;
    return old;
  }

  public int size() {
    return size;
  }
}

我猜想Sun没有采用这种方式,因为它在数组列表的典型用例中浪费了更多的内存(最后附加)。而且你只能在前面获得更高效的插入,在其他地方插入仍然是O(n)因为内容仍然需要移位。

BTW:根据您的使用案例,您可能对Rope数据结构感兴趣:http://en.wikipedia.org/wiki/Rope_%28data_structure%29