1.集合和数组的区别
1.数组长度是固定的,集合长度是可变的
2.数组可以是基本数据类型也可以是引用数据类型,集合只能是引用数据类型
3.数组只能存储同一种数据类型,集合可以存储不同的数据类型
2.Collection集合中常见的方法
boolean add(E,e) 在集合末尾添加元素
boolean remove(Object o) 若集合中的值有与o相等的值,删除该元素,并返回true
bolean contains(Object o) 判集合中是否保函某元素
boolean isEmpty 判断集合是否为空
boolean addAll(Collection c) 将一个类集c中的元素添加到另一个类集
int size() 返回集合中的元素个数
object[ ] toArray( )返回一个包含集合中所有元素的数组,类型为object
Intreator interator( ) 迭代器 ,集合中专用的遍历方式
clear() 清除集合中所有元素
3.常用集合的分类
1.Collection接口下的集合(单列集合)
List集合接口:元素按照进入先后有序保存,可重复
ArrayList:底层数据结构是数组,查询快,增删慢,线程不安全,效率高,可以存储重复元素
LinkedList 底层数据结构是链表,查询慢,增删快,线程不安全,效率高,可以存储重复元素
Vector:底层数据结构是数组,查询快,增删慢,线程安全,效率低,可以存储重复元素
常用方法:
void add(int index,object obj) 在指定位置添加元素
Object remove (int index) 根据指定索引删除元素,并把删除的元素返回
Object set(int index,object obj) 把指定索引位置的元素修改为指定的值返回修改前的值
Object get(int index) 获取指定索引位置的元素
Set集合 接口: 仅接收一次,不可重复,并做内部排序
HashSet 使用hash表(数组)存储元素元素无序且唯一,线程不安全,效率高,可以存 储null元素,唯一性是靠所存储元素类型是否重写hashCode()和equals()方法来保证的
实现唯一性的过程:
存储元素会使用Hash算法生成一个HashCode值,和所存储元素的HashCode值比较
如果不相等,则存储的两个对象一定不相等,此时存储当前的新的hashCode值处的 元素对象
如果HashCode值相等,也不一定相等,会调用equals()方法判断两个对象的内容是 否相等 如果内容相等,那么就是同一个对象,无需存储,如果比较的内容不相等, 那么就是不同的对象,就该存储,采用哈希的解决地址冲突算法,在当前 hashCode值处类似一个新的链表, 在同一个hashCode值的后面存储存储不同的对象,这样就保证了元素的唯一性。
LinkedHashSet :底层数据结构采用链表和哈希表共同实现。链表保证了元素的顺序与存储顺序一致,哈希表保证了元素的唯一性,线程不安全,效率高。
TreeSet 底层数据结构采用二叉树来实现,元素唯一且已经排好序;唯一性同样需要重写hashCode和equals()方法,二叉树结构保证了元素的有序性
List和Set总结:
List特点:元素有放入顺序,元素可重复可以动态增长,查找元素效率高,插入删除元 素效率低
Set特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉,检索元素效率低下,删除 和插入效率高,插入和删除不会引起元素位置改变
Map集合(双列集合)
Map用于保存具有映射关系的数据key和value,都可以使任何引用类型的数据,但key不能重复。所以通过指定的key就可以取出对应的value
Map集合常用方法:
boolean containsKey(Object key) 查询Map集合中是否包含指定的Key,如果包含就返回true
boolean containsValue(Object value)查询Map集合中是否包含指定值,如果包含就返回true
boolean isEmpty()查询map集合是否为空,为空返回true
boolean put(Object key, Object value)添加一个键值对,如果已有key值就覆盖掉
boolean remove(Object key)删除指定key对应的键值对,返回可以关联的值,没有就为null
Object get(Object value) 返回指定Key所对应的value,如果不包含就为null
Int size() 返回该map里的键值对个数
Set Key( ) 返回map中所有key组成的set集合
void putAll( Map m)将指定map集合中的键值对复制到map集合
HashMap和HashTable的区别
1.线程安全:
HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过 synchronized 修饰
2.效率:
因为线程安全的问题,HashMap 要比 HashTable 效率高(HashTable 基本被淘汰,保证线 程安全的话就使用 ConcurrentHashMap)
3.对Null key 和Null value的支持:
HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null
HashTable 中 put 进的键值只要有一个 null,直接抛NullPointerException
4.初始容量大小和每次扩充容量大小的不同:
HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍
Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1.
5.底层数据结构:
java7 是数组+链表(头插法) java8是数组+链表+红黑树(当链表长度大于8进行树化)
HashMap的工作原理
HashMap是基于hashing的原理,使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象
Hashmap中put()方法工作原理:
java7
1.put方法会去判断hashmap集合是否为空或者长度是否为0,如果是则调用 inflateTable()方法对hashmap进行初始化
2.判断key是否为空,为空则遍历以table下表0为首的链表,寻找是否存在key==null的键值对,若存在就覆盖旧值并返回旧值,若不存在就调用addEntry()进行插入
3.若key不为空,根据key计算数组索引值,循环链表,判断key是否存在,存在就覆盖旧值并返回
4.addEntry()先判断是否扩容,如果要扩容就进行扩容,不用扩容就生成Entry对象,使用头插法添加到当前链表中
java8
1.put()方法判断是否为空或者长度是否为0,如果是就进行初始化,调用resize进行扩容
2.put方法根据key通过hash算法得到数组下标
3.如果数组下标元素为空,将key和value封装成node<k,v>直接放入
4.如果下标元素不为空:
a.判断数组上该位置key是否相同,相同就进行覆盖并返回旧值
b.不相同就判断该节点上node的类型,判断是红黑树还是链表
c.如果是红黑树,将key和value封装成红黑树节点添加到树中,添加过程会判 断是否存在当前key,存在就更新value
d.如果是链表节点,将key和value封装成一个链表node通过尾插法插入到链表的最后位置,因为是尾插法,所以要遍历链表,遍历过程中判断是否存在当前key,如果存在就更新value,遍历完后将新链表node插入到链表中,插入后,如果链表姐节点大于等于8并且数组长度大于64则会将链表转化为红黑树
java7和Java8中hashmap中put方法区别
初始化方式:
java7采用InflateTable()
java采用resize()
底层数据结构:
java7采用数组加链表,无hash冲突放数组,有冲突存单链表
java8采用数组+链表+红黑树,无hash冲突放数组,有冲突&链表长度<8,存单链表
链表长度>8&数组长度>64存红黑树
插入数据方式:
java7头插法
java8尾插法
Hashmap中get()方法原理
JAVA7
1.get方法先判断key是否为空,为空就调用getForNullKey方法
a.首先判断Hashmap是否为空,为空就直接返回null
b.遍历以table下标0为头节点的链表,寻找key=null对应的目标节点,找到就直接返回目标节点对应的值,找不到就返回null
2.如果key不为空就调用getEntry()方法
a.首先判断Hashmap是否为空,为空就直接返回null
b.根据key计算hash值,根据hash值计算对应的索引下标,遍历以该数组下标为头节点的链表的所有节点,寻找该key对应的目标节点,找到就直接返回,找不到就直接返回null
JAVA8 getNode方法
1.首先根据key计算hash值得到在数组中的索引位置,判断node数组以及传入对象对应的索引位置的桶是否为空,为空就返回null
2.不为空就判断传入对象生成的hash值和key值与该桶第一个元素的是否相同,是就返回该桶第一个元素,不相同就继续判断该桶是否是红黑树
a.是就遍历红黑树,判断是否有key相同的目标节点,存在就返回目标节点,不存在就返回null值
b.不是红黑树就是链表,遍历链表,判断是否有key和hash值相同的目标节点,存在就返回目标节点,不存在就返回null值
Hashmap的扩容机制
JAVA7
扩容条件:元素的个数>=阈值并且当前数组节点不为空
1.先生成长度是老数组2倍的新数组
2.遍历老数组中桶的每个元素
3.根据key计算新数组的索引下标
4.按照索引将元素添加到新数组
5.所有元素转移完后,将新数组赋值给Hashmap的table属性
注:java7在多线程环境下扩容会产生链表成环的问题,JAVA8采用尾插法&链表重新链接解决了此问题
JAVA8
1.先生成长度是老数组2倍的新数组
2.遍历老数组中桶的每个元素
3.如果桶节点没有形成链表,计算出新数组的索引位置,直接转移到新数组
4.已经形成链表,将链表重新链接,按照低位区和高位区重新分配到新数组
5.如果桶节点已经树化,
调用split方法将红黑树重新分为低位区和高位区2个链表
判断低位区和高位区链表的长度,链表长度小于6则会进行取消树化的处理,否则就会将新生成的链表树化
6.所有元素转移完后,将新数组赋值给Hashmap的table属性
ConcurrentHashmap
1.什么是ConcurrentHashmap?
ConcurrentHashmap是属于JUC包下的一个集合类,可以实现线程安全
2.数据结构:和Hashmap基本类似,数组+链表+红黑树
区别:
ConcurrentHashmap内部在数据写入时加入了同步机制(分段锁)保证线程安全,读是无锁
ConcurrentHashmap的扩容是多并发执行的,扩容效率更高,使用于多并发场景
3.安全并发访问:
JAVA7利用 Unsafe操作+ReentrantLock+分段思想
1.采用分段锁技术将哈希表分成多个段,每个段有一个可重入锁,可以在多个线程访问时,只需要锁住需要操作的那个表,提高并发性能
注:这种方式可以减少锁竞争,在高并发下,仍然会出现竞争
JAVA8利用 Unsafe操作+Synchronized关键字+分段思想
1.使用CAS+synchronized来保证线程安全,简单的读写操作,用CAS操作+自旋锁来修改,复杂的流程则使用synchronized来同步修改,可以避免分段锁机制下的锁的粒度太大,以及高并发场景下线程数量过多导致的锁竞争问题,提高并发性能
Hashmap为什么线程不安全?
Hashmap是基于哈希表实现的,通过哈希算法保证元素的一致性,但在多线程下可能会出现以下问题:
1.多线程下扩容死循环:
JAVA7中采用的是头插法,在多线程的环境下进行扩容可能导致环形链表的出现,形成死循环,JAVA8采用尾插法,扩容时会保证链表元素的顺序
2.多线程下的put可能会导致元素丢失:
多线程同时执行put操作,若计算出来的索引位置相同,回导致前面的key被后面的key覆盖的问题,导致元素的丢失
3.put和get并发时可能导致get为null:
线程1执行put时因为元素个数超阈值进行扩容,线程2此时执行get可能导致这个问题
Hashset和Hashmap的区别
1.Hashmap实现了map接口 Hashset实现了set接口
仅存储键值对 仅存储对象
调用put方法添加元素 调用add方法添加元素
使用key计算hashcode 使用成员对象计算hsahcode,用equals来判断相 等性