Java集合知识详解

目录

1.什么是集合

1.集合

2.数组存储多个数据方面的缺点:即集合存储的优势

3.集合的选用方法

2.集合框架的两大接口

3.Collection之List接口

 1.ArrayList

2.LinkedList

3.Vector

4.ArrayList和LinkedList对比

5.ArrayList的源码

4.Collection之Set接口

1.HashSet  

2.LinkedHashSet

3.TreeSet

4.如何理解Set的无序、不可重复特性(以HashSet为例)

5.TreeSet的比较实现

 6.HashSet 、LinkedHashSet、TreeSet的异同

5.Map

1.HashMap

1.原理

        解决哈希冲突的方法:

        HashMap添加键值对的原理:

        HashMap扩容问题:

2.HashMap和HashSet对比

3.HashMap和Hashtable对比

4.HashMap和TreeMap对比

5.HashMap多线程下存在的问题

1)环形链表问题

2)数据覆盖问题

 6.HashMap的遍历方式

2.LinkedHashMap

3.TreeMap

4.Hashtable

5.Properties

6.ConcorrentHashMap

底层结构


1.什么是集合

1.集合

        Java集合,是对多个数据进行存储的结构,简称java容器

2.数组存储多个数据方面的缺点:即集合存储的优势

      1).初始化后,其长度无法修改;集合可以动态扩容

      2).数组中提供的方法极其有限,对于增删改查操作不方便;集合提供了多种操作方法

      3).数组是不支持泛型的,集合支持泛型;

      4).数组对于无序、不可重复的需求,不能满足;集合中的Set接口就能够满足这一点

3.集合的选用方法

1)只存放元素值:使用Collection接口实现

        保证元素唯一:选择Set接口下的TreeSet或者HashSet

        不保证元素唯一:先择List接口下的ArrayList或LinkedList

2)存放键值对:使用Map接口实现

        需要排序:使用TreeMap

        不需要排序:使用HashMap

2.集合框架的两大接口

1. Collection接口:单列集合,存储一个一个的对象

        List接口:存储有序的、可重复的数据;主要实现类:ArrayList,LinkedList,Vector

        Set接口:存储无序的、不可重复的数据;主要实现类:HashSet,LinkedHashSet,TreeSet

2.Map接口:双列集合,存储一对(key-value)的数据

        主要实现类:HashMap,LinkedHashMap,TreeMap,Hashtable,Properties

3.Collection之List接口

 1.ArrayList

底层实现:底层使用的是Object[ ]数组

是否可以添加null:可以添加null(慎用,可能报NullPointerException:空指针异常)

是否线程安全:线程不安全

继承与实现:继承了AbstarctList,实现了List、RandomAccess、Cloneable、Serializable

        RandomAccess:表示ArrayList支持随机访问,根据元素索引就能获取元素

        Cloneable:能够进行浅拷贝与深拷贝

        Serializable:能够实现序列化与反序列化

public class ArrayList extends AbstractList
    implements List, RandomAccess, Cloneable, java.io.Serializable{
}

ArrayList扩容机制

        使用无参数构造器创建ArrayList对象时,初始化为一个空数组,当开始使用add()方法后,数组容量赋值为10;当数组已满继续添加元素时,调用grow()方法进行扩容,grow方法中将新的数组容量变为原来的1.5倍,并将原来数组的数据复制到新数组中

2.LinkedList

底层实现:底层是双向链表

是否线程安全:线程不安全

3.Vector

底层实现:底层使用的是Object[ ]数组

是否线程安全:线程安全

4.ArrayList和LinkedList对比

底层结构:

        ArrayList:底层是Object[]数组

        LinkedList:底层是双向链表

是否线程安全:

        都是线程不安全

是否支持随机访问:

        ArrayList:继承了RandomAccess,支持随机访问;LinkedList:不支持

