JAVA_集合
一.体系
- Collection:单列
- list:有序可重复,可以放多个Null
- Arraylist ;Linkedlist ;Vector
- Set:无序不可重复,只能放一个Null
- HashSet ;LinkedHashSet ;TreeSet
- Queue:
- Deque:双端队列 ;BlockingQueue:阻塞队列 ;AbstractQueue:非阻塞队列
- list:有序可重复,可以放多个Null
- Map:双列,k-v键值对
- HashMap
- linkedHashMap
- TreeMap
- HashTable
- Properties
- HashMap
二.ArrayList、LinkedList、Vector三者的异同(使用场景)?
- 同:存储有序可重复的数据 就像数组一样
- 异:
- ArrayList
- 底层默认创建长度为10的数组:new Object[10];(数组就需要连续的内存空间)
- 空间不够自动扩容,扩展为原来 容量*1.5,同时将原有的元素复制到新的数组中
- jdk1.7:类似饿汉式,new Arraylist()就直接你创建一个数组
- jdk1.8:类似懒汉式,new Arraylist()还不创建数组,只有使用add()才创建。延迟数组的创建,节省内存
- 数组结构适合遍历和查找,不适合插入/删除(但可以优化)
- 线程不安全(效率高),可以使用Collections工具类变为线程安全,或使用juc
- 底层默认创建长度为10的数组:new Object[10];(数组就需要连续的内存空间)
- Vector
- 底层默认创建长度为10的数组:new Object[10];
- 空间不够自动扩容,扩展为原来 容量*2,同时将原有的元素复制到新的数组中
- 线程安全(所有方法synchronized修饰),但是效率太低,很少使用
- LinkedList
- 底层创建一个双向链表 (链表不需要连续的内存空间)
- 定义了一个Node内部类,里面有prev,element,next属性
- 链表结构适合插入和删除,遍历和查找比较慢
- 链表当然不需要扩容
- 只能使用iterator遍历,不能使用增强for遍历,因为需要get每一个值,遍历所有的元素,效率极低
- ArrayList
ArrayList和LinkedList性能对比? 建议使用ArrayList
- LinkedList底层维护一个内部类Node,每次添加新的元素创建一个Node对象,耗费资源。 而且使用不方便(遍历时)
- 对于ArrayList不适合插入/删除的特性,可以进行优化。 采用尾插法并指定初始容量可以极大的提升性能,甚至超过LinkedList。
- ArrayList 的空间浪费主要体现在在list列表的结尾预留一定的容量空间; LinkedList 的空间花费则体现在它的每一个元素都需要消耗存储指针节点对象的空间。
如何实现ArrayList和Array的转换?
- Arrays.asList(str); //转变为list
- list.toArray; //转变为array
三.阻塞队列
这里只说明api的使用
四.HashMap
前提知识:HashCode和equals,提前说明这两个东西,有助于理解HashMap
hashCode()相同,equals()也一定为true吗?
不是,这两个是配合使用的。
HashCode()是Object提供的一个native的方法,用来获取哈希码
内存中维护一个很大的哈希表,每一个对象存储到内存的时候,都在这个表中进行记录
这个表就相当于一个"记录表",记录着每个对象的地址。
什么是哈希表?
哈希表本质是一个升级版的数组,每一个对象都有一个关键字(k-v中的k),根据内部的"哈希算法"得出一个"哈希码"。
这个哈希码就相当于数组的索引(哈希表没有0,1那样的索引。哈希码就是索引),可以直接通过哈希码找到一个对象。
这样做就是为了提高执行的效率,快速定位对象。因为哈希表的初衷就是升级数组,数组已经很合适查询了,
但是哈希表"更块"。哈希表就是一种数据结构
(哈希表其实就是对数组的索引进行优化,"让索引和关键字(传入的对象)产生关系,从而快速找到对象的位置")
注意点:
相同的对象一定产生相同的哈希码
不同的对象也可能产生相同的哈希码 (产生哈希冲突,有对应的解决方案)
//上面这两条主要是因为哈希算法导致的,正因为这样,才需要用到equals
equals()被覆盖,hashCode()也必须被覆盖
为什么要有HashCode?(为什么搞一个"记录表")
以HashSet如何检查重复来说明为什么要有HashCode:
对象加入HashSet时,HashSet会计算对象的哈希码,从哈希表中检查是否索引的位置上有值(对象),
没有:就认为对象不重复,允许添加;
有值:就会调用equals()来判断两个对象是否相等:
相等:不允许添加
不相等:说明哈希冲突了,采用对应的解决方案放到其他的位置上,允许添加
这样做主要为了避免多次equals比较,提高效率。
HashSet底层就是创建一个HashMap,所以直接对hashMap进行解释
底层实现:
- jdk7:数组+链表
- new HashMap();//创建一个长度为16的数组
- jdk8:数组+链表+红黑树 (改为红黑树为了加快查询的速度)
- new HashMap();//类似于懒汉式,还没有创建数组,当调用put()时创建长度为16的数组
- 只有当 链表高度>8且数组长度>64,就把链表改为红黑树。数组长度<6就将红黑树转回链表
put添加过程(如何保证不重复)
- 添加的过程和上面hashSet使用哈希表添加的过程一样,只是哈希冲突问题采用七上八下。
- 注意:发现hashCode相同,equals相同,不是不允许添加,而是覆盖之前的元素
- 七上八下:遇到哈希冲突时
- jdk7是把新元素放在数组上,旧元素放在链表上,指向旧元素
- jdk8是把新元素放在链表上,旧元素指向新元素
扩容机制
- 数组超过临界值(临界值0.75) 扩展为原来的2倍, 将旧的元素复制到新的数组中,重新计算hash,按照列表/红黑树的方式排序起来
源码中重要的常量
- DEFAULT_INITIAL_CAPACITY:默认数组容量 16
- MAXIMUM_CAPACITY:最大的容量 2^30
- DEFAULT_LOAD_FACTOR:默认的加载因子 0.75(一个经过科学计算的数) 临界值=容量*0.75 比如16*0.75=12 容量达到12时,考虑扩容
- TREEIFY_THRESHOLD:链表转化为红黑树的 链表最低高度 8
- MIN_TREEIFY_CAPACITY: 链表转化为红黑树的 数组最小长度 64
- UNTREEIFY_THRESHOLD:红黑树转回链表的 数组长度 6
开发中你是怎么使用hashMap?
- 根据实际业务指定hashMap的长度,因为这样可以避免多次扩容,提高性能
- HashMap<String,Object> map = new HashMap<>(长度)
- 注意:new HashMap<>(7);//这种我们自定义长度的hashmap,在创建数组的时候,长度经过tableSizeFor(initialCapacity)方法变为大于指定长度的最低二次幂数,
- 比如1就变为2;7就变为8;11就变为16 等,所以上述是创建了一个长度为8的数组
Hashmap为什么不安全?
- jdk 1.7 hashmap底层使用数组 + 链表,当扩容时会调用transfer函数 ,在对table进行扩 容,需要将原来的数据复制到newtable中,采用头插法,会将链表反转,这个过程可能会导致死循环和数据丢失,也有可能造成数据覆盖
- jdk 1.8中 hashmap底层使用数组 + 链表 + 红黑树,采用尾插法,优化了死循环和数据丢失的 问题,但是还是会有数据覆盖的问题
HashMap和HashTable的区别?
- 底层不同
- HashMap:初始化16,扩容2倍
- HashTable:初始化11,扩容2倍+1
- HashMap线程不安全,HashTable线程安全(效率低)。 (即使需要线程安全也不用HashTable,而是使用concurrentHashMap,后面解释)
- HashMap可以存储null的k-v;HashTable不能存储null的k-v
寄语:任何你的不足,在你成功的那刻,都会被人说为特色。所以,坚持做你自己,而不是在路上被别人修改的面目全非