如何在Java中创建可调整大小的数组?

14

在Java中,如何创建一个可调整大小的数组?我尝试使用Vector,但插入元素时它会将所有元素向后移动,而我需要一个可以增长但元素位置不变的数组。我相信这个问题有一个简单的答案,但我还不太确定。


数组是一种静态数据结构,因此它们无法增长。您需要使用动态数据结构来实现这一点。链表是常见的,正如Alex所建议的那样。 - Jonas
1
不要使用数组,而是使用针对你所需的操作进行优化的列表实现。如果只需要添加,则ArrayList很好;如果需要在中间或头部插入和删除,则LinkedList很好。避免使用原始数组以及Vector和Hashtable,它们已经过时且基本上被弃用。 - user177800
1
如果您不希望在每个操作中发生装箱,那么在Java中仍然可能需要使用数组。 - Alex Budovski
2
当您将值 X 插入到位置 1 的数组 [1, 2, 3] 中时,您期望得到什么样的结果?在不移动某些值的情况下,该如何实现? - Joachim Sauer
9个回答

24
作为替代方案,您可以使用一个ArrayList。它是List接口的可调整大小的数组实现。
用法(使用字符串):
List<String> myList = new ArrayList<String>();
myList.add("a");
myList.add("c");
myList.add("b");

顺序将按照您的输入顺序进行:a, c, b。

您也可以像这样获取单个项:

String myString = myList.get(0);

这将为您提供第0个元素:"a"。


你需要导入java.util.Listjava.util.ArrayList - Ismail Badawi
@isbadawi 不,我在写C#时写了我的原始答案,并进行了一些奇怪的混合。我已经编辑过了。 - Kevin Crowell
请注意,当您将int添加到List中时,会发生自动装箱(因为Java不支持泛型中的原始类型)。 - Alex Budovski
这和 util.LinkedList 之间有区别吗? - Malabarba
它将给你列表的第一个元素,没有“零号”(+1,当然,好答案)。 - corazza

5

正如Sanjo所指出的:“数组是静态数据结构,因此它们无法增长”。

列表接口可以由数组(例如像Kevin在他的帖子中指出的ArrayList)支持。当列表结构已满并且必须向列表添加新项时,则首先创建一个新数组,该数组可以包含旧元素以及必须添加到列表中的新元素。

列表接口有不同的实现方式,它们都有其优缺点,您应该选择最适合解决问题集的实现方式。下面我将尝试简要总结何时使用哪种实现方式:

非线程安全的实现:

  • ArrayList:List接口的可变长数组实现。当您需要进行大量size,isEmpty,get,set,iterator和listIterator操作且运行时间为常数时,应使用此实现。 add操作以摊销常数时间运行,即添加n个元素需要O(n)时间。我认为在做更多查找(get())而非向列表中添加项目(add())时,应使用此实现。
  • LinkedList:此实现不是由数组备份支持,而是“链接”节点。在我看来,当你需要做更多add()而不是get()时,应使用此实现。

线程安全的实现:

请注意,这些列表实现不是线程安全的,这意味着在从多个线程访问它们时可能会出现竞态条件。如果您想从多个线程使用列表实现,我建议您学习java.util.concurrent包并使用该类中的实现。


非常小心使用LinkedList。尽管添加项仅需要O(1)复杂度,但获取第n个索引的顺序是O(n)复杂度。即使在数组上的大多数操作都是追加新项的情况下,LinkedList有时仍可能比其他替代方案慢得多。话虽如此,当正确使用LinkedList时,性能收益可能是巨大的。 - Jack G

3
您可能应该使用ArrayList而不是Vector,原因在其他回答中已经解释了。
然而...

我尝试使用Vector,但是当您进行插入操作时,所有元素都会向后移动,而我需要一个可以增长但元素保持原位的数组。

当您执行insertElementAt(pos, elem)时,您已经明确要求元素移动。如果您不想让元素移动,应该使用set(pos, elem)代替。或者,如果您想将元素添加到向量的末尾,也可以使用add(elem)
顺便说一下,前面的段落适用于所有List实现,不仅仅是Vector,尽管在不同类型的List中,实现细节和性能各不相同。

1
我尝试使用Vector,但是当你进行插入时,它会将所有元素向后移动,而我需要一个可以增长但元素保持原位的数组。
你可能想要使用ArrayList代替Vector。
它们都提供大致相同的接口,你可以通过调用set(idx, element)来替换它们的元素。这不会导致任何移位。它也不允许你增加数组的大小:你只能在已经占用的位置(不能超过数组的当前大小)插入,如果要添加新元素,则必须使用add(element)。
ArrayList和Vector之间的区别在于Vector具有同步代码,而你很可能不需要它,这使得ArrayList稍微快一些。

