【问题标题】:Why will I ever use list.clear()为什么我会使用 list.clear()
【发布时间】:2014-07-01 20:00:08
【问题描述】:

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

list.clear()

list = new ArrayList() ? 

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

【问题讨论】:

  • 因为java是传值的。
  • @SotiriosDelimanolis:变得迟钝。我想如果你再深入一点,你会发现 Java passes references by value.
  • clear() 将使其所有元素 GCAblae,其中将其分配给 new 实例将花费新对象创建的成本,它还将使所有元素 GCable + 列表的实际实例 GCable
  • 我认为所谓的重复询问了clear() 的时间方面,而这个询问了逻辑,同时假设(非常正确)clear() 调用可能会更慢。投票重新开放。
  • 讨论moved to chat.

标签: java collections garbage-collection


【解决方案1】:

当某些其他代码段引用同一列表时,您将使用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>()
}

【讨论】:

    【解决方案2】:

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

    但是,您可能会遇到多个对象共享列表的情况。然后,您需要处理该共享参考。

    【讨论】:

      【解决方案3】:

      看起来 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
      

      【讨论】:

      • clear 更快的主要情况是列表很大并且被完全重用。然而,即使在这种情况下,创建一个容量设置为正确级别的新列表也可能会产生更好的性能。在这两种情况下,GC 基本上是相同的(除了列表本身的额外 GC 可能是边际的)。除非您每 1 毫秒执行一次!
      • @assylias 如果您访问 L1 缓存中的数据,它比访问 L3 缓存中的数据快 10-20 倍。单独来看,没有什么区别,如果您的应用程序执行其他任何操作,如果您创建的垃圾意味着您最终使用较慢的缓存,那将明显变慢。
      • 无法反驳!
      • @assylias 如果您查看最差的延迟,那么创建新对象的时间会增加 100 倍;)
      【解决方案4】:

      我猜有几个用例,这是我想到的一个:

      private List<Foo> fooList = new ArrayList<>();
      private List<Foo> unmodifiableFooList = Collections.unmodifiableList(fooList);
      
      public List<Foo> getFooList(){
        return unmodifiableFooList;
      }
      
      ...
      
      fooList.clear();
      ...
      

      【讨论】:

        【解决方案5】:

        1) 在某些情况下,它是不可互换的。例如,您有一个列表作为参数。如果您希望空列表在方法之外可见,唯一的解决方案是 list.clear()。如果使用new 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) new ArrayList() 需要额外的内存。 list.clear() 没有。 list.clear() 是首选。

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

        【讨论】:

          【解决方案6】:

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

          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() 有一个用例。我承认它不是最好的,人们可能会想到更好的例子,但是拥有那里的功能意味着人们可以做到并且可以使用它而不是去通过其他扭曲来使实现按他们想要的方式工作。

          【讨论】:

            猜你喜欢
            • 2023-02-05
            • 1970-01-01
            • 1970-01-01
            • 2014-06-17
            • 1970-01-01
            • 2018-03-06
            • 2019-12-16
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多