Les collections ordonnées utilisent Collectors.groupingBy() ou Collectors.toMap() pour générer des problèmes dans le désordre

Sorties Collectors.groupingBy() hors service

Scènes

Par exemple, regroupez la liste de commandes ordonnée (par ordre décroissant de temps de création) par numéro de commande et renvoyez les informations de la liste de commandes

À l'aide de Collectors.groupingBy, les données finalement renvoyées au frontal ne sont pas dans le même ordre que la liste de commande ordonnée avant le regroupement, ce qui entraîne une sortie dans le désordre.

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.junit.jupiter.api.Test;

import java.util.*;
import java.util.stream.Collectors;

public class CollectorsTest {
    
    
    @Test
    public void testGroupingBy() {
    
    
        List<Order> list = Arrays.asList(
                new Order("339242501193350531", "苹果", DateUtil.parse("2021-10-10 10:10:10", DatePattern.NORM_DATETIME_PATTERN)),
                new Order("339242501193350531", "橘子", DateUtil.parse("2021-10-10 10:10:10", DatePattern.NORM_DATETIME_PATTERN)),
                new Order("339242501183340238", "香蕉", DateUtil.parse("2021-10-10 8:10:10", DatePattern.NORM_DATETIME_PATTERN)),
                new Order("339242501180357433", "手机壳", DateUtil.parse("2021-10-10 6:10:10", DatePattern.NORM_DATETIME_PATTERN)));
        List<Order> orderList = new ArrayList<>(list);
        orderList.forEach(System.out::println);
        Map<String, List<Order>> orderMap = orderList
                .stream()
                .collect(Collectors.groupingBy(Order::getOrderNo));
        System.out.println(orderMap);
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Order {
    
    
        private String orderNo;
        private String orderName;
        private Date createDate;
    }
}

sortir

CollectorsTest.Order(orderNo=339242501193350531, orderName=苹果, createDate=2021-10-10 10:10:10)

CollectorsTest.Order(orderNo=339242501193350531, orderName=橘子, createDate=2021-10-10 10:10:10)

CollectorsTest.Order(orderNo=339242501183340238, orderName=香蕉, createDate=2021-10-10 08:10:10)

CollectorsTest.Order(orderNo=339242501180357433, orderName=手机壳, createDate=2021-10-10 06:10:10)

{
    
    339242501180357433=[CollectorsTest.Order(orderNo=339242501180357433, orderName=手机壳, createDate=2021-10-10 06:10:10)], 339242501193350531=
[CollectorsTest.Order(orderNo=339242501193350531, orderName=苹果, createDate=2021-10-10 
10:10:10), CollectorsTest.Order(orderNo=339242501193350531, orderName=橘子, 
createDate=2021-10-10 10:10:10)], 339242501183340238=
[CollectorsTest.Order(orderNo=339242501183340238, orderName=香蕉, createDate=2021-10-10 
08:10:10)]}

La méthode Collectors.groupingBy() de Java est implémentée à l'aide de HashMap par défaut, et HashMap lui-même ne garantit pas l'ordre des éléments. Par conséquent, si vous devez garantir l'ordre de sortie, vous pouvez utiliser la méthode surchargée de Collectors.groupingBy() et spécifier une implémentation de Map ordonnée, telle que LinkedHashMap

    /**
     * Returns a {@code Collector} implementing a "group by" operation on
     * input elements of type {@code T}, grouping elements according to a
     * classification function, and returning the results in a {@code Map}.
     *
     * <p>The classification function maps elements to some key type {@code K}.
     * The collector produces a {@code Map<K, List<T>>} whose keys are the
     * values resulting from applying the classification function to the input
     * elements, and whose corresponding values are {@code List}s containing the
     * input elements which map to the associated key under the classification
     * function.
     *
     * <p>There are no guarantees on the type, mutability, serializability, or
     * thread-safety of the {@code Map} or {@code List} objects returned.
     * @implSpec
     * This produces a result similar to:
     * <pre>{@code
     *     groupingBy(classifier, toList());
     * }</pre>
     *
     * @implNote
     * The returned {@code Collector} is not concurrent.  For parallel stream
     * pipelines, the {@code combiner} function operates by merging the keys
     * from one map into another, which can be an expensive operation.  If
     * preservation of the order in which elements appear in the resulting {@code Map}
     * collector is not required, using {@link #groupingByConcurrent(Function)}
     * may offer better parallel performance.
     *
     * @param <T> the type of the input elements
     * @param <K> the type of the keys
     * @param classifier the classifier function mapping input elements to keys
     * @return a {@code Collector} implementing the group-by operation
     *
     * @see #groupingBy(Function, Collector)
     * @see #groupingBy(Function, Supplier, Collector)
     * @see #groupingByConcurrent(Function)
     */
    public static <T, K> Collector<T, ?, Map<K, List<T>>>
    groupingBy(Function<? super T, ? extends K> classifier) {
    
    
        return groupingBy(classifier, toList());
    }

