JavaSE-07(集合类框架知识)【精华总结】

集合概序

1、为什么使用集合框架?

假设,一个班级有30个人,我们需要存储学员的信息,是不是我们可以用一个一维数组就解决了?

那换一个问题,一个网站每天要存储的新闻信息,我们知道新闻是可以实时发布的,我们并不知道需要多大的空间去存储,我要是去设置一个很大的数组,要是没有存满,或者不够用,都会影响我们,前者浪费的空间,后者影响了业务!

如果并不知道程序运行时会需要多少对象,或者需要更复杂的方式存储对象,那我们就可以使用Java的集合框架!

2、集合框架包含的内容

Java集合框架提供了一套性能优良,使用方便的接口和类,他们位于java.util包中。
在这里插入图片描述
重中之重

  • Collection 接口存储一组不唯一,无序的对象
  • List 接口存储一组不唯一,有序的对象。
  • Set 接口存储一组唯一,无序的对象
  • Map 接口存储一组键值对象,提供key到value的映射
  • ArrayList实现了长度可变的数组,在内存中分配连续的空间。遍历元素和随机访问元素的效率比较高
  • LinkedList采用链表存储方式。插入、删除元素时效率比较高
  • HashSet:采用哈希算法实现的Set
    HashSet的底层是用HashMap实现的,因此查询效率较高,由于采用hashCode算法直接确定 元素的内存地址,增删效率也挺高的。

List接口及其集合类

ArrayList

ArrayList概述

ArrayList是可以动态增长和缩减的索引序列,它是基于数组实现的List类。

该类封装了一个动态再分配的Object[]数组,每一个类对象都有一个capacity【容量】属性,表示它们所封装的Object[]数组的长度,当向ArrayList中添加元素时,该属性值会自动增加。如果想 ArrayList中添加大量元素,可使用ensureCapacity方法一次性增加capacity,可以减少增加重分配的次数提高性能。

ArrayList的用法和Vector向类似,但是Vector是一个较老的集合,具有很多缺点,不建议使用。

另外,ArrayList和Vector的区别是:ArrayList是线程不安全的,当多条线程访问同一个ArrayList集合时,程序需要手动保证该集合的同步性,而Vector则是线程安全的。

ArrayList的数据结构

分析一个类的时候,数据结构往往是它的灵魂所在,理解底层的数据结构其实就理解了该类的实现思 路,具体的实现细节再具体分析。

ArrayList的数据结构是:
在这里插入图片描述
说明:底层的数据结构就是数组,数组元素类型为Object类型,即可以存放所有类型数据。我们对 ArrayList类的实例的所有的操作底层都是基于数组的。

【常用方法】

在这里插入图片描述

总结

  • 1)arrayList可以存放null。
  • 2)arrayList本质上就是一个elementData数组。
  • 3)arrayList区别于数组的地方在于能够自动扩展大小,其中关键的方法就是gorw()方法。
  • 4)arrayList中removeAll(collection c)和clear()的区别就是removeAll可以删除批量指定的元素,而clear是全是删除集合中的元素。
  • 5)arrayList由于本质是数组,所以它在数据的查询方面会很快,而在插入删除这些方面,性能下降很多,有移动很多数据才能达到应有的效果
  • 6)arrayList实现了RandomAccess,所以在遍历它的时候推荐使用for循环。

LinkedList

插入,删除操作频繁时,可使用LinkedList来提高效率

LinkedList提供对头部和尾部元素进行添加和删除操作的方法!

在这里插入图片描述

LinkedList概述

LinkedList是一种可以在任何位置进行高效地插入和移除操作的有序序列,它是基于双向链表实现的。

LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。

LinkedList 实现 List 接口,能对它进行队列操作。

LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。

LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。

LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。

LinkedList 是非同步的。

推荐文章:LinkList详解

【LinkedList的数据结构】

在这里插入图片描述
如上图所示,LinkedList底层使用的双向链表结构,有一个头结点和一个尾结点,双向链表意味着我们可以从头开始正向遍历,或者是从尾开始逆向遍历,并且可以针对头部和尾部进行相应的操作。

总结

  1. linkedList本质上是一个双向链表,通过一个Node内部类实现的这种链表结构
  2. 能存储null值
  3. 跟arrayList相比较,就真正的知道了,LinkedList在删除和增加等操作上性能好,而ArrayList在查询的性能上好
  4. . 从源码中看,它不存在容量不足的情况
  5. . linkedList不光能够向前迭代,还能像后迭代,并且在迭代的过程中,可以修改值、添加值、还能移除值。
  6. linkedList不光能当链表,还能当队列使用,这个就是因为实现了Deque接口。

Vector和Stack

