为什么 Joshua Bloch 在 Effective Java 中使用 2*size + 1 来调整堆栈大小?

Why did Joshua Bloch use 2*size + 1 for resizing the stack in Effective Java?

这是我正在谈论的代码:

public class Stack {
  private Object[] elements;
  private int size = 0;
  private static final int DEFAULT_INITIAL_CAPACITY = 16;
  public Stack() {
    elements = new Object[DEFAULT_INITIAL_CAPACITY];
  }
  public void push(Object e) {
    ensureCapacity();
    elements[size++] = e;
  }
  public Object pop() {
    if (size == 0)
      throw new EmptyStackException();
    return elements[--size];
  }
  /**
   * Ensure space for at least one more element, roughly
   * doubling the capacity each time the array needs to grow.
   */
  private void ensureCapacity() {
    if (elements.length == size)
      elements = Arrays.copyOf(elements, 2 * size + 1);
  }
}

为什么不简单地将最后一行保留为 elements = Arrays.copyOf(elements, 2 * size);

唯一可能有效的情况是 Stack 的初始大小为 0。但在这种情况下它是一个常量 - DEFAULT_INITIAL_CAPACITY(非零值)。并且没有其他重载的构造函数可以从用户那里获取这个值(或默认为 0)

我将其解释为 peace-of-mind 防御假设的未来错误。的确,如所写,这个 class 的数组容量不会为 0,因此添加 1 并不是绝对必要的,但是一旦添加更多功能,该假设可能会悄悄地失败。

可能的附加功能示例包括来自 java.util.ArrayList 的功能,它有一个可以将容量设置为 0 的 trimToSize() 方法,以及一个允许从(可能为空)初始化数据的构造函数集合,以及一个允许显式将容量设置为 0 的构造函数。您还可以想象一个功能,当它被清空时自动减少此 class 的分配容量。或者也许有人会编辑 DEFAULT_INITIAL_CAPACITY 常量。现在想象一下,capacity-changing 方法被满屏的 javadoc 注释分隔开,并拆分为多个子 class 方法。很容易忘记您应该防止容量变为 0。

如果 ensureCapacity() 取决于 non-zero 的大小,您在修改 class 时始终必须牢记这一假设。添加 +1 是一项 low-cost 更改,可以消除这种担忧。这也是处理边缘情况的简单算术方法的示例。

虽然 he/she 在 his/her 答案中确实有更简单的答案,但对我来说,它似乎比 @Boann 使用的所有措辞更明显。答案很简单,作者希望支持以零大小开始数组...设置 DEFAULT_INITIAL_CAPACITY = 0。这将导致代码在没有 +1 的情况下崩溃。没有其他方法可以使 elements 数组的大小永远为零,除非它以这种方式开始,这是 +1.

的唯一原因

正如代码所写,没有理由 +1