JAVA集合分析

说明:本文是我对自己学习知识的一个简单总结,可能存在许多不足,我希望通过此方式来回顾知识,加强理解,也希望大家能指出文中的错误与不足,互相学习,谢谢。

1.集合Collection分析

在开始说集合之前我们先看一下什么是变量,变量是由变量类型+变量名称组成,变量是用来保存信息的载体。那再看看集合,集合是由集合存储的类型+集合名称组成,简单来说就是存储一类数据的容器,在java中集合的父类是Collection(还有Map集合),集合属于数据结构的一种,在计算机中怎样存储数据考虑的就是通过怎样的数据结构更合理的将数据存储在计算机中。所以集合有不同的子类,通过不同的数据结构来存储数据。
下面是我总结的集合粗略体系图。

在这里插入图片描述

我们先对比查看一下Collection接口。

在这里插入图片描述

如上图所示,在jdk1.8版本中比jdk1.7版本多了几个方法,我们以一种一个方法为例说明。

 default Stream<E> parallelStream() {
        return StreamSupport.stream(spliterator(), true);
    }

2.List集合分析

之前的接口中,定义的是方法,而具体的实现则由子类完成,但是我们可以看到的是在这个Collection接口里面竟然有方法体实现,这是jdk1.8出来的新特性,在接口中可以由default(或者static)开头定义方法和方法体,那为什么有这样的特性呢?那是因为这样可以扩展接口的方法【这里扩展了Stream的功能】而不会破坏原有的继承体系,如果没有这个特性,那么只要是实现了Collection这个接口的子类也必须重写这些扩展的方法。在面向对象的编程中,我们需要遵守的法则是开闭原则,对修改封闭,对扩展放开。
现在我们查看一下集合中重要的两个子类,LinkList、 ArrayList,下面是他们的类图。

在这里插入图片描述

从类图中可以看出,集合的顶层接口是Collection,接下来是实现Collection接口的AbstractCollection和AbstractList,这两个类实现了Collection中的大部分方法,并定义了一些模板,子类只需要实现实现模板中的方法即可完成集合中的大部分操作了。

这里引出了设计模式-模板方法模式(定义了一套算法流程,里面实现了大部分方法,子类实现其中的小部分方法就可以实现整个流程)。

  • Collection代表的子类有ArrayList LinkList, ArrayList底层是通过数组方式实现,它的初始容量大小为10,以0.5的增装因子扩容,扩容时通过调用native方法复制数据(native方法是用来访问C语言代码的桥梁,Java是通过JVM访问内存的,C语言直接访问内存,所以为了解决JAVA的一些操作慢的问题,引入了Native方法),为了保障效率,一般给它一定的大小(根据数据量来计算)避免底层复制,由于底层是数组,存储的内存空间连续,对于查找操作,时间复制度为O(1)而删除和新增节点比较耗时。
  • LinkList通过链表形式存储数据,地址空间不连续,每一个节点为一个Node,数据通过Node连接,相比于ArrayList删除和新增操作效率更高,查找效率低。

补充:由上图可知LinkList实现了Deque(双端队列),即可以从头或从尾添加节点。分别为linkFirst(E e)方法与linkLast(E e)方法。

3.Set集合分析

从源码中定义可知,Set是一个没有包含重复元素的集合。它可以存入为null的元素,元素的类型可以不一样。但是不能保证顺序。

在这里插入图片描述

  • 这里简单分析下HashSet,从HashSet源码中可以知道它间接实现了Collection接口,所有具有添加、删除、得到集合大小等基本方法。HashSet它不能保证插入的顺序和遍历时一样,因为它是基于Hash值来存放元素的(HashSet构造方法中其实调用的是HashMap实例,HashMap是可以运行键值都为null,线程不安全),同时多线程访问时会有线程安全问题,要注意。
  • TreeSet相比于HashSet最大的不同是它可以保证添加元素的顺序,TreeSet的构造方法中创建了TreeMap实例(TreeMap是一个基于红黑树的实现)。
  • 注意点:在集合中进行遍历时候操作,如果操作不当会抛出异常。
//这段代码在遍历时候删除元素没有问题
ArrayList<Integer> list = ArrayList();
list.add();
list.add();
list.add();
list.add();
(i=0; i<list.size(); i++){
    list.remove(i);
    //下面这种输出remove移除的元素,但这里却只输出1 3。因为第一个元素移除后,第二个元素跑到第一个元素位置上,
    //后面元素依次前移,所以只输出1 3。
    //System.out.println(list.remove(i)); 
}
================华丽的分割线===================
//这段代码在遍历时候删除元素会报错(ConcurrentModificationException),这里涉及到一个名词,快速失败。
(Integer integer:list){
    list.remove(integer);
}

上面使用迭代器方式遍历删除元素时候,不允许对集合中元素增加或者删除。具体实现可以查看集合源码,这里就不做扩展了。

4.集合Map分析

  • Map集合里面提供三种集合视图,分别是键的集合,值的集合与键值对形式的集合,由源码定义可知,map集合的key值不能重复。
  • Map集合的代表子类有HashMap HashTable。这里简单介绍下这两种集合的不同之处,通过这些比较选择使用的场景。
    这两种MAP存储的数据结构都是通过数组+链表的形式(jdk1.7中),结构如下图;在jdk1.8中HashMap数据结构是数组+链表+红黑树。存储数据都是先通过hash函数计算出键在数组中的具体位置,然后存入数据,当数组同一个位置插入两个值时,如果两个值不同,将以链表形式连接起来(jdk1.7是头部插入法)。
    在这里插入图片描述
  • HashMap中存储的键和值可以为null,而HashTable不能。
  • HashMap中存在线程安全问题,put操作时没有加锁,当多个线程同时操作时可能导致值的丢失(比如当两个线程同时存入数据时,当它们的key的hash相同,值不同时,就有可能导致其中一个值被覆盖),jdk1.7中多线程操作还可能导致链表的死循环【可以自行查询】。HashTable线程安全,但是速度慢。
  • HashMap初始容量为16,加载因子为0.75:即当 元素个数 超过 容量长度的0.75倍 时,进行扩容,扩容为原来的两倍。HashTable初始容量为11,加载因子0.75,扩容大小为原来的两倍+1.

补充:

1.HashMap和HashTable的键均通过hash函数去映射到数组中。Hash函数在使用时候会有hash冲突,即两个不同键通过hash函数转换后地址相同,所以为了减少hash冲突,一般来说可以通过优化hash函数来减少冲突,比如HashMap、HashTable中的链地址法(数组+链表)。

2.HashMap的容量必须为2的幂,是因为为了提高计算效率,也是为了减少hash冲突。例如HashMap容量为16二进制为10000,通过这个算式tab[i = (n - 1) & hash] ,n-1后二进制为1111。这里假设hash值为1111和1110
在这里插入图片描述
当容量不为2的幂时,比如容量为15,二进制1111,n-1后二进制1110,假设hash值为1111和1110
在这里插入图片描述
由上可知,当容量不为2的幂时得出的结果均为1110,也就是发生了冲突。容量为2的幂就是因为能够减少冲突,使其分配均匀。
在这里插入图片描述
3.HashMap可以通过构造函数初始化容量大小,那是怎么保证容量为2的幂呢?如下代码

static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

通过上面的运算即可将输入的容量大小转换为大于等于它的2的幂的数。

4.key相同,hash值一定相同,hash值相同,key不一定相同。HashMap中如果key相同,那么后面put进去的值就会覆盖原来的值,如果想实现key相同,值不覆盖,那么可以使用google提供的类Multimap,它的数据结构可以是一个key对应多个值。
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值