Vector概述

  1. Vector是一个可变化长度的数组
  2. Vector增加长度通过的是capacity和capacityIncrement这两个变量\
  3. Vector也可以获得iterator和listIterator这两个迭代器,并且他们发生的是fail-fast,而不是failsafe,注意这里,不要觉得这个vector是线程安全就搞错了
  4. Vector是一个线程安全的类,如果使用需要线程安全就使用Vector(本质是源码中加了synchronized),如果不需要,就使用arrayList
  5. Vector和ArrayList很类似,就少许的不一样,从它继承的类和实现的接口来看,跟arrayList一模一 样。

注意:java1.5推出的java.uitl.concurrent包,为了解决复杂的并发问题的。
所以开发中,不建议用vector,需要线程安全的集合类直接用java.util.concurrent包下的类。

Stack

现在来看看Vector的子类Stack,学过数据结构都知道,这个就是栈的意思。那么该类就是跟栈的用法一 样了。

class Stack<E> extends Vector<E> {}

通过查看他的方法,和查看api文档,很容易就能知道他的特性。就几个操作,出栈,入栈等,构造方法也是空的,用的还是数组,父类中的构造,跟父类一样的扩增方式,并且它的方法也是同步的,所以也是线程安全
在这里插入图片描述

总结Vector和Stack

Vector总结(通过源码分析)】

  1. Vector线程安全是因为它的方法都加了synchronized关键字
  2. Vector的本质是一个数组,特点能是能够自动扩增,扩增的方式跟capacityIncrement的值有关
  3. 它也会fail-fast,还有一个fail-safe两个的区别在下面的list总结中会讲到。

【Stack的总结】

  1. 对栈的一些操作,先进后出
  2. 底层也是用数组实现的,因为继承了Vector
  3. 也是线程安全

List总结

arrayList和LinkedList区别

arrayList底层是用数组实现的顺序表,是随机存取类型,可自动扩增,并且在初始化时,数组的长度是0,只有在增加元素时,长度才会增加。默认是10,不能无限扩增,有上限,在查询操作的时候性能更好!


LinkedList底层是用链表来实现的,是一个双向链表,注意这里不是双向循环链表,顺序存取类型。 在源码中,似乎没有元素个数的限制。应该能无限增加下去,直到内存满了在进行删除,增加操作时性能更好!


两个都是线程不安全的,在iterator时,会发生fail-fast:快速失效。

arrayList和Vector的区别

arrayList线程不安全,在用iterator,会发生fail-fast


ector线程安全,因为在方法前加了Synchronized关键字。也会发生fail-fast

【为什么现在都不提倡使用vector了】

1)vector实现线程安全的方法是在每个操作方法上加锁,这些锁并不是必须要的,在实际开发中, 一般都是通过锁一系列的操作来实现线程安全,也就是说将需要同步的资源放一起加锁来保证线程安全。

2)如果多个Thread并发执行一个已经加锁的方法,但是在该方法中,又有vector的存在,vector本身实现中已经加锁了,那么相当于锁上又加锁,会造成额外的开销。

3)就如上面第三个问题所说的,vector还有fail-fast的问题,也就是说它也无法保证遍历安全,在遍历时又得额外加锁,又是额外的开销,还不如直接用arrayList,然后再加锁呢。

总结:Vector在你不需要进行线程安全的时候,也会给你加锁,也就导致了额外开销,所以在 jdk1.5之后就被弃用了,现在如果要用到线程安全的集合,都是从java.util.concurrent包下去拿相应的类。


Map接口

HashMap

问题:建立学生学号和学生姓名间的键值映射,并通过key对value进行操作,应该如何实现数据的存储和操作呢?

Map接口专门处理键值映射数据的存储,可以根据键实现对值的操作。 最常用的实现类是HashMap

public static void main(String[] args) {
    Map<String,String> map = new HashMap<String,String>();
    map.put("004","李清照");
    map.put("001","李白");
    map.put("003","王羲之");
    map.put("002","杜甫");

    System.out.println(map.get("003"));

    //获取所有key 值
    Set<String> keySet = map.keySet();
    for (String s : keySet){
        String s1 = map.get(s);
        System.out.println(s+"    "+s1);
    }

    //获取所有值
    Collection<String> values = map.values();
    for (String s : values){
        System.out.println(s);
    }
    
    //entrySet() 获取值
    Set<Map.Entry<String, String>> entrySet = map.entrySet();
    for (Map.Entry<String, String> m : entrySet){
        String key = m.getKey();
        String value = m.getValue();
        System.out.println(key+","+value);
    }
}

HashMap数据结构

HashMap是基于哈希表的Map接口实现的,它存储的是内容是键值对映射。

