Вопрос о производительности Набора Java

Я создал метод, который берет два 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;

Поскольку этот метод выполнит итерации по всему набору так или иначе.

5
задан Andreas_D 27 May 2010 в 08:53
поделиться

6 ответов

Я бы сказал: Уберите это! Это дублированный «код», Set выполняет ту же операцию «contains ()», поэтому здесь нет необходимости предварительно обрабатывать его. Если у вас нет огромного набора входных данных и блестящего O (1) теста для containsAll (); -)

Set достаточно быстр. Он имеет сложность O (n) в зависимости от размера ввода (одна содержит () и (возможно) одну операцию add () для каждой строки), и если тест target.containsAll () не проходит, contains () выполняется дважды для каждой строки -> менее эффективен.

РЕДАКТИРОВАТЬ

Некоторый псевдокод для визуализации моего ответа

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 () с дополнительным тестом всегда равно или больше того же кода без дополнительного теста.

РЕДАКТИРОВАТЬ 2 Предположим, у нас есть следующие коллекции:

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

Во-первых, тест containsAll не выполняется, потому что пустая строка в источнике не находится в dest (это небольшой недостаток дизайна в вашем коде;)).Затем вы создаете временный набор, который будет {"a", "b", "c"} (пустая строка и вторая "c" игнорируются). Наконец, вы добавляете все в 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);
}
7
ответ дан 18 December 2019 в 13:11
поделиться

Вы можете протестировать его, если это так важно. Я думаю, что вызов containsAll () , скорее всего, не поможет, хотя это может зависеть от того, как часто две коллекции имеют одинаковое содержимое.

Но этот код сбивает с толку. Пытается добавить новые элементы в dest ? Так почему он сначала очищает его? Вместо этого просто верните свой новый uniqueSet вызывающему абоненту, вместо того, чтобы беспокоиться. И разве ваша проверка containsAll () не отменена?

2
ответ дан 18 December 2019 в 13:11
поделиться

Код трудно читать и он не очень эффективен. Параметр "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; В результате source будет пустым. Пример:

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);
}
}
1
ответ дан 18 December 2019 в 13:11
поделиться

Я действительно не думаю, что понимаю, почему вам нужен этот метод, но, предполагая, что он имеет смысл, я бы реализовал его следующим образом:

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. Попытка скопировать коллекцию в себя также является ошибкой.
0
ответ дан 18 December 2019 в 13:11
поделиться

containsAll () не поможет, если target имеет больше элементов, чем пункт назначения :
цель: [a, b, c, d]
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);
3
ответ дан 18 December 2019 в 13:11
поделиться
  1. Слишком много запутанных имен параметров. dest и target имеют почти одинаковое значение. Лучше выбрать что-то вроде dest и source. Это сделает вещи намного более понятными даже для вас.

  2. У меня есть ощущение (не уверен, что оно верное), что вы используете API коллекций неправильно. Интерфейс Collection ничего не говорит об уникальности своих элементов, но вы добавляете ему это качество.

  3. Модификация коллекций, переданных в качестве параметров, - не самая лучшая идея (но, как обычно, все зависит от ситуации). В общем случае, мутабельность вредна и не нужна. Более того, что если переданные коллекции немодифицируемы/неизменяемы? Лучше вернуть новую коллекцию, а затем модифицировать входящие коллекции. В интерфейсе

  4. Collection есть методы addAll, removeAll, retainAll. Попробовали ли вы их сначала? Делали ли вы тесты производительности для кода типа:

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

    или

    target.removeAll (dest);
    dest.addAll (target);
    
1
ответ дан 18 December 2019 в 13:11
поделиться
Другие вопросы по тегам:

Похожие вопросы: