java中的集合

目录

1、ArrayList的重要兄弟及高堂

1.1、List的UML图 

1.2、List的三个重要实现类的特点

2、HashSet的重要的兄弟、后代及高堂

2.1、Set的UML图及关系分析

2.2、Set的三个重要实现类及其本质

2.3、有Map了为什么还要Set

3、HashMap的重要兄弟和高堂

3.1、Map的UML图

3.2、Map的五个重要实现类的不同点简述

4、List、Set、Map的寻亲之路

5、文末心得:

1、ArrayList的重要兄弟及高堂

上面这种语句没有少写吧,但是不知道你有没有看过它的UML图呢,它的上下文你有没有看过呢,其实他还有两个孪生兄弟LinkedList和Vector,只不过我们好像平时不怎么用它们 ,它们都继承自AbstractList<E>而AbstractList又是实现了List的接口,所以我们可以都用List接收他们

     List<String>  list1 = new ArrayList<>();
     List<String>  list2 = new LinkedList<>();
     List<String>  list3 = new Vector<>();

1.1、List的UML图 

List的UML图,根接口是Collection接口

从图可以看出LinkedList、ArrayList和Vector都继承自AbstractList并且都实现了List接口,所以他们三个是亲兄弟,返回值都可以用List来接收。

1.2、List的三个重要实现类的特点

那list接口的这三兄弟的最大的不同点在哪里呢?,话不多说,上图

先看Vector和ArrayList都有index,这是共同点,但是在Vector的方法上有一个synchronized,这一看就是线程安全的,所以Vector是线程安全的list,再看LinkedList有个node这个东西,而且名称也带link,这一看是基于链表的,而Vector和ArrayList都有index且其中一个还有Array这么看来是基于数组的,那么,由数组和链表的特点就知道他们的特性了ArrayList查询快,LinkedList增删快,而Vector是和ArrayList相似的但是有synchronized关键字,加了锁,所以是线程安全的,但是加锁后效率就降低了。

2、HashSet的重要的兄弟、后代及高堂

相比ArrayList的使用的广泛度,HashSet倒是不太常用,但是,在写程序中还是经常可以见到的,其实在它和List是类似的。直接看招

Set<String> set1 = new HashSet<>();
Set<String> set2 = new TreeSet<>(); // 兄弟
Set<String> set3 = new LinkedHashSet<>(); // 孩子

为啥说TreeSet是兄弟,而LinkedHashSet是孩子呢,来看看它们的血缘关系UML图

2.1、Set的UML图及关系分析

Set的UML图 ,根接口是Collection(好像和List同宗,而且都有个兄弟叫AbstractCollection,可能List是Set从小被拐卖了的兄弟)

从上面的UML图可以看出,HashSet和HashSet都继承自AbstractSet抽象类,所以这两个是兄弟,而LinkedHashSet又继承自HashSet所以LinkedHashSet是HashSet的后代,是HashSet和TreeSet的后代。但他们都实现了Set接口,所以名称都带着Set,都可以用Set接收返回值。

2.2、Set的三个重要实现类及其本质

 对于Set的三个实现类的特点,我们得先用火眼金睛看看HashSet、TreeSet、LinkedHashSet的本来面目是啥,上照妖镜

就看三个类的空参构造器,一下就颠覆了我的大脑,竟然是这样的空参构造的HashSet本质构造的是HashMap,空参构造的TreeSet本质构造的是TreeMap,而空参构造LinkedHashSet其实调用的是父类HashSet的三参构造器,本质是构造的LinkedHashMap,这一下就明白了。

他们的构造函数的真实面目如下:

public HashSet() { map = new HashMap<>(); }

public TreeSet() {this(new TreeMap<E,Object>());}

public LinkedHashSet() { map = new LinkedHashMap<>(16, .75f, true);}

2.3、有Map了为什么还要Set

既然找到了真面目都是Map,那为啥有Map还要有Set呢,他们又是什么关系呢?带着疑问,我们再来看一个方法,通过这个方法来找寻答案,先上图,看图说话。

