Java Collection性能问题

时间:2010-05-27 07:59:29

标签: java performance collections

我创建了一个方法,它将两个Collection<String>作为输入并将一个副本复制到另一个。

但是,我不确定在开始复制之前是否应该检查集合是否包含相同的元素,或者我是否应该只是复制。这是方法:

 /**
  * Copies from one collection to the other. Does not allow empty string. 
  * Removes duplicates.
  * Clears the too Collection first
  * @param src
  * @param dest
  */
 public static void copyStringCollectionAndRemoveDuplicates(Collection<String> src, Collection<String> dest) {
  if(src == null || dest == null)
   return;

  //Is this faster to do? Or should I just comment this block out
  if(src.containsAll(dest))
   return;

  dest.clear();
  Set<String> uniqueSet = new LinkedHashSet<String>(src.size());
  for(String f : src) 
   if(!"".equals(f)) 
    uniqueSet.add(f);

  dest.addAll(uniqueSet);
 }

也许删除

更快
if(src.containsAll(dest))
    return;

因为这种方法无论如何都会迭代整个集合。

6 个答案:

答案 0 :(得分:7)

我会说:删除它!它是重复的'代码',Set正在执行相同的'contains()'操作,因此不需要在此处预处理它。除非你有一个巨大的输入集合和一个出色的O(1)测试containsAll(); - )

该套装足够快。它具有基于输入大小的O(n)复杂度(一个包含()和(可能)每个String的一个add()操作)并且如果target.containsAll()测试失败,则contains()完成两次对于每个字符串 - &gt;性能较差。

修改

一些伪代码可视化我的答案

void copy(source, dest) {
  bool:containsAll = true;
  foreach(String s in source) {  // iteration 1
    if (not s in dest) {         // contains() test
       containsAll=false
       break
    }
  }
  if (not containsAll) {
    foreach(String s in source) { // iteration 2
      if (not s in dest) {        // contains() test
        add s to dest
      }
    }
  }
}

如果所有源元素都在dest中,则对每个源元素调用一次contains()。如果除了最后一个源元素之外的所有元素都在dest(最坏情况)中,那么contains()被调用(2n-1)次(n =源集合的大小)。 但是,带有额外测试的contains()test 的总数总是等于或大于没有额外测试的相同代码。

编辑2 让我们假设,我们有以下集合:

source = {"", "a", "b", "c", "c"}
dest = {"a", "b"}

首先,containsAll测试失败,因为源中的空String不在dest中(这是代码中的一个小设计缺陷;))。然后创建一个临时集合{"a", "b", "c"}(空字符串和第二个“c”被忽略)。最后你添加everthing到dest并假设dest是一个简单的ArrayList,结果是{"a", "b", "a", "b", "c"}。这是意图吗?一个较短的选择:

void copy(Collection<String> in, Collection<String> out) {
  Set<String> unique = new HashSet<String>(in);
  in.remove("");
  out.addAll(unique);
}

答案 1 :(得分:3)

如果containsAll()的元素多于target,则dest无效:
dest:[a,b,c]
target.containsAll(dest)是真的,所以dest是[a,b,c],但应该是[a,b,c,d]。

我认为以下代码更优雅:

Set<String> uniqueSet = new LinkedHashSet<String>(target.size());
uniqueSet.addAll(target);
if(uniqueSet.contains(""))
    uniqueSet.remove("");

dest.addAll(uniqueSet);

答案 2 :(得分:2)

如果重要的话,你可以对它进行基准测试。我认为对containsAll()的调用可能没有帮助,但它可能取决于两个集合具有相同内容的频率。

但这段代码令人困惑。它正在尝试向dest添加新项目?那为什么要先清楚呢?只需将新的uniqueSet返回给来电者,而不是打扰他。你的containsAll()支票是否被撤销了?

答案 3 :(得分:1)

  1. 参数名称过于混乱。 desttarget具有几乎相同的含义。您最好选择destsource之类的内容。它会使事情变得更加清晰。

  2. 我有一种感觉(不确定它是正确的)你以错误的方式使用集合API。接口Collection没有说明其元素的单一性,但是你可以为它添加这种质量。

  3. 修改作为参数传递的集合不是最好的主意(但通常,它取决于)。一般情况下,可变性是有害的,不必要的。而且,如果传递的集合是不可修改/不可变的呢?最好返回新的集合,然后修改传入的集合。

  4. Collection界面包含方法addAllremoveAllretainAll。你先试试了吗?您是否对代码进行了性能测试,如:

    Collection<String> result = new HashSet<String> (dest);
    result.addAll (target);
    

    target.removeAll (dest);
    dest.addAll (target);
    

答案 4 :(得分:1)