哈希表基于map接口的实现,这个实现提供了map所有的操作,并且提供了key和value,可以为 null,(HashMap和HashTable大致上是一样的,除了hashmap是异步的,和允许key和value为 null),这个类不确定map中元素的位置,特别要提的是,这个类也不确定元素的位置随着时间会不会保持不变。

HashMap在JDK1.8以前的数据结构和存储原理

通过数组和链表结合在一起使用,就叫做链表散列。这其实就是 hashmap存储的原理图。
在这里插入图片描述
HashMap的数据结构就是用的链表散列。那HashMap底层是怎么样使用这个数据结构进行数据存取的呢?分成两个部分:

第一步:HashMap内部有一个entry的内部类,其中有四个属性,我们要存储一个值,则需要一个key 和一个value,存到map中就会先将key和value保存在这个Entry类创建的对象中。

static class Entry<K,V> implements Map.Entry<K,V> {
    final K key; //就是我们说的map的key
    V value; //value值,这两个都不陌生
    Entry<K,V> next;//指向下一个entry对象
    int hash;//通过key算过来的你hashcode值。
}

Entry的物理模型图:
在这里插入图片描述
第二步:构造好了entry对象,然后将该对象放入数组中,如何存放就是这hashMap的精华所在了。

大概的一个存放过程是:通过entry对象中的hash值来确定将该对象存放在数组中的哪个位置上,如果在这个位置上还有其他元素,则通过链表来存储这个元素。

在这里插入图片描述

JDK1.8后HashMap的数据结构

数组+链表+红黑树
在这里插入图片描述
上图很形象的展示了HashMap的数据结构(数组+链表+红黑树),桶中的结构可能是链表,也可能是红黑树,红黑树的引入是为了提高效率。
在这里插入图片描述

HashMap的属性

HashMap的实例有两个参数影响其性能
初始容量:哈希表中桶的数量

加载因子:哈希表在其容量自动增加之前可以达到多满,的一种尺度

当哈希表中条目数超出了当前容量*加载因子(其实就是HashMap的实际容量)时,则对该哈希表进行 rehash操作,将哈希表扩充至两倍的桶数。

Java中默认初始容量为16,加载因子为0.75。

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
static final float DEFAULT_LOAD_FACTOR = 0.75f;

loadFactor加载因子
定义:loadFactor译为装载因子。装载因子用来衡量HashMap满的程度。loadFactor的默认值为 0.75f。计算HashMap的实时装载因子的方法为:size/capacity,而不是占用桶的数量去除以capacity。

loadFactor加载因子是控制数组存放数据的疏密程度,loadFactor越趋近于1,那么数组中存放的数据 (entry)也就越多,也就越密,也就是会让链表的长度增加,loadFactor越小,也就是趋近于0,那么数组中存放的数据也就越稀,也就是可能数组中每个位置上就放一个元素。

那有人说,就把loadFactor变为1最好吗,存的数据很多,但是这样会有一个问题,就是我们在通过key拿到我们的value时,是先通过key的hashcode值,找到对应数组中的位置,如果该位置中有很多元素,则需要通过equals来依次比较链表中的元素,拿到我们的value值,这样花费的性能就很高,如果能让数组上的每个位置尽量只有一个元素最好,我们就能直接得到value值了,所以有人又会说,那把loadFactor变得很小不就好了,但是如果变得太小,在数组中的位置就会太稀,也就是分散的太开,浪费很多空间,这样也不好,所以在hashMap 中loadFactor的初始值就是0.75,一般情况下不需要更改它。

static final float DEFAULT_LOAD_FACTOR = 0.75f;

【桶】

根据前面画的HashMap存储的数据结构图,你这样想,数组中每一个位置上都放有一个桶,每个桶里 就是装一个链表,链表中可以有很多个元素(entry),这就是桶的意思。也就相当于把元素都放在桶中。

【capacity】

capacity译为容量代表的数组的容量,也就是数组的长度,同时也是HashMap中桶的个数。默认值是 16。

一般第一次扩容时会扩容到64,之后好像是2倍。总之,容量都是2的幂。

【size的含义】

size就是在该HashMap的实例中实际存储的元素的个数

【threshold的作用】

int threshold;//threshold = capacity * loadFactor

threshold = capacity * loadFactor,当Size>=threshold的时候,那么就要考虑对数组的扩增了,也就是说,这个的意思就是衡量数组是否需要扩增的一个标准。

注意这里说的是考虑,因为实际上要扩增数组,除了这个size>=threshold条件外,还需要另外一个条件。

什么时候会扩增数组的大小?

在put一个元素时先size>=threshold并且还要在对应数组位置上有元素, 这才能扩增数组。

通过一张HashMap的数据结构图来分析:
在这里插入图片描述

HashMap的源码分析

1、HashMap的层次关系与继承结构

【HashMap继承结构】
在这里插入图片描述
上面就继承了一个abstractMap,也就是用来减轻实现Map接口的编写负担。

【实现接口】

public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
    
}

Map<K,V>:在AbstractMap抽象类中已经实现过的接口,这里又实现,实际上是多余的。但每个集合都有这样的错误,也没过大影响

Cloneable:能够使用Clone()方法,在HashMap中,实现的是浅层次拷贝,即对拷贝对象的改变会影响 被拷贝的对象。

Serializable:能够使之序列化,即可以将HashMap对象保存至本地,之后可以恢复状态。

2、HashMap类的属性

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>,Cloneable, Serializable {
    // 序列号
    private static final long serialVersionUID = 362498820763181265L;
    // 默认的初始容量是16
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
    // 最大容量
    static final int MAXIMUM_CAPACITY = 1 << 30;
    // 默认的填充因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    // 当桶(bucket)上的结点数大于这个值时会转成红黑树
    static final int TREEIFY_THRESHOLD = 8;
    // 当桶(bucket)上的结点数小于这个值时树转链表
    static final int UNTREEIFY_THRESHOLD = 6;
    // 桶中结构转化为红黑树对应的table的最小大小
    static final int MIN_TREEIFY_CAPACITY = 64;
    // 存储元素的数组,总是2的幂次倍
    transient Node<k,v>[] table;
    // 存放具体元素的集合
    transient Set<map.entry<k,v>> entrySet;
    // 存放元素的个数,注意这个不等于数组的长度。
    transient int size;
    // 每次扩容和更改map结构的计数器
    transient int modCount;
    // 临界值 当实际大小(容量*填充因子)超过临界值时,会进行扩容
    int threshold;
    // 填充因子
    final float loadFactor;
}

3、HashMap的构造方法

有四个构造方法,构造方法的作用就是记录一下16这个数给threshold(这个数值最终会当作第一次组的长度)和初始化加载因子。

注意,hashMap中table数组一开始就已经是个没有长度的数组了。

构造方法中,并没有初始化数组的大小,数组在一开始就已经被创建了,构造方法只做两件事情,一个 是初始化加载因子,另一个是用threshold记录下数组初始化的大小。注意是记录。
加粗样式
HashMap()

//看上面的注释就已经知道,DEFAULT_INITIAL_CAPACITY=16,DEFAULT_LOAD_FACTOR=0.75
//初始化容量:也就是初始化数组的大小
//加载因子:数组上的存放数据疏密程度。

public HashMap() {
    this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}

【HashMap(int)】

public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

【HashMap(int,float)】

public HashMap(int initialCapacity, float loadFactor) {
    // 初始容量不能小于0,否则报错
    if (initialCapacity < 0)
    throw new IllegalArgumentException("Illegal initial capacity: " +
                                            initialCapacity);
    // 初始容量不能大于最大值,否则为最大值
    if (initialCapacity > MAXIMUM_CAPACITY)
    initialCapacity = MAXIMUM_CAPACITY;
    // 填充因子不能小于或等于0,不能为非数字
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
    throw new IllegalArgumentException("Illegal load factor: " +
                                            loadFactor);
    // 初始化填充因子
    this.loadFactor = loadFactor;
    // 初始化threshold大小
    this.threshold = tableSizeFor(initialCapacity);
}

【HashMap(Map<? extends K, ? extends V> m) 】

public HashMap(Map<? extends K, ? extends V> m) {
    // 初始化填充因子
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    // 将m中的所有元素添加至HashMap中
    putMapEntries(m, false);
}

【putMapEntries(Map<? extends K, ? extends V> m, boolean evict)函数将m的所有元素存入本 HashMap实例中】


    /**
     * Implements Map.putAll and Map constructor.
     *
     * @param m the map
     * @param evict false when initially constructing this map, else
     * true (relayed to method afterNodeInsertion).
     */
    final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
        int s = m.size();
        if (s > 0) {
            // 判断table是否已经初始化
            if (table == null) { // pre-size
                // 未初始化,s为m的实际元素个数
                float ft = ((float)s / loadFactor) + 1.0F;
                int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                         (int)ft : MAXIMUM_CAPACITY);
                // 计算得到的t大于阈值,则初始化阈值
                if (t > threshold)
                    threshold = tableSizeFor(t);
            }
            // 已初始化,并且m元素个数大于阈值,进行扩容处理
            else if (s > threshold)
                resize();
            // 将m中的所有元素添加至HashMap中
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
                K key = e.getKey();
                V value = e.getValue();
                putVal(hash(key), key, value, false, evict);
            }
        }
    }

4、HashMap常用方法

【put(K key, V value)】

public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

HashMap并没有直接提供putVal接口给用户调用,而是提供的put函数,而put函数就是通过putVal来插入元素的。

【get(Object key)】

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

HashMap并没有直接提供getNode接口给用户调用,而是提供的get函数,而get函数就是通过 getNode来取得元素的。

【总结】

要知道hashMap在JDK1.8以前是一个链表散列这样一个数据结构,而在JDK1.8以后是一个数组加 链表加红黑树的数据结构。

通过学习,hashMap是一个能快速通过key获取到value值得一个集合,原因是内部使用的 是hash查找值得方法。


Set接口

Set注重独一无二的性质,该体系集合可以知道某物是否已经存在于集合中,不会存储重复的元素,用于存储无序(存入和取出的顺序不一定相同)元素,值不能重复。

该集合中没有特有的方法,直接继承自Collection接口:

/**
 * Collection
 *      \--List
 *          有序(存储顺序和取出顺序一致),可重复
 *      \--Set
 *          无序(存储顺序和取出顺序不一致),唯一
 * HashSet:它不保证set的迭代顺序;特别是它不保证该顺序恒久不变
 * 注意:虽然set集合的元素无序,但是,作为集合来说,它肯定有它自己的存储顺序,
 * 而你的顺序恰巧和它的存储顺序一致,这代表不了有序,你可以多存储一些数据就能看到效果
 **/

案例:set集合添加元素并使用增强for循环遍历:

public static void method1() {
    Set<String> set = new HashSet<>();
    set.add("1");
    set.add("5");
    set.add("2");

    set.add("5");//重复的不会添加进去
    for (String s : set) {
        System.out.println(s);
    }
}

//最后输出顺序是: 1、2、5

HashSet

HashSet是一个没有重复元素的集合,它其实是由HashMap实现的,HashMap保存的是建值对,然而我们只能向HashSet中添加Key,原因在于HashSet的Value其实都是同一个对象,这是HashSet添加元素的方法,可以看到辅助实现HashSet的map中的value其实都是Object类的同一个对象。

特点:

  • 底层数据结构是HashMap.
  • 对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的元素顺序一致
  • 没有带索引的方法,所以不能使用普通for循环遍历,需使用增强for循环
  • 由于是Set集合,所以是不包含重复元素的集合

存储规则

HashSet不存入重复元素的规则. 使用hashcodeequals

由于Set集合是不能存入重复元素的集合。那么HashSet也是具备这一特性的。

HashSet如何检查重复?

答: HashSet会通过元素的hashcode()和equals方法进行判断元素是否重复。

当你试图把对象加入HashSet时,HashSet会使用对象的hashCode来判断对象加入的位置。同时也会与其他已经加入的对象的hashCode进行比较,如果没有相等的hashCode,HashSet就会假设对象没有重复出现。

简单一句话,如果对象的hashCode值是不同的,那么HashSet会认为对象是不可能相等的。


因此我们自定义类的时候需要重写hashCode,来确保对象具有相同的hashCode值。
如果元素(对象)的hashCode值相同,是不是就无法存入HashSet中了?当然不是,会继续使用equals 进行比较。如果 equals为true 那么HashSet认为新加入的对象重复了,所以加入失败。如果equals 为false那么HashSet 认为新加入的对象没有重复,新元素可以存入。

总结:
元素的哈希值是通过元素的hashcode方法来获取的, HashSet首先判断两个元素的哈希值,如果哈希值一样,接着会比较equals方法 如果 equls结果为true ,HashSet就视为同一个元素。如果equals 为false就不是同一个元素。

哈希值相同equals为false的元素是怎么存储呢,就是在同样的哈希值下顺延(可以认为哈希值相同的元素放在一个哈希桶中)。也就是哈希一样的存一列!!
在这里插入图片描述

LinkedHashSet

特点:

  • 哈希表和链表实现的Set接口,具有可预测的迭代次序
  • 由链表保证元素有序,也就是说元素的存储和取出顺序是一致的
  • 由哈希表保证元素唯一,也就是说没有重复的元素

TreeSet

TreeSet优点

问题:现在有一批数据,要求不能重复存储元素,而且要排序。ArrayList 、 LinkedList不能去除重复数据。HashSet可以去除重复,但是是无序。

所以这时候就要使用TreeSet了!!

TreeSet简介

红-黑树的数据结构,默认对元素进行自然排序

TreeSet 是一个有序的集合,它的作用是提供有序的Set集合。
它继承于AbstractSet抽象类,实现了NavigableSet, Cloneable, java.io.Serializable接口。

TreeSet 继承于AbstractSet,所以它是一个Set集合,具有Set的属性和方法。

TreeSet 实现了NavigableSet接口,意味着它支持一系列的导航方法。比如查找与指定目标最匹配项。

TreeSet 实现了Cloneable接口,意味着它能被克隆。

TreeSet 实现了java.io.Serializable接口,意味着它支持序列化。

TreeSet是基于TreeMap实现的。

TreeSet中的元素支持2种排序方式自然排序 或者 根据创建TreeSet 时提供的 Comparator 进行排序。这取决于使用的构造方法

TreeSet为基本操作(add、remove 和 contains)提供受保证的 log(n) 时间开销。

特点:

  • 元素有序,这里的顺序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序方式取决于构造方法

    • TreeSet():根据其元素的自然非序进行排序

    • TreeSet(Comparator comparator):根据指定的比较器进行排序

  • 没有带索引的方法,所以不能使用普通for循环遍历,需要使用foreach增强循环。

  • 由于是Set集合,所以不包含重复元素的集合

TreeSet自然顺序

即让元素自身具备比较性,也就是元素需要实现Comparable接口,覆盖compareTo 方法。

元素自身具备比较性,需要元素实现Comparable接口,重写compareTo方法,也就是让元素自身具备比较性,这种方式叫做元素的自然排序也叫做默认排序。

即存入TreeSet的类要实现Comparable接口,并重写compareTo()方法,TreeSet对象调用add()方法时,会将存入的对象提升为Comparable类型,然后调用对象中的compareTo()方法进行比较,根据比较的返回值进行存储。

因为TreeSet底层是二叉树,当compareTo方法返回0时,不存储;当compareTo方法返回正数时,存入二叉树的右子树;当compareTo方法返回负数时,存入二叉树的左子树。如果一个类没有实现Comparable接口就将该类对象存入TreeSet集合,会发生类型转换异常。


案例:创建Student类,有姓名,年龄。存入集合后,先根据年龄大小,再根据姓名来进行排序插入集合中

public class Student implements Comparable<Student> {
    public String name;
    public int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int compareTo(Student o) {
        int i = this.age-o.age;
        int n = i==0?this.name.compareTo(o.name):i;
        return n;
    }
}

重写compareTo()方法,返回值有三种情况

  1. 返回值为0:不插入集合
  2. 返回值为1:往后插入集合
  3. 返回值为-1:往前插入集合
public class Demo {
    public static void main(String[] args) {
        Student s1 = new Student("xishi",25);
        Student s2 = new Student("yangyuhuan",29);
        Student s3 = new Student("diaochan",28);
        Student s4 = new Student("wangzhaojun",30);
        Student s5 = new Student("libai",30);
        Student s6 = new Student("libai",30);

        TreeSet<Student> ts = new TreeSet<>();

        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);
        ts.add(s6);

        for(Student s : ts){
            System.out.println(s.name+"---"+s.age);
        }

    }
}

TreeSet自定义排序

需求:当元素自身不具备比较性,或者元素自身具备的比较性不是所需的。

那么这时只能让容器自身具备。

定义一个类实现Comparator 接口,覆盖compare方法。

并将该接口的子类对象作为参数传递给TreeSet集合的构造函数。

当Comparable比较方式,及Comparator比较方式同时存在时,以Comparator比较方式为主。

public class Demo5 {
    public static void main(String[] args) {
        TreeSet ts = new TreeSet(new MyComparator());
        ts.add(new Book("think in java", 100));
        ts.add(new Book("java 核心技术", 75));
        ts.add(new Book("现代操作系统", 50));
        ts.add(new Book("java就业教程", 35));
        ts.add(new Book("think in java", 100));
        ts.add(new Book("ccc in java", 100));
 
        System.out.println(ts); 
    }
}
 
class MyComparator implements Comparator {
 
    public int compare(Object o1, Object o2) {
        Book b1 = (Book) o1;
        Book b2 = (Book) o2;
        System.out.println(b1+" comparator "+b2);
        if (b1.getPrice() > b2.getPrice()) {
            return 1;
        }
        if (b1.getPrice() < b2.getPrice()) {
            return -1;
        }
        return b1.getName().compareTo(b2.getName());
    }
 
}
 
class Book {
    private String name;
    private double price;
 
    public Book() {
 
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public double getPrice() {
        return price;
    }
 
    public void setPrice(double price) {
        this.price = price;
    }
 
    public Book(String name, double price) {
 
        this.name = name;
        this.price = price;
    }
 
    @Override
    public String toString() {
        return "Book [name=" + name + ", price=" + price + "]";
    }
 
}

迭代器

所有实现了Collection接口的容器类都有一个iterator方法用以返回一个实现Iterator接口的对象

实现Iterator接口的对象称作为迭代器,用以方便的对容器内元素的遍历操作。

Iterator接口定义了如下方法:

