ArrayList--->LInkedList-->HashMap/LinkedHashMap/HashSet/LinkedHashSet-->HashTable-->ConcurrentHashMap-->TreeMap/TreeSet-->weakhashmap-->PriorityQueue-->ArrayDeque
提问上节课学习内容--->查看作业--->提交预习任务--->具体讲解
对这张图的继承关系和接口、类具有的特点进行讲解。
(1)Collection接口: 是存放一组单值的最大接口。 所谓的单值是指集合中的每个元素都是一个对象。一般很少直接使用此接口直接操作。
(2)List接口: 是Collection接口的子接口,也是最常用的接口。此接口对Collection接口进行了大量的扩充,里面的内容是允许重复允许为NULL的并且有序。
(3)Set接口: 是Collection接口的子类, 没有对Collection接口进行扩充,里面不允许存放重复内容!
(4)Map接口: 是存放一对值的最大接口!即接口中的每个元素都是一对, 以key --> value的形式保存
(5)Iterator接口: 集合的输出接口,用于输出集合中的内容,只能进行从前到后的单向输出!(这里会举一个例子帮助同学们理解什么是迭代器)
(6)ListIterator接口: 是Iterator接口的子接口,可以进行双向输出
(7)Enumeration接口:是最早的输出接口,用于输出指定集合中的内容
(8)SortedSet接口: 单值的排序接口。实现此接口的集合类,里面的内容可以使用比较器排序(Comparable和Comparator的区别)
(9)SortedMap接口: 存放一对值得排序接口。实现此接口的集合类,里面的内容按照key排序,使用比较器排序
(10)Queue接口: 队列接口。此接口的子类可以实现队列操作
(11)Map.Entry接口: Map.Entry的内容接口,每一个Map.Entry对象都保存着一对 key --> value的内容,每个Map接口中都保存有多个Map.Entry接口实例。(具体HashMap的Entry类中有实现)
(12)AbstractCollection :public abstract Iterator
(13)AbstractList :add(), set(), remove() get() size()
(14)AbstractSequentialList:size()、listIterator(),返回一个 ListIterator你需要实现一个 ListIterator, 实现它的 hasNext(), hasPrevious(), next(), previous(), 还有那几个 获取位置 的方法,这样你就得到一个不可变的 ListIterator 了。如果你想让它可修改,还需要实现 add(), remove(), set() 方法。
(1)对具体类实现的接口和继承类的讲解
我会选择讲解的第一个集合是ArrayList,再讲这个集合之前我会想把这个集合实现的接口继承的类所具有的特点讲解一遍。
(2)然后讲解具体的方法:
如何使用——>属性和构造函数(分析内部结构并且画图)——>常用方法讲解实现方法讲解(自己实现这个集合)——>如何遍历(迭代器提一下迭代器模式)——>引导学生自己实现集合和迭代器——>与前面所学的集合对比。
(3)功能相似集合特点比较
(4)学以致用:
① 提问:你认为今天学的新集合能够在哪些场景下使用?
② 布置作业:利用所学内容完成相应作业。
底层结构对比。根据底层数据结构首先要让学生明白两点:1.ArrayList底层是数组,所以对ArrayList的操作实际上就是对数组的操作。2.LinkedList的操作实际上就是操作双向链表,也就是对双向链表头尾指针以及其next域的操作。注:add()操作实现的是尾插。然后引导学生学习例理解为什么设计成数组和双向链表的形式给我们带来了哪些便利从而明确在什么情况下应该使用哪种链表。分析属性分析到modCount用法引出fail—fast机制和fail—safe机制。
问题解答:
(1)如何复制某个ArrayList到另一个ArrayList中去?写出你的代码?addAll()和clone()方法。引出问题深拷贝浅拷贝
(2)setMyArray()方法的陷阱。
public void setMyArray(String[] myArray){
this.myArray = myArray;
}
public void setMyArray(String[] newMyArray){
if(newMyArray == null){
this.myArray = myArray;
}else{
this.myArray = Arrays.copyOf(newMyArray,newMyArray);
}
}
(3)在索引中ArrayList的增加或者删除某个对象的运行过程?效率很低吗?解释一下为什么?
(4)并集、 交集、差集、
一、讲解顺序:ArrayList--->LInkedList-->HashMap/LinkedHashMap/HashSet/LinkedHashSet-->HashTable-->ConcurrentHashMap-->TreeMap/TreeSet-->weakhashmap-->PriorityQueue-->ArrayDeque 二、课程详细安排1.上课流程提问上节课学习内容--->查看作业--->提交预习任务--->具体讲解 2.拿图说话对这张图的继承关系和接口、类具有的特点进行讲解。
接口的具体特点如下:(1)Collection接口: 是存放一组单值的最大接口。 所谓的单值是指集合中的每个元素都是一个对象。一般很少直接使用此接口直接操作。 (2)List接口: 是Collection接口的子接口,也是最常用的接口。此接口对Collection接口进行了大量的扩充,里面的内容是允许重复允许为NULL的并且有序。 (3)Set接口: 是Collection接口的子类, 没有对Collection接口进行扩充,里面不允许存放重复内容! (4)Map接口: 是存放一对值的最大接口!即接口中的每个元素都是一对, 以key --> value的形式保存 (5)Iterator接口: 集合的输出接口,用于输出集合中的内容,只能进行从前到后的单向输出!(这里会举一个例子帮助同学们理解什么是迭代器) (6)ListIterator接口: 是Iterator接口的子接口,可以进行双向输出 (7)Enumeration接口:是最早的输出接口,用于输出指定集合中的内容 (8)SortedSet接口: 单值的排序接口。实现此接口的集合类,里面的内容可以使用比较器排序(Comparable和Comparator的区别) (9)SortedMap接口: 存放一对值得排序接口。实现此接口的集合类,里面的内容按照key排序,使用比较器排序 (10)Queue接口: 队列接口。此接口的子类可以实现队列操作 (11)Map.Entry接口: Map.Entry的内容接口,每一个Map.Entry对象都保存着一对 key --> value的内容,每个Map接口中都保存有多个Map.Entry接口实例。(具体HashMap的Entry类中有实现) (12)AbstractCollection :public abstract Iterator (13)AbstractList :add(), set(), remove() get() size() (14)AbstractSequentialList:size()、listIterator(),返回一个 ListIterator你需要实现一个 ListIterator, 实现它的 hasNext(), hasPrevious(), next(), previous(), 还有那几个 获取位置 的方法,这样你就得到一个不可变的 ListIterator 了。如果你想让它可修改,还需要实现 add(), remove(), set() 方法。 3.详细讲解(1)对具体类实现的接口和继承类的讲解 我会选择讲解的第一个集合是ArrayList,再讲这个集合之前我会想把这个集合实现的接口继承的类所具有的特点讲解一遍。 (2)然后讲解具体的方法: 如何使用——>属性和构造函数(分析内部结构并且画图)——>常用方法讲解实现方法讲解(自己实现这个集合)——>如何遍历(迭代器提一下迭代器模式)——>引导学生自己实现集合和迭代器——>与前面所学的集合对比。 (3)功能相似集合特点比较 (4)学以致用: ① 提问:你认为今天学的新集合能够在哪些场景下使用? ② 布置作业:利用所学内容完成相应作业。 三、各个集合讲解重点1.ArrayList/LinkedList底层结构对比。根据底层数据结构首先要让学生明白两点:1.ArrayList底层是数组,所以对ArrayList的操作实际上就是对数组的操作。2.LinkedList的操作实际上就是操作双向链表,也就是对双向链表头尾指针以及其next域的操作。注:add()操作实现的是尾插。然后引导学生学习例理解为什么设计成数组和双向链表的形式给我们带来了哪些便利从而明确在什么情况下应该使用哪种链表。分析属性分析到modCount用法引出fail—fast机制和fail—safe机制。 问题解答: (1)如何复制某个ArrayList到另一个ArrayList中去?写出你的代码?addAll()和clone()方法。引出问题深拷贝浅拷贝 (2)setMyArray()方法的陷阱。 public void setMyArray(String[] myArray){ (3)在索引中ArrayList的增加或者删除某个对象的运行过程?效率很低吗?解释一下为什么? (4)并集、 交集、差集、
2.HashMap/HashSet上课前先举例说明hashmap在实际中的使用,解释什么事Key-value结构,什么是Hash冲突,解决Hash冲突有哪些方法。如果两个不同的元素,通过哈希函数得出的实际存储地址相同怎么办?也就是说,当我们对某个元素进行哈希运算,得到一个存储地址,然后要进行插入的时候,发现已经被其他元素占用了,其实这就是所谓的哈希冲突,也叫哈希碰撞。前面我们提到过,哈希函数的设计至关重要,好的哈希函数会尽可能地保证 计算简单和散列地址分布均匀,但是,我们需要清楚的是,数组是一块连续的固定长度的内存空间,再好的哈希函数也不能保证得到的存储地址绝对不发生冲突。那么哈希冲突如何解决呢?哈希冲突的解决方案有多种:开放定址法(发生冲突,继续寻找下一块未被占用的存储地址),再散列函数法,链地址法,而HashMap即是采用了链地址法,也就是数组+链表的方式, 加载因子:a. 加载因子越大、填满的元素越多 = 空间利用率高、但冲突的机会加大、查找效率变低(因为链表变长了)b. 加载因子越小、填满的元素越少 = 空间利用率小、冲突的机会减小、查找效率高(链表不长)很多空间还没有利用就开始扩容 空间利用率很低 链表不会过长查询效率提高冲突概率减小但是频繁扩容导致耗费性。 问题解答: (5)为什么hashmap的容量要保持2的幂
a.使得h & (length-1) = h % length;因为取余的速效率低 为了提高效率采取位运算 & 3.LinkedHashMap/LinkedHashSet特点、底层结构通过双向链表保证了数据的有序性,在添加元素的时候多一步以及两者之间的关系。 4.HashTable:特点、实现 以讲解他与HashMap之间区别的形式进行,给学生一个多线程和锁的概念。 5.ConcurrentHashMap(计划多线程讲完再讲)锁优化,与HashTable的区别。扩容扩的是什么? 6.weakhashmap实现原理以及Java中的4中引用,分别有什么作用? 7.PriorityQueue:复习如何建立最大堆最小堆明确堆顶元素在整个堆中的关系。当添加新元素的时候现将元素放在堆的末尾然后再做出调整。 8.ArrayDeque什么是双端队列,引导学生自己建立双端队列。 9.Collections工具类Collections提供以下方法对List进行排序操作void reverse(List list):反转 void shuffle(List list),随机排序 void sort(List list),按自然排序的升序排序 void sort(List list, Comparator c);定制排序,由Comparator控制排序逻辑 void swap(List list, int i , int j),交换两个索引位置的元素 void rotate(List list, int distance),旋转。当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 list的前distance个元素整体移到后面。 查找,替换操作int binarySearch(List list, Object key), 对List进行二分查找,返回索引,注意List必须是有序的 int max(Collection coll),根据元素的自然顺序,返回最大的元素。 类比int min(Collection coll) int max(Collection coll, Comparator c),根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c) void fill(List list, Object obj),用元素obj填充list中所有元素 int frequency(Collection c, Object o),统计元素出现次数 int indexOfSubList(List list, List target), 统计targe在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target). boolean replaceAll(List list, Object oldVal, Object newVal), 用新元素替换旧元素。 同步控制Collections中几乎对每个集合都定义了同步控制方法,例如 SynchronizedList(), SynchronizedSet()等方法,来将集合包装成线程安全的集合。下面是Collections将普通集合包装成线程安全集合的用法,
设置不可变(只读)集合Collections提供了三类方法返回一个不可变集合, emptyXXX(),返回一个空的只读集合(这不知用意何在?) singleXXX(),返回一个只包含指定对象,只有一个元素,只读的集合。 unmodifiablleXXX(),返回指定集合对象的只读视图。 三、集合特点比较1.ArrayList和vector的区别(1)Vector的方法都是同步的(Synchronized),是线程安全的(thread-safe),而ArrayList的方法不是,由于线程的同步必然要影响性能,因此,ArrayList的性能比Vector好。 (2)当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加50%的大小,这样,ArrayList就有利于节约内存空间 (3)Vector可以设置capacityIncrement,而ArrayList不可以,从字面理解就是capacity容量,Increment增加,容量增长的参数。 2.ArrayLisst与LinkedList用法与区别(1)ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。 (2) 对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。 (3)对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。 3.Hashtable与HashMap区别
4.ConcurrentHashMap和Hashtable的区别(1) HashTable的线程安全使用的是一个单独的全部Map范围的锁,ConcurrentHashMap抛弃了HashTable的单锁机制,使用了锁分离技术,使得多个修改操作能够并发进行,只有进行SIZE()操作时ConcurrentHashMap会锁住整张表。 (2) HashTable的put和get方法都是同步方法, 而ConcurrentHashMap的get方法多数情况都不用锁,put方法需要锁。但是ConcurrentHashMap不能替代HashTable,因为两者的迭代器的一致性不同的,hash table的迭代器是强一致性的,而concurrenthashmap是弱一致的。 ConcurrentHashMap的get,clear,iterator 都是弱一致性的。 (3)初始化容量Hashtable:11,ConcurrentHashMap:16,
四、集合预习问题(1)该集合继承了哪些类实现了哪些接口,根据这些接口列出你觉得ArrayList具有哪些特点? (2)学会使用该集合的增删改查。 (3)该集合各个属性的含义,根据这些属性的含义你认为他底层是什么样数据结构。 (4)需不需要扩容,如何扩容,几倍扩容?(看情况) (5)根据该集合的特点你觉得你会在什么情况下使用它。(自由发挥)
|
上课前先举例说明hashmap在实际中的使用,解释什么事Key-value结构,什么是Hash冲突,解决Hash冲突有哪些方法。如果两个不同的元素,通过哈希函数得出的实际存储地址相同怎么办?也就是说,当我们对某个元素进行哈希运算,得到一个存储地址,然后要进行插入的时候,发现已经被其他元素占用了,其实这就是所谓的哈希冲突,也叫哈希碰撞。前面我们提到过,哈希函数的设计至关重要,好的哈希函数会尽可能地保证 计算简单和散列地址分布均匀,但是,我们需要清楚的是,数组是一块连续的固定长度的内存空间,再好的哈希函数也不能保证得到的存储地址绝对不发生冲突。那么哈希冲突如何解决呢?哈希冲突的解决方案有多种:开放定址法(发生冲突,继续寻找下一块未被占用的存储地址),再散列函数法,链地址法,而HashMap即是采用了链地址法,也就是数组+链表的方式,
加载因子:a. 加载因子越大、填满的元素越多 = 空间利用率高、但冲突的机会加大、查找效率变低(因为链表变长了)b. 加载因子越小、填满的元素越少 = 空间利用率小、冲突的机会减小、查找效率高(链表不长)很多空间还没有利用就开始扩容 空间利用率很低 链表不会过长查询效率提高冲突概率减小但是频繁扩容导致耗费性。
问题解答:
(1)为什么不直接采用经过hashCode()处理的哈希码 作为 存储数组table的下标位置?这个与后面的hashTable原因相同
结论:容易出现 哈希码 与 数组大小范围不匹配的情况,即 计算出来的哈希码可能 不在数组大小范围内,从而导致无法匹配存储位置哈希吗一般是整型,二进制的32位大夫海的int范围 = 2^31 ~ 2 ^ 31 -1 约为40亿的映射空间
HashMap的容量范围(数组大小)最小值:初始默认大小 = 16最大值:2^30 若设置的容量过大,将被最大值替换冲突: hashMap的容量范围一般不会取到最大值从而导致算出来的哈希码可能不再数组大小范围内,从而导致无法匹配存储位置为了解决 “哈希码与数组大小范围不匹配” 的问题,HashMap给出了解决方案:哈希码 与运算(&) (数组长度-1)。
(2)为什么采用 哈希码 与运算(&) (数组长度-1) 计算数组下标?
结论:根据HashMap的容量大小(数组长度),按需取哈希码一定数量的低位作为存储的数组下标位置,从而 解决 “哈希码与数组大小范围不匹配” 的问题
(3)为什么在计算数组下标前,需对哈希码进行二次处理:扰动处理?
结论:加大哈希码低位的随机性,使得分布更均匀,从而提高对应数组存储下标位置的随机性 & 均匀性,最终减少Hash冲突 让哈希码分布的更加均匀 从而避免出现哈希冲突
(4)若对应的key已存在,则 使用 新value 替换 旧value
注:当发生 Hash冲突时,为了保证 键key的唯一性哈希表并不会马上在链表中插入新数据,而是先查找该key是否已存在,若已存在,则替换即可
(5)为什么hashmap的容量要保持2的幂
a.使得h & (length-1) = h % length;因为取余的速效率低 为了提高效率采取位运算 &
b.保证了数组长度一定数偶数,h & (length-1)的结果可能是奇数也可能是偶数,保证了存储的随机分布。如果数组长度是奇数h & (length-1) 结果一定是0,也就是存储位置一定是偶数。
特点、底层结构通过双向链表保证了数据的有序性,在添加元素的时候多一步以及两者之间的关系。
特点、实现 以讲解他与HashMap之间区别的形式进行,给学生一个多线程和锁的概念。
锁优化,与HashTable的区别。扩容扩的是什么?
实现原理以及Java中的4中引用,分别有什么作用?
复习如何建立最大堆最小堆明确堆顶元素在整个堆中的关系。当添加新元素的时候现将元素放在堆的末尾然后再做出调整。
什么是双端队列,引导学生自己建立双端队列。
void reverse(List list):反转
void shuffle(List list),随机排序
void sort(List list),按自然排序的升序排序
void sort(List list, Comparator c);定制排序,由Comparator控制排序逻辑
void swap(List list, int i , int j),交换两个索引位置的元素
void rotate(List list, int distance),旋转。当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 list的前distance个元素整体移到后面。
int binarySearch(List list, Object key), 对List进行二分查找,返回索引,注意List必须是有序的
int max(Collection coll),根据元素的自然顺序,返回最大的元素。 类比int min(Collection coll)
int max(Collection coll, Comparator c),根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c)
void fill(List list, Object obj),用元素obj填充list中所有元素
int frequency(Collection c, Object o),统计元素出现次数
int indexOfSubList(List list, List target), 统计targe在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target).
boolean replaceAll(List list, Object oldVal, Object newVal), 用新元素替换旧元素。
Collections中几乎对每个集合都定义了同步控制方法,例如 SynchronizedList(), SynchronizedSet()等方法,来将集合包装成线程安全的集合。下面是Collections将普通集合包装成线程安全集合的用法,
|
Collections提供了三类方法返回一个不可变集合,
emptyXXX(),返回一个空的只读集合(这不知用意何在?)
singleXXX(),返回一个只包含指定对象,只有一个元素,只读的集合。
unmodifiablleXXX(),返回指定集合对象的只读视图。
(1)Vector的方法都是同步的(Synchronized),是线程安全的(thread-safe),而ArrayList的方法不是,由于线程的同步必然要影响性能,因此,ArrayList的性能比Vector好。
(2)当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加50%的大小,这样,ArrayList就有利于节约内存空间
(3)Vector可以设置capacityIncrement,而ArrayList不可以,从字面理解就是capacity容量,Increment增加,容量增长的参数。
(1)ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
(2) 对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
(3)对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
|
Hashtable |
HashMap |
继承的父类不同 |
Dictionary |
AbstractMap |
都实现了同时实现了map、Cloneable(可复制)、Serializable(可序列化)这三个接口 |
||
默认容量 |
11 |
16 |
并发操作 |
使用同步机制, 实际应用程序中,仅仅是Hashtable本身的同步并不能保证程序在并发操作下的正确性,需要高层次的并发保护。 下面的代码试图在key所对应的value值等于x的情况下修改value为x+1 { value = hashTable.get(key); if(value.intValue()== x){ hashTable.put(key, new Integer(value.intValue()+1)); } } 如2个线程同时执行以上代码,可能放入不是x+1,而是x+2. |
没有同步机制,需要使用者自己进行并发访问控制 |
数据遍历的方式 |
Iterator 和 Enumeration |
Iterator |
是否支持fast-fail |
用Iterator遍历,支持fast-fail 用Enumeration不支持fast-fail. |
支持fast-fail |
是否接受值为null的Key 或Value? |
不接受 |
接受 |
根据hash值计算数组下标的算法 |
当数组长度较小,并且Key的hash值低位数值分散不均匀时,不同的hash值计算得到相同下标值的几率较高
hash = key.hashCode(); index=(hash&0x7FFFFFFF) % tab.length;//去掉其符号位置 |
优于hashtable,通过对Key的hash做移位运算和位的与运算,使其能更广泛地分散到数组的不同位置
final int hash(Object k) { h ^= k.hashCode();
hash = hash (k); index = indexFor(hash, table.length);
static int hash(Object x) { int h = x.hashCode(); h += ~(h << 9); h ^= (h >>> 14); h += (h << 4); h ^= (h >>> 10); return h; }把后面几位移动到前面 static int indexFor(int h, int length) { return h & (length-1); } |
Entry数组的长度 |
缺省初始长度为11,初始化时可以指定initial capacity |
缺省初始长度为16,长度始终保持2的n次方初始化时可以指定initial capacity,若不是2的次方,HashMap将选取第一个大于initial capacity 的2n次方值作为其初始长度 |
LoadFactor负荷因子 |
0.75 |
|
负荷超过(loadFactor * 数组长度)时,内部数据的调整方式 |
扩展数组:2*原数组长度+1 |
扩展数组: 原数组长度 * 2 |
两者都会重新根据Key的hash值计算其在数组中的新位置,重新放置。算法相似,时间、空间效率相同 |
(1) HashTable的线程安全使用的是一个单独的全部Map范围的锁,ConcurrentHashMap抛弃了HashTable的单锁机制,使用了锁分离技术,使得多个修改操作能够并发进行,只有进行SIZE()操作时ConcurrentHashMap会锁住整张表。
(2) HashTable的put和get方法都是同步方法, 而ConcurrentHashMap的get方法多数情况都不用锁,put方法需要锁。但是ConcurrentHashMap不能替代HashTable,因为两者的迭代器的一致性不同的,hash table的迭代器是强一致性的,而concurrenthashmap是弱一致的。 ConcurrentHashMap的get,clear,iterator 都是弱一致性的。
(3)初始化容量Hashtable:11,ConcurrentHashMap:16,