    /**
     * Returns a {@code Collector} implementing a cascaded "group by" operation
     * on input elements of type {@code T}, grouping elements according to a
     * classification function, and then performing a reduction operation on
     * the values associated with a given key using the specified downstream
     * {@code Collector}.
     *
     * <p>The classification function maps elements to some key type {@code K}.
     * The downstream collector operates on elements of type {@code T} and
     * produces a result of type {@code D}. The resulting collector produces a
     * {@code Map<K, D>}.
     *
     * <p>There are no guarantees on the type, mutability,
     * serializability, or thread-safety of the {@code Map} returned.
     *
     * <p>For example, to compute the set of last names of people in each city:
     * <pre>{@code
     *     Map<City, Set<String>> namesByCity
     *         = people.stream().collect(groupingBy(Person::getCity,
     *                                              mapping(Person::getLastName, toSet())));
     * }</pre>
     *
     * @implNote
     * The returned {@code Collector} is not concurrent.  For parallel stream
     * pipelines, the {@code combiner} function operates by merging the keys
     * from one map into another, which can be an expensive operation.  If
     * preservation of the order in which elements are presented to the downstream
     * collector is not required, using {@link #groupingByConcurrent(Function, Collector)}
     * may offer better parallel performance.
     *
     * @param <T> the type of the input elements
     * @param <K> the type of the keys
     * @param <A> the intermediate accumulation type of the downstream collector
     * @param <D> the result type of the downstream reduction
     * @param classifier a classifier function mapping input elements to keys
     * @param downstream a {@code Collector} implementing the downstream reduction
     * @return a {@code Collector} implementing the cascaded group-by operation
     * @see #groupingBy(Function)
     *
     * @see #groupingBy(Function, Supplier, Collector)
     * @see #groupingByConcurrent(Function, Collector)
     */
    public static <T, K, A, D>
    Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
                                          Collector<? super T, A, D> downstream) {
    
    
        return groupingBy(classifier, HashMap::new, downstream);
    }

La sortie de HashMap est dans le désordre, ce qui est lié à la façon dont il stocke les données.

HashMap utilise une table de hachage pour stocker et utilise la méthode d'adresse de chaîne pour résoudre les conflits de hachage. Il y a une structure de liste chaînée sur chaque élément du tableau. Lorsque les données sont hachées, la valeur de hachage perturbée est obtenue, puis l'indice du tableau est obtenu par une opération bit-AND, et enfin les données sont placées sur la liste chaînée des indices du tableau .

Lors du parcours de la sortie, c'est le tableau tab[] qui est parcouru, et après avoir calculé la valeur de hachage du tableau, les données stockées peuvent être incohérentes avec l'ordre de la liste d'origine.

Si vous souhaitez que la sortie soit ordonnée, LinkedHashMap est recommandé.

En plus d'implémenter HashMap, LinkedHashMap maintient également une liste doublement liée. LinkedHashMap ajoute un prédécesseur et un successeur à chaque entrée. Chaque fois qu'une paire clé-valeur est insérée dans linkedHashMap, en plus de l'insérer dans la position correspondante de la table de hachage, elle est également insérée à la fin de la liste chaînée doublement circulaire .

solution

Afin d'assurer une sortie ordonnée, LinkedHashMap est sélectionné et le plan de modification spécifique est le suivant :

    @Test
    public void testGroupingBy() {
    
    
        List<Order> list = Arrays.asList(
                new Order("339242501193350531", "苹果", DateUtil.parse("2021-10-10 10:10:10", DatePattern.NORM_DATETIME_PATTERN)),
                new Order("339242501193350531", "橘子", DateUtil.parse("2021-10-10 10:10:10", DatePattern.NORM_DATETIME_PATTERN)),
                new Order("339242501183340238", "香蕉", DateUtil.parse("2021-10-10 8:10:10", DatePattern.NORM_DATETIME_PATTERN)),
                new Order("339242501180357433", "手机壳", DateUtil.parse("2021-10-10 6:10:10", DatePattern.NORM_DATETIME_PATTERN)));
        List<Order> orderList = new ArrayList<>(list);
        orderList.forEach(System.out::println);
//        Map<String, List<Order>> orderMap = orderList
//                .stream()
//                .collect(Collectors.groupingBy(Order::getOrderNo));
        Map<String, List<Order>> orderMap = orderList
                .stream()
                .collect(Collectors.groupingBy(Order::getOrderNo, LinkedHashMap::new, Collectors.toList()));
        System.out.println(orderMap);
    }