add方法的对比图

 从上图可以看出,其实add方法都是调用的他们真正使用的Map的put方法,LinkedHashSet的没有add方法是应为使用的是父类HashSet的add方法。我们只用搞懂HashSet的add方法就知道为啥有Map还要搞个Set出来了。

看图中的最左边HashSet的add方法中,调用的Map的put方法,key是我们add方法传入的值,而这个value则是默认值,探究value是个static final 的一个new Object(),就是一个空对象嘛,在看Map的put方法,其实put的返回值是key对应的旧的value,那么这个add中的==null就知道是咋回事了,是为了阻止add相同的值,这里set的一个重要特性就出来了:Set集合中的值不能重复,他是怎么阻止的呢,我们来看看:因为Map中的put方法只要key相同就会去替换value,put方法会返回旧的value,当第一次add一个值的时候,返回的是null所以==null就为true,则add成功,此时map中的插入了一条数据key为我们传入的值,value是一个空对象,而第二次add相同的值的时候,去替换了第一次插入的值,返回的第一次插入的空对象,这里是空对象,而不是null,所以==null为false告诉add方法失败,但是底层确实是执行可一次put的,所以Set集合表现的是不能插入相同数据。

​​​​​​​上面这张图就很说明问题了,我们在add相同的值时时,其实Map是进行了put操作的。

所以Set其实只是使用了Map中的key的属性而已。

那么这么以来就知道为啥有了Map还要有Set,其实Set就是把M啊跑、封装了一层,指提取Map的key的属性,如果不封装,我们要去使用Map的key属性就得自己new一个map,每次还得传value,太麻烦了,现在这样一封装就爽多了。

所以要搞清楚Set的其他属性,则需要我们去搞清楚Map的key的属性,接下来就去搞搞Map。

3、HashMap的重要兄弟和高堂

只要写过Java的人应该都用过HashMap,并且在初、中级的java面试中Map基本都会问到,那么这个HashMap到底是什么样子,就让我来揭开它神秘的面纱吧。还是先来看看他们一家。

Map<String,String> map1 = new HashMap<>();
Map<String,String> map2 = new TreeMap<>();
Map<String,String> map3 = new LinkedHashMap<>();
Map<String,String> map5 = new ConcurrentHashMap<>();
Map<String,String> map4 = new Hashtable<>();

上面这么多,其实常用的并不多,就我个人而言,到现在为止,只用过Ha shMap和LinkedHashMap而已,但是我们总得看看他们的关系吧。

3.1、Map的UML图

Map的UML图,根接口为Map

从图中一看,一目了然他们的关系HashMap、TreeMap、ConcurrentHashMap 是三个亲兄弟,而LinkedHashMap是HashMap的亲儿子,子侄辈的,至于Hashtable,从名字都可以看出是远房亲戚。但是他们都实现了老一辈Map的宏愿,因此都是亲戚。

3.2、Map的五个重要实现类的不同点简述

map一个接口为啥会有5个重要的实现类呢?他们各有什么不同,一定要五个来实现?我们一起来看看:

先看他们的无参构造器。

Map​​​​​​​的五个重要实现类的无构造器

上图展示了五个类的无参构造器,分别来简述下:

      HashMap:无参构造只是设定了加载因子为默认值0.75;

     TreeMap:无参构造只是将它的比较器设置为null;

      LinkedHashMap:无参构其实调用的是父类HashMap的无参构造器,同时设置了下 支持的排序类型(false基于插入顺序,true基于访问顺序)这个属性就可以看出TreeMap是有序的;

     ConcurrentHashMap:无参构造什么也没有做;

     Vector:调用了自己的有参构造器并掺入初始化大小11和加载因子0.75,在有参构造器中创建了一个初始化容量为11的Entry数组,设定了加载因子,并且设置了阈值(判断是否需要扩容的临界值)。

看完无参数构造器,再看看put方法:

 首先看看它们对key、value的限制,如下图

Map实现类put方法对key、value的限制