  • boolean hashNext();//判断是否有元素没有被遍历
  • Object next();//返回游标当前位置的元素并将游标移动到下一个位置
  • void remove();//删除游标左边的元素,在执行完next之后该操作只能执行一次

问题:如何遍历Map集合呢?

方法1:通过迭代器Iterator实现遍历

获取Iterator :Collection 接口的iterator()方法

Iterator的方法:

  • boolean hasNext(): 判断是否存在另一个可访问的元素
  • Object next(): 返回要访问的下一个元素
Set keys = dogMap.keySet(); //取出所有key的集合
Iterator it = keys.iterator(); //获取Iterator对象
while (it.hasNext()) {
    String key = (String) it.next(); //取出key
    Dog dog = (Dog) dogMap.get(key); //根据key取出对应的值
    System.out.println(key + "\t" + dog.getStrain());
}

方法2:增强for循环

for(元素类型t 元素变量x : 数组或集合对象){
    引用了x的java语句
}

Collections工具类

Java提供了一个操作Set、List和Map等集合的工具类Collections,该工具类提供了大量方法对集合进 行排序、查询和修改等操作,还提供了将集合对象置为不可变、对集合对象实现同步控制等方法。

注意: Collection是集合接口,而Collections是集合工具类!!

这个类不需要创建对象,内部提供的都是静态方法

Collectios概述

在这里插入图片描述
此类完全由在 collection 上进行操作或返回 collection 的静态方法组成。

它包含在 collection 上操作的多态算法,即“包装器”,包装器返回由指定 collection 支持的新 collection,以及少数其他内容。如果为 此类的方法所提供的 collection 或类对象为 null,则这些方法都将抛出 NullPointerException 。

2、排序操作

static void reverse(List<?> list)//反转列表中元素的顺序。

static void shuffle(List<?> list) //对List集合元素进行随机排序。

static void sort(List<T> list) //根据元素的自然顺序 对指定列表按升序进行排序

static <T> void sort(List<T> list, Comparator<? super T> c) //根据指定比较器产生的顺序对指定列表进行排序。
    
static void swap(List<?> list, int i, int j)   //在指定List的指定位置i,j处交换元素。

static void rotate(List<?> list, int distance)
 //当distance为正数时,将List集合的后distance个元素“整体”移到前面;当distance为负数时,将list集合的前distance个元素“整体”移到后边。该方法不会改变集合的长度。

演示

public static void main(String[] args) {
    ArrayList list = new ArrayList();
    list.add(3);
    list.add(-2);
    list.add(9);
    list.add(5);
    list.add(-1);
    list.add(6);

    //输出:[3, -2, 9, 5, -1, 6]
    System.out.println(list);
    //集合元素的次序反转
    Collections.reverse(list);
    //输出:[6, -1, 5, 9, -2, 3]
    System.out.println(list);
    //排序:按照升序排序
    Collections.sort(list);
    //[-2, -1, 3, 5, 6, 9]
    System.out.println(list);
    //根据下标进行交换
    Collections.swap(list, 2, 5);
    //输出:[-2, -1, 9, 5, 6, 3]
    System.out.println(list);
    
    /*//随机排序
    Collections.shuffle(list);
    //每次输出的次序不固定
    System.out.println(list);*/
    
    //后两个整体移动到前边
    Collections.rotate(list, 2);
    //输出:[6, 9, -2, -1, 3, 5]
    System.out.println(list);
}

演示

创建学生集合,加入数据,并自定义排序,先根据年龄,再根据首字母:

public class Student {
    public String name;
    public int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}


public static void main(String[] args) {
    ArrayList<Student> array = new ArrayList<>();
    Student s1 = new Student("lingqingxia",20);
    Student s2 = new Student("wangxizhi",30);
    Student s3 = new Student("libai",25);
    Student s4 = new Student("dufu",25);
    //Student s5 = new Student("dufu",25);

    array.add(s1);
    array.add(s2);
    array.add(s3);
    array.add(s4);
    //array.add(s5);
    Collections.sort(array, new Comparator<Student>() {
        @Override
        public int compare(Student o1, Student o2) {
            int i = o1.age-o2.age;
            int n = i==0?o1.name.compareTo(o2.name):i;
            return n;
        }
    });

    for (Student s:array){
        System.out.println(s.name+","+s.age);
    }
}

3、查找、替换操作

方法

//使用二分搜索法搜索指定列表,以获得指定对象在List集合中的索引。
//注意:此前必须保证List集合中的元素已经处于有序状态。
static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key)

