二、复习
-
整体把握
- 层次一:针对不同特点的数据,能够选择对应接口的主要的类进行实例化和方法的调用
- 层次二:熟悉接口的不同的实现类的区别、特点
- 层次三:相关接口实现类的底层实现:存储结构
-
集合框架
- List结构
|-----Collection:存储一个一个的数据 * |-----List:存储有序的、可以重复的数据 : 替换数组,"动态数组" * |-----ArrayList:List的主要实现类;线程不安全的,效率高;底层使用Object[]存储 |-----LinkedList:底层使用双向链表存储数据;对于频繁的插入、删除操作,使用此类效率高。 |-----Vector:List的古老实现类;线程安全的,效率低;底层使用Object[]存储
- Set结构
|-----Set:存储无序的、不可重复的数据: 高中的集合 * |-----HashSet:是Set的主要实现类;线程不安全的;可以存储null值 |-----LinkedHashSet:是HashSet的子类;在添加数据之外,还通过一对指针记录先后添加的 顺序,使得遍历Set元素时,较HashSet效率更高。 |-----TreeSet:可以按照添加的元素的指定的属性的大小进行遍历;底层使用的是红黑树(排序二叉树的一种)
- Map结构
|-----Map:存储一对一对的数据(key-value):高中的函数。 y = f(x) (x1,y1),(x2,y2) * |-----HashMap:主要实现类;线程不安全的,效率高;存储null的key和value * |-----LinkedHashMap:是HashMap的子类,可以按照添加key-value的顺序实现遍历。 * 底层在HashMap结构的基础上,给前后添加的key-value额外添加了一对指针,记 录添加的先后顺序。 * |-----TreeMap:可以按照key-value中的key的大小实现排序遍历。底层使用红黑树实现的 * |-----Hashtable:古老实现类;线程安全的,效率低;不可以存储null的key或value * |-----Properties:是Hashtable的子类,key和value都是String类型,常用来处理属性文件 *
-
添加的单个元素所在的类是否要重写相关方法
- 元素如果添加到List中:要求元素所在的类要重写equals()
- 元素如果添加到HashSet、LinkedHashSet:要求元素所在的类要重写equals()和hashCode()
- 元素如果添加到TreeSet:要求考虑此元素的排序问题:① 自然排序 ②定制排序
-
添加的key-valule元素所在的类是否要重写相关方法
- 将key-value添加到HashMap或LinkedHashMap中时:
- key所在的类要重写hashCode()和equals()
- value所在的类重写equals()
- 将key-value添加到TreeMap中时:
- 要求考虑此key的排序问题:① 自然排序 ②定制排序
- 将key-value添加到HashMap或LinkedHashMap中时:
技巧:向集合中添加的自定义类的元素,将其equals()和hashCode()都重写一下,肯定没错!
- 面试题:
- ArrayList、LinkedList、Vector的区分
- HashMap和Hashtable的区分
- HashMap的源码解析
- ConcurrentHashMap、Hashtable的区分
- Collection、Collections的区分
三、Map的实现
3.1 HashMap的底层实现
原理的描述
3.1 jdk7中的实现过程:
* HashMap map = new HashMap();//底层创建长度为16的Entry数组table
* 。。。
* map.put(key1,value1);
*
* 。。。
*
* 1. 当添加key1-vulue1时,首先通过key1所在类的hashCode()方法,计算key1的哈希值
* 2. 此哈希值经过某种算法以后,确定其在table数组中的存放位置:i
* 3. 如果table[i]位置为空,则key1-value1添加成功 --->情况1
* 如果table[i]位置不为空,则比较key1与table[i]位置现有元素key2-value2进行对比
* 4.比较key1和key2的哈希值,如果哈希值不相同,则key1-value1添加成功 --->情况2
* 比较key1和key2的哈希值,如果哈希值相同,调用key1所在类的equals(),将key2作为参数传入equals()
* 5. 如果equals()返回false,则key1-value1添加成功 --->情况3
* 如果equals()返回true,则用value1替换原有的value2
*
* 情况1:将e1直接保存在数组的指定位置
* 情况2、情况3:此时e1与现有索引位置上的元素,以链表的方式进行保存。
*
* 3.2 jdk8相较于jdk7的不同:
* ① new HashMap():底层并没有创建一个长度为16的数组
* ② 当首次调用put()时,底层才创建长度为16的数组
* ③ jdk8中底层的数组是Node[],而非Entry[]
* ④ jdk7:新的元素方法到数组中,指向原有的元素
* jdk8:已有的元素的末尾指向新的元素.
* ⑤ 当某一个索引位置上的元素数操作8且数组table的长度超过64时,此索引位置上的所有元素要从链表结构改为红黑树结构
JDK7中的源码
- jdk7中:初始化底层数组的源码:HashMap map = new HashMap()
static final int DEFAULT_INITIAL_CAPACITY = 16;//默认数组的容量
static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认的加载因子
transient Entry<K,V>[] table;//保存Map添加的数据
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) //此循环确定底层数组的长度
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
table = new Entry[capacity];//确定了底层数组在默认初始化时,长度为16
useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
init();
}
Entry定义如下:
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, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
}
- put操作源码
public V put(K key, V value) {
if (key == null) //体现:HashMap可以添加null的key的
return putForNullKey(value);
int hash = hash(key); //二次hash
int i = indexFor(hash, table.length); //确定key在数组中的存放位置
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);//将key-value添加到当前table[i]中
return null;
}
final int hash(Object k) {
int h = 0;
if (useAltHashing) {
if (k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h = hashSeed;
}
h ^= k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
return h & (length-1);
}
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
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);
size++;
}
JDK8中的源码
- HashMap map = new HashMap()过程中:
static final float DEFAULT_LOAD_FACTOR = 0.75f;
transient Node<K,V>[] table;
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
- put()调用过程中:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) { //二次哈希
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
static final int TREEIFY_THRESHOLD = 8;//当某个索引的长度超过8时,要考虑将链表改为红黑树
static final int MIN_TREEIFY_CAPACITY = 64;
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//如果是首次调用put,此时table是null,就会调用resize()
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//此时i = (n - 1) & hash操作即为确定key-value在底层数组中的索引位置
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元素的下一个元素
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)
//当key值与现有的某个key相同时,将新的key对应的value替换原有的value
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;
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; //确定当前map中的临界值!首次threshold值为12
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab; //确定了底层的数组是长度为16的Node型数组
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;
}
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
3.2 LinkedHashMap的底层实现
-
添加key-value时,使用的父类HashMap中的put()
-
在调用put()中,调用了LinkeHashMap重写父类的newNode()
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) { LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<K,V>(hash, key, value, e); linkNodeLast(p); return p; }
static class Entry<K,V> extends HashMap.Node<K,V> { Entry<K,V> before, after; //用来记录添加的key-value的前一个元素和后一个元素 Entry(int hash, K key, V value, Node<K,V> next) { super(hash, key, value, next); } }
-
对于频繁的遍历Map的操作时,建议使用LinkedHashMap
3.3 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集合
总结:
增:put(Object key,Object value)
删:remove(Object key)
改:put(Object key,Object value)
查:get(Object key)
长度:size()
遍历:keySet();values();entrySet()
@Test
public void test1(){
HashMap map = new HashMap();
map.put("AA", 78);
map.put("BB", 88);
map.put("BB", 66);//修改操作
map.put("DD", 98);
map.put(43, "CC");//43:自动装箱
map.put(new Person("Tom",12), 78);
map.put(new Person("Tom",12), 99);//修改操作
System.out.println(map);
// System.out.println(map.size());
Object value = map.remove("DD");
System.out.println(value);
System.out.println(map);
System.out.println(map.get(new Person("Tom",12)));
System.out.println(map.containsKey(43));
System.out.println(map.containsValue(66));
}
@Test
public void test2(){
HashMap map = new HashMap();
map.put("Tom", 88);
map.put("Lily", 98);
map.put("Jerry", 98);
map.put("Jim", 78);
//遍历所有的key:keySet
Set keySet = map.keySet();
for(Object obj : keySet){
System.out.println(obj);
}
System.out.println();
//遍历所有的value集:
//方式一:values()
Collection values = map.values();
Iterator iterator = values.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println();
//方式二:get()
Set keySet1 = map.keySet();
for(Object obj : keySet1){
System.out.println(map.get(obj));
}
System.out.println();
//遍历所有的key-value
//方式一:
Set keySet2 = map.keySet();
for(Object obj : keySet2){
System.out.println(obj + "---> " + map.get(obj));
}
System.out.println();
//方式二:entrySet()
Set entrySet = map.entrySet();
for(Object obj : entrySet){
Map.Entry entry = (Map.Entry) obj;
// System.out.println(obj);//obj:即为每一个entry
System.out.println(entry.getKey() + "****" + entry.getValue());
}
3.4 TreeMap的使用举例(了解)
/*
* 1. 向TreeMap中添加key-value,要求所有的key必须是同一个类的对象。
* 2. 针对于key,实现自然排序或定制排序
*
*/
- 自然排序
//自然排序
@Test
public void test1(){
TreeMap map = new TreeMap();
Person p1 = new Person("Tom",12);
Person p2 = new Person("Jim",32);
Person p3 = new Person("Jerry",26);
Person p4 = new Person("Mike",43);
Person p5 = new Person("Lily",26);
map.put(p1,78);
map.put(p2,78);
map.put(p3,76);
map.put(p4,98);
map.put(p5,56);
Iterator iterator = map.keySet().iterator();
while(iterator.hasNext()){
Object key = iterator.next();
System.out.println(key + "--->" + map.get(key));
}
}
- 定制排序
//定制排序
@Test
public void test2(){
Comparator com = new Comparator(){
@Override
public int compare(Object o1, Object o2) {//o1,o2应该为Person的实例
if(o1 instanceof Person && o2 instanceof Person){
Person p1 = (Person)o1;
Person p2 = (Person)o2;
return -p1.getName().compareTo(p2.getName());
}
return 0;
}
};
TreeMap map = new TreeMap(com);
Person p1 = new Person("Tom",12);
Person p2 = new Person("Jim",32);
Person p3 = new Person("Jerry",26);
Person p4 = new Person("Mike",43);
Person p5 = new Person("Lily",26);
map.put(p1,78);
map.put(p2,78);
map.put(p3,76);
map.put(p4,98);
map.put(p5,56);
Iterator iterator = map.entrySet().iterator();
while(iterator.hasNext()){
Map.Entry entry = (Entry) iterator.next();
System.out.println(entry.getKey() + "--->" + entry.getValue());
}
}
其中,Person类定义如下:
package com.atguigu.java;
public class Person implements Comparable{
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
@Override
public int hashCode() { //return age + name.hashCode();
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
System.out.println("Person equals....");
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
//先按照年龄从小到大排列,再按照姓名从小到大排
@Override
public int compareTo(Object o) {
if(o instanceof Person){
Person p = (Person)o;
int value = this.age - p.age;
if(value != 0){
return value;
}else{
return this.name.compareTo(p.name);
}
}
throw new RuntimeException("输入的类型不匹配");
}
}
3.5 Properties的使用举例 (重点)
/*
* 1. Properties是Hashtable的子类
* 2. Properties中的key和value都是String类型
* 3. Properties在开发中,常用来处理属性文件
*
*/
@Test
public void test1() throws IOException{
Properties pros = new Properties();
FileInputStream fis = new FileInputStream("info.properties");
pros.load(fis);
String name = pros.getProperty("name");
String password = pros.getProperty("passord");
System.out.println("name = " + name + ",password = " + password);
}
四、集合的工具类:Collections (熟悉)
/*
* Collections:是一个操作 Set、List 和 Map 等集合的工具类
*
*
* 面试题:区分Collection 和 Collections
* Collection:java中提供的用于存储单列数据的集合框架接口,子接口有List和Set
*/
public class CollectionsTest {
/*
* 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 对象的所有旧值
*
*/
@Test
public void test1(){
List list = Arrays.asList(43,65,67,43,6,34,76,8,97,0,-8,5);
System.out.println(list);
// Collections.reverse(list);
// Collections.shuffle(list);
// Collections.sort(list);
// Collections.swap(list,1,3);
// int frequency = Collections.frequency(list,433);
//错误的写法
// ArrayList dest = new ArrayList();
// Collections.copy(dest, list);
//正确的写法
List dest = Arrays.asList(new Object[list.size()]);
Collections.copy(dest, list);
//Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合
//包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题
List synchronizedList = Collections.synchronizedList(list);
System.out.println(dest);
}
}
@Test
public void test12(){
List list = Arrays.asList(43,65,67,43,6,34,76,8,97,0,-8,5);
Collections.sort(list,new Comparator(){
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof Integer && o2 instanceof Integer){
Integer i1 = (Integer) o1;
Integer i2 = (Integer) o2;
return -i1.compareTo(i2);
}
return 0;
}
});
System.out.println(list);
}