志之难也,不在胜人,在自胜也
集合与数组的区别
相同点:
都是容器,可以存储多个数据
不同点:
数组的长度是不可变的,集合的长度是可变的
数组可以存基本数据类型和引用数据类型
集合只能存引用数据类型,如果要存基本数据类型,需要存对应的包装类
集合体系结构
基本数据结构
栈
二叉树的遍历方式
前序遍历:根左右
中序遍历:左根右
后序遍历:左右根
层序遍历:
二叉查找树
弊端:但数据有序、单一,会造成树偏移
平衡二叉树
平衡二叉树保持平衡机制(旋转)
左旋
自定义链式左旋:左旋后的节点即将作为根节点且不全有左右字数
1. 首先找到不平衡的节点,也就是要确定支点
2. 进行左旋
3. 左旋步骤
4. 左旋后
自定义叉式左旋:左旋后即将作为根节点且已经有了两个左右子节点
1. 寻找支点进行左旋,7是支点,且左旋的部分涉及到分叉
2. 左旋步骤,先把即将要变为根节点的多余的节点当做没有,按照自定义链式左旋,左旋后,
3. 再将刚才多余的节点,链接到旧根节点
右旋
左旋和右旋的原理一样,只是旋转方向不同
需要旋转的情况
左左——整体右旋
左右——局部左旋整体右旋
右右——整体左旋
右左——局部右旋整体左旋
红黑树
是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构
1972年出现,被称为平衡二叉B树
是一种特殊的二叉查找树,红黑树的每一个节点上都有存储位表示节点的颜色
每一个节点可以是红或者黑,红黑树不是高度平衡的,他的平衡是通过”红黑规则”实现
红黑规则
每一个节点或是红色的、或是黑色的
根节点必须是黑色
如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点Nil是黑色的
如果某一个节点是红色的,那么它的子节点必须是黑色(不能出现两个红色相连的情况)
对每一个节点,从该节点到其所有后叶代节点的简单路径上,均包含相同数目的黑色节点(路径上黑色节点的数量是一样的)
添加节点的规则
单列集合:一次添加一个数据
List系列集合:
有序:存入的顺序和取出来的顺序是一致的。
可重复:集合中的元素可以重复。
有索引:可以按照根据索引取出对应的元素。
set系列集合:
无序:存入的顺序和取出的顺序不一定是一致的。
不重复:集合中的元素是不可以重复。
无索引:不能通过索引获取每一个元素
使用场景
Collection集合
概述
是单列集合的祖宗接口,它的功能是全部单列集合可以继承使用的
是单例集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素
JDK 不提供此接口的任何直接实现.它提供更具体的子接口(如Set和List)实现
创建Collection集合的对象
1 Collection<String> coll = new ArrayList <>();
Collection集合常用方法
方法名
说明
boolean add(E e)
添加元素
boolean remove(Object o)
从集合中移除指定的元素
boolean removeIf(Object o)
根据条件进行移除
void clear()
清空集合中的元素
boolean contains(Object o)
判断集合中是否存在指定的元素
boolean isEmpty()
判断集合是否为空
int size()
集合的长度,也就是集合中元素的个数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 Collection<String> coll = new ArrayList <>(); coll.add("aaa" ); coll.clear(); coll.remove("aaa" ); boolean result = coll.contains("aaa" );System.out.println(result); public boolean contains (Object o) { return indexOf(o) >= 0 ; } public int indexOf (Object o) { return indexOfRange(o, 0 , size); } int indexOfRange (Object o, int start, int end) { Object[] es = elementData; if (o == null ) { for (int i = start; i < end; i++) { if (es[i] == null ) { return i; } } } else { for (int i = start; i < end; i++) { if (o.equals(es[i])) { return i; } } } return -1 ; } boolean result = coll.isEmpty();System.out.printlf(result); int size = coll.size();System.out.println(size);
Collection集合的遍历方式
集合的遍历方式为通用遍历方式,即:单列、双列集合均可以使用
迭代器遍历:不依赖索引、创建指针、移动指针获取元素
迭代器Java中的类是Iterator,集合的专用遍历方式
Iterator iterator(): 返回此集合中元素的迭代器,通过集合对象的iterator()方法得到
Iterator中的常用方法
boolean hasNext(): 判断当前位置是否有元素可以被取出
E next(): 获取当前位置的元素,将迭代器对象移向下一个索引位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class IteratorDemo1 { public static void main (String[] args) { Collection<String> c = new ArrayList <>(); c.add("hello" ); c.add("world" ); c.add("java" ); c.add("javaee" ); Iterator<String> it = c.iterator(); while (it.hasNext()) { String s = it.next(); System.out.println(s); } } }
细节点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class A04_CollectionDemo4 { public static void main (String[] args) { Collection<String> coll = new ArrayList <>(); coll.add("aaa" ); coll.add("bbb" ); coll.add("ccc" ); coll.add("ddd" ); Iterator<String> it = coll.iterator(); while (it.hasNext()){ String str = it.next(); System.out.println(str); } System.out.println(it.hasNext()); Iterator<String> it2 = coll.iterator(); while (it2.hasNext()){ String str = it2.next(); System.out.println(str); } } }
迭代器中的删除方法
void remove(): 删除迭代器对象当前指向的元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class IteratorDemo2 { public static void main (String[] args) { ArrayList<String> list = new ArrayList <>(); list.add("a" ); list.add("b" ); list.add("b" ); list.add("c" ); list.add("d" ); Iterator<String> it = list.iterator(); while (it.hasNext()){ String s = it.next(); if ("b" .equals(s)){ it.remove(); } } System.out.println(list); } }
增强for遍历
它是JDK5之后出现的,其内部原理是一个Iterator迭代器
实现Iterable接口的类才可以使用迭代器和增强for
简化数组和Collection集合的遍历(所有的单列集合才能用增强for)
格式 for(集合/数组中元素的数据类型 变量名 : 集合/数组名) { // 已经将当前遍历到的元素封装到变量中了,直接使用变量即可 }
快速生成增强for代码:
集合名字.for 回车
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class MyCollectonDemo1 { public static void main (String[] args) { ArrayList<String> list = new ArrayList <>(); list.add("a" ); list.add("b" ); list.add("c" ); list.add("d" ); list.add("e" ); list.add("f" ); for (String str : list){ System.out.println(str); } } }
forEach遍历方式
JDK8开始的Lambda表达式,提供了一种更简单、更直接的遍历集合的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class A07_CollectionDemo7 { public static void main (String[] args) { Collection<String> coll = new ArrayList <>(); coll.add("zhangsan" ); coll.add("lisi" ); coll.add("wangwu" ); coll.forEach(s -> System.out.println(s)); } }
List集合
Collection的方法List都继承了
List集合因为有索引,所以有很多索引操作的方法
List集合特有的方法
方法名
描述
void add(int index,E element)
在此集合中的指定位置插入指定的元素
E remove(int index)
删除指定索引处的元素,返回被删除的元素
E set(int index,E element)
修改指定索引处的元素,返回被修改的元素
E get(int index)
返回指定索引处的元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 public class MyListDemo { public static void main (String[] args) { List<String> list = new ArrayList <>(); list.add("aaa" ); list.add("bbb" ); list.add("ccc" ); } private static void method4 (List<String> list) { String s = list.get(0 ); System.out.println(s); } private static void method3 (List<String> list) { String result = list.set(0 , "qqq" ); System.out.println(result); System.out.println(list); } private static void method2 (List<String> list) { String s = list.remove(0 ); System.out.println(s); System.out.println(list); } private static void method1 (List<String> list) { list.add(0 ,"qqq" ); System.out.println(list); } }
List集合的遍历方式
迭代器
列表迭代器
增强for
Lambda表达式
普通for循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 List<String> list = new ArrayList <>(); list.add("aaa" ); list.add("bbb" ); list.add("ccc" ); ListIterator<String> it = list.listIterator(); while (it.hasNext()){ String str = it.next(); if ("bbb" .equals(str)){ it.add("qqq" ); } } System.out.println(list);
ArrayList集合
底层原理
空参构造创建ArrayList对象的时候,他在底层先创建了一个默认长度为0的数组。 数组名字:elementDate 定义变量size,记录数组长度
当添加第一个元素的时候,底层会创建一个新的长度为10的数组,size++
size这个变量有两层含义:
①:元素的个数,也就是集合的长度
②:下一个元素的存入位置
存满时,会创建新的数组,长度为原数组的1.5被,将原数组的数据拷贝在新数组中
如果一次添加多个元素,1.5倍还放不下,则创建新数组,长度以实际为准
第一次添加一个元素时:
当添加元素需要扩容时:
LinkedList集合
底层原理
底层数据结构是双链表、查询慢、增删快
如果操作的是首尾元素,速度也是极快的,LinkedList本身多了许多直接操作首尾元素的API
1 2 3 4 5 6 7 8 9 10 11 private static class Node <E> { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this .item = element; this .next = next; this .prev = prev; } }
LinkedList第一次添加元素
过程:
创建LinkedList对象,会产生一个堆内存,其中的两个结点
Node first:用于存储目前链表中,第一个结点的地址值 Node last:用于存储目前链表中,最后一个结点的地址值
调用LinkedList对象中的add方法,add方法中会调用linkLast方法(拼接在后面)
在linkLast方法中,先用变量l来获取目前链表中最后一个结点的地址值last(因为链表刚创建、所以此处last为空)
创建一个新结点,按照固定传参new Node<>(l,e,null),将链表最后一个结点的地址值l放入新结点的前驱(此时,头部也为空,因为是第一个结点,没有前结点)。
将链表最后一个结点的地址值last进行更新,更新为新结点的地址值
判断l是否为空,也就是判断新节点的前驱是否为空,也就是判断该结点是不是链表中的第一个结点,此处是,将链表中的第一个结点的地址first更新为这个新结点的地址值
此时第一次创建的新结点 既是链表的第一个结点(first地址值),也是链表的最后一个结点(last地址值)
完成添加第一个结点
LinkedList第n次添加元素(n>=2)
过程:
调用LinkedList对象中的add方法,add方法中会调用linkLast方法(拼接在后面)
在LinkedList方法中,先用变量l来获取目前链表中最后一个结点的地址值last,准备用于插入到新节点的前驱
创建一个新结点 new Node<>(l, e, null),然后将刚才取出的链表最后一个结点的地址值last、元素e填入新结点中
已有新的结点准备链入,此时更新链表最后一个结点的地址值last为新结点的地址值
此时,链表中最后一个结点l链接新的结点,也就是最后一个结点的后继链接新结点的地址值,需注意:此处为l,因为last已更新为最新结点的地址值,而l是存放链表最后一个结点的地址值,l.next=newNode,将链表上一个结点的后继更新为新结点的地址值
完成链入
迭代器
底层原理、并发修改异常原理
泛型深入
泛型是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,它提供了编译时类型安全检测机制
泛型的格式:<数据类型>
泛型只能支持引用数据类型,基本数据类型的包装类,指定泛型的类型后,可以传入该类类型和其子类类型
泛型的好处:1.把运行时期的问题提前到了编译期间 2.避免了强制类型转换
Java中的泛型是伪泛型,当元素存入集合中,底层还是会当做Object类来处理,当获取这些元素的时候,集合底层自动进行了强转,转为泛型对应的数据类型。称为泛型的擦除。
泛型定义的范围
泛型可以定义在类后面、方法上面、接口后面
泛型类
使用场景:当一个类中,某个变量的基本数据类型不确定的时候,就可以定义带有泛型的类
泛型方法
其中E…e为可变参数,可以传递任意个参数,使用循环遍历参数。
泛型接口
泛型的继承和通配符
泛型不具备继承性,但数据具备继承性
采用通配符就可以限制传递参数
Set集合
无序:存取顺序不一致
不重复:可以去重
无索引:没有带索引的方法,不能使用普通for循环遍历,也不能通过索引来获取元素
Set集合的实现类
HashSet:无序、不重复、无索引
LinkedHashSet:有序、不重复、无索引
TreeSet:可排序、不重复、无索引
Set接口中的方法基本上与Collection的API一致
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class MySet1 { public static void main (String[] args) { Set<String> set = new TreeSet <>(); set.add("ccc" ); set.add("aaa" ); set.add("aaa" ); set.add("bbb" ); Iterator<String> it = set.iterator(); while (it.hasNext()){ String s = it.next(); System.out.println(s); } System.out.println("-----------------------------------" ); for (String s : set) { System.out.println(s); } s.forEach(str -> System.out.println(str)); } }
HashSet集合
底层数据结构是哈希表
存取无序
不可以存储重复元素
没有索引,不能使用普通for循环遍历
哈希表
JDK1.8以前 数组 + 链表
JDK1.8以后
节点个数少于等于8个 数组 + 链表
节点个数多于8个 数组 + 红黑树
哈希值
是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
该方法定义在Object类中,所有对象都可以调用,默认使用地址值计算
一般情况下,会重写hashCode方法,利用对象内部的属性值计算哈希值
对象的哈希值的特点
如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
在小部分情况下,不同属性值或者不同的地址值计算出来的哈希值也有可能一样(哈希碰撞)
HashSet JDK8以前的底层原理
创建一个默认长度16,默认加载因子为0.75的数组,数组名table
2. 根据元素的哈希值跟数组的长度进行计算,算出应存入的位置
3. 判断当前位置是否为null,如果是null直接存入 4. 如果当前计算的位置不是null,表示有元素,则调用equals方法去比较属性值 5. 一样:不存 不一样:存入数组
JDK8以前:新元素存入数组,老元素挂在新元素的下面
JDk8以后:新元素直接挂在老元素的下面
如果集合中存储的是自定义对象,必须重写hashCode和equals方法
加载因子:是数组扩容时间,当数组内元素达到16*0.75个元素 12个,就会触发扩容机制,扩容为原先的2倍
当数组的长度大于等于64且链表(挂的元素)的长度大于8,链表会自动转换为红黑树
一个元素下面挂多个元素的情况,实际上是由于哈希冲突造成的。当不同的元素经过哈希计算得到相同的数组索引位置时,它们会被存储在同一个位置对应的链表或者红黑树中。这种设计可以保证在HashSet中快速存储和查找元素,同时保证了存储的效率和性能。
HashSet的三个问题
为什么存和取的顺序不一样?
因为元素添加到数组中,是从数组的0索引开始依次向后遍历,全部遍历,遇到链表和红黑树,会先取链表和红黑树中元素,因此顺序会改变
HashSet为什么没有索引?
底层涉及数组、链表、红黑树无法使用索引
HashSet是利用什么机制保证数据去重的?
利用HashCode方法和equals方法,不同的数据算出的哈希值不同,如果遇到哈希冲突,再用equals方法比较数据属性值是否相同,相同则剔除
LinkedHashSet集合
底层原理
底层数据结构依旧是哈希表,只是每个元素又额外多了一个双链表的机制记录存储的顺序
遍历的时候从双向链表的头结点遍历,保证了数据的有序
TreeSet集合
添加自定义对象时,必须指定排序规则
不重复、无索引、可排序(默认从小到大)
TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都很好
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class TreeSetDemo01 { public static void main (String[] args) { TreeSet<Integer> ts = new TreeSet <Integer>(); ts.add(10 ); ts.add(40 ); ts.add(30 ); ts.add(50 ); ts.add(20 ); ts.add(30 ); for (Integer i : ts) { System.out.println(i); } ts.forEach(s->System.out.print(s)); } }
TreeSet集合默认的规则
对于数值类型,Integer、Double,默认按照从小到大的顺序进行排序
对于字符、字符串类型:按照字符在ASCII码表中的数字升序进行排序(从小到大),多个字符的字符串,从第一个字母开始依次比较字母的ASCII值
TreeSet比较规则
这里的需求是指一些特殊的排序,如字符长度等,第一种默认是数字从小到大,字母ASCII码从小到大
比较器内的写法
this:表示当前要添加的元素
o:表示已经在红黑树存在的元素
返回值:
负数:认为要添加的元素是小的,在左边
正数:认为要添加的元素是大的,在右边
0:认为添加的元素已经存在
方式一:
默认排序/自然排序:javabean类实现Comparable接口指定比较规则
需求:按照学生对象的年龄进行排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 public class Student implements Comparable <Student>{ private String name; private int age; public Student () { } public Student (String name, int age) { 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 "Student{" + "name='" + name + '\'' + ", age=" + age + '}' ; } @Override public int compareTo (Student o) { int result = this .age - o.age; result = result == 0 ? this .name.compareTo(o.getName()) : result; return result; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class MyTreeSet2 { public static void main (String[] args) { TreeSet<Student> ts = new TreeSet <>(); Student s1 = new Student ("zhangsan" ,23 ); Student s2 = new Student ("lisi" ,24 ); Student s3 = new Student ("wangwu" ,25 ); Student s4 = new Student ("zhaoliu" ,26 ); Student s5 = new Student ("qianqi" ,27 ); ts.add(s1); ts.add(s2); ts.add(s3); ts.add(s4); ts.add(s5); for (Student student : ts) { System.out.println(student); } } }
方式二:创建TreeSet对象时候,传递比较器Comparator指定规则 有参构造,传递接口的实现类,重写比较方法 o1:表示当前要添加的元素
o2:表示已经在红黑树存在的元素
返回值规则与之前相同
案例需求
存储老师对象并遍历,创建TreeSet集合使用带参构造方法
要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class Teacher { private String name; private int age; public Teacher () { } public Teacher (String name, int age) { 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 "Teacher{" + "name='" + name + '\'' + ", age=" + age + '}' ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class MyTreeSet4 { public static void main (String[] args) { TreeSet<Teacher> ts = new TreeSet <>(new Comparator <Teacher>() { @Override public int compare (Teacher o1, Teacher o2) { int result = o1.getAge() - o2.getAge(); result = result == 0 ? o1.getName().compareTo(o2.getName()) : result; return result; } }); Teacher t1 = new Teacher ("zhangsan" ,23 ); Teacher t2 = new Teacher ("lisi" ,22 ); Teacher t3 = new Teacher ("wangwu" ,24 ); Teacher t4 = new Teacher ("zhaoliu" ,24 ); ts.add(t1); ts.add(t2); ts.add(t3); ts.add(t4); for (Teacher teacher : ts) { System.out.println(teacher); } } }
双列集合:一次添加一对数据
双列集合的特点:
双列集合一次需要存一对数据,分别为键和值
键不能重复,值可以重复
键和值是一一对应的,每一个键只能找到自己对应的值
键+值这个整体我们称之为“键值对”或“键值对对象”,在Java中叫做Entry对象
Map集合
Map是双列集合的顶层接口,它的功能是全部双列集合都可以继承使用的
Map常见的API
方法名
说明
V put(K key,V value)
添加元素
V remove(Object key)
根据键删除键值对元素
void clear()
移除所有的键值对元素
boolean containsKey(Object key)
判断集合是否包含指定的键
boolean containsValue(Object value)
判断集合是否包含指定的值
boolean isEmpty()
判断集合是否为空
int size()
集合的长度,也就是集合中键值对的个数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class MapDemo02 { public static void main (String[] args) { Map<String,String> map = new HashMap <String,String>(); map.put("张无忌" ,"赵敏" ); map.put("郭靖" ,"黄蓉" ); map.put("杨过" ,"小龙女" ); System.out.println(map.remove("郭靖" )); System.out.println(map.remove("郭襄" )); map.clear(); System.out.println(map.containsKey("郭靖" )); System.out.println(map.containsKey("郭襄" )); System.out.println(map.isEmpty()); System.out.println(map.size()); System.out.println(map); } }
Map集合获取值的方式
方法名
说明
V get(Object key)
根据键获取值
Set keySet()
获取所有键的集合
Collection values()
获取所有值的集合
Set<Map.Entry<K,V>> entrySet()
获取所有键值对对象的集合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class MapDemo03 { public static void main (String[] args) { Map<String, String> map = new HashMap <String, String>(); map.put("张无忌" , "赵敏" ); map.put("郭靖" , "黄蓉" ); map.put("杨过" , "小龙女" ); Collection<String> values = map.values(); for (String value : values) { System.out.println(value); } } }
Map集合遍历方式
键找值
遍历思路
我们刚才存储的元素都是成对出现的,所以我们把Map看成是一个夫妻对的集合
把所有的丈夫给集中起来
遍历丈夫的集合,获取到每一个丈夫
根据丈夫去找对应的妻子
步骤分析
获取所有键的集合。用keySet()方法实现
遍历键的集合,获取到每一个键。用增强for实现
根据键去找值。用get(Object key)方法实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class MapDemo01 { public static void main (String[] args) { Map<String, String> map = new HashMap <String, String>(); map.put("张无忌" , "赵敏" ); map.put("郭靖" , "黄蓉" ); map.put("杨过" , "小龙女" ); Set<String> keySet = map.keySet(); for (String key : keySet) { String value = map.get(key); System.out.println(key + "," + value); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class HashMapDemo1 { public static void main (String[] args) { HashMap<String, String> hm = new HashMap <>(); hm.put("西游记" ,"吴承恩" ); hm.put("红楼梦" ,"曹雪芹" ); hm.put("水浒传" ,"施耐庵" ); hm.put("三国演义" ,"罗贯中" ); Set<String> keySet = hm.keySet(); for (String key : keySet) { String value = hm.get(key); System.out.println(key + "作者:" + value); } System.out.println("===========================================" ); Iterator<String> it = keySet.iterator(); while (it.hasNext()){ String value2 = it.next(); System.out.println(value2+"作者:" + hm.get(value2)); } System.out.println("===========================================" ); keySet.forEach(s -> System.out.println(s+"的作者:" +hm.get(s))); } }
键值对
遍历思路
我们刚才存储的元素都是成对出现的,所以我们把Map看成是一个夫妻对的集合
获取所有结婚证的集合
遍历结婚证的集合,得到每一个结婚证
根据结婚证获取丈夫和妻子
步骤分析
获取所有键值对对象的集合
Set<Map.Entry<K,V>> entrySet():获取所有键值对对象的集合
遍历键值对对象的集合,得到每一个键值对对象
根据键值对对象获取键和值
用getKey()得到键
用getValue()得到值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class MapDemo02 { public static void main (String[] args) { Map<String, String> map = new HashMap <String, String>(); map.put("张无忌" , "赵敏" ); map.put("郭靖" , "黄蓉" ); map.put("杨过" , "小龙女" ); Set<Map.Entry<String, String>> entrySet = map.entrySet(); for (Map.Entry<String, String> me : entrySet) { String key = me.getKey(); String value = me.getValue(); System.out.println(key + "," + value); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package JiHe.map;import java.util.HashMap;import java.util.Iterator;import java.util.Map;import java.util.Set;public class HashMapDemo2 { public static void main (String[] args) { Map<String, String> map = new HashMap <>(); map.put("富" ,"强" ); map.put("民" ,"主" ); map.put("文" ,"明" ); map.put("和" ,"谐" ); Set<Map.Entry<String, String>> entries = map.entrySet(); for (Map.Entry<String, String> entry : entries) { String key = entry.getKey(); String value = entry.getValue(); System.out.println(key + value); } System.out.println("===========================" ); Iterator<Map.Entry<String, String>> it = entries.iterator(); while (it.hasNext()){ Map.Entry<String, String> next = it.next(); String value1 = next.getValue(); String key1 = next.getKey(); System.out.println(key1 + value1); } System.out.println("===========================" ); entries.forEach(entry ->{ String key2 = entry.getKey(); String value2 = entry.getValue(); System.out.println(key2+value2); }); } }
Lambda表达式遍历
底层原理;
底层实现就是通过调用entrySet方法将键值对对象放入Set集合中,然后使用增强for遍历,使用getKey、getValue方法取出键、值
此处的Lambda遍历与之前的Lambda遍历方式不同点在于:
之前的Lambda表达式是将键或键值对对象放入单列集合使用Lambda表达式遍历,实现的是Consumer函数式接口
此处的Lambda表达式是直接遍历Map集合,实现的是BiConsumer函数式接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class HashMapDemo3 { public static void main (String[] args) { HashMap<String, String> map = new HashMap <>(); map.put("鱼香" ,"肉丝" ); map.put("宫爆" ,"鸡丁" ); map.put("夫妻" ,"肺片" ); map.put("红烧" ,"肉" ); map.forEach((key,value)->{ System.out.println(key + value); }); } }
HashMap集合
无序、不重复、无索引
HashMap是Map里面的一个实现类
HashMap跟HashSet底层原理是一模一样的,都是哈希表结构
底层原理:
创建一个数组
利用键计算哈希值,跟值无关
如果索引位置为空,则直接存入,如果键的位置已存在数据,比较属性值,属性值是一样的,就会覆盖原entry对象
注意:
如果键存储的是自定义对象,需要重写HashCode和equals方法
如果值存储自定义对象,并不需要重新HashCode和equals方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 public class Student { private String name; private Integer age; public Student (String name, Integer age) { this .name = name; this .age = age; } public Student () { } public String getName () { return name; } public void setName (String name) { this .name = name; } public Integer getAge () { return age; } public void setAge (Integer age) { this .age = age; } @Override public boolean equals (Object o) { if (this == o) return true ; if (o == null || getClass() != o.getClass()) return false ; Student student = (Student) o; return Objects.equals(name, student.name) && Objects.equals(age, student.age); } @Override public int hashCode () { return Objects.hash(name, age); } @Override public String toString () { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}' ; } } public class HashMapDemo4 { public static void main (String[] args) { HashMap<Student, String> hm = new HashMap <>(); hm.put(new Student ("zhangsan" , 23 ), "sichuan" ); hm.put(new Student ("lisi" , 24 ), "fujian" ); hm.put(new Student ("wangwu" , 25 ), "beijing" ); Set<Student> keySets = hm.keySet(); Set<Map.Entry<Student, String>> entries = hm.entrySet(); for (Student key : keySets) { String value1 = hm.get(key); System.out.println(key + value1); } System.out.println("============================" ); Iterator<Map.Entry<Student, String>> it = entries.iterator(); while (it.hasNext()){ Map.Entry<Student, String> next = it.next(); Student key2 = next.getKey(); String value2 = next.getValue(); System.out.println(key2 + value2); } System.out.println("==============================" ); hm.forEach((key3,value3)->{ System.out.println(key3+ value3); }); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class HashMapDemo5 { public static void main (String[] args) { String[] arr = {"A" ,"B" ,"C" ,"D" }; Random r = new Random (); ArrayList<String> list = new ArrayList <>(); for (int i = 0 ; i < 80 ; i++) { String ticket = arr[r.nextInt(arr.length)]; list.add(ticket); } HashMap<String, Integer> map = new HashMap <>(); for (String name : list) { if (map.containsKey(name)){ Integer count = map.get(name); count++; map.put(name,count); }else { map.put(name,1 ); } } map.forEach((key,value)-> System.out.println(key+"的票数是" +value+"票" )); } }
HashMap底层源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 static class Node <K, V> implements Map .Entry<K, V> { final int hash;LinkedHashMap.Entry<K, V> { int hash; value; TreeNode<K, V> left; deletion boolean red; transient Node<K, V>[] table; static final float DEFAULT_LOAD_FACTOR = 0.75f ; HashMap() { this .loadFactor = DEFAULT_LOAD_FACTOR; defaulted } HashMap<String, Integer> hm = new HashMap <>(); hm.put("aaa" ,111 ); hm.put("bbb" ,222 ); hm.put("ccc" ,333 ); hm.put("ddd" ,444 ); hm.put("eee" ,555 ); 2.2 数组位置不为null ,键不重复,挂在下面形成链表或者红黑树 2.3 数组位置不为null ,键重复,元素覆盖返回值,被覆盖元素的值,没有覆盖返回null public V put (K key,V value) { 第一个字段是将key传过去计算键的hash值 第四个参数,表示当前的参数是否保留,相同值时覆盖数据,就是这个参数决定 return putVal(hash(key),key,value,false ,true ); } return (key==null )?0 :(h=key.hashCode())^(h>>>16 ); }参数4 :如果键重复了,是否保留 false :旧元素的值不会保留,覆盖 final V putVal (int hash,K key,V value,boolean onlyIfAbsent, boolean evict) {方法运行在栈中,数组存放在堆中,需要反复从栈到堆中寻找使用 V>[]tab; 把hash表中数组的地址值,赋值给局部变量tab tab=table if (tab==null ||(n=tab.length) ==0 ) tab=resize(); i=(n-1 )&hash; p=tab[i]; if (p == null ){ tab[i]=newNode(hash,key,value,null ); }else { Node<K, V> e;K k; boolean b1 = p.hash==hash; if ( b1 &&((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 ) treeifyBin(tab,hash); break ; } if (e.hash==hash&&((k=e.key)==key||(key!=null &&key.equals(k)))) break ; p=e; } } if (e!=null ){ V oldValue=e.value; if (!onlyIfAbsent||oldValue==null ){ e.value=value; } afterNodeAccess(e); return oldValue; } } ++modCount; if (++size>threshold){ resize(); } afterNodeInsertion(evict); return null ; }
LinkedHashMap集合
由键决定:有序,不重复,无索引
底层依旧是哈希表,每个键值对又多了一个双链表的机制记录存储的顺序
TreeMap集合
添加自定义对象时,必须指定排序规则
底层是红黑树
由键决定特性:不重复、无索引、可排序
可排序:对键进行排序
默认按照从小到大进行排序,也可以自己规定键的排序
书写两种排序规则
实现comparable接口,指定比较规则
创建集合时传递Comparator比较器对象,指定比较规则
案例需求
创建一个TreeMap集合,键是学生对象(Student),值是籍贯(String),学生属性姓名和年龄按照年龄进行排序并遍历
要求按照学生的年龄进行排序,如果年龄相同则按照姓名进行排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 public class Student implements Comparable <Student>{ private String name; private int age; public Student () { } public Student (String name, int age) { 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 "Student{" + "name='" + name + '\'' + ", age=" + age + '}' ; } @Override public int compareTo (Student o) { int result = o.getAge() - this .getAge(); result = result == 0 ? o.getName().compareTo(this .getName()) : result; return result; } } public class Test1 { public static void main (String[] args) { TreeMap<Student,String> tm = new TreeMap <>(); Student s1 = new Student ("xiaohei" ,23 ); Student s2 = new Student ("dapang" ,22 ); Student s3 = new Student ("xiaomei" ,22 ); tm.put(s1,"江苏" ); tm.put(s2,"北京" ); tm.put(s3,"天津" ); tm.forEach( (Student key, String value)->{ System.out.println(key + "---" + value); } ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class TreeMapDemo1 { public static void main (String[] args) { String s = "abcdbcdbbcdaacbfgfgdfggjuhiulghvbsdawfcggjjloioplpihjgbncvcvbbaxaaefdbacbd" ; TreeMap<Character, Integer> tm = new TreeMap <>(); for (int i = 0 ; i < s.length(); i++) { char c = s.charAt(i); if (tm.containsKey(c)){ Integer count = tm.get(c); count++; tm.put(c,count); }else { tm.put(c,1 ); } } tm.forEach((key,value)-> System.out.print(key+"(" +value+")" )); } }
TreeMap底层源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 private static final boolean RED = false ; private static final boolean BLACK = true ; static final class Entry <K,V> implements Map .Entry<K,V> { K key; V value; Entry<K,V> left; Entry<K,V> right; Entry<K,V> parent; boolean color = BLACK; } comparator; private transient Entry<K,V> root; private transient int size = 0 ; } comparator) { this .comparator = comparator; } 参数一:键 参数二:值 参数三:当键重复的时候,是否需要覆盖值 true :覆盖 false :不覆盖 private V put (K key, V value, boolean replaceOld) { Entry<K,V> t = root; if (t == null ) { addEntryToEmptyMap(key, value); return null ; } int cmp; Entry<K,V> parent; Comparator<? super K> cpr = comparator; if (cpr != null ) { do { parent = t; cmp = cpr.compare(key, t.key); if (cmp < 0 ) t = t.left; else if (cmp > 0 ) t = t.right; else { V oldValue = t.value; if (replaceOld || oldValue == null ) { t.value = value; } return oldValue; } } while (t != null ); } else { Comparable<? super K> k = (Comparable<? super K>) key; do { parent = t; cmp = k.compareTo(t.key); if (cmp < 0 ) t = t.left; else if (cmp > 0 ) t = t.right; else { V oldValue = t.value; if (replaceOld || oldValue == null ) { t.value = value; } return oldValue; } } while (t != null ); } addEntry(key, value, parent, cmp < 0 ); return null ; } private void addEntry (K key, V value, Entry<K, V> parent, boolean addToLeft) { Entry<K,V> e = new Entry <>(key, value, parent); if (addToLeft) parent.left = e; else parent.right = e; fixAfterInsertion(e); size++; modCount++; } private void fixAfterInsertion (Entry<K,V> x) { x.color = RED; while (x != null && x != root && x.parent.color == RED) { if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { Entry<K,V> y = rightOf(parentOf(parentOf(x))); if (colorOf(y) == RED) { setColor(parentOf(x), BLACK); setColor(y, BLACK); setColor(parentOf(parentOf(x)), RED); x = parentOf(parentOf(x)); } else { if (x == rightOf(parentOf(x))) { x = parentOf(x); rotateLeft(x); } setColor(parentOf(x), BLACK); setColor(parentOf(parentOf(x)), RED); rotateRight(parentOf(parentOf(x))); } } else { Entry<K,V> y = leftOf(parentOf(parentOf(x))); if (colorOf(y) == RED) { setColor(parentOf(x), BLACK); setColor(y, BLACK); setColor(parentOf(parentOf(x)), RED); x = parentOf(parentOf(x)); } else { if (x == leftOf(parentOf(x))) { x = parentOf(x); rotateRight(x); } setColor(parentOf(x), BLACK); setColor(parentOf(parentOf(x)), RED); rotateLeft(parentOf(parentOf(x))); } } } root.color = BLACK; } 6. 课堂思考问题: 6. 1TreeMap添加元素的时候,键是否需要重写hashCode和equals方法?此时是不需要重写的。 6. 2HashMap是哈希表结构的,JDK8开始由数组,链表,红黑树组成的。既然有红黑树,HashMap的键是否需要实现Compareable接口或者传递比较器对象呢? 不需要的。 因为在HashMap的底层,默认是利用哈希值的大小关系来创建红黑树的 6. 3TreeMap和HashMap谁的效率更高?如果是最坏情况,添加了8 个元素,这8 个元素形成了链表,此时TreeMap的效率要更高 但是这种情况出现的几率非常的少。 一般而言,还是HashMap的效率要更高。 6.4 你觉得在Map集合中,java会提供一个如果键重复了,不会覆盖的put方法呢?此时putIfAbsent本身不重要。 传递一个思想: 代码中的逻辑都有两面性,如果我们只知道了其中的A面,而且代码中还发现了有变量可以控制两面性的发生。 那么该逻辑一定会有B面。 习惯: boolean 类型的变量控制,一般只有AB两面,因为boolean 只有两个值 int 类型的变量控制,一般至少有三面,因为int 可以取多个值。 6.5 三种双列集合,以后如何选择? HashMap LinkedHashMap TreeMap 默认:HashMap(效率最高) 如果要保证存取有序:LinkedHashMap 如果要进行排序:TreeMap
可变参数
方法形参的个数是可以发生变化的
格式:属性类型…名字
int…args
可变参数的细节:
Collections
java.util.Collections:是集合工具类
作用:Collections不是集合,而是集合的工具类
常用API
不变集合
创建不可变的集合
不能修改的集合:长度不能修改、内容不能修改
如果某个数据不能被修改,把它防御性拷贝到不可变集合中是很好的实践
当集合对象被不可信的库调用时,不可变形式是安全的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class ImmutableDemo1 { public static void main (String[] args) { List<String> list = List.of("张三" , "李四" , "王五" , "赵六" ); System.out.println(list.get(0 )); System.out.println(list.get(1 )); System.out.println(list.get(2 )); System.out.println(list.get(3 )); System.out.println("---------------------------" ); for (String s : list) { System.out.println(s); } System.out.println("---------------------------" ); Iterator<String> it = list.iterator(); while (it.hasNext()){ String s = it.next(); System.out.println(s); } System.out.println("---------------------------" ); for (int i = 0 ; i < list.size(); i++) { String s = list.get(i); System.out.println(s); } System.out.println("---------------------------" ); list.set(0 ,"aaa" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class ImmutableDemo2 { public static void main (String[] args) { Set<String> set = Set.of("张三" , "张三" , "李四" , "王五" , "赵六" ); for (String s : set) { System.out.println(s); } System.out.println("-----------------------" ); Iterator<String> it = set.iterator(); while (it.hasNext()){ String s = it.next(); System.out.println(s); } System.out.println("-----------------------" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 public class ImmutableDemo3 { public static void main (String[] args) { Map<String, String> map = Map.of("张三" , "南京" , "张三" , "北京" , "王五" , "上海" , "赵六" , "广州" , "孙七" , "深圳" , "周八" , "杭州" , "吴九" , "宁波" , "郑十" , "苏州" , "刘一" , "无锡" , "陈二" , "嘉兴" ); Set<String> keys = map.keySet(); for (String key : keys) { String value = map.get(key); System.out.println(key + "=" + value); } System.out.println("--------------------------" ); Set<Map.Entry<String, String>> entries = map.entrySet(); for (Map.Entry<String, String> entry : entries) { String key = entry.getKey(); String value = entry.getValue(); System.out.println(key + "=" + value); } System.out.println("--------------------------" ); } } public class ImmutableDemo4 { public static void main (String[] args) { HashMap<String, String> hm = new HashMap <>(); hm.put("张三" , "南京" ); hm.put("李四" , "北京" ); hm.put("王五" , "上海" ); hm.put("赵六" , "北京" ); hm.put("孙七" , "深圳" ); hm.put("周八" , "杭州" ); hm.put("吴九" , "宁波" ); hm.put("郑十" , "苏州" ); hm.put("刘一" , "无锡" ); hm.put("陈二" , "嘉兴" ); hm.put("aaa" , "111" ); Map<String, String> map = Map.copyOf(hm); map.put("bbb" ,"222" ); } }