适用场景:   

        ArrayList:适合查找元素,不适合插入、删除操作

                插入和删除的时间复杂度:

                        头部插入/删除、指定位置插入/删除:时间复杂度O(n)

                        尾部插入/删除:时间复杂度O(1)

        LinkedList:对于频繁的插入、删除操作,效率比ArrayList高

                插入和删除的时间复杂度:

                        头部插入/删除、尾部插入/删除:时间复杂度O(1)

                        指定位置插入/删除:时间复杂度O(n)

实用性:一般使用LinkedList的场景都可以用ArrayList代替,性能会更好,所以LinkedList用的很少

4.Collection之Set接口

1.HashSet  

        set接口的主要实现类;是线程不安全的;可以存储null值;

2.LinkedHashSet

        作为HashSet的子类,遍历内部数据时,可以按照添加的顺序遍历

3.TreeSet

        可以按照添加的对象指定属性,进行排序

4.如何理解Set的无序、不可重复特性(以HashSet为例)

        无序性:不等于随机性;存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定,添加时是无序的

      不可重复性:保证添加的元素按照equals()判断时,不能返回true;即相同的元素只能添加一个。

5.TreeSet的比较实现

        1.向TreeSet中添加的数据,要求是相同类的对象,不能添加不同类的对象。

        2.两种排序方式:Comparable接口、Comparator接口

        3.自然排序中,比较两个对象是否相同的标准为compareTo()方法返回0;不再是equals()方法

        4.定制排序中,比较两个对象是否相同的标准是compare()返回0,不再是equals()方法
代码示例

    //Comparable 自然排序
public class Person implements Comparable {
    @Override
    public int compareTo(Object o) {
        if (o instanceof Person){
            Person oo = (Person)o;
            return this.age - (oo.getAge());
        }else {
            throw new RuntimeException("输入的类型不一致");
        }
    }
}
    //comparator:定制排序
     Set set3 = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                if (o1 instanceof Person && o2 instanceof Person){
                    Person oo1 = (Person)o1;
                    Person oo2 = (Person)o2;
                    return oo1.getName().compareTo(oo2.getName());
                }else{
                    throw new RuntimeException("输入的类型不一致。");
                }
            }
        });

 6.HashSet 、LinkedHashSet、TreeSet的异同

相同点:都实现了set接口;都是线程不安全的

不同点:

        底层结构:

                HashSet:底层是哈希表

                LinkedHashSet:链表+哈希表

                TreeSet:红黑树

        功能:

                HashSet:底层能够存取null值

                LinkedHashSet:能够保证元素按照添加的顺序遍历,且元素的添加与取出满足FIFO

                TreeSet:能够实现指定规则排序

5.Map

1.HashMap

1.原理

        线程是否安全:线程不安全,效率高;可以存储null的key和value。

        底层:jdk7之前:数组+链表

                   jdk8:数组+链表+红黑树

        解决哈希冲突的方法:

        jdk1.8之后,HashMap解决哈希冲突时,需要满足两个条件才将链表转为红黑树:链表长度大于阈值(默认为8);数组长度大于64(若小于64,则先进行扩容而不是转为红黑树)

        HashMap添加键值对的原理:

HashMap被实例化后,底层创建长度为16的一维数组Entry[ ] table。

        调用put(key1,value1)后,会先调用key1所在类的hashCode()方法计算key1的哈希值,此哈希值经过计算后得到Entry数组在底层的存放位置:

                若此位置为空,则key1-value1添加成功

                若此位置不为空,则比较key1和该位置元素(假设为key2-value2)的哈希值:

                        若key1的哈希值和该元素key2哈希值不相同,则key1-value1添加成功

                        若key1的哈希值和该元素key2哈希值相同,则比较key1所在类的equals(key2)方法

                                若equals()返回false,则key1-value1添加成功

                                若equals返回true,则value1将value2进行替换

        HashMap扩容问题:

         HashMap默认初始化大小为16,每次扩容容量转为原来的2倍;若是创建时指定初始容量,则会扩充为2的幂次方;

        2的幂次方的由来:

        Hash值是一个很大的值,大概有40亿的映射空间;但这个值不内存中存放不下,因此需要进行一定的运算才能得到对应的数组下标,计算方法是  hash & (n-1)(n代表数组长度);而这个方法得到的结果,和对2的幂次方取余是一样的,只是和 “&” 相比, “%”效率更高

