集合的框架体系
Collection与Map的区别
Collection:单列集合
Map:双列集合
List接口基本介绍:
List接口时Collection接口的子接口
- List集合类中元素有序(即添加顺序和取出循序一致)、且可重复
- List集合中的每个元素都有其对应的顺序索引,即支持索引
- List容器中的元素都对应一个整数型的序号即在其在容器中的位置,可以根据序号去存容器中的元素。
- JDK API中List接口的实现类有:
package com.NXY.gather_;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Vector;
/**
* @author 不会写代码的16号
*/
public class List_ {
public static void main(String[] args) {
//List集合类中元素有序(即添加顺序和取出循序一致)、且可重复
List list = new ArrayList();
list.add("jack");
list.add("tom");
list.add("smith");
System.out.println(list);
//List集合中的每个元素都有其对应的索引, 索引是从0开始的
//List容器中的元素 都对应一个整数型的序号记载其在容器中的位置
System.out.println(list.get(0));
}
}
List中常用的方法介绍
List集合里添加了一些根据索引来操作集合元素的方法
- void add(int index, Object ele): 在Index位置插入ele元素
- boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
- Object get(int Index):获取指定Index位置的元素
- int indexOf(Object obj):返回obj在集合中首次出现的位置
- int lastIndexOf(Object obj):返回Obj在当前集合中末次出现的位置
- Object remove(int index): 移除指定index位置的元素,并返回此元素
- Object set(int index,Object ele): 设置指定Index位置的元素ele,相当于是替换
- List subList(int fromIndex, int toIndex): 返回从formIndex到toIndex位置的子集合
package com.NXY.gather_;
import java.util.ArrayList;
import java.util.List;
/**
* @author 不会写代码的16号
*/
public class ListMethod_ {
public static void main(String[] args) {
List list = new ArrayList();
list.add("jack");
list.add("tom");
//void add(int index, Object ele): 在Index位置插入ele元素
list.add(1,"smith");//在下标为1的位置添加smith这个元素
//boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
List list2 = new ArrayList();
list.add("老子");
list.addAll(1,list2);//从下标为1开始 添加list2全部元素进入list
//Object get(int Index):获取指定Index位置的元素
list.get(1);
//int indexOf(Object obj):返回obj在集合中首次出现的位置
list.indexOf("jack");
//int lastIndexOf(Object obj):返回Obj在当前集合中末次出现的位置
list.add("jack");
list.indexOf("jack");
//Object remove(int index): 移除指定index位置的元素,并返回此元素
list.remove(0);
//Object set(int index,Object ele): 设置指定Index位置的元素ele,相当于是替换
list.set(0,"jack");
//List subList(int fromIndex, int toIndex): 返回从formIndex到toIndex位置的子集合
list.subList(0,3);
}
}
List三种遍历方式
-
方式一:使用iterator迭代方式
Iterator iter = col.iterator();
while(iter.hasNext()){
Object o = iter.next();
} -
方式二:使用增强for
for(Object o : cal){
} -
方式三: 使用普通for
for(int i = 0; i < list.size(); i ++){
Object object = list.get(i);
System.out.println(object);
}
重点.如果list中存入的元素是定义的类,需要向下转型
package com.NXY.gather_;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @author 不会写代码的16号
*/
public class LIstFor_ {
public static void main(String[] args) {
List list = new ArrayList();
list.add("tom");
list.add("jack");
list.add("smith");
Iterator iterator = list.iterator();
while(iterator.hasNext()){
Object o = iterator.next();
System.out.println(o);
}
}
}
ArrayList集合讲解
ArrayList特性
- ArrayList是由数组来实现数据储存的
- ArrayList基本等同于Vectoe,除了ArrayList是线程不安全(执行效率高) 看源码,在多线程的情况下不建议使用ArrayList
重点.ArrayList底层结构和源码分析
结论:
3. ArrayList中维护了一个Object类型的数组elementData.
4. transient Object[] elementData;//transient 表示瞬间,短暂的,表示该属性不会被序列号
5. 当创建ArrayList对象时,如果使用的无参构造器,则初始elementData容量为0(JDK7 为10),第一次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍。
6. 如果使用的时指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍
底层源码剖析
1.无参构造器
//无参数据库
public ArrayList() {
//创建了一个空的 elementData数组
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public boolean add(E e) {
modCount++;//集合修改的次数 +1
add(e, elementData, size);//进入e 为传入的数据
//elementData为前面创建的数组,size表示 数组中的数据的个数
return true;
}
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)//判断是否要扩容
elementData = grow();//如果需要扩容 则进入该函数
elementData[s] = e;//惊醒赋值
size = s + 1;
}
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);//扩大1.5倍
if (newCapacity - minCapacity <= 0) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
//第一次扩容 容量为10 DEFAULT_CAPACITY = 10
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
//下面语句为 第一次扩容之后的 扩容 将容量扩大1.5呗
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
LinkedList讲解
LinkedList特点
- LinkedList实现了双向链表和双端队列特点
- 可以添加任意元素(元素可以重复),包括null
- 线程不安全,没有实现同步
LinkedList底层原理讲解
- LinkedList底层维护了一个双向链表
- LinkedList中维护了两个属性first 和 last 分别指向 首节点和尾节点
- 每个节点(Node对象) 里面有维护了prev、next、item三个属性,其中通过prev指向前一个节点,通过next指向后一个节点,最终实现双向链表
- 所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);//创建新的节点
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
Vector底层原理讲解
Vector底层特点:
- Vector底层也是一个对象数组,protected Object[] elementData;
- Vector是线程同步的,即线程安全,Vector类的操作方法带有synchronized
- 在开发中,需要线程同步时,考虑使用Vector
package com.NXY.gather_;
import java.util.Vector;
/**
* @author 不会写代码的16号
*/
public class Vector_ {
public static void main(String[] args) {
Vector vector = new Vector(8);
for(int i = 0; i <10; i++){
vector.add(i);
}
vector.add(100);
System.out.println(vector);
}
}
public Vector(int initialCapacity) {
// 如果时上面的new Vetor(8) 则是使用这个函数
this(initialCapacity, 0);
}
/**
* Constructs an empty vector so that its internal data array
* has size {@code 10} and its standard capacity increment is
* zero.
*/
public Vector() {
//如果是new Vector() 则使用的是这个函数 默认第一次扩容为10
this(10);
}
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
// 创建一个容量为initialCapacity的elementData数组
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
//添加函数
public synchronized boolean add(E e) {
modCount++;
add(e, elementData, elementCount);
return true;
}
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)//判断是否需要扩容
elementData = grow();
elementData[s] = e;
elementCount = s + 1;
}
//扩容
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);//判断是否需要扩容 如果需要扩容则扩容两倍
if (newCapacity - minCapacity <= 0) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
Set集合讲解
Set集合特点:
-
set接口的实现类对象(Set接口对象),不能存放重复的元素,可以添加一个null
-
set接口对象存放数据是无序的(即添加的顺序和取出的顺序不一致)
-
取出的顺序是固定的 不会随着运行而改变
Set接口的遍历方式
同Collection的遍历方式一样,因为Set接口时Collection接口的子接口- 可以使用迭代器
- 增强for
package com.NXY.gather_;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* @author 不会写代码的16号
*/
public class Set_ {
public static void main(String[] args) {
Set set = new HashSet();
set.add("john");
set.add("luck");
set.add("john");//重复
set.add("jack");
set.add(null);
set.add(null);
System.out.println(set);//[null, luck, john, jack]
//遍历
//迭代器
Iterator iterator = set.iterator();
while(iterator.hasNext()){
Object obj = iterator.next();
System.out.println(obj);
}
//增强for
for(Object obj:set){
System.out.println(obj);
}
}
}
HashSet集合讲解
HashSet特点:
- HashSet实际上是HashMap
- 可以存放null值 但只是只能有一个null
- HashSet不保证元素是有序的,取决于hash后 在确定索引的结果
- 不能有重复元素\对象
- 在添加一个元素时,先得到hash值 会转成 索引值
- 找到存储数据标table 看这个索引位置是否已经存放元素
- 如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后面
- 在java8中,如果一条链表的元素个数超过TREEIFY_YHTESHOLD(默认是8)并且table的大小>=MIN_TREEIFY_CAPACITY(默认64)就会进行树化(红黑树)
Set底层机制说明
- HashSet底层是HashMap,第一次添加时 table数组扩容到16 临界值(threshold) 是 16*加载因子(loadFactor) 是0.75 = 12
- 如果table数组是同到了临界值12 就会扩容到162 = 32 新的临界值 就是320.75 = 24 以此类推
public HashSet() {
map = new HashMap<>();
}
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}//hash(key) 先获取元素的哈希值(hashCode方法) 对哈希值进行运算,得出一个索引值 即为要存放在哈希表中的元素
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
// 判断该元素是否存在其他元素 如果没有则直接存放
// 如果该位置已经有其他元素 则需要进行equals判断,如果相等则不再添加
}
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;
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;
}
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;//第一次扩容 DEAULT_INITIAL_CAPACITY = 16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
// 临界值12 0.75 * 16
}
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];//创建一个容量为 newCap 的数组
table = newTab; //令table = 创建的数组
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;
}
/*
源码解读:
1.构造器:
public HashSet() {
map = new HashMap<>();
}
2.执行add():
public boolean add(E e) {
return map.put(e, PRESENT)==null;
// PRESENT是 private static final Object PRESENT = new Object();
}
3.执行put(): 该方法会执行hash(key) 得到key的hash值
// k是 java 字符串 value 是 PRESENT
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
4.执行 putVal():
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i; //定义辅助变量
//table 就是HashMap的一个数组 类型是Node[]
//if语句 是 如果table==null 或者大小 ==0时
//进行第一次扩容 扩容到大小为16
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//(1)根据key传入的Hash值 计算得到 key应该放入table表哪个索引位置
//并把该位置的对象 赋给p
//(2)判断p 是否为null
//(2.1)如果p为空 表示还没存储元素 就创建一个Node(key="java",value=PRESENT)
//(2.2)就放到该位置 tab[i] = newNode(hash,key,value,null)
//
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
//在需要局部变量时候的再创建
Node<K,V> e; K k;//辅助变量
//如果当索引位置对应的链表的第一个元素和准备添加的key的hash值一样
//并且满足 以下两个条件:
//(1) 准备加入的key 和 p 指向的Node 结点的key 是同一个对象
//(2) p 指向的Node结点 的 key 的equals() 和准备加入的key比较后相同
// 所以就不能加入
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//在判断 p 是不是一颗红黑树
//如果是一颗红黑树 就调用 putTreeVal,来进行添加
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 如果table 对应的索引位置 已经是个链表时 就使用for循环比较
//(1) 依次和该链表的每一个元素比较后,都不相同 则加入该链表的最后
// 注意在把元素添加到链表后 立即判断,该链表是否已经达到8个结点
// 若达到 就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树)
// 注意,在转成红黑树时,要进行判断 判断条件
// if(tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
resize();
//如果上面条件成立 先将table扩容
//如果条件不成立 才进行转成红黑树
//(2) 依次和该链表的每个元素比较过程中 如果有相同情况 就直接break
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;
}
*/
TreeSet集合讲解
源码讲解
package com.Set_;
import java.util.Comparator;
import java.util.TreeSet;
/**
* @author 不会写代码的16号
*/
public class TreeSet_ {
public static void main(String[] args) {
//1. 当我们使用无参构造器,创建TreeSet时,仍然是无序的
//2. 希望添加的元素,按照字符串大小来排序
//3. 使用TreeSet 提供的一个构造器,可以传入一个比较器(匿名内部类)
// 并指定排序规则
//4. 简单看看源码
/*
1. 构造器把传入的比较器对象,赋给了 TreeSet的底层的 TreeMap的属性this.comparator
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
2. 在 调用 treeSet.add("tom"), 在底层会执行到
if (cpr != null) {//cpr 就是我们的匿名内部类(对象)
do {
parent = t;
//动态绑定到我们的匿名内部类(对象)compare
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else //如果相等,即返回0,这个Key就没有加入
return t.setValue(value);
} while (t != null);
}
*/
// TreeSet treeSet = new TreeSet();
TreeSet treeSet = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//下面 调用String的 compareTo方法进行字符串大小比较
//如果要求加入的元素,按照长度大小排序
//return ((String) o2).compareTo((String) o1);
return ((String) o1).length() - ((String) o2).length();
}
});
//添加数据.
treeSet.add("jack");
treeSet.add("tom");//3
treeSet.add("sp");
treeSet.add("a");
treeSet.add("abc");//3
System.out.println("treeSet=" + treeSet);
}
}
Map接口讲解
Map接口实现类的特点
- Map与Collection并列存在。用于保存具有映射关系的数据:key—Value
- Map中的key和value可以是任何引用类型的数据,会封到HashMap$Node对象中
- Map中的key不允许重复,原因和HashSet一样,前面分析过源码
- Map中的value可以重复
- Map的key可以为null,value也可以为null,注意key为null,只能一个,value为null,可以为多个
- 常用String类作为Map的key
- key和value之间存在单向一对一关系,即通过指定key中能找到对应的value
package com.Map_;
import java.util.HashMap;
import java.util.Map;
/**
* @author 不会写代码的16号
*/
@SuppressWarnings({"all"})
public class Map_ {
public static void main(String[] args) {
//解读Map 接口实现类的特点, 使用实现类HashMap
//1. Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value(双列元素)
//2. Map 中的 key 和 value 可以是任何引用类型的数据,会封装到HashMap$Node 对象中
//3. Map 中的 key 不允许重复,原因和HashSet 一样,前面分析过源码.
//4. Map 中的 value 可以重复
//5. Map 的key 可以为 null, value 也可以为null ,注意 key 为null,
// 只能有一个,value 为null ,可以多个
//6. 常用String类作为Map的 key
//7. key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到对应的 value
Map map = new HashMap();
map.put("no1", "jack");//k-v 输出 no1=jack
map.put("no2", "smith");//k-v
map.put("no1", "tom");//当有相同的k , 就等价于替换.
map.put("no3", "tom");//k-v
map.put(null, null); //k-v
map.put(null, "abc"); //等价替换
map.put("no4", null); //k-v
map.put("no5", null); //k-v
map.put(1, "john");//k-v
map.put(new Object(), "luck");//k-v
// 通过get 方法,传入 key ,会返回对应的value
System.out.println(map.get("no2"));
System.out.println("map=" + map);
}
}
package com.Map_;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* @author 不会写代码的16号
*/
@SuppressWarnings({"all"})
public class MapSource_ {
public static void main(String[] args) {
Map map = new HashMap();
map.put("no1", "jack");//k-v 输出 no1=jack
map.put("no2", "张无忌");//k-v
//解读
//1. k-v 最后是 HashMap$Node node = newNode(hash, key, value, null)
//2. k-v 为了方便程序员的遍历,还会 创建 EntrySet 集合 ,该集合存放的元素的类型 Entry, 而一个Entry
// 对象就有k,v EntrySet<Entry<K,V>> 即: transient Set<Map.Entry<K,V>> entrySet;
//3. entrySet 中, 定义的类型是 Map.Entry ,但是实际上存放的还是 HashMap$Node
// 这时因为 static class Node<K,V> implements Map.Entry<K,V>
//4. 当把 HashMap$Node 对象 存放到 entrySet 就方便我们的遍历, 因为 Map.Entry 提供了重要方法
// K getKey(); V getValue();
Set set = map.entrySet();
System.out.println(set.getClass());// HashMap$EntrySet
for (Object obj : set) {
//System.out.println(obj.getClass()); //HashMap$Node
//为了从 HashMap$Node 取出k-v
//1. 先做一个向下转型 之前是object类 引用类型
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "-" + entry.getValue() );
}
Set set1 = map.keySet();
System.out.println(set1.getClass());
Collection values = map.values();
System.out.println(values.getClass());
}
}
@SuppressWarnings({"all"})
class Car {
}
@SuppressWarnings({"all"})
class Person{
}
Map六种遍历方法
package com.NXY.gather_;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* @author 不会写代码的16号
*/
public class MapFor {
public static void main(String[] args) {
Map map = new HashMap();
map.put("jack","jack1");
map.put("smith","smith1");
map.put("john","john1");
map.put("luck","luck1");
// 第一组:选取出 所有key 通过key 取出对应的values;
Set set = map.keySet();//得到一个key 的set集合
//方法一: 增强for循环
for(Object key:set){
System.out.println(key + "-" + map.get(key));
}
//方法二:迭代器 keySet是Set类型 可以用迭代器
Iterator iterator = set.iterator();
while(iterator.hasNext()){
Object next = iterator.next();
System.out.println(next + "-" + map.get(next));
}
//第二组:把所以的values 取出
Collection values = map.values();
//方法一:增强for
for (Object value :values) {
System.out.println(value);
}
// (2).迭代器
Iterator iterator1 = values.iterator();
while (iterator1.hasNext()) {
Object next = iterator1.next();
System.out.println(next);
}
//第三组:通过EntrySet 来获取 k-v
Set set1 = map.entrySet();//EntrySet<Map.Entry<k,v>
for(Object entry:set1){
//将entry 转成 Map.Entry
Map.Entry m = (Map.Entry) entry;//向下转型从引用类型 转成Map.Entry
System.out.println(m.getKey() + "-" + m.getValue());
}
Iterator iterator2 = set1.iterator();
while(iterator2.hasNext()){
Map.Entry m = (Map.Entry) iterator2.next();//向下转型
System.out.println(m.getKey() + "-" + m.getValue());
}
}
}
HashMap底层原理剖析.重点
- (k,v)是一个Node实现了Map.Entry<k,v>,查看HashMap的源码可以看到
- jdk7.0的hashMap底层实现了[数组+链表],jdk8.0底层[数组+链表+红黑树]
HashMap扩容机制
- HashMap底层维护了Node类型的数组table,默认为null
- 当创建对象时,将加载因子(lodafactor) 初始化为0.75
- 当添加key-val时,通过key的哈希值得到在table的索引。然后判断该索引是否有元素,如果没有元素直接添加,如果该索引处有元素,继续判断该元素的key是否和准备加入的key相等,如果相等,则直接替换val,如果不相等需要判断树结构还是链表结构,做出相应的处理,如果添加时发现容量不够,则需要扩容
- 第一次添加,则需要扩容table容量为16,临界值(threshold)为12
- 之后再次扩容,则需要扩容table容量为原来的2倍,临界值为原来的2倍
- 在java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8) 并且table的大小>=MIN_TREEIFY_CAPACITY(默认64)就会进行树化(红黑树)
package com.Map_;
import java.util.HashMap;
/**
1. @author 不会写代码的16号
*/
public class HashMapSource1 {
public static void main(String[] args) {
HashMap map = new HashMap();
map.put("java", 10);//ok
map.put("php", 10);//ok
map.put("java", 20);//替换value
System.out.println("map=" + map);//
// table 一开始为HashNode 数组 里面储存的是 Node内部类的结点
/*解读HashMap的源码+图解
1. 执行构造器 new HashMap()
初始化加载因子 loadfactor = 0.75
HashMap$Node[] table = null
2. 执行put 调用 hash方法,计算 key的 hash值 (h = key.hashCode()) ^ (h >>> 16)
public V put(K key, V value) {//K = "java" value = 10
return putVal(hash(key), key, value, false, true);
}
3. 执行 putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;//辅助变量
//如果底层的table 数组为null, 或者 length =0 , 就扩容到16
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//取出hash值对应的table的索引位置的Node, 如果为null, 就直接把加入的k-v
//, 创建成一个 Node ,加入该位置即可
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null); //内部类Node 创建一个 新的结点 包含了 kv
else {
Node<K,V> e; K k;//辅助变量
// 如果table的索引位置的key的hash相同和新的key的hash值相同,
// 并 满足(table现有的结点的key和准备添加的key是同一个对象 || equals返 回真)
// 就认为不能加入新的k-v
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)//如果当前的table的已有的Node 是红黑树,就按照红黑树的方式处理
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);
//加入后,判断当前链表的个数,是否已经到8个,到8个,后
//就调用 treeifyBin 方法进行红黑树的转换
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash && //如果在循环比较过程中,发现有相同,就break,就只是替换value
((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; //替换,key对应value
afterNodeAccess(e);
return oldValue;
}
}
++modCount;//每增加一个Node ,就size++
if (++size > threshold[12-24-48])//如size > 临界值,就扩容
resize();
afterNodeInsertion(evict);
return null;
}
5. 关于树化(转成红黑树)
//如果table 为null ,或者大小还没有到 64,暂时不树化,而是进行扩容.
//否则才会真正的树化
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();
}
*/
}
}
HashTable集合讲解
HashTable特点讲解:
- 存放的元素是键值对:即k-v
- HashTable的键和值都不能为null
- hashTable使用方法基本上和HashMap一样
- HashTable是线程安全,HashMap是线程不安全的
package com.Map_;
import java.util.Hashtable;
/**
* @author 不会写代码的16号
*/
@SuppressWarnings({"all"})
public class HashTable {
public static void main(String[] args) {
Hashtable hashTable = new Hashtable();
hashTable.put("john",100);
//键 和 值 不能为空 即 k-v不能为null
// hashTable.put(null,100);
// hashTable.put("john",null);
hashTable.put("lucy",100);
hashTable.put("lic",100);
hashTable.put("lic",88);
hashTable.put("hello1", 1);
hashTable.put("hello2", 1);
hashTable.put("hello3", 1);
hashTable.put("hello4", 1);
hashTable.put("hello5", 1);
hashTable.put("hello6", 1);
System.out.println(hashTable);
//简单说明一下Hashtable的底层
//1. 底层有数组 Hashtable$Entry[] 初始化大小为 11
//2. 临界值 threshold 8 = 11 * 0.75
//3. 扩容: 按照自己的扩容机制来进行即可.
//4. 执行 方法 addEntry(hash, key, value, index); 添加K-V 封装到Entry
//5. 当 if (count >= threshold) 满足时,就进行扩容
//5. 按照 int newCapacity = (oldCapacity << 1) + 1; 的大小扩容.
}
}