【问题标题】:Optimize insertion from ArrayList to HashMap优化从 ArrayList 到 HashMap 的插入
【发布时间】:2020-11-25 18:13:30
【问题描述】:

我正在尝试以最佳方式将数据从 ArrayList 插入 HashMap

许多项目可能具有相同的 languge_name(代码如下),因此我需要在 Language 类中对具有相同语言的项目进行分组,并将语言存储在 HashMap 中,并以语言名称作为键。

物品

String name;
String language_name;

语言

String language_name;
int numberItems; 
LinkedList<String> Items;

我解决了这个问题:

        ArrayList<Item> items; // given array of items
        HashMap<String, Language> languages = new HashMap<String, Language>();

        items.forEach(item -> {
            /** case 1: language isn't specified */
            if (item.getLanguageName() == null) {
                item.setLanguageName("unknown");
            }
            /** case 2: language already added */
            if (languages.containsKey(item.getLanguageName())) {
                languages.get(item.getLanguageName()).getItems().add(item.getName());
                languages.get(item.getLanguageName())
                        .setNumberItems(languages.get(item.getLanguageName()).getNumberItems() + 1);
            } else {
                /** case 3: language isn't added yet */
                LinkedList<String> languageItems = new LinkedList<String>();
                languageItems.add(item.getName());
                Language language = new Language(item.getLanguageName(), 1, languageItems);
                languages.put(item.getLanguageName(), language);
            }
        });

任何帮助将不胜感激!

【问题讨论】:

  • 您可能会在Code Review 获得更好的答案。
  • 为什么您的语言课需要手动计数器int numberItems;?如果您需要知道它包含多少项目,您总是可以在您的列表中调用.size()。添加一个手动增加的额外计数器似乎有点多余。
  • 了解局部变量的威力。它们允许您只执行一次像get(item.getLanguageName()) 这样的操作,而不是连续执行三次。您甚至可以省略在if(languages.containsKey(item.getLanguageName())) 中进行的第四次哈希查找,首先执行Language lang = get(item.getLanguageName());,然后执行if(lang != null) { lang.getItems().add(item.getName()); lang.setNumberItems(lang.getNumberItems() + 1); } else …,尽管正如前面的评论者所说,更新这个计数器已经过时了。还有don’t use LinkedList

标签: java optimization arraylist collections hashmap


【解决方案1】:

假设您使用的是 Java 8 或更高版本,这可以通过内置的流函数很好地完成。

HashMap<String, List<Items>> itemsGroupedByLanguage =
 items.stream().collect(Collectors.groupingBy(Items::getLanguage));

【讨论】:

  • 但是结果应该是HashMap&lt;String, Language&gt;,而不是HashMap&lt;String, List&lt;Items&gt;&gt;
  • 是的,结果是HashMap,HashMap>里面有冗余。流收集是可读的,但不响应我的需要
  • 只是想知道,但将其置于该特定格式的目的是什么?例如,由于所有信息都包含在语言对象中,是否有理由认为它是 hashmap 而不是 List
【解决方案2】:

tl;dr

使用 Java (8+) 内置收集器无法实现您想要的,但您可以编写自己的自定义收集器并编写如下代码以收集到地图中 -

Map&lt;String, Language&gt; languages = items.stream().collect(LanguageCollector.toLanguage());

我们先来看看Collector&lt;T, A, R&gt;接口

public interface Collector<T, A, R> {
    /**
     * A function that creates and returns a new mutable result container.
     */
    Supplier<A> supplier();

    /**
     * A function that folds a value into a mutable result container.
     */
    BiConsumer<A, T> accumulator();

    /**
     * A function that accepts two partial results and merges them.  The
     * combiner function may fold state from one argument into the other and
     * return that, or may return a new result container.
     */
    BinaryOperator<A> combiner();

    /**
     * Perform the final transformation from the intermediate accumulation type
     */
    Function<A, R> finisher();

    /**
     * Returns a  Set of Collector.Characteristics indicating
     * the characteristics of this Collector.  This set should be immutable.
     */
    Set<Characteristics> characteristics();
}

其中T 是要收集的流中项目的通用类型。 A 是累加器的类型,在收集过程中将在其上累加部分结果的对象。 R 是结果的对象类型(通常,但不总是,集合) 来自收集操作

