一、List和Map
6e28d28f400af4b6d139d1eeae66bd3e.png特点
(1)传统的数组结构存储数据会在内存中开辟连续得空间,结合下标从而使得可以快速访问数据,但是删除和添加数据就很浪费资源
d2979e3a573f78f18cce5170747ef76f.png
(2)链表不需要开辟连续空间,使用指针来指向数据,因此删除和添加操作比较快,但是查询数据需要遍历全部得元素
91d7b026b0f978ed523681d0a554639c.png
(3)而哈希表[散列表]结合两者得长处,合二为一。使得哈希表比较牛掰(初始容量,数组长度默认为16,分为单指针和双指针,双指针每个元素指向前面元素同时指向后面元素)
7387cedc4acd90cf37e77fde1f0d0f55.png
(1)、List
1、可以允许重复的对象。
2、可以插入多个null元素。
3、是一个有序容器,保持了每个元素的插入顺序,输出的顺序就是插入的顺序。
4、常用的实现类有 ArrayList、LinkedList 和 Vector。ArrayList 最为流行,它提供了使用索引的随意访问,而 LinkedList 则对于经常需要从 List 中添加或删除元素的场合更为合适。
(2)、Map
1、不是collection的子接口或者实现类。Map是一个接口。
2、Map的每个 Entry 都持有两个对象,也就是一个键一个值,Map 可能会持有相同的值对象但键对象必须是唯一的。
3、TreeMap 也通过 Comparator 或者 Comparable 维护了一个排序顺序。
4、Map 里你可以拥有随意个 null 值但最多只能有一个 null 键。
5、Map 接口最流行的几个实现类是 HashMap、LinkedHashMap、Hashtable 和 TreeMap。(HashMap、TreeMap最常用)。
(3)、区别
1、list是存储单列数据的集合,map是存储双列数据的集合;
2、list中存储的数据是有序的,map中存储的数据是无序的;
3、list允许重复,map的键不能重复,值可以重复。
(4)、Array和ArrayList
Array(数组)是基于索引(index)的数据结构,它使用索引在数组中搜索和读取数据是很快的。
Array获取数据的时间复杂度是O(1),但是要删除数据却是开销很大,因为这需要重排数组中的所有数据。
List
List—是一个有序的集合,可以包含重复的元素,提供了按索引访问的方式,它继承Collection。
1、ArrayList底层采用数组实现,当使用不带参数的构造方法生成ArrayList对象时,实际上会在底层生成一个长度为10的Object类型的数组。
2、如果增加的元素个数超过10个,那么ArrayList底层会生成一个新的数组,长度为原数组的1.5倍+1,然后将原数组的内容复制到新数组中,并且后续增加的内容都会放到新的数组当中,当新的数组无法容纳增加的元素时,重读该过程。
3、对于ArrayList元素的删除操作,需要将被删除元素的后续元素向前移动,代价比较大。
4、集合当中只能放置对象的引用,无法放置原生数据类型,我们必须使用原生数据的包装类才能加入到集合当中。
5、集合当中都是Object类型,因此取出来的也是Object类型,那么必须要使用强制类型转化将其转换成真正的类型(放置进去的类型)。
二、List
1、Arraylist和Vector的区别
(1)、都实现了List接口(List接口继承了Collection接口)都是有序集合List集合规范制定(数据允许重复,可有通过索引取出数据)
(2)、ArrayList和Vector都是基于数组,因此大致代码相似。区别在于 Vector在Jdk1.0就有的古老集合, 因此包含大量方法名很长的方法。 Jdk1.2开始引入集合框架引入了List接口 Vector实现了List接口因此增加了一些List接口中的方法
(3)、总体来讲ArrayList可以完全取代Vector,除非有一些老古董强制要求Vector。Vectory是线程安全的因此性能较差。ArrayList是非线程安全所以接口性能较好。即使在多线程中也应该用ArrayList,因为可以通过Collections吧ArrayList和LInkedList转换成一个线程安全的集合
例子:List ls = Collections.synchronizedList(new ArrayList<>());
2、ArrrayList、Vecor、LinkedList的存储性能和特性
(1)、ArrayList Vector ==>> 数组方式存储数据
数组元素的数据 > 实际存储的数据以及增加和插入元素
按序号索引元素
插入元素 ==>>数组移动等内存操作
索引数据快、插入数据慢
(2)、Vector ==>> synchronized方法(线程安全)
性能上较ArrayList差
(3)、LinkedList ==>> 双向链表实现存储
序号索引数据需要向前或向后遍历
插入数据时只需要记录本项的前后项即可
所以插入速度较快
线程不安全
3、ArrrayList、Vecor、LinkedList容量以及适用场景
(1)、ArrayList
特点:
1、ArrayList 内部是通过动态数组实现的,它允许对元素进行快速随机访问;
2、当数组大小不满足时需要扩容,需要将已有数组移动到新的内存空间;
3、当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进行复制、移动代价比较高;
4、线程不安全。
初始容量:10初始容量:10
扩容容量:(原始容量 x 3 ) / 2 + 1。
适用场景:ArrayList 适合单线程,或多线程环境,但 List 只会被单个线程操作;随机查找和遍历,不适合插入和删除。
(2)、LinkedList
特点:
1、LinkedList 是基于双向链表存储数据的,很适合数据的动态插入和删除;
2、可根据索引值获取(get(int index)))或删除(remove(int index))节点(实现原理:通过计数索引值实现,当 index > 链表长度的1/2,从链表尾部开始遍历;反之,从链表头部开始遍历;
3、可作为队列和栈使用
4、线程不安全。
5、相对于ArrayList,LinkedList插入是更快的。因为LinkedList不像ArrayList一样,不需要改变数组的大小,也不需要在数组装满的时候要将所有的数据重新装入一个新的数组,这是ArrayList最坏的一种情况,时间复杂度是O(n),而LinkedList中插入或删除的时间复杂度仅为O(1)。ArrayList在插入数据时还需要更新索引(除了插入数组的尾部)。
6、类似于插入数据,删除数据时,LinkedList也优于ArrayList。
7、LinkedList需要更多的内存,因为ArrayList的每个索引的位置是实际的数据,而LinkedList中的每个节点中存储的是实际的数据和前后节点的位置。
适用场景:适合单线程中,顺序读取,不适合随机读取和随机删除。
(3)、Vecor
特点:
1、其特点大致与 ArrayList 一样。
2、线程安全(因为内部方法都是通过 synchronized 关键字同步的)。
初始容量:10初始容量:10
扩容容量:若扩容系数 > 0,则将容量的值增加“扩容系数”;否则,将容量大小增加一倍。
适用场景:能不用就别用。
(4)、ArrayList相比LinkedList
顺序插入速度ArrayList会比较快,因为ArrayList是基于数组实现的,数组是事先new好的,只要往指定位置 塞一个数据就好了
LinkedList则不同,每次顺序插入的时候LinkedList将new一个对象出来,如果对象比较大,那么new的时间 势必会长一点,再加上一些引用赋值的操作,所以顺序插入LinkedList必然慢于ArrayList
ArrayList的遍历效率会比LinkedList的遍历效率高一些
LinkedList做插入、删除的时候,慢在寻址,快在只需要改变前后Node的引用地址
ArrayList做插入、删除的时候,慢在数组元素的批量copy,快在寻址
如果确定插入、删除的元素是在前半段,那么就使用LinkedList
如果确定插入、删除的元素在比较靠后的位置,那么可以考虑使用ArrayList
如果不能确定插入、删除是在哪儿呢?建议使用LinkedList,
一来LinkedList整体插入、删除的执行效率比较稳定,没有ArrayList这种越往后越快的情况
二来插入元素的时候,弄得不好ArrayList就要进行一次扩容,而ArrayList底层数组扩容是一个既消 耗时间又消耗空间的操作
三、Map
Hash也称散列。基本原理就是把任意长度的输入,通过Hash算法变成固定长度得输出,这个映射得规则对应的就是Hash算法,而原始数据映射后得二进制串就是Hash值
entry----key----hash----index哈希值就是把各种类型的key算成统一的下标(.hashcode())
07082e086ee0dc41f936c4553dde2b47.png
Hash特点
1、从Hash值不可反向推导出原始数据
2、输入数据的微小变化也会得到完全不同的hash值,相同的数据的道相同的hash值
3、哈希算法执行高效,长文本也能快速计算出哈希
4、Hash算法冲突概率较小
Hash表在jdk1.7中使用数组+链表 jdk1.8中加入了红黑树
由于Hash的原理是将 输入空间的值 映射成 hash空间内,而hash值空间远小于输入的空间。
根据抽屉原理,一定会存在不同的输入被映射成相同的输出。
抽屉原理:9个抽屉放10个苹果,怎么放都会有一个抽屉里有2个及以上的苹果
(5)HashMap的继承体系
1、HashMap与HashTable简介
类似于ArrayList和LinkedList的区别, HashTable是JDK1.0 就存在的老东西,因此 HashTable是一个继承Dictionary 的古老集合。JDk1.2引入了集合框架的Map接口,HashTable也实现了Map接口,因此HashTable也增加了一些Map的方法
2、HashMap与HashTable区别
(1)、HashMap允许使用null作为Key或者Value,HashTable不允许
(2)、HashMap是线程不安全的因此性能较好,而HashTable是线程安全的因此性能较差
3、场景
多线程环境下也是用HashMap,Collections可以把HashMap转换成线程安全的集合.
例:Map map = Collections.synchronizedList(new HashMap())
4、底层一些常量与属性和构造方法
常量:
//缺省大小
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//数组的最大长度
static final int MAXIMUM_CAPACITY = 1 << 30;
//缺省负载因子大小
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//树化阈值(链表长度超过8之后并且数组长度大于64就会升级成一个树了)
static final int TREEIFY_THRESHOLD = 8;
//树降级成为链表的阈值(删除树中元素,元素=6降级为链表)
static final int UNTREEIFY_THRESHOLD = 6;
//数组长度大于64(并且某个链表长度>8)升级为红黑树
static final int MIN_TREEIFY_CAPACITY = 64;
属性:
//Hash表,put时初始化
transient Node<K,V>[] table;
//
transient Set<Map.Entry<K,V>> entrySet;
//当前Hash表中元素个数
transient int size;
//当前Hash表结构修改次数(插入元素或删除元素,替换不会计数)
transient int modCount;
//扩容阈值(Hash表中元素超过阈值时触发扩容,超过阈值不扩容影响查找性能。链化严重)
int threshold;
//负载因子(计算threshold = capacity数组长度 *loadFactor负载因子)
final float loadFactor;
构造方法:
//
public HashMap(int initialCapacity, float loadFactor) {
//做了逻辑校验
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;
//赋值扩容阈值 位移计算只能是2的次方数导致table的长度只能是2的次方
传过来init。。为7计算为8,9计算为16
this.threshold = tableSizeFor(initialCapacity);
}
//设置默认负载因子
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
5、put源码分析
put方法时会默认(new entry(key,value,null=指针next))
public V put(K key, V value) {
//table的length不是特变长的情况下,让让key的hash值高于16位也参与路由运算
return putVal(hash(key), key, value, false, true);
}
//实际用put向散列表插入数据
/**
* Implements Map.put and related methods
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent散列表存在欲插入的key就不插了(有就替换)
* @param evict
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//tab:引用当前HashMap的散列表
//p:表示当前散列表的元素
//n:表示散列表数组的长度
//i:表示路由寻址 结果
Node<K,V>[] tab; Node<K,V> p; int n, i;
//第一次插入数据时才会初始化,只是new出来并不会初始化(好多new出来但并不用)
//延迟初始化逻辑
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//寻址找桶位,刚好为null,这时直接将当前key-value转成node塞进去
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
总的来说向Vector和HashTable这种JDK1.0产物尽量不使用,除非某些api强制使用~~果然老东西得退休
原文链接:https://blog.csdn.net/qq_36407919/article/details/122872643
网友评论