集合 collection
集合框架结构图
集合的概念
1.集合类是用来存放某类对象的。集合类有一个共同特点:就是它们只容纳对象
(实际上是对象名,即指向地址的指针)
2.这一点和数组不同,数组可以容纳对象和简单数据
3.集合类容纳的对象都是Object类的实例,一旦把一个对象置入集合类中,它的类信息将丢失,
也就是说,集合类中容纳的都是指向Object类对象的指针
4.这样的设计是为了使集合类具有通用性,因为Object类是所有类的祖先
5.数组的长度是不可以变的,数组只能存放同一种类型的对象(或者说对象的引用)
6.常用的数据结构还有:队列、链接表、树和散列表等等
7.Java提供了对象的数种保存方式,除了内置的数组以外,其余的称为集合类
8.JDK中提供了Java集合类,所有Java集合类都位于Java.util包中
9.Java集合不能存放基本数据类型数据,而只能存放对象的引用
集合与数组的区别
1.数组中的元素可以是基本数据类型,也可以是对象(实际是对象的引用变量)
2.集合中只能保存对象(实际上也是保存对象的引用变量
3.数组的长度是固定的,
4.集合长度可变
5..数组只能通过下标访问数组元素,且类型固定
6.有的集合可以通过任意类型查找所映射的具体对象
常用的集合类型
Collection接口
Collection接口
父Collection:中可以存储的元素间无序,可以重复组各自独立的元素
每个位置仅持有一个元素,同时允许有多个null元素对象
子List:有序(元素存入集合的顺序和取出的顺序一致),元素都有索引,允许重复
子Set:无序(存入和取出顺序有可能不一致),不允许重复,必须保证元素唯一性
主要方法:
add(E);添加元素
remove(object);删除元素
toArray() 和toArray([])将集合转成数组
Iterator iterator():获取集合中元素上迭代功能的迭代器对象
Iterator中只有hasNext()判断是否有下一个元素
next()取出下一个元素
remove() 删除元素
hashCode():计算得到哈希值
List接口
List本身是Collection接口的子接口,具备了Collection的所有方法!
List 体系特有的共性方法都有索引(角标)
List的特有方法都是围绕索引(角标)定义的
List:有序(元素存入集合顺序和取出一致),元素都有索引允许重复元素
自定义元素类型都要复写equals方法
ArrayList:底层的也是数组结构,也是长度可变的,可以理解为长度可变的数组
线程不同步的,替代了Vector。增删速度不快
查询速度很快。(因为在内存中是连续空间)
ArrayList的数据结构为线性表
LinkedList:底层的数据结构是链表,线程不同步的。增删速度很快
查询速度较慢。(因为在内存中需要一个个查询、判断地址来寻找下一元素)
Vector:底层的数据结构是数组。数组是可变长度的。线程同步,增删和查询慢
可变长度数组的原理: 不断地new 新数组并将原数组元素复制到新数组,
ArrayList是通过原始容量*3/2+1
List集合支持对元素的增、删、改、查
1.添加(增):
add(index, element):在指定的索引位插入元素。
addAll(index, collection):在指定的索引位插入一堆元素。
2.删除(删):
remove(index):删除指定索引位的元素。 返回被删的元素。
3.修改(改):
element set(index, newElement):对指定索引位进行元素的修改
4.获取(查):
element get(index):通过索引获取指定元素
int indexOf(element):获取指定元素第一次出现的索引位,不存在返回—1
int lastIndexOf(element) :反向索引指定元素的位置
List subList(start,end) :获取子列表
5.获取所有元素(查全部)
ListIterator listIterator():list集合特有的迭代器
ListIterator这个列表迭代器接口具备了对元素的增、删、改、查的动作
Iterator中只有hasNext,next,remove方法
ArrayList的contains方法底层使用的equals方法判别的
自定义元素类型中必须复写Object的equals方法
往集合里面存储自定义元素,该元素所属类一定要重写equals、toString方法
遍历ArrayList集合
//方式1:使用foreach遍历list
List<String> list=new ArrayList<String>();
for (String str:list){
System.out.println(str);
}
//方式2 把列表变为数组相关的内容进行遍历
List<String> list=new ArrayList<String>();
String[] strArray=new String[list.size()];
list.toArray(strArray);
for(int i=0;i<strArray.length;i++){
System.out.println(strArray[i]);
}
//方式3 使用迭代器进行遍历
List<String> list=new ArrayList<String>();
Iterator<String> ite=list.iterator();
while(ite.hasNext())
{
System.out.println(ite.next());
}
LinkedList 链表
List接口的链表实现,底层是一个双向循环链表
可以利用LinkedList实现堆栈、队列这两个数据结构
LinkedList数据结构是链表。链表数据结构的特点是每个元素分配的空间不必连续、
插入和删除元素时速度非常快、但访问元素的速度较慢
特有方法:
addFirst();在链表头部添加一个元素
addLast();在链表尾部添加一个元素
removeFirst():获取链表中的第一个元素,并删除链表中的第一个元素
如果链表为空,抛出NoSuchElementException
removeLast();
pollFirst();获取链表中的第一个元素,并删除链表中的第一个元素链表为空
如果链表为空,返回null
pollLast();
getFirst():获取链表中的第一个元素链表为空,NoSuchElementException
getLast();
peekFirst();获取链表中的第一个元素,如果链表为空,返回null
peekLast()
内部结构:
LinkedList类每个结点用内部类Node表示,
LinkedList通过first和last引用分别指向链表的第一个和最后一个元素,
当链表为空时,first和last都为NULL值
Node节点一共有三个属性:
item代表节点值,
prev代表节点的前一个节点,
next代表节点的后一个节点。
每个结点都有一个前驱和后继结点
Set接口
java.util.Set接口,一个不包含重复元素的 collection,最多包含一个 null 元素
Set:不允许重复元素。和Collection的方法相同,取出方法只有一个:迭代器
HashSet
底层数据结构是哈希表(散列表)。无序,比数组查询的效率高,线程不同步,为了保证哈希表中元素的唯一性,
容器中存储元素所属类应该复写Object类的hashCode、equals方法。
哈希表就是存储哈希值的结构。HashSet接口不保证 set 的迭代顺序特别是它不保证该顺序恒久不变。
此类允许使用null 元素,堆内存的底层实现就是一种哈希表结构,需要通过哈希算法来计算对象在该结构中
存储的地址每个对象都具备,叫做hashCode()方法,
hashCode()方法本身调用的是windows系统本地的算法,可以自己定义
LinkedHashSet
LinkedhashSet:有序,HashSet的子类
哈希表的原理
1.对对象元素中的关键字(对象中的特有数据),进行哈希算法的运算
并得出一个具体的算法值,这个值称为哈希值
2.哈希值就是这个元素的位置(内存地址)
3.如果哈希值出现冲突,再次判断这个关键字对应的对象是否相同
如果对象相同,就不存储,因为元素重复,
如果对象不同,就存储,在原来对象的哈希值基础 +1顺延
4.存储哈希值的结构,我们称为哈希表
5.既然哈希表是根据哈希值存储的,为了提高效率,最好保证对象的关键字
是唯一的,可以尽量少的判断关键字对应的对象是否相同
提高了哈希表的操作效率
哈希表的特点:
1.不允许存储重复元素,因为会发生查找的不确定性
2.不保证存入和取出的顺序一致,即不保证有序
3.比数组查询的效率高
哈希冲突:
当哈希算法算出的两个元素的值相同时,称为哈希冲突
如何保证哈希表中元素的唯一性?
元素必须重写hashCode() 方法 和 equals() 方法
//重写hashCode() 方法是为了根据元素自身的特点确定哈希值。
public int hashCode(){
final int NUMBER = 31;
return name.hashCode()+ age*NUMBER;
}
//重写equals() 方法,是为了解决哈希值的冲突
public boolean equals(Object obj){
if (this == obj)
return true;
if(!(obj instanceof Student))
throw new ClassCastException(obj.getClass().getName()+"类型错误");
Student stu = (Student)obj;
return this.name.equals(stu.name) && this.age == stu.age;
}
}
TreeSet
底层是自平衡的二叉树结构,可以对Set集合的元素按照指定规则进行排序,线程不同步,
add方法新添加元素必须可以同容器已有元素进行比较,
所以元素所属类应该实现Comparable接口的compareTo() 方法,
以完成排序或者添加Comparator比较器,实现compare() 方法
排序和有序是两个不同的概念有序:
存入和取出的顺序一致。--> List
排序:升序or降序。--> TreeSet
往TreeSet集合中存入的是自定义元素:
元素自身具备比较功能元素需要实现Comparable接口,并重写compareTo()方法,
普通的自定义类不具备排序的功能,所以元素要实现Comparable接口,并重写compareTo()方法,
强制让元素具备比较性,
TreeSet第一种排序方式:
需要元素具备比较功能元素需要实现Comparable接口。重写compareTo()方法
TreeSet第二种排序方式:
需要集合具备比较功能定义一个比较器。所以要实现java.util.Comparator<T>接口,
重写compare()方法将Comparator接口的对象,作为参数传递给TreeSet集合的构造函数
Collection集合各个具体实现类的名字特征
前缀名是数据结构名,后缀名是所属体系名:
ArrayList:数组结构。看到数组,就知道查询快,看到List,就知道可以重复。可以增删改查
LinkedList:链表结构,增删快。xxxFirst、xxxLast方法,xxx:add、get、remove
HashSet:哈希表,查询速度更快,就要想到唯一性、元素必须覆盖hashCode()、equals()。
看到Set,就知道不可以重复,不保证有序
LinkedHashSet:链表+哈希表。可以实现有序(有点特殊),因为有链表。但保证元素唯一性
TreeSet:二叉树,可以排序。就要想到两种比较方式(两个接口):
一种是自然排序Comparable,一种是比较器Comparator
Map<K, V>接口
java.util.Map<K,V>接口,将键映射到值的对象。一个映射不能包含重复的键
每个键最多只能映射到一个值。要保证键的唯一性-->Set。值可以重复-->Collection
Map:双列集合,一次存一对,键值对
|--HashMap:底层是哈希表数据结构,是线程不同步的,允许存储null键,null值
|--TreeMap:底层是二叉树结构,线程不同步的。可以对map集合中的键进行指定顺序的排序
|--Hashtable:底层是哈希表数据结构,是线程同步的,不允许存储null键,null值
|--Properties:用来存储键值对型的配置文件的信息,可以和IO技术相结合
HashSet、TreeSet的底层是用HashMap、TreeMap实现的,只操作键,就是Set集合
在Java集合框架中,线程同步的只用Hashtable和Vector两个
Map集合存储和Collection有着很大不同
1.Collection一次存一个元素,而Map一次存一对元素
2.Collection是单列集合,而Map是双列集合
3.Map中的存储的一对元素:一个是键,一个是值,键与值之间有对应(映射)关系
Map接口中的共性方法:
1.添加: 会改变集合长度
v put(key, value):当存储的键相同时,新的值会替换老的值并将老值返回。
如果键没有重复,返回null
putAll(Map<k,v> map);
2.删除: 会改变集合长度
v remove(key):删除指定键
void clear():清空
3.判断:
boolean containsKey(Object key):是否包含key键
boolean containsValue(Object value):是否包含value值
boolean isEmpty();是否为空
4.取出:
v get(key):通过指定键获取对应的值。如果返回null,可以判断该键不存在
特殊情况:在Hashmap集合中,是可以存储null键null值的i
5.长度
nt size():返回长度
6.获取Map中的所有元素
原理:map中是没有迭代器,collection具备迭代器,将map集合转成Set集合,可以使用迭代器了
转成set,是因为map集合具备着键的唯一性,
set集合就来自于map,set集合底层其实用的就是map的方法。
方式1: Set keySet();
可以将map集合中的键都取出存放到set集合中。
对set集合进行迭代迭代完成,
再通过get()方法对获取到的键进行值的获取
方式2: Set entrySet();
取的是键和值的映射关系。
Map.Entry是一个Map接口中的内部接口
entry是访问键值关系的入口,是map的入口,访问的是map中的键值对
//方式1 Set keySet();
Set keySet = map.keySet();
Iterator it = keySet.iterator();
while(it.hasNext()) {
Object key = it.next();
Object value = map.get(key);
System.out.println(key+":"+value);
}
//方式2 Set entrySet();
Set entrySet = map.entrySet();
Iterator it = entrySet.iterator();
while(it.hasNext()) {
Map.Entry me = (Map.Entry)it.next();
System.out.println(me.getKey()+"::::"+me.getValue());
}
HashMap
HashMap:key-value的形式存取数据,使用put方法存数据,get方法取数据。
Map<String, String> hashMap = new HashMap<String, String>();
hashMap.put("name", "josan");
String name = hashMap.get("name");
HashMap继承了Map接口,实现了Serializable等接口
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable{.................}
HashMap的数据是存在table数组中的,它是一个Entry数组,Entry是HashMap的一个静态内部类
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
Entry其实就是封装了key和value
有一个类型为Entry的next,它是用于指向下一个Entry的引用
所以table中存储的是Entry的单向链表
构造方法:
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
initialCapacity是HashMap的初始化容量(即初始化table时用到),默认为16
loadFactor为负载因子,默认为0.75
threshold是HashMap进行扩容的阀值,当HashMap的存放的元素个数超过该值时,会进行扩容
它的值为HashMap的容量乘以负载因子。比如,HashMap的默认阀值为16*0.75,即12
put方法:
根据key来算得hash值,得到hash值以后,调用indexFor方法,去算出当前值在table数组的下标
static int indexFor(int h, int length) {
return h & (length-1);
//相当于h%(lenght-1)
//h为hash值,length为HashMap的当前长度。而&是位运算,效率要高于%
//length为2的幂次方,即一定是偶数,偶数减1,即是奇数,
//这样保证了(length-1)在二进制中最低位是1
//而&运算结果的最低位是1还是0完全取决于hash值二进制的最低位。
//如果length为奇数,则length-1则为偶数
//则length-1二进制的最低位横为0,则&位运算的结果最低位横为0
//table数组就只可能在偶数下标的位置存储了数据,浪费了所有奇数下标的位置
//也更容易产生hash冲突
//也是HashMap的容量为什么总是2的平方数的原因
}
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
// 先判断hash值是否一样,如果一样,再判断key是否一样
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
先取出table中下标为i的Entry
然后判断该Entry的hash值和key是否和要存储的hash值和key相同,
如果相同,则表示要存储的key已经存在于HashMap,
只需要替换已存的Entry的value值即可
如果不相同,则取e.next继续判断,其实就是遍历table中下标为i的Entry单向链表
找是否有相同的key已经在HashMap中
如果有,就替换value为最新的值,所以HashMap中只能存储唯一的key
扩容就是先创建一个长度为原来2倍的新table
然后通过遍历的方式,将老table的数据,重新计算hash并存储到新table的适当位置
最后使用新的table,并重新计算HashMap的扩容阀值
get方法
1.最简单粗暴的方式肯定是遍历table,并且遍历table中存放的单向链表,时间复杂度就是O(n的平方)
2.可以用key的hash值算出key对应的Entry所在链表在在table的下标,时间复杂度降低到O(n)
entrySet取数据:
遍历HashMap的方式取数据
Map<String, String> hashMap = new HashMap<String, String>();
hashMap.put("name1", "josan1");
hashMap.put("name2", "josan2");
hashMap.put("name3", "josan3");
Set<Entry<String, String>> set = hashMap.entrySet();
Iterator<Entry<String, String>> iterator = set.iterator();
while(iterator.hasNext()) {
Entry entry = iterator.next();
String key = (String) entry.getKey();
String value = (String) entry.getValue();
System.out.println("key:" + key + ",value:" + value);
}
remove方法
根据key移除HashMap中对应的Entry
先根据key算出hash,然后根据hash得到在table上的index,再遍历talbe[index]的单向链表
线程安全问题:
HashMap的put和get方法真实操作的都是Entry[] table这个数组
所有操作都没有进行同步处理,所以HashMap是线程不安全的
想要实现线程安全,推荐使用ConcurrentHashMap
TreeMap
TreeMap 是一个有序的key-value集合,它是通过红黑树实现的.
根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序
继承于AbstractMap,所以它是一个Map,即一个key-value集合
实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合
实现了Cloneable接口,意味着它能被克隆
实现了java.io.Serializable接口,意味着它支持序列化
基本操作 containsKey、get、put 和 remove 的时间复杂度是 log(n)
TreeMap的本质是R-B Tree(红黑树),它包含几个重要的成员变量: root, size, comparator
root 是红黑数的根节点。它是Entry类型,Entry是红黑数的节点,它包含了红黑数的6个基本组成成分:
key(键)、value(值)、left(左孩子)、right(右孩子)、parent(父节点)、color(颜色)
Entry节点根据key进行排序,Entry节点包含的内容为value
红黑数排序时,根据Entry中的key进行排序
,Entry中的key比较大小是根据比较器comparator来进行判断的
size是红黑数中节点的个数
TreeMap是通过红黑树实现的,TreeMap存储的是key-value键值对,TreeMap的排序是基于对key的排序。
红黑树的特性:
1.每个节点或者是黑色,或者是红色。
2.根节点是黑色。
3.每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
4.如果一个节点是红色的,则它的子节点必须是黑色的。
5.从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
主要是用它来存储有序的数据,它的时间复杂度是O(lgn),效率非常之高
红黑树的基本操作(一) 左旋和右旋
旋转的目的是让树保持红黑树的特性
左旋:对x进行左旋,意味着"将x变成一个左节点"。
x进行左旋,意味着,将“x的右孩子”设为“x的父亲节点
被旋转的节点将变成一个左节点
右旋:对x进行 右旋,意味着"将x变成一个 右节点"。
对x进行右旋,意味着,将“x的左孩子”设为“x的父亲节点
被旋转的节点将变成一个右节点
左旋 和 右旋 是相对的两个概念,原理类似
红黑树的基本操作(二) 添加
1.将红黑树当作一颗二叉查找树,将节点插入
2.将插入的节点着色为"红色
将插入的节点着色为红色,不会违背"特性(5)"!少违背一条特性,意味着需要处理的情况越少
3.通过一系列的旋转或着色等操作,使之重新成为一颗红黑树
1.情况说明:被插入的节点是根节点。
处理方法:直接把此节点涂为黑色。
2.情况说明:被插入的节点的父节点是黑色。
处理方法:什么也不需要做。节点被插入后,仍然是红黑树。
3.情况说明:被插入的节点的父节点是红色
1.当前节点的父节点是红色,且当前节点的祖父节点
的另一个子节点(叔叔节点)也是红色。
将“父节点”设为黑色。
将“叔叔节点”设为黑色。
将“祖父节点”设为“红色”。
将“祖父节点”设为“当前节点”,之后继续对“当前节点”进行操作
2.当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的右孩子
将“父节点”作为“新的当前节点”
以“新的当前节点”为支点进行左旋
3.当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的左孩子
将“父节点”设为“黑色”
将“祖父节点”设为“红色”
以“祖父节点”为支点进行右旋
核心思路都是:将红色的节点移到根节点;然后,将根节点设为黑色
红黑树的基本操作(三) 删除
1.将红黑树当作一颗二叉查找树,将节点删除。
被删除节点没有儿子,即为叶节点。那么,直接将该节点删除就OK了
被删除节点只有一个儿子。那么,直接删除该节点,并用该节点的唯一子节点顶替它的位置
2.通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树
TreeMap的构造函数
1.默认构造函数
使用默认构造函数构造TreeMap时,使用java的默认的比较器比较Key的大小,
从而对TreeMap进行排序
2.带比较器的构造函数
3.带Map的构造函数,Map会成为TreeMap的子集
会调用putAll()将m中的所有元素添加到TreeMap中
将m中的key-value逐个的添加到TreeMap中
4.带SortedMap的构造函数,SortedMap会成为TreeMap的子集
SortedMap是一个有序的Map
通过buildFromSorted()来创建对应的Map
TreeMap的Entry相关函数
firstEntry()、 lastEntry()、
都是用于获取第一个节点
firstEntry() 是对外接口; getFirstEntry() 是内部接口
firstEntry() 是通过 getFirstEntry() 来实现的
防止用户修改返回的Entry。getFirstEntry()返回的Entry是可以被修改的
经过firstEntry()返回的Entry不能被修改,只可以读取Entry的key值和value值
getFirstEntry()返回的是Entry节点,而Entry是红黑树的节点
可以调用Entry的getKey()、getValue()来获取key和value值
调用setValue()来修改value的值
lowerEntry()、 higherEntry()、
floorEntry()、 ceilingEntry()、
pollFirstEntry() 、 pollLastEntry()
TreeMap的key相关函数
firstKey()、lastKey()、lowerKey()、higherKey()、floorKey()、ceilingKey()
ceilingKey(K key)的作用是“返回大于/等于key的最小的键值对所对应的KEY,没有的话返回null”
ceilingKey()是通过getCeilingEntry()实现的
getCeilingEntry(K key)的作用是“获取TreeMap中大于/等于key的最小的节点
若不存在(即TreeMap中所有节点的键都比key大),就返回null
TreeMap的values()函数:
values() 返回“TreeMap中值的集合
Values继承于AbstractCollection
AbstractCollection 实现了除 size() 和 iterator() 之外的其它函数
Values集合中元素的个数=该TreeMap的元素个数
TreeMap的entrySet()函数
entrySet() 返回“键值对集合”,集合的元素是“键值对”
entrySet()返回的是一个EntrySet对象
EntrySet是“TreeMap的所有键值对组成的集合”,而且它单位是单个“键值对”
EntrySet是一个集合,它继承于AbstractSet
TreeMap其它函数
1 顺序遍历和逆序遍历
TreeMap中的元素是从小到大的顺序排列的
顺序遍历,就是从第一个元素开始,逐个向后遍历
倒序遍历则恰恰相反,它是从最后一个元素开始,逐个往前遍历
keyIterator()的作用是返回顺序的KEY的集合,
descendingKeyIterator()的作用是返回逆序的KEY的集合
TreeMap遍历方式
1.遍历TreeMap的键值对
1.根据entrySet()获取TreeMap的“键值对”的Set集合
2.通过Iterator迭代器遍历“第一步”得到的集合
// 假设map是TreeMap对象
2.遍历TreeMap的键
1.根据keySet()获取TreeMap的“键”的Set集合
2.通过Iterator迭代器遍历“第一步”得到的集合
3.遍历TreeMap的值
1.根据value()获取TreeMap的“值”的集合
2.通过Iterator迭代器遍历“第一步”得到的集合
// map中的key是String类型,value是Integer类型
Integer integ = null;
Iterator iter = map.entrySet().iterator();
while(iter.hasNext()) {
Map.Entry entry = (Map.Entry)iter.next();
// 获取key
key = (String)entry.getKey();
// 获取value
integ = (Integer)entry.getValue();
}
// 假设map是TreeMap对象
// map中的key是String类型,value是Integer类型
String key = null;
Integer integ = null;
Iterator iter = map.keySet().iterator();
while (iter.hasNext()) {
// 获取key
key = (String)iter.next();
// 根据key,获取value
integ = (Integer)map.get(key);
}
// 假设map是TreeMap对象
// map中的key是String类型,value是Integer类型
Integer value = null;
Collection c = map.values();
Iterator iter= c.iterator();
while (iter.hasNext()) {
value = (Integer)iter.next();
}
Hashtable
Hashtable 是一个散列表,它存储的内容是键值对(key-value)映射。继承于Dictionary
实现了Map、Cloneable、java.io.Serializable接口
Hashtable 的函数都是同步的,这意味着它是线程安全的
它的key、value都不可以为null,Hashtable中的映射不是有序的
Hashtable中的key-value都是存储在table数组中的
private transient Entry[] table
数据节点Entry的数据结构
Entry 实际上就是一个单向链表
Entry 实现了Map.Entry 接口
Hashtable的构造函数
1.默认构造函数,指定的容量大小是11;加载因子是0.75
2.指定“容量大小”的构造函数
3. 指定“容量大小”和“加载因子”的构造函数
4. 包含“子Map”的构造函数
Hashtable的主要对外接口:
1.clear() 的作用是清空Hashtable。它是将Hashtable的table数组的值全部设为null
2.contains() 和 containsValue() 的作用都是判断Hashtable是否包含“值(value)”
3.containsKey() 的作用是判断Hashtable是否包含key
4.elements() 的作用是返回“所有value”的枚举对象
5.通过put()将“key-value”添加到Hashtable中
6.putAll() 的作用是将“Map(t)”的中全部元素逐一添加到Hashtable中
7.remove() 的作用就是删除Hashtable中键为key的元素
Hashtable遍历方式:
1.遍历Hashtable的键值对
1.根据entrySet()获取Hashtable的“键值对”的Set集合
2.通过Iterator迭代器遍历“第一步”得到的集合
// 假设table是Hashtable对象
// table中的key是String类型,value是Integer类型
Integer integ = null;
Iterator iter = table.entrySet().iterator();
while(iter.hasNext()) {
Map.Entry entry = (Map.Entry)iter.next();
// 获取key
key = (String)entry.getKey();
// 获取value
integ = (Integer)entry.getValue();
}
2.通过Iterator遍历Hashtable的键
1.根据keySet()获取Hashtable的“键”的Set集合
2.通过Iterator迭代器遍历“第一步”得到的集合
// 假设table是Hashtable对象
// table中的key是String类型,value是Integer类型
String key = null;
Integer integ = null;
Iterator iter = table.keySet().iterator();
while (iter.hasNext()) {
// 获取key
key = (String)iter.next();
// 根据key,获取value
integ = (Integer)table.get(key);
}
3.通过Iterator遍历Hashtable的值
1.根据value()获取Hashtable的“值”的集合
2.通过Iterator迭代器遍历“第一步”得到的集合
// 假设table是Hashtable对象
// table中的key是String类型,value是Integer类型
Integer value = null;
Collection c = table.values();
Iterator iter= c.iterator();
while (iter.hasNext()) {
value = (Integer)iter.next();
}
4.通过Enumeration遍历Hashtable的键
1.根据keys()获取Hashtable的集合
2.通过Enumeration遍历“第一步”得到的集合
Enumeration enu = table.keys();
while(enu.hasMoreElements()) {
System.out.println(enu.nextElement());
}
5.通过Enumeration遍历Hashtable的值
1.根据elements()获取Hashtable的集合
2.通过Enumeration遍历“第一步”得到的集合
Enumeration enu = table.elements();
while(enu.hasMoreElements()) {
System.out.println(enu.nextElement());
}
Properties
Properties 类位于 java.util.Properties,是Java 语言的配置文件所使用的类
继承了Hashtable 类,以Map 的形式进行放置值,put(key,value) get(key)
Xxx.properties 为Java 语言常见的配置文件,如数据库的配置 jdbc.properties,
class.getResourceAsStream 加载
class.getClassLoader().getResourceAsStream 加载
getResourceAsStream获取项目下的指定资源
1.当前类名.class.getResourceAsStream(String path)
path 不以/开头时默认是从此类所在的包下取资源,以/开头则是从ClassPath根下
/代表src
2.当前类名.class.getClassLoader().getResourceAsStream(String path)
默认是从ClassPath根下获取,path不能以/开头,最终是由ClassLoader获取资源
LinkedHashMap
LinkedHashMap继承于HashMap,LinkedHashMap是有序的,且默认为插入顺序
提供了key-value的存储方式,并提供了put和get方法来进行数据存取
LinkedHashMap是线程不安全的
LinkedHashMap存储数据是有序的,而且分为两种:插入顺序和访问顺序
put方法:
LinkedHashMap没有重写put方法,所以还是调用HashMap得到put方法
扩容:
如果发现前元素个数超过了扩容阀值时,会调用resize方法
get方法:
LinkedHashMap并没有重写getEntry方法,所以调用的是HashMap的getEntry方法
在访问顺序的LinkedHashMap进行了get操作以后,重新排序,把get的Entry移动到双向链表的表尾
遍历方式取数据:
LinkedHashMap是有序的,且是通过双向链表来保证顺序的
从头结点Entry header的下一个节点开始,
只要把当前返回的Entry的after作为下一个应该返回的节点即可。
直到到达双向链表的尾部时
ConcurrentHashMap
ConcurrentHashMap是HashMap的线程安全版本的实现版本
数据结构底层是:散列表+链表+红黑树
ConcurrentHashMap使用了一个table来存储Node
ConcurrentHashMap同样使用记录的key的hashCode来寻找记录的存储index
处理哈希冲突的方式与HashMap也是类似
冲突的记录将被存储在同一个位置上,形成一条链表,当链表的长度大于8的时候会将链表转化为一棵红黑树
从而将查找的复杂度从O(N)降到了O(lgN)
ConcurrentHashMap支持高并发情况下对哈希表的访问和更新
ConcurrentHashMap与HashTable相似,与HashMap不同
ConcurrentHashMap的所有操作都是线程安全的
它不允许null用作键或值
get操作没有上锁。是非阻塞的。所以在并发情况下可以与阻塞的put或remove函数交迭
ConcurrrentHashMap关键属性:
volatile Node<K,V>[] table:装载Node的数组,作为ConcurrentHashMap的数据容器
采用懒加载的方式,直到第一次插入数据的时候才会进行初始化操作
数组的大小总是为2的幂次方,(因为继承自HashMap)
transient volatile Node<K,V>[] nextTable:扩容时使用,平时为null,
只有在扩容的时候才为非null,逻辑机制和ArrayList底层的数组扩容一致
transient volatile long baseCount:元素数量基础计数器,通过CAS的方式进行更改。
transient volatile int sizeCtl:散列表初始化和扩容的大小都是由该变量来控制
当为负数时,它正在被初始化或者扩容
-1表示正在初始化
-N表示N-1个线程正在扩容
当为整数时
此时如果当前table数组为null的话表示table正在初始化过程中
sizeCtl表示为需要新建的数组的长度,默认为0
若已经初始化了,表示当前数据容器(table数组)可用容量也可以理解成临界值
ConcurrentHashMap为何存在
HashMap在多线程情况下是线程不安全的
HashTable虽然是线程安全的,但是在所有涉及多线程的操作上都加上了synchronized来锁住整个table
ConcurrentHashMap为每个数组元素加上了桶锁并结合CAS算法来实现同步
什么是CAS
CAS: 全称Compare and swap,字面意思:”比较并交换“
我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。
比较 A 与 V 是否相等。(比较)
如果比较相等,将 B 写入 V。否则什么都不做(交换),返回操作是否成功
当多个线程尝试使用CAS同时更新同一个变量的时候,只有其中一个线程能够更新变量的值
当其他线程失败后,不会像获取锁一样被挂起,而是可以再次尝试
或者不进行任何操作,这种灵活性就大大减少了锁活跃性风险
ConcurrentHashMap 常见Api
initTable()
putVal(K key, V value, boolean onlyIfAbsent)
当且仅当table中不存在该key对应的Entry时才插入该Entry。
否则不替换table中原有的Entry中的value
get(Object key)
ConcurrrentHashMap关键类
Node类实现了Map.Entry接口,主要存放key-value对,并且具有next域
用volatile进行修饰的,也是为了保证并发情况下的该属性的可见性