两种排序,分组的方式及TreeSet排序数据丢失问题

有时候我们会碰到这样的需求,将数据分组以后并且需要按照指定的字段排序,

我使用jdk8,实现了两种方式。

初始化数据

 Map>  map=new HashMap<>();
        Map>  map2=new HashMap<>();

        List list = Arrays.asList(
                new Book(1,"三国",12)
                ,new  Book(2,"水浒",1)
                ,new  Book(3,"Java++",2)
                ,new  Book(1,"C++",12)
                ,new  Book(2,"java 入门到放弃",3)
                ,new  Book(3,"java入门到走火入魔",6)
                ,new  Book(1,"helloworld",23)
        );

方式1      使用TreeSet

分组不排序

   list.forEach(e->{
         //普通分组
           map2.computeIfAbsent(e.getId(),s -> new ArrayList<>()).add(e);
       });

分组并且排序

list.forEach(book->{
            map.computeIfAbsent(book.getId(),s->new  TreeSet<>(Comparator.comparing(Book::getNumber).reversed())).add(book);

        });

方式2  使用Stream流

分组不排序

 //stream方式分组
        Map> collect = list.stream().collect(Collectors.groupingBy(Book::getId));

分组排序

 //stream方式分组并且排序  默认方式为正序排列
        Map> streamMap = list.stream().sorted(Comparator.comparingInt(Book::getNumber).reversed()).collect(Collectors.groupingBy(Book::getId));
        

Compartor的比较方式默认是按照正序的方式,如果需要倒叙的话使用reversed()方法

存在的问题

正常方式2的排序分组的结果和方式1排序分组的结果是一样的,测试结果如下:

方式1的结果:

{1=[Book [bookId=1, name=helloworld, number=23], Book [bookId=1, name=三国, number=12]],
 2=[Book [bookId=2, name=java 入门到放弃, number=3], Book [bookId=2, name=水浒, number=1]], 
 3=[Book [bookId=3, name=java入门到走火入魔, number=6], Book [bookId=3, name=Java++,number=2]]}

方式2的结果

{1=[Book [bookId=1, name=helloworld, number=23], Book [bookId=1, name=三国, number=12], Book [bookId=1, name=C++, number=12]],
 2=[Book [bookId=2, name=java 入门到放弃, number=3], Book [bookId=2, name=水浒, number=1]], 
 3=[Book [bookId=3, name=java入门到走火入魔, number=6], Book [bookId=3, name=Java++, number=2]]}

我们发现丢失了一个数据,这个数据丢失问题是由于比较属性number的数量相等。

把其中一个12改成13,结果如下:方式1的结果如下:

{1=[Book [bookId=1, name=helloworld, number=23], Book [bookId=1, name=C++, number=13], Book [bookId=1, name=三国, number=12]], 2=[Book [bookId=2, name=java 入门到放弃, number=3], Book [bookId=2, name=水浒, number=1]], 3=[Book [bookId=3, name=java入门到走火入魔, number=6], Book [bookId=3, name=Java++, number=2]]}

treeset数据为什么会丢失呢?

treeSet的底层是treemap它的添加方法对应treemapd的添加方法,源码如下:

  /**
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for the key, the old
     * value is replaced.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     *
     * @return the previous value associated with {@code key}, or
     *         {@code null} if there was no mapping for {@code key}.
     *         (A {@code null} return can also indicate that the map
     *         previously associated {@code null} with {@code key}.)
     * @throws ClassCastException if the specified key cannot be compared
     *         with the keys currently in the map
     * @throws NullPointerException if the specified key is null
     *         and this map uses natural ordering, or its comparator
     *         does not permit null keys
     */
    public V put(K key, V value) {
        Entry t = root;
        if (t == null) {
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry parent;
        // split comparator and comparable paths
        Comparator cpr = comparator;
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable k = (Comparable) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        Entry e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

      发现当调用TreeSet的add()方法时,在TreeSet的内部会间接调用compareTo()方法、然后和TreeSet中已经存在的其他元素一一进行比较,在比较的过程中完成“判断是否重复”以及“排序”的功能:当在某次比较的过程中发现compareTo()返回0,就会认为待加入的元素已经存在于TreeSet中,返回-1或1的话就会根据TreeSet默认的比较器进行排序。

解决方式如下:treeset使用下面的方式进行比较等于0的时候不返回,
 

 list.forEach(book->{
            map.computeIfAbsent(book.getId(),s->new  TreeSet<>((e1,e2)->{
                int flag = e2.getNumber() - e1.getNumber();
                if(flag>0||flag==0){
                    return 1;
                }else  { return -1;}
            })).add(book);
       })

当然如果相等时你还可以指定再次使用另一个字段进行排序。

这样的方式就和使用stream得到的结果一模一样。

你可能感兴趣的:(java杂记)