现在我们来看看自定义的LanguageCollector

  public class LanguageCollector
      implements Collector<Item, Map<String, Language>, Map<String, Language>> {

    /**
     * The supplier method has to return a Supplier of an empty accumulator - a parameterless
     * function that when invoked creates an instance of an empty accumulator used during the
     * collection process.
     */
    @Override
    public Supplier<Map<String, Language>> supplier() {

      return HashMap::new;
    }

    /**
     * The accumulator method returns the function that performs the reduction operation. When
     * traversing the nth element in the stream, this function is applied with two arguments, the
     * accumulator being the result of the reduction (after having collected the first n–1 items of
     * the stream) and the nth element itself. The function returns void because the accumulator is
     * modified in place, meaning that its internal state is changed by the function application to
     * reflect the effect of the traversed element
     */
    @Override
    public BiConsumer<Map<String, Language>, Item> accumulator() {

      return (map, item) -> {
        if (item.getLanguageName() == null) {
          item.setLanguageName("unknown");
        } else if (map.containsKey(item.getLanguageName())) {
          map.get(item.getLanguageName()).getItems().add(item.getName());
          map.get(item.getLanguageName())
              .setNumberItems(map.get(item.getLanguageName()).getNumberItems() + 1);
        } else {
          Language language = new Language(item.getLanguageName(), 1);
          language.add(item.getName());
          map.put(item.getLanguageName(), language);
        }
      };
    }

    /**
     * The combiner method, return a function used by the reduction operation, defines how the
     * accumulators resulting from the reduction of different subparts of the stream are combined
     * when the subparts are processed in parallel
     */
    @Override
    public BinaryOperator<Map<String, Language>> combiner() {
      return (map1, map2) -> {
          map1.putAll(map2);
          return map1;
       };
    }

    /**
     * The finisher() method needs to return a function which transforms the accumulator to the
     * final result. In this case, the accumulator is the final result as well. Therefore it is
     * possible to return the identity function
     */
    @Override
    public Function<Map<String, Language>, Map<String, Language>> finisher() {
      return Function.identity();
    }

    /**
     * The characteristics, returns an immutable set of Characteristics, defining the behavior of
     * the collector—in particular providing hints about whether the stream can be reduced in
     * parallel and which optimizations are valid when doing so
     */
    @Override
    public Set<Characteristics> characteristics() {
      return Collections.unmodifiableSet(
          EnumSet.of(Characteristics.IDENTITY_FINISH));
    }

    /**
     * Static method to create LanguageCollector
     */
    public static LanguageCollector toLanguage() {
      return new LanguageCollector();
    }
  }

我已经稍微修改了你的类(以遵循命名约定和更多可读的累加器操作)。

班级Item

public class Item {
    private String name;
    private String languageName;

    public Item(String name, String languageName) {
      this.name = name;
      this.languageName = languageName;
    }
    //Getter and Setter
  }

班级Language

public class Language {
    private String languageName;
    private int numberItems;
    private LinkedList<String> items;

    public Language(String languageName, int numberItems) {
      this.languageName = languageName;
      this.numberItems = numberItems;
      items = new LinkedList<>();
    }

    public void add(String item) {
      items.add(item);
    }

    // Getter and Setter

    public String toString() {
      return "Language(languageName=" + this.getLanguageName() + ", numberItems=" + this.getNumberItems() + ", items=" + this.getItems() + ")";
    }
  }

运行代码

public static void main(String[] args) {
    List<Item> items =
        Arrays.asList(
            new Item("ItemA", "Java"),
            new Item("ItemB", "Python"),
            new Item("ItemC", "Java"),
            new Item("ItemD", "Ruby"),
            new Item("ItemE", "Python"));

    Map<String, Language> languages = items.stream().collect(LanguageCollector.toLanguage());

    System.out.println(languages);
  }

打印

{Java=Language(languageName=Java, numberItems=2, items=[ItemA, ItemC]), Ruby=Language(languageName=Ruby, numberItems=1, items=[ItemD]), Python=Language(languageName=Python, numberItems=2, items=[ItemB, ItemE])}

有关更多信息,请阅读《现代 Java 实战:Lambdas、流、函数式和反应式编程》第 6.5 章或查看this link

【讨论】:

  • 当您的收集器使用HashMap 时,不要指定CONCURRENT 特征。 HashMap 类不是线程安全的,您的收集器不是并发收集器。除此之外,合并功能不必要地复杂。可能只是(map1, map2) -&gt; { map1.putAll(map2); return map1; }
  • 但这一切看起来很像一个已经存在的内置收集器,所以我建议重新考虑内置收集器是否真的不可能:Collectors.toMap(item -&gt; item.getLanguageName(), item -&gt; new Language(item.getLanguageName(), 1, new ArrayList&lt;&gt;(Arrays.asList(item))), (lang1, lang2) -&gt; { lang1.getItems().addAll(lang2.getItems()); return lang1; })Collectors.groupingBy(item -&gt; item.getLanguageName(), Collectors.collectingAndThen(Collectors.toList(), list -&gt; new Language(list.get(0).getLanguageName(), list.size(), list)))
  • 感谢@Holger 阅读长篇文章:-)。我同意您对评论 1 的看法,并相应地进行了更改。关于评论2,让我试试看
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-01-05
  • 1970-01-01
  • 2023-03-10
  • 1970-01-01
  • 2018-11-30
  • 2013-01-12
  • 1970-01-01
相关资源
最近更新 更多