怎么遍历list集合赋值_Java集合⼊门知识参考了⽹上很多⼤佬的博客,只能夸夸⾃⼰太菜了,不说了,进⼊正题.
和数组⼀样,集合也是存储数据的,但是两者有区别,先来说下.
数组和集合的区别
jdk怎么使用1, 数组中存储的数据类型是相同的,集合可以不同.
2, 数组的长度是确定的,⽽集合是不确定的.
接下来我们认识⼀下集合.
集合存储数据有两个顶层接⼝,⼀个是 Collection 接⼝,⽤于存储单个元素的集合,⼀个是Map 接⼝,存储键值对映射.认识集合
丑丑的结构图,如有错误或建议,可以多多指正.
Collection
Collection 接⼝下有Set 和 List 接⼝.
Set 接⼝下有HashSet 和 TreeSet 这两个重要的实现类
List 接⼝下有ArrayList , LinkedList 和 Vector 实现类
让我们⼀起来⼤概了解下这⼏个常⽤集合的特点:
Set
存储⽆序,⽆索引,不可重复.
HashSet
底层:哈希表
哈希表通过重写hashCode和equals⽅法来共同保证元素的唯⼀性.
根据存储的元素计算出hashCode 值,然后根据hashCode 值到存储的bucket 的下标,如果bucket
中没有元素,就直接存;如果有元素,就根据要存的元素和该元素进⾏equals ⽅法⽐较,如果为true ,则不存;如果结果为false,那么就以链表的形式存储.
特点:查询和增删的效率都⾼,存储唯⼀元素并且允许null,线程不安全,由HashMap⽀持
遍历:1, foreach 2, 迭代器
package
TreeSet
底层:红⿊树
List
有序可以重复,能根据索引取值
ArrayList
底层:数组结构
特点:查询效率⾼,增删效率低;线程不安全
扩容:默认初始化⼤⼩是0,第⼀次添加元素后为10,使⽤ArrayList 的copyOf ⽅法进⾏扩容,每次扩容为原来的1.5倍
遍历:1, for循环 2, 迭代器(Iterator)
package
注意:
使⽤Iterator 迭代集合的过程中,不可修改集合元素,否则会引发异常.并且Iterator只能向后迭代
如果你想在循环过程中删除某个元素,只能调⽤Iterator 的remove ⽅法,不能使⽤ArrayList 的remove ⽅法,否则会出现异常LinkedList
底层:双向链表结构
特点:查询慢,增删快;线程不安全;
提供了对头尾元素操作的⽅法
addFirst
Vector
底层:数组结构
特点:查询快,增删慢;线程安全,效率低.
扩容:每次扩容为原来的两倍
过时,被ArrayList 取代
Map
map下有⼏个重要的实现类 HashMap,HashTable,ConcurrentHashMap
hashMap
底层:哈希表(Java1.7 数组+链表,Java1.8 数组+链表+红⿊树)
特点:允许有且仅有⼀个唯⼀的null键,线程不安全
可以通过Collections.synchronizedMap(hashMap())⽅法来保证线程同步.
扩容:初始容量为16,加载因⼦0.75,每次扩容为原来的2倍.
put() ⽅法详解
使⽤put() ⽅法添加元素时,会先调⽤⼀个hash() ⽅法,得到当前key 的⼀个hash 值,⽤于确定当前key 存放在数组中的下标位置,判断当前下标位置是否已经存在元素,若没有,则把key , value 包装成Node 节点,直接添加到此位置.若有,如果key 值也相等,覆盖该位置的值,如果当前是红⿊树结构,则把它加⼊到红⿊树中,如果是普通链表结构,采⽤尾插法,将新节点加⼊到链表尾部,长度超过阀值,则转为红⿊树
hashTable
底层:哈希表
特点:不允许有null的键,线程安全
扩容:初始容量为11,加载因⼦0.75,每次扩容为原来的2倍+1
ConcurrentHashMap
是在HashTable 的基础上进⾏优化的,因为hashMap 在多线程条件下是不安全的,在put 时,插⼊的元素超过了容量(由负载因⼦决定)的范围就会触发扩容操作,就是rehash,这个过程会重新将原数组的内容重新hash 到新的扩容数组中,如果在多线程环境下,存在其他元素同时进⾏put 操作,hash 值相同时,可能出现同时在同⼀数组下⽤链表表⽰,造成闭环,导致在get 时会出现死循环.
hashTable 是线程安全的,但是他会在所有设计多线程的地⽅都有synchronized 关键字锁住整个table ,但是这个⽅式⽆疑使的效率⼤⼤降低,在多线程的环境下,对不同的数据集进⾏操作时其实根本就不需要去竞争⼀个锁,因为他们不同hash值,不会因为rehash造成线程不安全,所以互不影响,这就是锁分离技术.⽽ConcurrentHashMap 就是采⽤的这种分段锁的⽅式对hashTable 进⾏优化的.
JDK1.7 ConcurrentHashMap 的实现
在1.7 时,ConcurrentHashMap 的数据结构是由⼀个Segment 数组(锁的粒度)和多个HashEntry (数组+链表)组成
Segment 数组的意义就是将⼀个⼤的table 分割成多个⼩的table 来进⾏加锁,也就是上⾯说的锁分离技术,⽽每⼀个Segment 元素存储的是HashEntry 数组+链表,这个和HashMap 存储结构⼀样.
初始化
通过位与运算来初始化Segment 的⼤⼩,⽤size 来表⽰.
int
Segment 的⼤⼩默认是16,取值都是以2的N次⽅表⽰,最⼤65536.
每⼀个Segment 元素下的HashEntry 的初始化也是按照位与运算来计算,⽤cap 表⽰.
int
HashEntry ⼤⼩的计算也是2的N次⽅,cap 的初始值为1,所以HashEntry 最⼩的容量为2.
put 操作
put操作时,会进⾏第⼀次key的hash来定位Segment的位置,如果该Segment还没有初始化,即通过CAS操作进⾏赋值,然后进⾏第⼆次hash操作,到相应的HashEntry的位置,这⾥会利⽤继承过来的锁的特性,在将数据插⼊指定的HashEntry位置时(链表的尾端),会通过继承ReentrantLock的tryLock()⽅法尝试去获取锁,如果获取成功就直接插⼊相应的位置,如果已经有线程获取该Segment的锁,那当前线程会以⾃旋的⽅式去继续的调⽤tryLock()⽅法去获取锁,超过指定次数就挂起,等待唤醒.
JDK1.8 的实现
⽤Node数组+HashEntry(锁的粒度,⾸节点,数组+链表+红⿊树(提⾼查询效率))的数据结构来实现,并发控制使⽤Synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap,虽然在JDK1.8中还能看到Segment的数据结构,但是已经简化了属性,只是为了兼容旧版本.
put 操作
1.如果没有初始化就先调⽤initTable()⽅法来进⾏初始化过程
2.如果没有hash冲突就直接CAS(⽐较)插⼊
3.如果还在进⾏扩容操作就先进⾏扩容
4.如果存在hash冲突,就加锁来保证线程安全,这⾥有两种情况,⼀种是链表形式就直接遍历到尾端插⼊,⼀种是红⿊树就按照红⿊树结构插⼊,
5.最后⼀个如果该链表的数量⼤于阈值8,就要先转换成⿊红树的结构,break再⼀次进⼊循环
6.如果添加成功就调⽤addCount()⽅法统计size,并且检查是否需要扩容感谢您的阅读,希望能对你有所收获,如果有写的不对的地⽅,可以留⾔.
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论