目录
集合的理解和好处
数组
集合的理解和好处
继承图
编辑
简单实例
Collection接口和常用方法
1) add:添加单个元素
2) remove:删除指定元素
3) contains:查找元素是否存在
4) size:获取元素个数
5) isEmpty:判断是否为空
编辑
6) clear:清空
7) addAll:添加多个元素
编辑
8) containsAl:查找多个元素是否都存在
9) removeAll: 删除多个元素
Collection接口遍历元素
方式1-使用Iterator(迭代器)
Iterator实例
快速生成Iterator的while循环快捷键 itit
方式2-for循环增强
遍历实例
List接口和常用方法
☆ 常用方法
// list的冒泡排序实例
ArrayList的注意事项:
☆☆☆ArrayList底层结构和源码分析
无参构造器debug过程
idea工具配置完整显示数据
Vector底层结构和源码剖析
Vector和ArrayList比较
LinkedList底层结构
LinkedList的全面说明
LinkedList底层操作机制
模拟一个简单的双向链表
演示链表插入数据有多方便
编辑
LinkedList底层结构
// 进入无参构造器
LinkedList的遍历方式
ArrayList 和 LinkedList的比较
set接口和常用方法
Set接口基本介绍
Set接口的常用方法
Set接口的遍历方式
实例
HashSet的全面说明
HashSet底层机制说明
模拟HashMap(HashSet)的底层
HashSet底层机制具体说明
debug源码实例
// 1.第一个add
// 2.分析第二个add(无重复冲突)
// 3.分析第三个add(有重复冲突)
HashSet(HashMap)扩容和转成红黑树机制分析
测试怎么形成链表
补充扩容知识
练习题分析
编辑
编辑
LinkedHashSet全面说明
LinkedHashSet底层机制示意图
TreeSet
继承图
TreeSet区分与HashSet的最大特点是他可以排序
追源码
知识点补充:
Map接口
Map接口实现类的特点[很实用]
map接口的常用方法
map的六大遍历方式
HashMap小结
HashMap的底层机制和源码剖析
HashTable
继承图
HashTable的基本介绍
简单说一下底层机制
编辑
HashTable和HashMap的对比
Properties
介绍
什么时候回用到
常用方法
TreeMap
继承图
总结开发中该怎么选择哪种集合(参考)
collections的工具类
Collections工具类介绍(第一组)
实例
reverse(List)
编辑 shuffle(List)
编辑 sort(List)
编辑 sort(List, Comparator)
swap(List, int, int)
Collections工具类介绍(第二组)
集合的理解和好处
前面我们保存多个数据使用的是数组,那么数组有不足的地方,我们分析一下。
数组
1)长度开始时必须指定,而且一旦指定,不能更改
2)保存的必须为同一类型的元素
3)使用数组进行增加元素的示意代码 -比较麻烦
集合的理解和好处
集合
1)可以动态保存任意多个对象,使用比较方便!
2)提供了一系列方便的操作对象的方法:add、remove、set、get等
3)使用集合添加,删除新元素的示意代码-简洁了
继承图
简单实例
Collection接口和常用方法
1)collection实现子类可以存放多个元素,每个元素可以是0bject
2)有些Collection的实现类,可以存放重复的元素,有些不可以
3)有些Collection的实现类,有些是有序的(List),有些不是有序(Set)
4) Collection接口没有直接的实现子类,是通过它的子接口Set 和 List 来实现的
1) add:添加单个元素
2) remove:删除指定元素
3) contains:查找元素是否存在
4) size:获取元素个数
5) isEmpty:判断是否为空
6) clear:清空
7) addAll:添加多个元素
8) containsAl:查找多个元素是否都存在
9) removeAll: 删除多个元素
Collection接口遍历元素
方式1-使用Iterator(迭代器)
1) lterator对象称为迭代器,主要用于遍历 Collection 集合中的元素。
2)所有实现了Collection接口的集合类都有一个iterator()方法,用以返回 一个实现了lterator接口的对象,即可以返回一个迭代器。
3) lterator的结构.[图:]
4) lterator 仅用于遍历集合, Iterator 本身并不存放对象。
Iterator实例
快速生成Iterator的while循环快捷键 itit
记不住快捷键可以用ctrl+j 打开提示
方式2-for循环增强
增强for循环,可以代替iterator迭代器,特点:增强for就是简化版的iterator, 本质一样。只能用于遍历集合或数组。
遍历实例
List接口和常用方法
// List 接口是Collection 接口的子接口
1) List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复
2) List集合中的每个元素都有其对应的顺序索引,即支持索引
3) List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
// 实现List的其他类
☆ 常用方法
1) void add(int index, Object ele): //在index位置插入ele元素
2) boolean addAll(int index, Collection eles): //从index位置开始将 eles中的所有元素添加进来
3) Object get(int index): //获取指定index位置的元素
4) int indexOf(Object obj): //返回obj在集合中首次出现的位置
5) int lastIndexOf(Object obj): //返回obj在当前集合中末次出现的位置
6) Object remove(int index): //移除指定index位置的元素,并返回此元素
7) Object set(int index, Object ele): //设置指定index位置的元素为ele, 相当于是替换
8) List subList(int fromlndex, int tolndex): //返回从fromlndex到 tolndex位置的子集合
// list的冒泡排序实例
ArrayList的注意事项:
1)permits all elements, including null, ArrayList 可以加入null,并且多个
2)ArrayList 是由数组来实现数据存储的
3) ArrayList基本等同于Vector,除了ArrayList是线程不安全(执行效率高)看源码. 在多线程情况下,不建议使用ArrayList
☆☆☆ArrayList底层结构和源码分析
1)Arraylist中维护了一个0bject类型数组 transient Object[] elementData。(transient表示在序列化的时候忽略该变量)
2)当创建对象时,如果使用的是无参构造器,则初始elementData容量为0 (jdk7是10)。
3)当添加元素时: 先判断是否需要扩容,如果需要扩容,则调用grow方法,否则直接添加元素到合适位置。
4)如果使用的是无参构造器,如果第一次添加,需要扩容的话,则扩容elementData为10, 如果需要再次扩容的话,则扩容elementData为1.5倍。
5)如果使用的是指定容量capacity的构造器,则初始elementData容量为capacity。
6)如果使用的是指定容量capacity的构造器,如果需要扩容,则直接扩容elementData为 1.5倍。
无参构造器debug过程
// 进入
// 这里可以看到默认的是一个空数组, 也就是this.elementData={}
// 退出来继续往下走
// 再进去把 i 传给valueOf (是int类型的时候会处理成包装类) 因为只能存储对象类型而不是基本数据类型
// 再出来 再去执行
// 进入到add 方法, 这里面先通过ensureCapacityInternal去确认数组的大小是否够我添加一个数据, 然后去在把e赋值给elementData数组
// ensureCapacityInternal 这个方法先去确定这个elementData是不是一个空数组
// 如果是第一次添加, 是一个空数组, 就赋值给minCapacity为10
// 进入ensureExplicitCapacity
// modCount是记录当前这一个集合被修改的次数(主要是为了防止有多个线程同时去修改它, 如果有同时修改它会抛出一个异常)
// 这个判断是说, 如果传进来的值减去elementData的值大于0 就是说这个list的这个底层数组不够放传进来的值, 需要去扩容, 也就是说到这里调用grow()方法才是去扩容
// 进入grow()方法, 可以看到, 它先将elementData的容量复制给oldCapacity, 在将 oldCapacity + (oldCapacity >> 1) 赋值给newCapacity, 这里的oldCapacity >> 1 右移一位本质是除以2, 也就是oldCapacity+ (oldCapacity /2 ) 相当于是扩容1.5倍给newCapacity, 因为第一次的oldCapacity为0 , 所以第一次并没有使用这个1.5倍的扩容机制
[java基础揉碎]位运算符-CSDN博客
// 第一次的时候就将传进来的值10 复制给newCapacity (本来传进来的是1, 还记得上面ensureCapacityInternal 方法将低于10 的值都赋值为了10 )
// 如果 newCapacity 比数组的最大值还要大, 启用hugeCapacity进行处理
// 通过Arrays.copyOf进行扩容(在原有的基础上进行扩容, 以前就是会保留原本的数组进行扩容), 赋值后此时elementData在就有10个空的数组
注意: java8底层代码有所变化, 已实际当下debug为准, 但大同小异
注意, 注意, 注意因为list每次按照1.5倍进行扩容的, 那么当数据有超过15的时候应该被扩容为22个, 为什么显示还是16个?
是因为idea这个工具默认情况下, debug显示的数据是简化后的
idea工具配置完整显示数据
idea做以下设置
可以看到完成数据
Vector底层结构和源码剖析
Vector和ArrayList比较
LinkedList底层结构
LinkedList的全面说明
1) LinkedList实现了双向链表和双端队列特点
2)可以添加任意元素(元素可以重复),包括null
3)线程不安全,没有实现同步
LinkedList底层操作机制
1) LinkedList底层维护了一个双向链表
2) LinkedList中维护了两个属性first和last分别指向首节点和尾节点
3)每个节点 (Node对象象),里面又维护了prev、next、item三个属性,其中通过 prev指向前一个,通过next指向后一个节点。最终实现双向链表
4)所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高
//为什么说效率高, 因为当链表要删除一个对象的时候, 不需要加扩容, 只需要改一下指向就可以实现,
模拟一个简单的双向链表
演示链表插入数据有多方便
LinkedList底层结构
// 进入无参构造器
// 返回准备进如add
// 进行了int的装箱
// 返回在进入add
// linkLast 听其名知其意也就是说把e挂在了链表的最后
// 追进来 , 此时last 为null , 赋值给l 也就是说l 也为空, 然后new了一个新节点, 这个新节点的情况是 next 为null , item 为1 , prev为null, 前一个和后一个的指向都为null, first和last都指向它
// 查看new的这个新节点可以看到next 为null , item 为1 , prev为null
// 查看第二个add的执行情况
// 到这一步的时候, 第一步l =last, 将last指向第一个节点, 第二步创建一个新节点, l 作为prev 传进去, 也就是说prev指向第一个节点, item=e=2, 然后第三步last指向了新的节点, l.next也就是第一个节点的next指向了新的第二个节点, 此时就形成了一个双向链表, 然后size++就是说现在有两个元素, modCount++修改了两次
// 查看删除节点时候的情况
// 进去执行removeFirst(), 删除第一个节点
// 第一行代码先让 f 指向 first; 然后进行判断防止删除一个空链表, 最后执行unlinkFirst()
// 追进来, 第一步f.item 赋值给element, 也就是把第一个节点的内容取出来, 第二步f.next赋值给next , 也就是新节点next指向first的下一个节点, 第三步f.item=null, 也就是说置空第一个节点的内容, f.next=null,也就是将指向下一个节点的链条断掉了, 第四步 fist= next, 将first指向next新节点, 第五步将next.prev=null, 将新节点的上一个节点指向置空, 此时就删除了第一个节点
LinkedList的遍历方式
因为也是继承了List,所以和ArrayList的遍历方式相同
ArrayList 和 LinkedList的比较
如何选择ArrayList和LinkedList:
1)如果我们改查的操作多,选择ArrayList (可以根据索引直接定位)
2)如果我们增删的操作多,选择LinkedList (如果是查询是第一个节点一个一个查所以效率比较慢)
3)一般来说,在程序中,80%-90%都是查询,因此大部分情况下会选择ArrayList
4)在一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是ArrayList, 另外一个模块是LinkedList
注意 : ArrayList和LinkedList都是现成不安全的, 也就是说最好在单线程下面执行
set接口和常用方法
Set接口基本介绍
1) 无序(添加和取出的顺序不一致),没有索引
2) 不允许重复元素,所以最多包含一个null
3) JDK API中Set接口的实现类有:
Set接口的常用方法
和List接口一样, Set接口也是Collection的子接口,因此,常用方法和 Collection接口一样
Set接口的遍历方式
同Collection的遍历方式一样,因为Set接口是Collection接口的子接口。
1.可以使用迭代器
2.增强for
3.不能使用索引的方式来获取
实例
// 运行结果可以看出, null 可以被添加进去, 重复的元素不能被添加 , 添加的顺序和取出的顺序不一致
// 取出的顺序虽然不是添加的顺序, 但是取出的顺序是一致的, 不会一会一变
// 迭代器遍历
// 增强for遍历
HashSet的全面说明
1)HashSet实现了set接口
2)HashSet实际是HashMap, 源码:
3)可以存放null值,但是只能有一个null
4) HashSet不保证元素是有序的,取决于hash后,再确定索引的结果
5)不能有重复元素对象,前面Set接口使用已经讲过
注意: 我们添加两次add(new Dog("tom")) 可以添加进去 , 添加两次add(new String("hsp"))添加不进去, 具体需要接着看下面章节源码
HashSet底层机制说明
分析HashSet底层是HashMap, HashMap底层是(数组+链表(单向)+红黑树)
// 当它的链表到达一定量的时候, 而且满足数组的大小在一定范围的时候, 就会将这个链表进行一个树化, 变成一棵红黑树
模拟HashMap(HashSet)的底层
// 创建一个节点(单向没有prev了)
// 创建一个节点数组
// 将数组下标为2的位置添加john节点, 在john后面挂载jack节点
// 在jack后面挂载rose节点
// 此时节点数组如下, 为什么要使用 数组+链表(单向)+红黑树 ? 为了数据存取高效
HashSet底层机制具体说明
1.HashSet 底层是HashMap
2.添加一个元素时, 先得到hash值- 会转成- > 索引值
3.找到存储数据表table, 看这个索引位置是否已经存放的有元素
4.如果没有, 直接加入
5.如果有, 调用equals比较, 如果小童, 就放弃添加, 如果不相同, 则添加到最后
6.在java8中, 如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8 ), 并且table的大小 >= MIN_TREEIFY_CAPACITY(默认64), 就会进行树化(红黑树)
debug源码实例
// 1.第一个add
// 进入构造器, 可以看到底层是HashMap
// 进入add方法
// 进去后调的map的put方法
// 追进去put方法可以看到 , 一个是key, 一个是value, 这个key是传进来的字符串常量"java", value是PRESENT
// PRESENT是HashSet的一个共享不变对象, 这里主要是占位的目的, 能够让HashSet使用HashMap
// 进入到hash(key)里面去
// 进入 hash(key)可以看到在里面求出key的hashCode值, 然后将h右移16位再取异或得到一个值(所以这个值并不完全等价于hashCode, 因为还有算法)
// 得到这个值后再出来进入putVal方法里面去
// 详解 putVal 方法
--第一行定义了节点数组, 节点, 两个int 辅助变量
--第二行这里的table就是放节点的一个数组
--当它等于null的时候, 将执行resize()的返回值交给tab, 并计算他的长度赋值给n
-- 进去resize()查看执行了什么:
-- 这里oldCap等于0 , 执行的 这两个语句, 第一句就是给它一个新的值, 用于给数组到底开辟多大的空间使用
-- 这个值1左移 4位, 也就是1x2x2x2x2=16
--第二句通过0.75这个加载因子, 和这个默认值得到一个int整型的临界值
--这个临界值是做什么的? 本来这个数组的空间是16, 在装满16个元素的时候去去扩容, 但是在这里到达这个临界值12 的时候就准备去扩容了, 为什么这么做, 因为如果到16的时候去扩容, 如果此时有多个其他线程同时添加到这等待扩容的时候, 就阻塞在这了, 所以就做了这个叫做加载因子的处理, 就是缓冲层
--临界值赋值给threshold, 将16这个空间的新节点数组赋值给table
--最终返回的就是这个 newTab
--在执行完这个resize()之后, tab和 table都变成16个大小的了
--下一步 就是将刚才key对应hash值通过计算得到我这个key应该存放在table表的哪个索引位置 , 并且把这个位置的变量赋值给辅助变量p, 在判断这个p是否为空
-- 如果p为空, 表示还没有存放过元素, 就创建一个node, key就是要存放的值“java”, value就是前面说的PRESENT, 为什么要把这个key对应的hash传进去呢? 在将来它会去比较, 如果再有的话它会去比较你这个key对应的hash和下一个相不相等, 如果相等会往后面怼
-执行完后可以看到"java"已经被挂上去了
-- 此处执行到最后++modeCount表示修改过一次了, 然后++size代表现在放进去了一个是不是比这个12临界值大, 如果大于12 进行resize()在扩容
--此时没有大于12 执行到下面这条语句, 这条语句其实是HashMap留给它的子类比如像LinkedHashMap去实现再去做一些动作的, 对于HashMap来说这个方法是个空方法
--追进去可以看到什么都没干 (留着是为了它的子类去实现做一些动作的, 比如为了去实现一个有序的链表, 挂载它的一个双向链表设置的), 对与HashMap这个方法可以忽略
--接着往下可以看到这个时候返回一个空, 返回一个空代表成功
--如果不是空, 其实是返回的它原先的一个旧的值
// 出来可以看到put了之后, 如果返回的是空, 返回的就是true
// 自此就把第一个add数据java加进去了
// 2.分析第二个add(无重复冲突)
// 追进去
// 这里的hash(key)和第一次一样的, 就不追进去了, 看putVal()方法
// putVal()方法 详解
--此时第一个判断语句就不会进去了, 在table里面已经有数据了
--进入到第二个if , 此时判断tab[i=(n-1)&hash]这个位置是不是有值, 如果没有就去在这个位置上把key挂载上去
--然后走到下面返回
// 3.分析第三个add(有重复冲突)
// 同样追进去
// putVal()方法 详解
--执行到这里注意这个分支也肯定不为空了, 因为之前加过一个"java"相同的数据, 这里计算得到的也是相同的位置
--然后走这个else语句, 里面有分为了三种情况
--(1)第一个if是判断 如果当前索引位置对应的链表的第一个元素和准备添加的这个key的hash值一样 && 并且这个链表里面p的这个key 就是和我准备添加的这个key是同一个对象 || 或者 这个key不等于空,并且内容相同(不是同一个对象, 但是内容相同, 这里针对的是一个对象, 他们不是同一个对象, 但是有可能经过equals方法的重写, 调equals之后内容是相同的也可以)
--(2)第二个if是判断 p 是不是一棵红黑树, 如果是红黑树, 就调用putTreeVal来进行添加
--(3)第三个情况, 链表情况, 通过for循环去对链表进行挨个比较, 例如要进来的是tom, 在它一一比较的时候, 如果发现有tom就走掉了, 如果没有就直接挂载到最后
--在走第三种情况, 元素挂载到链表后, 会立即判断是不是得到8个节点, 是会执行treeifyBin()方法, 在这个里面会进行判断是否table表小于64, 也就是说如果节点到达8个并且表到达64就会转为红黑树
-- 回到第三个add("java")时直接走的第一种情况
--再往下此时e不等于空, 不等于空会返回oldValue, 其实就是PRESENT
--这里就不会返回空, 不返回空就是失败了
HashSet(HashMap)扩容和转成红黑树机制分析
测试怎么形成链表
下面的A类当中重写了hashCode(写死的一个数值), 让下面的这个循环一直处于hash相等但是内容不同的情况下, 就会一直在一个链表上挂载
补充扩容知识
扩容达到临界值12的时候会触发扩容16到32, 那么这个触发是只单独指table表到12是触发, 还是链表到12时也会触发 ? 结论是不管是链表还是table只要是新加一个节点newNode就会计算进去都会触发
练习题分析
// 以下为什么内容相同却加入了三个, 因为它的hash计算不同, 尽管内容相同但是他们是不同的对象
// 所以此时还没有满足当name和age的值相同时, 认为是相同的员工 , 不能添加进HashSet的条件, 怎么做呢? 需要重写hashCode值和equals
// 重写完之后当进行了第三个值"milan"添加的时候,他和第一个的name和age都相同, 那么他们的hash计算相同, 并且equals返回true内容相同, 那么HashSet就加不进了, 实现了上面的要求
LinkedHashSet全面说明
1) LinkedHashSet是HashSet的子类
2) LinkedHashSet 底层是一个 LinkedHashMap,底层维护了一个数组+双向链表
3) LinkedHashSet 根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序(图),这使得元素看起来是以插入顺序保存的。
4) LinkedHashSet不允许添重复元素
LinkedHashSet底层机制示意图
源码走的HashSet那一套
LinkedHashSet用的before和after形成的双向链表等价于pre和next
TreeSet
继承图
TreeSet区分与HashSet的最大特点是他可以排序
// 在没做任何处理的时候(也就是使用无参构造器的时候)TreeSet也是无序的
// TreeSet有以下五中构造器, 其中有一种是Comparator比较器
// Comparator比较器是一个接口,用到里面的compare进行排序
// 如果需要排序我们通过匿名内部类将这个排序规则传入到TreeSet里面
compareTo方法
1.返回参与比较的前后两个字符串的asc码的差值,如果两个字符串首字母不同,则该方法返回首字母的asc码的差值2.即参与比较的两个字符串如果首字符相同,则比较下一个字符,直到有不同的为止,返回该不同的字符的asc码差值,
3.如果两个字符串不一样长,可以参与比较的字符又完全一样,则返回两个字符串的长度差值
4.返回为正数表示a1>a2, 返回为负数表示a1<a2, 返回为0表示a1==a2;
追源码
// 反复退出进去跳过类加载等, 直到追到构造器这里 才算进入源码, 可以看到传给了TreeMap
// 在追进来可以看到将这个比较器赋值给了 它的一个属性, 就是comparator
// 退出来在执行看是怎么添加进去的
// 进入后看到是执行的这个put方法
// 在追进去, 进入到TreeMap的put方法里面
// 当在put方法执行到这一步的时候key是看到, 将comparator赋值给了cpr
// 执行到这里就是真正进行排序的操作, 这个cpr就是我们传进去的匿名内部类对象,当cmd小于0,将插入的元素放在当前节点的左子树中, 大于0则放在右子树中, 等于0则不放入 , 重新设置一下值(TreeSet的不重复就是在这里实现的, 如果key相同, 就重新设置值)
TreeSet底层的数据结构是红黑树也就是二叉搜索树(Binary Search Tree)。每个节点都有一个值和两个指针,分别指向左子节点和右子节点。
当使用比较器进行添加时,如果比较结果cmd小于0,表示要插入的元素应该放在当前节点的左子树中。此时,t会指向当前节点的左子节点,即t = t.left。
同理,如果比较结果cmd大于0,表示要插入的元素应该放在当前节点的右子树中。此时,t会指向当前节点的右子节点,即t = t.right。
// !!!注意如果将比较器改为根据长度大小排序
// 那么在add的时候, 长度相等的则不会加进去
// 因为在进行比较的时候, 有比较器cpr != null 就进入到了这个判断里面 如果等于0时就直接return 了, 也不会在往下执行了添加的操作
知识点补充:
在这里第一次添加的时候t等于空, 也掉了一下compare方法, 就是还是会动态绑定到我们那个匿名内部类, 但是在这里传的是两个key, 这两个key是同一个, 也就是肯定会是0 , 所以它也没有去接收, 并不对后面产生影响, 那掉这个方法主要目的是什么呢? 主要是为了检查是不是为空, 为空时会抛出一个异常
// 等会我们追进去的时候可以看到
// 在这里进行了操作, 如果为空就自己调用compareTO返回0, 如果不为空则走比较器的
// 还有一点要注意, 如果使用无参构造器的时候添加为null 的时候, 会在此处抛出空指针异常
// 使用比较器则不会(TreeMap同理)
Map接口
Map接口实现类的特点[很实用]
注意:这里讲的是JDK8的Map接口特点
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
8) Map存放数据的key-value示意图,一对k-v是放在一个Node中的,因为Node实现了 Entry 接口,有些书上也说一对k-v就是一个Entry(如图)[代码演示]
实际上在上文HashSet分析HashMap源码的时候可以看到, 真正的key和value是放在Node这个对象中的, 而entry里面只是指向了它(建立了引用), 在entry里面是将key放在set集合里面的,将value放在collection下面接口的一个实现子类里面的, entry放在entrySet集合里面, 为什么要把Node对象放在entrySet里面? 为了方便遍历
// 可以看到KeySet实现了Set接口的KeySet集合, values是实现了Collection的values集合
KeySet集合和values集合都是HashMap的内部类
--源码可以看到这两个内部类的继承关系
map接口的常用方法
1) put: 添加
2) remove: 根据键删除映射关系
3) get: 根据键获取值
4) size:获取元素个数
5) isEmpty:判断个数是否为0
6) clear:清除
7) containsKey:查找键是否存在
map的六大遍历方式
// 第一组 通过keySet 取出key value
// 第二组通过values 取出value
// 第三组 通过EntrySet 过去key value
HashMap小结
1) Map接口的常用实现类:HashMap、Hashtable和Properties
2) HashMap是Map 接口使用频率最高的实现类
3) HashMap 是以 key-val对的方式来存储数据[案例 Entry ]
4) key 不能重复,但是是值可以重复,允许使用null键和null值。
5)如果添加相同的key,则会覆盖原来的key-val,等同于修改.(key不会替换,val会替换)
6) 与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的
7)HashMap没有实现同步,因此是线程不安全的
HashMap的底层机制和源码剖析
//源码在HashSet已经分析过, 因为HashSet的源码就是用的HashMap, 再来回顾一下
//debug源码实例
//先初始化了一个加载因子, 这个加载因子是0.75
// 出来继续执行 进入put
// 对基本数据类型进行封装 (因为HashMap的键和值都必须是对象类型, 同时,通过使用包装类对象,我们可以在HashMap中存储null值,而基本数据类型是不允许为null的)
// 退出再进一次可以看到执行的就是putVal方法
//计算hash值
// 进入到 putVal 方法 后面怎么执行看HashSet源码实例, 是一样的
HashTable
继承图
// HashTable继承了Dictionary字典这个父类, 实现了map接口, 和串行化Serializable接口
HashTable的基本介绍
1)存放的元素是键值对:即K-V
2) hashtable的键和值都不能为null,否则会抛出NullPointerException
3) hashTable 使用方法基本上和HashMap一样
4) hashTable是线程安全的(加了synchronized),hashMap是线程不安全的
5) hashTable的底层就是一个HashTable$Entry的数组+单向链表 不会转为红黑树
简单说一下底层机制
1)底层有一个HashTable$Entry的数组 初始化大小为11
2) 有一个临界值 threshold = (int)0.75 * 11 到达临界值的时候进行扩容从11变成23 临界值变为17
3) 扩容机制
--当满8个的时候往里面追
--进行一个自动装箱 (转换成对应的包装类对象, 不允许储存基本类型)
--出来在往里面追, 先进行了一个null判断, 所以值不能为空
-- 直接往下走到这一步 就是在这里进行添加的
--进入 addEntry 现在table还是11 个大小, 里面已经存了8个元素 表里面有6个, 链表里面挂载有2个
-- 当count满足临界值之后就会进行扩容了, 执行rehash()
-- 进来后可以看到他的扩容计算规则是 (oldCapacity << 1)+1 也就是乘以2加以
--new了一个新Entry对象进行扩容
HashTable和HashMap的对比
Properties
介绍
1. Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对的形式来保存数据
2. 他的使用特点和Hashtable类似
3. Properties 还可以用于从xxx.properties 文件中,加载数据到Properties类对象, 并进行读取和修改
4. 说明:工作后xxx.properties文件通常作为配置文件,这个知识点在IO流举例
什么时候回用到
例如, 我们需要用用户名和密码对数据库进行操作, 如果我们直接将用户名和密码写在代码当中, 如果后面的数据库有变化的话, 比如部署到用户的服务器, 用的用户的数据库,那么不可能说要求用户数据库的用户名和密码也要和我们代码中的保持一致, 所以可以将用户名和密码保存到Properties文件当中, 在程序启动的时候去读取用户名和密码, 在对数据库进行操作
常用方法
TreeMap
继承图
提供有四种构造器
// 使用默认构造器创建的 TreeMap还是无序的
// 如果需要排序, 需要传入比较器, 和上面 TreeSet一样, TreeSet底层使用的就是TreeMap
源码见TreeSet, 注意事项也一样
总结开发中该怎么选择哪种集合(参考)
collections的工具类
Collections工具类介绍(第一组)
1) Collections是一个操作 Set、List 和 Map等集合的工具类
2) Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作排序操作:(均为static方法)
1) reverse(List):反转 List 中元素的顺序
2) shuffle(List):对List 集合元素进行随机排序
3) sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
4) sort(List, Comparator):根据指定的Comparator 产生的顺序对 List 集合元素进行排序
5) swap(List, int, int):将指定list 集合中的i处元素和j处元素进行交换
实例
reverse(List)
shuffle(List)
sort(List)
sort(List, Comparator)
swap(List, int, int)
Collections工具类介绍(第二组)
1) Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
2) Object max(Collection, Comparator):根据 Comparator指定的顺序, 返回给定集合中的最大元素
3) Object min(Collection)
4) Object min(Collection, Comparator)
5) int frequency(Collection, Object):返回指定集合中指定元素的出现次数
6) void copy(List dest,List src): 将src中的内容复制到dest中
7) boolean replaceAll(List list, Object oldVal, Object newVal):使用新值替换 List 对象的所有旧值