为什么我会使用list.clear()

时间:2014-07-01 20:00:09

标签: java collections garbage-collection

java中的Arraylist有一个方法调用clear()。为什么我会选择使用明确的辞职参考?

list.clear()

VS

list = new ArrayList() ? 

看起来list.clear()会慢一些,在第二种情况下,GC会处理清理并让我们的生活更轻松吗?

6 个答案:

答案 0 :(得分:9)

当某些其他代码引用同一列表时,您会使用list.clear()

例如,如果您遇到这种情况,重新分配将不起作用:

class Watcher {
    private final List<String> list;
    public Watcher(List<String> l) { list = l; }
    public void doSomething() {
        // Use list
    }
}

void main() {
    List<String> lst = new ArrayList<String>();
    Watcher w = new Watcher(lst);
    ...
    // At this point lst.clear() is different from lst = new List<String>()
}

答案 1 :(得分:4)

如果您的列表是本地的,并且您可以完全控制,那么创建一个新列表肯定不是一个坏主意 - 在许多情况下,创建新列表会更快(尽管并非总是如此)。请参阅示例this related answer

但是,您可能处于多个对象共享列表的情况。然后,您需要处理该共享引用。

答案 2 :(得分:1)

  

看起来list.clear()会慢一些,

并非总是如此。

clear()必须清除你使用的引用,但是当你创建一个新对象时,它必须先清除对象的内存,然后才能使用它。

  

在第二种情况下,GC会处理清理并让我们的生活变得轻松吗?

GC不是免费的。当你用垃圾填充你的CPU缓存时,他们不会很好地工作。您可以通过重用对象来显着加快代码速度。这取决于你的用例哪个更快。


很难找到一个体面的微基准来证明这一点,但在实际程序中,代码更复杂,其影响远远超出您的预期。

public class Main {

    public static void main(String... args) {
        for (int i = 0; i < 10; i++) {
            long t1 = timeReuse();
            long t2 = timeNewObject();
            System.out.printf("Reuse time: %,d, New ArrayList time: %,d%n", t1, t2);
        }
    }

    static final int RUNS = 50000;
    static final byte[] a = new byte[8 * 1024];
    static final byte[] b = new byte[a.length];

    private static long timeReuse() {
        long start = System.nanoTime();
        List<Integer> ints = new ArrayList<Integer>();
        for (int i = 0; i < RUNS; i++) {
            ints.clear();
            for (int j = -128; j < 128; j++)
                ints.add(j);

            System.arraycopy(a, 0, b, 0, a.length);
        }
        long time = System.nanoTime() - start;
        return time / RUNS;
    }

    private static long timeNewObject() {
        long start = System.nanoTime();
        for (int i = 0; i < RUNS; i++) {
            List<Integer> ints = new ArrayList<Integer>(256);

            for (int j = -128; j < 128; j++)
                ints.add(j);
            System.arraycopy(a, 0, b, 0, a.length);
        }
        long time = System.nanoTime() - start;
        return time / RUNS;
    }
}

打印

Reuse time: 1,964, New ArrayList time: 1,866
Reuse time: 1,889, New ArrayList time: 1,770
Reuse time: 1,163, New ArrayList time: 1,416
Reuse time: 1,250, New ArrayList time: 1,357
Reuse time: 1,253, New ArrayList time: 1,393
Reuse time: 1,106, New ArrayList time: 1,203
Reuse time: 1,103, New ArrayList time: 1,207
Reuse time: 1,113, New ArrayList time: 1,315
Reuse time: 1,104, New ArrayList time: 1,215
Reuse time: 1,106, New ArrayList time: 1,335

注意:复制的缓冲区大小有所不同。


如果考虑延迟,情况要糟糕得多。这打印出每次运行的最差延迟。

public class Main {

    public static void main(String... args) {
        for (int i = 0; i < 10; i++) {
            long t1 = timeReuse();
            long t2 = timeNewObject();
            System.out.printf("Reuse time: %,d, New ArrayList time: %,d%n", t1, t2);
        }
    }