//根据元素的自然顺序,返回给定collection 的最大元素。
static Object max(Collection coll)

       //根据指定比较器产生的顺序,返回给定 collection 的最大元素。
    static Object max(Collection coll,Comparator comp)

    //根据元素的自然顺序,返回给定collection 的最小元素。
    static Object min(Collection coll)
    
    //根据指定比较器产生的顺序,返回给定 collection 的最小元素。
    static Object min(Collection coll,Comparator comp)
    
    使用指定元素替换指定列表中的所有元素。
    static <T> void fill(List<? super T> list, T obj)
    
    //返回指定 collection 中等于指定对象的出现次数。
    static int frequency(Collection<?> c, Object o)
    
    //返回指定源列表中第一次出现指定目标列表的起始位置;如果没有出现这样的列表,则返回-1。
    static int indexOfSubList(List<?> source, List<?> target) 
    
    //返回指定源列表中最后一次出现指定目标列表的起始位置;如果没有出现这样的列表,则返回-1。
    static int lastIndexOfSubList(List<?> source, List<?> target)
    
    //使用一个新值替换List对象的所有旧值oldVal
    static <T> boolean replaceAll(List<T> list, T oldVal, T newVal)

演示:实例使用查找、替换操作

public static void main(String[] args) {
    ArrayList list = new ArrayList();
    list.add(3);
    list.add(-2);
    list.add(9);
    list.add(5);
    list.add(-1);
    list.add(6);
    //[3, -2, 9, 5, -1, 6]
    System.out.println(list);
    //输出最大元素9
    System.out.println(Collections.max(list));
    //输出最小元素:-2
    System.out.println(Collections.min(list));
    //将list中的-2用1来代替
    System.out.println(Collections.replaceAll(list, -2, 1));
    //[3, 1, 9, 5, -1, 6]
    System.out.println(list);
    list.add(9);
    //判断9在集合中出现的次数,返回2
    System.out.println(Collections.frequency(list, 9));
    //对集合进行排序
    Collections.sort(list);
    //[-1, 1, 3, 5, 6, 9, 9]
    System.out.println(list);
    //只有排序后的List集合才可用二分法查询,输出2
    System.out.println(Collections.binarySearch(list, 3));
}

4、同步控制

Collectons提供了多个synchronizedXxx()方法,该方法可以将指定集合包装成线程同步的集合,从 而解决多线程并发访问集合时的线程安全问题。

正如前面介绍的HashSet,TreeSet,arrayList,LinkedList,HashMap,TreeMap都是线程不安全的。 Collections提供了多个静态方法可以把他们包装成线程同步的集合。

方法

//返回指定 collection 支持的同步(线程安全的)collection。
static <T> Collection<T> synchronizedCollection(Collection<T> c)
    
//返回指定列表支持的同步(线程安全的)列表。
static <T> List<T> synchronizedList(List<T> list)

//返回由指定映射支持的同步(线程安全的)映射。
static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)

//返回指定 set 支持的同步(线程安全的)set
static <T> Set<T> synchronizedSet(Set<T> s)

实例

public static void main(String[] args) {
    //下面程序创建了四个同步的集合对象
    Collection c = Collections.synchronizedCollection(new ArrayList());
    List list = Collections.synchronizedList(new ArrayList());
    Set s = Collections.synchronizedSet(new HashSet());
    Map m = Collections.synchronizedMap(new HashMap());

}

5、Collesction设置不可变集合

方法

//返回一个空的、不可变的集合对象,此处的集合既可以是List,也可以是Set,还可以是Map。
emptyXxx()

//返回一个只包含指定对象(只有一个或一个元素)的不可变的集合对象,此处的集合可以是:List,Set,Map。
singletonXxx()
    
//返回指定集合对象的不可变视图,此处的集合可以是:List,Set,Map。
unmodifiableXxx():

上面三类方法的参数是原有的集合对象,返回值是该集合的”只读“版本。

实例

public static void main(String[] args) {
    //创建一个空的、不可改变的List对象
    List<String> unmodifiableList = Collections.emptyList();
    //unmodifiableList.add("java");
    //添加出现异常:java.lang.UnsupportedOperationException
    System.out.println(unmodifiableList);// []
    //创建一个只有一个元素,且不可改变的Set对象
    Set unmodifiableSet = Collections.singleton("Struts2权威指南");
    //[Struts2权威指南]
    System.out.println(unmodifiableSet);
    //创建一个普通Map对象
    Map scores = new HashMap();
    scores.put("语文", 80);
    scores.put("Java", 82);
    //返回普通Map对象对应的不可变版本
    Map unmodifiableMap = Collections.unmodifiableMap(scores);
    //下面任意一行代码都将引发UnsupportedOperationException异常
    unmodifiableList.add("测试元素");
    unmodifiableSet.add("测试元素");
    unmodifiableMap.put("语文", 90);
    
}

The End!!创作不易,欢迎点赞/评论!!欢迎关注个人GZH!!

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值