sortir

CollectorsTest.Order(orderNo=339242501193350531, orderName=苹果, createDate=2021-10-10 10:10:10)
CollectorsTest.Order(orderNo=339242501193350531, orderName=橘子, createDate=2021-10-10 10:10:10)
CollectorsTest.Order(orderNo=339242501183340238, orderName=香蕉, createDate=2021-10-10 08:10:10)
CollectorsTest.Order(orderNo=339242501180357433, orderName=手机壳, createDate=2021-10-10 06:10:10)
{
    
    339242501193350531=[CollectorsTest.Order(orderNo=339242501193350531, orderName=苹果, createDate=2021-10-10 10:10:10), CollectorsTest.Order(orderNo=339242501193350531, orderName=橘子, createDate=2021-10-10 10:10:10)], 339242501183340238=[CollectorsTest.Order(orderNo=339242501183340238, orderName=香蕉, createDate=2021-10-10 08:10:10)], 339242501180357433=[CollectorsTest.Order(orderNo=339242501180357433, orderName=手机壳, createDate=2021-10-10 06:10:10)]}

Sorties Collectors.toMap() hors service

Scènes

Identique à Collectors.groupingBy(), l'utilisation de Collectors.toMap() utilise également HashMap pour recevoir par défaut, et il y aura également des problèmes hors service

    @Test
    public void testToMap() {
    
    
        List<Order> list = Arrays.asList(
                new Order("339242501193350531", "苹果", DateUtil.parse("2021-10-10 10:10:10", DatePattern.NORM_DATETIME_PATTERN)),
                 new Order("339242501183340238", "香蕉", DateUtil.parse("2021-10-10 8:10:10", DatePattern.NORM_DATETIME_PATTERN)),
                new Order("339242501180357433", "手机壳", DateUtil.parse("2021-10-10 6:10:10", DatePattern.NORM_DATETIME_PATTERN)));
        List<Order> orderList = new ArrayList<>(list);
        orderList.forEach(System.out::println);
        Map<String, Order> orderMap = orderList
                .stream()
                .collect(Collectors.toMap(Order::getOrderNo, order -> order, (k1, k2) -> k1));
        System.out.println(orderMap);
    }

sortir

CollectorsTest.Order(orderNo=339242501193350531, orderName=苹果, createDate=2021-10-10 10:10:10)
CollectorsTest.Order(orderNo=339242501183340238, orderName=香蕉, createDate=2021-10-10 08:10:10)
CollectorsTest.Order(orderNo=339242501180357433, orderName=手机壳, createDate=2021-10-10 06:10:10)
{
    
    339242501180357433=CollectorsTest.Order(orderNo=339242501180357433, orderName=手机壳, createDate=2021-10-10 06:10:10), 339242501193350531=CollectorsTest.Order(orderNo=339242501193350531, orderName=苹果, createDate=2021-10-10 10:10:10), 339242501183340238=CollectorsTest.Order(orderNo=339242501183340238, orderName=香蕉, createDate=2021-10-10 08:10:10)}