    static final int RUNS = 50000;
    static final byte[] a = new byte[8 * 1024];
    static final byte[] b = new byte[a.length];

    private static long timeReuse() {
        List<Integer> ints = new ArrayList<Integer>();
        long longest = 0;
        for (int i = 0; i < RUNS; i++) {
            long start = System.nanoTime();
            ints.clear();
            for (int j = -128; j < 128; j++)
                ints.add(j);

            System.arraycopy(a, 0, b, 0, a.length);
            long time = System.nanoTime() - start;
            longest = Math.max(time, longest);
        }
        return longest;
    }

    private static long timeNewObject() {
        long longest = 0;
        for (int i = 0; i < RUNS; i++) {
            long start = System.nanoTime();
            List<Integer> ints = new ArrayList<Integer>(256);
            for (int j = -128; j < 128; j++)
                ints.add(j);
            System.arraycopy(a, 0, b, 0, a.length);
            long time = System.nanoTime() - start;
            longest = Math.max(time, longest);
        }
        return longest;
    }
}
使用-Xmx32m

运行时打印

Reuse time: 74,879, New ArrayList time: 2,441,586
Reuse time: 26,889, New ArrayList time: 2,203,096
Reuse time: 25,920, New ArrayList time: 1,514,465
Reuse time: 13,013, New ArrayList time: 1,342,395
Reuse time: 12,368, New ArrayList time: 1,708,658
Reuse time: 12,272, New ArrayList time: 1,258,990
Reuse time: 12,559, New ArrayList time: 1,433,898
Reuse time: 12,144, New ArrayList time: 1,259,413
Reuse time: 12,433, New ArrayList time: 1,221,945
Reuse time: 12,352, New ArrayList time: 1,318,024

答案 3 :(得分:0)

我想有几个用例,这里有一个来自我的头脑:

private List<Foo> fooList = new ArrayList<>();
private List<Foo> unmodifiableFooList = Collections.unmodifiableList(fooList);

public List<Foo> getFooList(){
  return unmodifiableFooList;
}

...

fooList.clear();
...

答案 4 :(得分:0)

1)有些情况,当它不可互换时。例如,您有一个列表作为参数。如果您希望在方法外部显示空列表,唯一的解决方案是list.clear()。如果使用新的ArrayList(),则更改将是本地的,并且方法外的列表不会更改。

static void doSmth(List list){
    list.clear();
}

void main(...){
    ...
    list.add(...);
    doSmth(list);
    // Here we get the empty list
    System.out.println(list);
}

在这种情况下你别无选择。只有list.clear()可以使用。

2)新的ArrayList()需要额外的内存。 list.clear()没有。 list.clear()是首选。

3)list.clear()更快。这与GC无关。无论如何,列表中包含的元素将被GC编辑。但是在list.clear()的情况下,仅将内部计数器设置为0.对于新的ArrayList(),将分配新对象,将调用构造函数并初始化新对象。因此新的ArrayList()速度较慢。 但实际上,与应用程序中其他部分的性能相比,差异可能非常小。

答案 5 :(得分:0)

您正在实现某种类型的队列......并且有几个工作线程。每个人在创建队列时都会传递对队列的引用。

LinkedList q = new LinkedList();
Worker worker1 = new Worker(q);
Worker worker2 = new Worker(q);

是的,我意识到围绕该声明的同步有各种各样的问题...你可能真的想要使用像LinkedBlockingDeque这样的东西......

所以,你快乐地将东西插入这个队列,而工作人员正在做q.poll() ...并且由于某种原因,你想要刷新队列。

此时,您不能只执行q = new LinkedList(),因为工作人员拥有旧的q,并且不会看到您创建新的q。就此而言,如果那里还有你要转储(或不处理)的东西。

有很多方法可以解决这个问题......告诉每个工人这里新的q.clear()就是一个。但另一个只是致电list.clear(),你已经刷新了每个人都在工作的清单。

因此,{{1}}有一个用例。我承认它不是最好的,人们可能会想到更好的例子,但是那里的功能意味着人们可以做到这一点而可以使用它而不是去通过其他扭曲来实现他们想要的工作。