2.HashMap和HashSet对比

存储对象:

        HashMap存储的为键值对,是双列集合;是因为HashMap实现的是Map接口

        HashSet存储的为单列集合;是因为HashSet实现的是Collection接口下的Set接口

哈希值计算:

        HashMap基于键计算哈希值

        HashSet基于存储对象计算哈希值

3.HashMap和Hashtable对比

底层结构:

        HashMap底层是数组+链表+红黑树;

        Hashtable底层是数组+链表;

是否线程安全:

        HashMap线程不安全,Hashtable是线程安全的;

        因此HashMap的效率高于Hashtable

是否能存Null值:

        HashMap可以存储为null的键与值,但是为null的键只能有一个,为null的value可以有多个;        Hashtable不能存储为null的键和值

扩容机制:

        HashMap默认初始化大小为16,每次扩容容量转为原来的2倍;

        Hashtable默认初始化大小为11,每次扩容转为原来的2n+1倍;

4.HashMap和TreeMap对比

底层结构:

        HashMap底层是数组+链表+红黑树;

        TreeMap底层是红黑树

实现接口:

        HashMap和TreeMap都继承了AbstractMap;

        TreeMap比HashMap多实现了一个NavigableMap,而NavigableMap又继承了SortedMap;因此TreeMap能够对集合元素根据key排序(默认按照升序排列)

public class HashMap extends AbstractMap
    implements Map, Cloneable, Serializable {
}


public class TreeMap extends AbstractMap
    implements NavigableMap, Cloneable, java.io.Serializable{
}

5.HashMap多线程下存在的问题

1)环形链表问题

jdk1.7:若产生哈希冲突,对比equals()方法不同后,将新元素存入数组中,旧元素作为链表存在,新元素指向旧元素,这就是头插法;若在多线程情况下,若多个线程同时put()出现哈希冲突,很有可能造成链表的环形链表情况,即多线程下:由于使用头插法存入元素,元素A与元素B相互指向

jdk1.8:此时采用尾插法,插入的元素都放在链表末尾,指向前一个元素,避免了环形链表问题

建议:在多线程环境下,使用ConcurrentHashMap

2)数据覆盖问题

在多线程环境下,若线程A和线程B同时put(),并且发生了哈希冲突,线程A执行哈希冲突判断和可能会被挂起,线程B此时完成插入操作,随后线程A继续执行,但A已经进行了哈希判断,所以直接插入,此时A的数据会覆盖B数据

 6.HashMap的遍历方式

        1.Iterator迭代器遍历:分为EntrySet 和 KeySet方式

        2.For Each遍历:分为EntrySet 和 KeySet方式

        3.Lambda表达式遍历

        4.Stream API遍历

一般采用EntrySet遍历

详细可以参考:

HashMap 的 7 种遍历方式与性能分析!「修正篇」 (qq.com)

2.LinkedHashMap

        保证在遍历map元素时,可以按照添加的顺序实现遍历。

        原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。

                   对于频繁的遍历操作,效率高于HashMap

3.TreeMap

        可以按照添加的key- value进行排序,实现排序遍历。此时考虑key的自然排序或定制排序。底层使用红黑树。

4.Hashtable

        作为古老的实现类;线程安全的,效率低;不能存储null的key和value

5.Properties

        常用来处理配置文件;key和value都是String类型

6.ConcorrentHashMap

底层结构

        jdk1.7以前:Segment数组加链表,每个Segment数组结构有一个HashEntry数组,每一个HashEntry数组都可以构成链表结构;与此同时,对Segment数组进行分段加锁,在多线程情况下可以访问不同的Segment分段数组,从而实现线程安全

        jdk1.8:使用Node数组+链表+红黑树,不再采用Segment分段锁,而是synchronized锁定链表或红黑树的首节点,从而实现线程安全。

你可能感兴趣的:(java,开发语言)