代码难以阅读且效率不高。 “dest”参数令人困惑:它作为参数传递,然后将其清除并将结果添加到其中。它是一个参数有什么意义?为什么不简单地返回一个新的集合?我能看到的唯一好处是调用者可以确定集合类型。这有必要吗?

我认为这段代码可以更清晰,更有效地编写如下:

public static Set<String> createSet(Collection<String> source) {
    Set<String> destination = new HashSet<String>(source) {
        private static final long serialVersionUID = 1L;

        public boolean add(String o) {
            if ("".equals(o)) {
                return false;
            }
            return super.add(o);
        }
    }; 
    return destination;
}

另一种方法是创建自己的集类型:

public class NonEmptyStringSet extends HashSet<String> {
    private static final long serialVersionUID = 1L;

    public NonEmptyStringSet() {
        super();
    }

    public NonEmptyStringSet(Collection<String> source) {
        super(source);
    }

    public boolean add(String o) {
        if ("".equals(o)) {
            return false;
        }
        return super.add(o);
    }
}

用法:

createSet(source);
new NonEmptyStringSet(source);

返回集合的性能更高,因为您不必先创建临时集,然后将所有内容添加到dest集合中。

NonEmptyStringSet类型的好处是你可以继续添加字符串并仍然检查空字符串。

<强> EDIT1:

删除“if(src.containsAll(dest))return;”使用source == dest调用方法时,代码引入了“bug”;结果是源将为空。例如:

Collection<String> source = new ArrayList<String>();
source.add("abc");
copyStringCollectionAndRemoveDuplicates(source, source);
System.out.println(source);

<强> EDIT2:

我做了一个小的基准测试,表明我的实现比初始实现的简化版快约30%。此基准测试是初始实现的最佳案例,因为dest集合为空,因此不必清除它。另外,我的实现不是使用HashSet而是使用LinkedHashSet,这使我的实现更快一些。

基准代码:

public class SimpleBenchmark {
public static void main(String[] args) {
    Collection<String> source = Arrays.asList("abc", "def", "", "def", "", 
            "jsfldsjdlf", "jlkdsf", "dsfjljka", "sdfa", "abc", "dsljkf", "dsjfl", 
            "js52fldsjdlf", "jladsf", "dsfjdfgljka", "sdf123a", "adfgbc", "dslj452kf", "dsjfafl", 
            "js21ldsjdlf", "jlkdsvbxf", "dsfjljk342a", "sdfdsa", "abxc", "dsljkfsf", "dsjflasd4" );

    int runCount = 1000000;
    long start1 = System.currentTimeMillis();
    for (int i = 0; i < runCount; i++) {
        copyStringCollectionAndRemoveDuplicates(source, new ArrayList<String>());
    }
    long time1 = (System.currentTimeMillis() - start1);
    System.out.println("Time 1: " + time1);


    long start2 = System.currentTimeMillis();
    for (int i = 0; i < runCount; i++) {
        new NonEmptyStringSet(source);
    }
    long time2 = (System.currentTimeMillis() - start2);
    System.out.println("Time 2: " + time2);

    long difference = time1 - time2;
    double percentage = (double)time2 / (double) time1;

    System.out.println("Difference: " + difference + " percentage: " + percentage);
}

public static class NonEmptyStringSet extends HashSet<String> {
    private static final long serialVersionUID = 1L;

    public NonEmptyStringSet() {
    }

    public NonEmptyStringSet(Collection<String> source) {
        super(source);
    }

    @Override
    public boolean add(String o) {
        if ("".equals(o)) {
            return false;
        }
        return super.add(o);
    }
}

public static void copyStringCollectionAndRemoveDuplicates(
        Collection<String> src, Collection<String> dest) {
    Set<String> uniqueSet = new LinkedHashSet<String>(src.size());
    for (String f : src)
        if (!"".equals(f))
            uniqueSet.add(f);

    dest.addAll(uniqueSet);
}
}

答案 5 :(得分:0)

我真的不认为我明白为什么你会想要这个方法,但假设它值得,我会按如下方式实现它:

public static void copyStringCollectionAndRemoveDuplicates(
        Collection<String> src, Collection<String> dest) {
    if (src == dest) {
         throw new IllegalArgumentException("src == dest");
    }
    dest.clear();
    if (dest instanceof Set) {
        dest.addAll(src);
        dest.remove("");
    } else if (src instance of Set) {
        for (String s : src) {
            if (!"".equals(s)) {
                dest.add(s);
            }
        }
    } else {
        HashSet<String> tmp = new HashSet<String>(src);
        tmp.remove("");
        dest.addAll(tmp);
    }
}

注意:

  1. 在所有情况下,这并不保留src参数中元素的顺序,但方法签名暗示这是无关紧要的。
  2. 我故意不检查null。如果将null作为参数提供,则是一个错误,正确的做法是允许抛出NullPointerException
  3. 尝试将集合复制到自身也是一个错误。