文章目录
1.Java集合框架概述
一方面,面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储。另一方面,使用Array存储对象方面具有 一些弊端,而Java集合就像一种容器,可以 动态地 把多个对象的引用放入容器中。
- 数组在内存存储方面的特点:
1. 数组初始化以后,长度就确定了。
2. 数组一旦确定了,其元素的类型也就确定了。也就只能操作指定类型的数据- 数组在存储数据方面的弊端:
1.数组初始化以后,长度就不可变了,不便于扩展
2.数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高。同时无法直接获取存储元素的个数(数组长度固定,元素个数不定)
3.数组存储的数据是有序的、可以重复的。–>存储数据的特点单一- Java集合类可以用于存储数量不等的多个 对象,还可用于保存具有映射关系的
关联数组。
Java集合可分为Colleption和Map两种体系:
- Collection接口:单列集合,定义了存取一组对象的方法的集合
1. List(子接口): 元素有序、可重复的集合 -->“动态”数组
实现类:ArraysList、LinkedList、Vector
2. Set(子接口): 元素无序、不可重复的集合 -->高中的“集合”
实现类:HashSet、LinkedHashSet、TreeSet
- Map接口:双列集合,保存具有映射关系“key-value对”的集合 -->y = f(x)[一对一、多对一]
实现类:HashMap、LinkedHashMap、TreeMap、Hashtable、Properties
2.Collection接口中的通用方法
向 Collection接口的实现类的对象中添加数据 obj时,要求 obj所在类要重写 equals()方法 (比较、删除、Set的添加等都要通过equals()来比较两个元素是否相等,其中Set还要额外重写HashCode()方法,因为它是 数组+链表结构)
1、添加
add(Object obj)
addAll(Collection coll)
2、获取有效元素的个数
int size()
3、清空集合
void clear()
4、是否是空集合
boolean isEmpty()
5、是否包含某个元素
boolean contains(Object obj):
【是通过元素的equals方法来判断是否是同一个对象】
boolean containsAll(Collection c):
【也是调用元素的equals方法来比较的】判断 c集合中的元素是否都在当前集合中
6、删除
boolean remove(Object obj):
通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素
boolean removeAll(Collection coll):
取当前集合的差集:删除和 coll中相同的元素(去重)
【通过元素的equals方法】
7、取两个集合的交集
boolean retainAll(Collection c):
把交集的结果存在当前集合中,不影响c:把相同的元素存到当前集合中,会替换当前集合原来的元素
8、集合是否相等
boolean equals(Object obj)
若是 list,要求顺序、元素相等;若是 set,要求元素相等
9、转成对象数组
Object[] toArray()
10、获取集合对象的哈希值
hashCode()
11、遍历
iterator():
返回迭代器对象,用于集合遍历
List 与 Set 通用的方法 …
增: boolean add(Object obj)
删: remove(Object obj),List 根据下标来删除,并返回该对象 Object;Set 根据对象来查找删除,返回删除的结果 boolean
3.Iterator迭代器接口
集合元素的遍历操作,使用 Iterator接口:
Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。
Iterator仅用于遍历集合,Iterator木身并不提供承装对象的能力。如果需要创建
terator对象,则必须有一个被迭代的集合
集合对象每次调用 iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
Iterator接口中的方法:
3.1 源码
ArrayList类中的 iterator()方法源码:
public Iterator<E> iterator() {
return new Itr();
}
Itr 是ArrayList类中的一个内部类,实现了 Iterator接口,是它的实现类
iterator返回了实现 Iterator接口的实现类 Itr
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
...
}
3.2 示例
hasNect()与next()实现遍历:
public class Main{
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add("aaa");
coll.add(123);
coll.add(new Date());
coll.add(true);
Iterator iterator = coll.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
// 方式1:
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// 方式二:
// for(int i=0;i<coll.size();i++){
// System.out.println(iterator.next());
// }
}
}
3.3 iterator实现原理
iterator 开始指针指向 0 位置,iterator.hasNext()判断下一个位置有没元素,有返回true,iterator.next()将指针向下移,输出元素
3.4 Iterator接口remove()方法
可以在遍历的时候,删除集合中的元素 (iterator指向目标元素时,调用 remove()方法删除)
注意:
一定要在使用了 Iterator.next()之后使用 Iterator.remove(),因为开始在 0 位置
不能连续使用两次 Iterator.remove()方法
public class Main02 {
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add("aaa");
coll.add(123);
coll.add(new Date());
coll.add("aaa");
coll.add(true);
coll.add("aaa");
Iterator iterator = coll.iterator();
while(iterator.hasNext()){
if("aaa".equals(iterator.next())){
iterator.remove(); // 删除当前指向的元素
}
}
// 使用一个新的迭代器遍历,上一个迭代器已指向末尾
Iterator iterator1 = coll.iterator();
while(iterator1.hasNext()){
System.out.println(iterator1.next());
}
/* 123
Tue Apr 26 21:22:04 CST 2022
true */
}
}
3.5.foreach循环遍历集合元素
Java5.0提供了 foreach循环迭代访问 Collection和数组。
遍历集合的底层调用 Iterator完成操作。
遍历操作不需获取 Collection或数组的长度,无需使用索引访问元素。
foreach还可以用来遍历数组。
for(类型 变量:数组/集合){ System.out.println(变量); }
public class Main{
public static void main(String[] args){
Collection coll = new ArrayList();
coll.add("aaa");
coll.add(123);
coll.add(new Date());
coll.add(true);
for(Object obj:coll){
System.out.println(obj);
}
/* aaa
123
Tue Apr 26 21:22:04 CST 2022
true */
}
}
4.Collection子接口:list
鉴于Java中数组用来存储数据的局限性,我们通常使用 List替代数组
Lst集合类中 元素有序、且可重复,集合中的每个元素都有其对应的顺序索引。
Lst容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
JDK API中 List接口的实现类常用的有:ArrayList、LinkedList和 Vector。
ArrayList、LinkedList和 Vector的异同 ?
相同点:
三个类都是实现了L1st接口,存储数据的特点相同:存储有序的、可重复的数据
不同:
ArrayList: 作为 List接口的主要实现类,线程不安全,效率高;底层使用 Object数组存储
LinkedList: 对于频繁的插入、删除操作,此类效率相比 ArrayList较高;底层使用双向链表存储。线程不安全
Vector: 作为 List的古老实现类:线程安全的,效率低
4.1 List接口常用方法
(在实现了Collection接口的方法后,添加了自身独有的方法 )
void add(int index,Object ele):在index位置插入element元素
boolean addALL(int index,Collection eles):从index位置开始将eLes中的所有元素添加进来
Object get(int index):获取指定index位置的元素
int index0f(Object obj):返回obj在集合中首次出现的位置
int LastIndex(0f(Object obj):返回obj在当前集合中末次出现的位置
Object remove(int index):移除指定index位置的元素,并返回此元素
Object set(int index,.Object ele):设置指定index位置的元素为eLe
List sublist(int fromIndex,int toIndex,):返回从fromIndextoIndex位置的子集合
总结:常用方法
增:add(Object obj) / add(int index,Object ele)
刪:remove(int index) / remove(Object obj)
改:set(int index,object ele)
查:get(int index),
长度:size()
遍历:1.普通的循环 2.Iterator.迭代器方式 3.增强for循环
4.2 ArrayList
ArrayList源码分析:
底层使用 Object数组存储:transient Object[] elementData;
JDK 7:
Arraylist list=new ArrayList(); // 底层创建了长度为 10的object[]数组eLementData
List.ad(123); // eLementData[0]new Integer(123);
List.add(11); // 如果此次的添加导致底层eLementData数组容量不够,则扩容。
默认情况下,扩容为原来的容量的1.5倍,同时会将原有数组中的数据复制到新的数组中。
结论:建议开发中使用带参的构造器:ArrayList list=new ArrayList(int capacity)
JDK 8:
ArrayList List=new ArrayList(); // 底层object[]eLementData初始化为[].并没有创建长度为10的数组
List.add(123); // 第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到 eLementData中,后续的添加和扩容与JDK 7相同
小结:jdk7中的Arraylist的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存
4.3 ArrayList与LinkList
5.Collection子接口:Set
Set 接口是Collection的子接口,set接口没有提供额外的方法,使用的是Collection声明过的方法
Set 接口实现类对象 元素无序、不可重复
1.Set 接口对象不能存放重复的元素,可以存放null,但只能有一个
2.Set 接口对象存放数据无序(取出顺序固定)
常见的Set实现类:
HashSet: 作为 Set接口的主要实现类,线程不安全,可以存储 null值
LinkedHashSet: 作为 HaSet的子类,增加了前后指针,遍历其内部数据时,可以按照添加的顺序遍历
TreeSet: 可以按照添加的对象的指定属性,进行排序。
Set的无序性,不可重复性:
1.无序性: 存储的数据在底层 ‘数组’ 中不是按照 ‘数组’ 的索引存储,而是根据数据的哈希值
2.不可重复性: 先比较哈希值,哈希值相同再使用equals比较。(重写hashCode,让属性一样的对象的哈希值相同)
5.1 HashSet
HashSet的底层是HashMap,HashMap的底层是(数组+链表+红黑树)
HashSet扩容机制:
分析HashSet的添加元素底层是如何实现(hash()+equals()):
1.HashSet底层是HashMap
2.添加一个元素时,先得到hash值 – 会转成 -> 索引值
3.找到存储数据表table,看这个索引位置是否已经存放的有元素
4.如果没有,直接加入
5.如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添动加到最后
6.在Java8中,如果一条链表的元素个数超过TREEIFY THRESHOLD(默认是8),并且tabel的大小>=
MIN TREEIFY CAPACITY(默认64),
就会进行树化(红黑树)
存储结构示例(table):
public class Main{
public static void main(String[] args) {
Node[] table = new Node[16];
Node node2= new Node("Node2_01",new Node("Node2_02", new Node("Node2_03", null)));
// 散列表中索引为 2的链表
table[2] = node2;
}
}
class Node {
Object o;
Node next;
public Node(Object o, Node next) {
this.o = o;
this.next = next;
}
}
使用Set存放 person对象数据 (当属性 name、age都相同时,不能存入)
1、Person没有重写 HashCode()、equals()方法时:
class Person{
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class test {
public static void main(String[] args) {
Person person1 = new Person("张三",17);
Person person2 = new Person("李四",17);
Person person3 = new Person("张三",17);
Set set = new HashSet();
set.add(person1);
set.add(person2);
set.add(person3);
System.out.println(set);
}
}
输出结果是:[Person{name=‘张三’, age=17}, Person{name=‘张三’, age=17}, Person{name=‘李四’, age=17}]
因为 Set的特点(无序,不可重复),Person在没有重写 HashCode()和equals()方法时,先通过 HashCode()方法得到 Person对象的哈希值都是不相同的,所以存入三个。
2、Person重写 HashCode()、equals()方法时:
class Person{
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age;
}
@Override
public int hashCode() {
return age;
}
}
public class test {
public static void main(String[] args) {
Person person1 = new Person("张三",17);
Person person2 = new Person("李四",17);
Person person3 = new Person("张三",17);
Set set = new HashSet();
set.add(person1);
set.add(person2);
set.add(person3);
System.out.println(set);
}
}
输出结果是: [Person{name=‘张三’, age=17}, Person{name=‘李四’, age=17}]
通过重写 HashCode()和equals()方法,
重写后的 HashCode()方法如果 name和age(可选任意个属性) 相同时,返回相同的 hash值
[ person的hashcode值根据 属性 得到的hashcode值通过一定的算法得到,若 person的属性有自定义的类类型,则该类也要重写 hashcode()和equals(),让该类对象(person中的属性)在该类属性值相同时,返回相同的 hashcode()值。]
重写后的 equals()方法在 name和age 相同时,返回 true
存储数据时经过这两个方法,最终都通过两个属性值是否相同来判断两个元素是否相等
5.3 LinkHashSet
在原有的 HashSet基础上,添加了双向链表结构(每个元素都有前后指针),
优点,对于频繁的遍历操作,LinkHashSet效率高于HashSet
5.4 TreeSet
可以按照添加对象的指定属性,进行排序
(TreeSet只能添加相同类的对象,只有相同类的对象,才可以比较大小 【存储:树形结构】)
TreeSet中的比较两个元素的规则有所不同,不再是用equals()方法,而是用的是 compareTo()方法,即在添加时会进行比较比较排序,若与已有的某个元素比较的值相同时 (compareTo()方法返回0) 会无法添加成功,如:
name.compareTo(person.name) 返回0时认为这两个元素相同,要添加的元素经比较后无法添加成功
public class test {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet();
treeSet.add("123");
treeSet.add("234");
treeSet.add("012");
Iterator iterator = treeSet.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next()); // 012 123 234
// String类型按照 String.comparaTo(实现了Comparable接口的compareTo方法)进行排序
}
}
}
添加数据时,如果是自定义的类,需要有排序的规则才能添加成功:
1.自然排序(自定义类实现compareble接口,重写compareTo方法),
2.定制排序(在创建TreeSet时构造器中,添加compareble接口的实现类对象)
1.自然排序:
让自定义的类实现compareble接口,重写compareTo方法,compareTo方法中的既是添加时排序比较的规则
// 按照name顺序从小到大进行排序,其次age相同时也将其添加,按照age从小到大的顺序(二级排序)
class Person implements Comparable{
String name;
int age;
public Person(String name,int age){
this.name = name;
this.age = age;
}
@Override
public int compareTo(Object o) {
if(o instanceof Person){
Person person = (Person)o;
if(!name.equals(person.name)){
return name.compareTo(person.name);
}else{
return Integer.compare(age,person.age);
}
// 从大到小顺序进行排序(return -name.compareTo(person.name);)
}else{
throw new RuntimeException("输入的类型不一致!");
}
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Main{
public static void main(String[] args) {
TreeSet treeSet = new TreeSet();
treeSet.add(new Person("absd",12));
treeSet.add(new Person("aasd",13));
treeSet.add(new Person("adsd",10));
Iterator iterator = treeSet.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
/**
*Person{name='aasd', age=13}
*Person{name='absd', age=12}
*Person{name='adsd', age=10}
*/
}
}
2.定制排序:
按照添加的规则进行排序,CompareTor接口的实现类对象
TreeSet treeSet = new TreeSet(Comperator comparator)
若添加的 Comparator参数,将按照comparator中的规则对其进行排序;若没有添加该参数,则按照自然排序(继承comparable接口,重写comparaTo()方法中的排序规则) 对其进行排序
public class Main{
public static void main(String[] args) {
Comparator comparator = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof Person && o2 instanceof Person){
Person person1 = (Person)o1;
Person person2 = (Person)o2;
return Integer.compare(person1.age,person2.age);
}else{
throw new RuntimeException("添加的数据类型不符合!");
}
}
};
TreeSet treeSet = new TreeSet(comparator);
treeSet.add(new Person("a",12));
}
}
6.Map接口
Map: 存放双列数据,存储 Key-value对的数据 — 类似于高中的函数: y=f(x)
Map实现类有:
Hashtable以及它的子类Properties,HashMap以及它的子类LinkedHashMap,TreeMap
1.Hashtable:作为最先的实现类(1.0);线程安全的,效率低;不能存储 null的key和value【被HashMap(1.2)更替,接而再出现LinkedHashMap(1.4)】
2.HashMap:作为Map的主要实现类;线程不安全的,效率高;可以存储 null的key和value。底层在jdk7之前是:数组+链表,之后是:数组+链表+红黑树
3.LinkedHashMap:在遍历 map元素时,可以按照添加的顺序实现遍历(添加了双向指针,对于频繁的遍历操作,效率高于HashMap)
4.TreeMap:按照添加的key-value进行排序,实现排序遍历。此时考虑key的自然排序和定制排序。底层使用红黑树结构存储。
5.Properties:常用来处理配置文件。key和value都是String类型
继承关系图:
Map结构的理解:
1.Map中的key: 无序的、不可重复的,使用 Set存储所有的kye
[ key所在的类要重写 equals()和 hashCode()方法 ],[ HashMap–>HashSet,LinkedHashMap–>LinkedHashSet ]
2.Map中的value:无序的、可重复的,使用Collectin存储所有的value
[ value所在类要重写 equals()方法 ]
3.一个键值对:key-value构成了一个Entry对象.[ 相当于函数( y=|x| ) ]
4.Map中的entry:无序的、不可重复的,使用Set存储所有的 entry对象
6.1 Map接口中的常用方法
添加、删除、修改操作:
Object put(Object key,Object value): 将指定 key-value:添加到(或修改)当前 map对象中
void putAll(Map m): 将m中的所有 key-value对存放到当前 map中
Object remove(Object key): 移除指定 key的 key-value对,并返回 value
void clear(): 清空当前 map中的所有数据
元素查询的操作:
Object get(Object key): 获取指定 key对应的 value
boolean containsKey(Object key): 是否包含指定的 key
boolean containsValue(Object value): 是否包含指定的 value
int size(): 返回 map中 key-value对的个数
boolean isEmpty): 判断当前 map是否为空
boolean equals(Object obj)): 判断当前 map和参数对象 obj是否相等
元视图操作的方法:
Set keySet(): 返回所有 key构成的 Set集合
Collection values(): 返回所有 value构成的 Collection集合
Set entrySet(): 返回所有 key-value对构成的 Set集合
6.2 HashMap的底层实现原理
1.JDK7:数组+链表
1.HashMap map = new HashMap();
在实例化以后,底层创建了长度是16的一维数组Entry[] table.
2.map.put(key1,value1) {n次}
首先,通过 key1所在类的 hash(HashCode())方法得到哈希值,再经过算法得到在Entry数组中的存放位置,
若此位置上的数据为空,则此 key1-value1(entry1)添加成功;
若此位置的数据不为空,即此位置存在一个或多个数据(以链表的形势存在),如果 key1的哈希值与已经存在此位置链表中的所有数据哈希值都不相同,则此 key1-value1(entry1)添加成功。若 key1的哈希值与已经存在的某一个数据的哈希值相同,继续比较:
调用 key1所在类的 equals()方法,比较:如果 equals()返回 false: 此时 key1-value1添加成功。如果 equals()返回true: 使用value1替换相同key的value值。
此位置不为空,key1-value1与原来的数据不相同的情况,key1-value1将以链表的方式存
储。若与原来的数据相同,则会替换原来的元素
2.JDK8:数组+链表+红黑树
new HashMap:底层没有创建一个长度为16的数组
JDK 8底层使用的数组是: Node[],而非Entry[]
首次调用put()方法时,底层创建长度为16的数组
当数组的某一个索引位置上的元素以链表形式存在的数据个数>8且当前数组的长度>64时,此时此索引位置上的所有数据改为使用红黑树存储(提升查找效率)。
6.3 HashMap的源码分析
1.JDK 7:
构造过程:
HashMap map = new HashMap();
// DEFAULT INITIAL CAPACITY: HashMap的默认容量,16;
// DEFAULT LOAD FACTOR: HashMap的默认加载因子;
public HashMap(){
this(DEFAULT_INITIAL_CAPACITY,DEFAULT_LOAD_FACTOR);
}
/* 有参数构造器(默认容量和默认的加载因子) */
public HashMap(int initialCapacity,float loadFactor){
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity:"+initialCapacity);
if (initialcapacity>MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <=0 Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor:"+loadFactor);
// Find a power of 2 >initialcapacity
int capacity 1;
while (capacity < initialCapacity) // initialcapacity: 16(容量),如果调用是此有参构造器,指定此参数为15时,也会进行向左移一位,只到大于等于16
capacity <<= 1;
this.loadFactor = loadFactor; // 加载因子(默认0.75)
threshold = (int)Math.min(capadity * loadFactor,MAXIMUM_CAPACITY + 1); // 16*0.75 = 12
// threshold: 临界因子,影响扩容的大小,数组+链表的存储结构,不是达到数组的容量时才扩容的,而是达到一定的容量时进行扩容
table = new Entry[capacity]; // Entry<K,V>[] table,底层用Entry数组存储
useAltHashing sun.misc.VM.isBooted()&&
(capacity >Holder.ALTERNATIVE_HASHING_THRESHOLD);
init();
}
添加过程:
public V put(K key,V value){
if (key = null)
return putForNullKey(value);
int hash = hashhash(key); // 计算 key的哈希值
int i=indexFor(hash,table.length); // 拿到 key哈希值,通过一定的算法得到存储的下标
for (Entry<K,V> e = table[i];e != null;e = e.next){ // 此位置有值的情况下,判断是否相同,如果相同,替换原来的 key上的value值,并在将此旧的value值返回
Object k;
if (e.hash =hash &&((k e.key)==keykey.equals(k))){
V oldValue e.value;
e.valuevalue;
e.recordAccess(m:this);
return oldValue;
}
}
modCount++;
addEntry(hash,key,value,i); // 将此键值对添加到Entry数组中(哈希值,key,value,下标i)
return null;
}
void addEntry(int hash,K key,V value,int bucketIndex){
if ((size > threshold)&&(null != table[bucketIndex])){ // 在size大于扩容因子,要添加的元素在此位置上不为空的情况下进行扩容
resize(newCapacity:2 table.length); // 扩容为原来的2倍
hash = (null !key)?hash(key):0; // 扩容后重新计算此要添加的元素的哈希值
bucketIndex indexFor(hash,table.length);
}
createEntry(hash,key,value,bucketIndex);
}
void createEntry(int hash,K key,V value,int bucketIndex){
Entry<K,V> e = table[bucketIndex]; // 把原有的那个元素取出来
table[bucketIndex] = new Entry<>(hash,key,value,e); // 把原来的元素作为新的元素的next出现
size++;
}
static class Entry<K,V> implements Map.Entry<K,V>
final K key
V value;
Entry<K,V> = next;
int hash;
// Creates new entry.
Entry(int h,Kk,VV,Entry<K,V>n){
value V;
next n;
key k;
hash h;
}
}
2.JDK 8:
构造过程:
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
添加过程:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true); // hash(key)方法拿到 key的哈希值
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length; // 首次调用 table末初始化,为null;resize()方法创建了容量为16的数组(同时此方法也是进行扩容的方法)
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; // 与此位置元素相等的情况下,取此位置哈希值赋于e,再于后面替换此位置的元素,
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;
}
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY; // 首次创建时到达这里,newCap = 16,newThr = 12
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr; // 赋值给临界因子
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; // 创建容量为16的数组
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab; // 返回创建(新)的数组
}
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize(); // 当表不为空时或者长度小于MIN_TREEIFY_CAPACITY(64)时不进行转化红黑树
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
DEFAULT_INITIAL_CAPACITY: HashMap的默认容量,16
DEFAULT_L0 AD_FACTOR: HashMap的默认加载因子:B.75
threshold: 扩容的临界值,= 容量*填充因子:16 * 0.75=>12
TREEIFY THRESHOLD: Bucket中连表长度大于核款认值,转化为红黑柄:8
MINTREEIFY CAPACITY: 桶中Node被树化时最小hash表容量:64
6.4 LinkedHashMap
添加了双向指针,能够以添加的顺序遍历元素,存储的顺序是无序的
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
public class Main{
public static void main(String[] args) {
Map map = new LinkedHashMap();
map.put("12",123);
map.put("14",122);
map.put("10",1);
System.out.println(map); // {12=123, 14=122, 10=1}
}
}
6.5 TreeMap
TreeMap中是按照 key进行排序的,要求 key必须是同一个类的对象(自然排序、定制排序)。实现方式与 TreeSet相同
6.6 Properties
Properties是Hashtable的子类,该对象用于处理属性文件
由于属性文件里的 key、value都是字符串类型,所以 Properties中的 key、value都是 String类型
7.Collections工具类
Collections 是一个操作 Set、List和 Map的等集合的工具类
Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法
排序操作(均为static方法):
reverse(List): 反转List中元素的顺序
shuffle(List): 对List集合元素进行随机排序
sort(List): 根据元素的自然顺序对指定List集合元素按升序排序
sort(List,Comparator): 根据指定的Comparator产生的顺序对List集合元素进行排序
swap(List,int,int): 将指定list集合中的i处元素和j处元素进行交换
查找、替换:
Object max(Collection): 根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection,Comparator): 根据Comparator指定的顺序,返回给定集合中的最大元素
Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object): 返回指定集合中指定元素的出现次数
void copy(List dest,List src): 将src中的内容复制到dest中
boolean replaceAll(List list,Object oldVal,.Object newVal)): 使用新值替换 List对象的所有旧值