key为null的情况:从上图可以看出,TreeMap、ConcurrentHashMap、Hashtable中对key值都进行了限制,这三个类中的红色框中的代码可以直观的看出ConcurrentHashMap的key为null时会抛出空指针异常,Hashtable中因为有key.hashCode(),所以如果key为null也会抛出空指针异常,TreeMap中因为用到了compare比较key而这个方法里面的代码也限制了不能为努力了,所以总结:只有HashMap和LinkedHashMap中的key可以为null

value为null的情况:上图中的黄色框中圈出,ConcurrentHashMap、Hashtable中如果value为null会抛出空指针异常,所以总结:只有ConcurrentHashMap、Hashtable中的value不能为null

搞清楚它们的key和value的情况,再来看看别的不同点,还是看它们的put方法

Map的五个重要实现的的put方法

 a、LinkedHashMap怎么实现的双向链表。

对于HashMap中的put方法,其实主要是想说LinkedHashMap中重写了部分方法,例如这newNode方法,LinkedHashMap中从写了这个方法,在HashMap的node节点外包了一层添加了一个before和after属性,都是解节点,用于存储当前节点的前一个节点和后一个节点,源码如下

    // 重写HashMap的newNode方法
    Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
        // 内部调用自己封装的node
        LinkedHashMap.Entry<K,V> p =
            new LinkedHashMap.Entry<K,V>(hash, key, value, e);
        // 设置LinkedHashMap.Entry<K,V> p对象的before和after将所有节点串起来,并设置LinkedHashMap的head和tail首尾节点。
        linkNodeLast(p);
        return p;
    }

    //自己封装的node实体,其实在HashMap的node实体外包了层,增加了两个实体对象before和after前一个对象和后一个对象
    static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }

图中的LinkedHashMap中红色框中的部分就是上面代码中的linkNodeLast的实现其实就是每次新增的节点作为LinkedHashMap的tail尾节点,并将每个节点的before和after放入相应的节点串起来,形成双向链表。

b、TreeMap的有序是怎么实现的

上图中的TreeMap可以看到其中有一个compare(key,key)方法,其实就可以看出在put时对比了key值,还有一个parent属性,这就说明是有序的,每一个node都有parent这不就连成一条线了么,所以就有序了,对于key的排序如果没有使用自己定义的比较器,默认使用的自然排序。要使用自己的比较器,有对应的TreeMap的构造方法可以创建TreeMap.

c、关于ConcurrentHashMap和Hashtable是线程安全的问题,也可以看上图中红框圈出来的部分分,ConcurrentHashMap调用的casTabAt方法里面使用的是CAS(compareAndSwap)这个来保证的这个是java1.8及以后的,在1.7及以前使用的是分段锁(segment)有兴趣的可以去看下。

static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
                                        Node<K,V> c, Node<K,V> v) {
        return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
    }

对于Hashtable如何实现线程安全的,一看这个方法上的synchronized马上就知道是安全的了,对于锁这一块,后面再说。

4、List、Set、Map的寻亲之路

看一个List、Set和Map的总图

从上图可以看出java的集合主要分为两个流派,collection接口和map接口,这个两个是依赖关系

collection又被list和set接口继承。

     list接口有具体的实现类:ArrayList、LinkedList、Vector

    set接口有具体的实现类:HashSet、TreeSet、LinkedHashSet,而这三个set的实现其实是依赖的HashMap、TreeMap、LinkedHashMap的key来实现的,所以TreeSet、LinkedHashSet,不能存储null值。

Map接口的具体实现类有五个:HashMap、TreeMap、LinkedHashMap、ConcurrentHashMap、Hashtable其中的TreeMap、ConcurrentHashMap、Hashtable的key不能为null,而ConcurrentHashMap、Hashtable的value不能为null,且ConcurrentHashMap、Hashtable是线程安全的。

5、文末心得:

小弟第一次尝试写作,如有不妥之处望各位大神指正!

关于集合这块,每天工作之余抽出一点时间来整理,耗时一周写出这么点希望对自己和看到这篇文章的人有所帮助,一直以来自己对集合这一块也是知道一些但是不知到怎么说,现在尝试着看看代码来分析,如有不妥之处望各位大神指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值