Prenez Collectors.toMap(Order::getOrderNo, order -> order, (k1, k2) -> k1) comme exemple, Collectors.toMap() a trois paramètres, le premier paramètre Order::getOrderNo est la clé de Map, le deuxième Le deuxième paramètre ordre -> ordre est valeur, et le troisième paramètre (k1, k2) -> k1 indique que lorsque la même clé apparaît, la valeur correspondant à l'ancienne valeur de clé est prise.

    /**
     * Returns a {@code Collector} that accumulates elements into a
     * {@code Map} whose keys and values are the result of applying the provided
     * mapping functions to the input elements.
     *
     * <p>If the mapped
     * keys contains duplicates (according to {@link Object#equals(Object)}),
     * the value mapping function is applied to each equal element, and the
     * results are merged using the provided merging function.
     *
     * @apiNote
     * There are multiple ways to deal with collisions between multiple elements
     * mapping to the same key.  The other forms of {@code toMap} simply use
     * a merge function that throws unconditionally, but you can easily write
     * more flexible merge policies.  For example, if you have a stream
     * of {@code Person}, and you want to produce a "phone book" mapping name to
     * address, but it is possible that two persons have the same name, you can
     * do as follows to gracefully deals with these collisions, and produce a
     * {@code Map} mapping names to a concatenated list of addresses:
     * <pre>{@code
     *     Map<String, String> phoneBook
     *         people.stream().collect(toMap(Person::getName,
     *                                       Person::getAddress,
     *                                       (s, a) -> s + ", " + a));
     * }</pre>
     *
     * @implNote
     * The returned {@code Collector} is not concurrent.  For parallel stream
     * pipelines, the {@code combiner} function operates by merging the keys
     * from one map into another, which can be an expensive operation.  If it is
     * not required that results are merged into the {@code Map} in encounter
     * order, using {@link #toConcurrentMap(Function, Function, BinaryOperator)}
     * may offer better parallel performance.
     *
     * @param <T> the type of the input elements
     * @param <K> the output type of the key mapping function
     * @param <U> the output type of the value mapping function
     * @param keyMapper a mapping function to produce keys
     * @param valueMapper a mapping function to produce values
     * @param mergeFunction a merge function, used to resolve collisions between
     *                      values associated with the same key, as supplied
     *                      to {@link Map#merge(Object, Object, BiFunction)}
     * @return a {@code Collector} which collects elements into a {@code Map}
     * whose keys are the result of applying a key mapping function to the input
     * elements, and whose values are the result of applying a value mapping
     * function to all input elements equal to the key and combining them
     * using the merge function
     *
     * @see #toMap(Function, Function)
     * @see #toMap(Function, Function, BinaryOperator, Supplier)
     * @see #toConcurrentMap(Function, Function, BinaryOperator)
     */
    public static <T, K, U>
    Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                    Function<? super T, ? extends U> valueMapper,
                                    BinaryOperator<U> mergeFunction) {
    
    
        //默认使用HashMap
        return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
    }

solution

Vous pouvez utiliser LinkedHashMap pour conserver l'ordre, il enregistrera les paires clé-valeur dans l'ordre d'insertion. Par exemple:

    @Test
    public void testToMap() {
    
    
        List<Order> list = Arrays.asList(
                new Order("339242501193350531", "苹果", DateUtil.parse("2021-10-10 10:10:10", DatePattern.NORM_DATETIME_PATTERN)),
                 new Order("339242501183340238", "香蕉", DateUtil.parse("2021-10-10 8:10:10", DatePattern.NORM_DATETIME_PATTERN)),
                new Order("339242501180357433", "手机壳", DateUtil.parse("2021-10-10 6:10:10", DatePattern.NORM_DATETIME_PATTERN)));
        List<Order> orderList = new ArrayList<>(list);
        orderList.forEach(System.out::println);
//        Map<String, Order> orderMap = orderList
//                .stream()
//                .collect(Collectors.toMap(Order::getOrderNo, order -> order, (k1, k2) -> k1));
        Map<String, Order> orderMap = orderList
                .stream()
                .collect(Collectors.toMap(Order::getOrderNo, order -> order, 
                (k1, k2) -> k1, LinkedHashMap::new));
        System.out.println(orderMap);
    }

Le quatrième paramètre ici est une fonction Supplier qui renvoie une nouvelle instance de LinkedHashMap pour contenir les paires clé-valeur.
sortir

CollectorsTest.Order(orderNo=339242501193350531, orderName=苹果, createDate=2021-10-10 10:10:10)
CollectorsTest.Order(orderNo=339242501183340238, orderName=香蕉, createDate=2021-10-10 08:10:10)
CollectorsTest.Order(orderNo=339242501180357433, orderName=手机壳, createDate=2021-10-10 06:10:10)
{
    
    339242501193350531=CollectorsTest.Order(orderNo=339242501193350531, orderName=苹果, createDate=2021-10-10 10:10:10), 
339242501183340238=CollectorsTest.Order(orderNo=339242501183340238, orderName=香蕉, createDate=2021-10-10 08:10:10), 
339242501180357433=CollectorsTest.Order(orderNo=339242501180357433, orderName=手机壳, 
createDate=2021-10-10 06:10:10)}

Je suppose que tu aimes

Origine blog.csdn.net/qq_43842093/article/details/131506861
conseillé
Classement