1
如果您想在所有元素已插入或删除后操作数组数据,有一种方法是尝试创建一个LinkedList或ArrayList,它们可以简单地调整大小。在数据输入完成后,您可以将ArrayList转换为Array,然后像平常对待数组一样进行所有操作。

1

ArrayList和LinkedList

空间复杂度:

a) ArrayList: 初始化时分配一块内存,每次动态添加元素时达到最大大小时就加倍。

b) LinkedList: 仅在向列表添加项目时分配内存。

运行时间复杂度:

a) ArrayList: 搜索速度更快,插入和删除速度比链表慢。

b) LinkedList: 插入和删除速度更快,搜索速度比数组列表慢。


1
还要注意LinkedList占用了更多的内存,如果过度使用,有时会导致可怕的内存碎片化。 - Jack G

1
在Java中,数组不能动态调整大小。解决方法是使用ArrayList或创建另一个临时数组并将其分配。您可以找到有关ArrayList的教程,但如果您只想在Java中自定义ResizableArray,则可以使用它。但是不建议使用!这只是一个虚假的可调整大小的数组,当您创建太多对象时,堆内存会增加。这只是为了向您展示这个想法。
  • 接口
public interface Resizable<T> {

    void add(T data);
    int delete(int index);
    int size();
    void print();
}
  • 实现类
public class ResizeableImpl<T> implements Resizable<T> {

    private Object[] temp = null;
    private Object[] originals = new Object[0];

    @Override
    public void add(T data) {
        Object[] temp = new Object[originals.length+1];
        for (int i=0; i<originals.length; i++) {
            temp[i]=originals[i];
        }
        temp[originals.length]=data;
        originals=temp;
    }

    @Override
    public int delete(int index) {
        int success=0;
        switch (originals.length) {
            case 0: //No Data to delete
                success=0;
                break;
            case 1: //One Data is delete and so no data, too!
                originals = new Object[0];
                success = 1;
                break;
            default: //>=2
                int count=0;
                originals[index]=null;
                temp = new Object[originals.length-1];
                for (int i=0; i<originals.length; i++) {
                    if (originals[i]!=null)
                    temp[count++]=originals[i];
                }
                originals = temp;
                success = 1;
        }

        return success;
    }

    @Override
    public int size() {
        return originals.length;
    }

    @Override
    public void print() {
        StringBuilder sb = null;

        if (originals.length==0) {
            System.out.println("No data available!");
            return;
        }

        for (int i=0; i<originals.length; i++) {
            if (sb==null) {
                sb = new StringBuilder();
                sb.append(originals[i]);
            }
            else {
                sb.append(", "+originals[i]);
            }
        }
        sb.append(".");
        System.out.println(sb.toString());
    }
}
  • 主方法
public class App {

    public static void main(String[] args) {
        //Program to interfaces, not implementations
        Resizable<Integer> obj = new ResizeableImpl<>();

        obj.add(13);
        obj.add(20);
        obj.add(17);
        obj.add(25);
        obj.add(100);
        obj.add(12);
        obj.print();

        int result = obj.delete(2); //This will delete 17.
        if (result==1) {
            System.out.println("Deletion is successful!");
        }
        obj.print();

        obj.delete(3); //This will delete 100.
        obj.print();
    }
}

输出

13, 20, 17, 25, 100, 12.
Deletion is successful!
13, 20, 25, 100, 12.
13, 20, 25, 12.

0
请使用ArrayList或LinkedList。

-2

使用集合框架中的精美类比使用数组更好。 但是,如果您的问题是从“问答”角度出发的,那么您应该这样做。 创建自己的调整大小方法,例如:

  int[] oldArray = {1,2,3};

  int oldSize = java.lang.reflect.Array.getLength(oldArray);
  Class elementType = oldArray.getClass().getComponentType();
  Object newArray = java.lang.reflect.Array.newInstance(
         elementType,newSize);
  int preserveLength = Math.min(oldSize,newSize);
  if (preserveLength > 0)
      System.arraycopy (oldArray,0,newArray,0,preserveLength);

  oldArray = newArray;

这并不会调整数组的大小,而是创建一个新的数组。这意味着持有对旧数组引用的任何对象都将看不到更改。 - Thilo
我承认有罪,请忽略我之前的回答。 - ring bearer
1
如果我们应该忽略你的回答,也许你可以将其全部删除?这样你也不会再次冒着被踩的风险了。 - Alfred
我没有给你点踩;)。我反对自己点踩,除非一个答案/问题完全没用。 - Alfred

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