Java 集合 学习笔记(2021.09.9~09.22)
集合
- 集合
- 一、简要集合结构图
- 二、集合应用场合
- 三、Collection接口
- 四、List接口(不唯一,有序)
- 五、ArrayList实现类
- 六、Vector实现类
- 七、泛型
- 八、LinkedList实现类
- 九、iterator()、iterator、iterable的关系(面试题)
- 十、ListIterator迭代器
- 十一、Set接口 (唯一、无序)
- 十二、HashSet实现类
- 十三、LinkedHashSet实现类
- 十四、TreeSet实现类
- 十五、Map基本使用<K,V>
- 十六、Hashtable和LinkedHashMap
- 十七、TreeMap实现类
- 十八、HashMap简单原理介绍
- 十九、经典面试题
- 二十、HashSet底层原理
- 二十一、TreeMap底层原理
- 二十二、TreeSet底层原理
- 二十三、Collections工具类
一、简要集合结构图

二、集合应用场合
前端后端数据库进行交互,当需要将相同结构的个体整合到一起时,需要使用集合。

实际应用场合:


三、Collection接口
惯例,在api中进行初步自学
1. 增加
add(E e)
addAll(Collection<? extends E> c)
2. 删除
clear()
remove(Object o)
3. 查看
size()
iterator()
4. 改动
5. 判断
contains(Object o)
containsAll(Collection<?> c)
equals(Object o)
isEmpty()
demo:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
public class Demo01 {
public static void main(String[] args) {
//创建一个集合
//使用实现类创建,多态用法
Collection col = new ArrayList();
//集合有一个特性:只能存放引用数据类型,不能存放基本数据类型
//add
col.add(33);//33自动装箱成为Integer
col.add(11);
col.add(12);
System.out.println(col);
List integers = Arrays.asList(new Integer[]{11, 2, 33, 4});//asList()将一个数组变成集合
col.addAll(integers);
System.out.println(col);
//clear()清除所有元素
//col.clear();
System.out.println("集合元素个数"+col.size());//size()查看集合内元素个数
System.out.println("集合是否空"+col.isEmpty());
//remove()移除指定的元素
boolean isRemove = col.remove(4);
System.out.println(col);
System.out.println("元素是否删除成功:"+isRemove);
//equals(),比较两个集合是否相等
List<Integer> col1 = Arrays.asList(new Integer[]{1, 2, 3, 5, 4, 5});
List<Integer> col2 = Arrays.asList(new Integer[]{1, 2, 3, 5, 4, 5});
System.out.println("两个集合是否一样:"+col1.equals(col2));
System.out.println(col1==col2);
//contains(),是否包含指定元素
System.out.println(col1.contains(3));
}
}
集合的遍历:
package com.xiaowei9s.container;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
public class Demo02 {
public static void main(String[] args) {
List col1 = Arrays.asList(new Integer[]{1, 2, 3, 5, 4, 5});
//对集合进行遍历
//方式一:普通for循环
/*for (int i = 0; i < col1.size(); i++) {
col1.
}*/
//方式二:增强for循环
for (Object o :
col1) {
System.out.println(o);
}
//方式三:iterator()
System.out.println("-------------------");
Iterator iterator = col1.iterator();
while(iterator.hasNext()){//hasNext():判断有没有下一个元素
System.out.println(iterator.next());//next():获取下一个元素并且,下移指针,一开始在第一个元素前,这样才能保证第一个元素能被获取
}
}
}
迭代器iterator的原理:

四、List接口(不唯一,有序)
注意:是util包下的List
自行在api中提前学习
List接口中常用方法
1. 增加
add(int index, E element)
2. 删除
remove(int index)
remove(Object o)
3. 查看
get(int index)
4. 改动
set(int index, E element)
5. 判断
demo
package com.xiaowei9s.container;
import java.util.ArrayList;
public class Demo03 {
public static void main(String[] args) {
/*
1. 增加
add(int index, E element)
2. 删除
remove(int index)
remove(Object o)
3. 查看
get(int index)
4. 改动
set(int index, E element)
5. 判断
*/
//add()
ArrayList list = new ArrayList();
list.add(1);
list.add("aa");
list.add(7);
list.add(3);
System.out.println(list);
list.add(3,"test");//在指定位置插入元素
System.out.println(list);
//set() 在指定位置修改元素
list.set(3,"bb");
System.out.println(list);
//remove()
list.remove(3);//当集合中存的数据是Integer类型的时候,调用的方法是remove(int index)
System.out.println(list);
//get()
System.out.println(list.get(0));
//在List接口下的遍历方式
//方式一:普通for循环
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
//方式二:加强for循环,同Collection接口
//方式三:iterator(),同Collection接口
}
}
五、ArrayList实现类
源码类似StringBuilder类
切换JDK方法

1. ArrayList实现类JDK1.7源码解析
-
首先现在JDK文档中了解阅读基本的remove等方法。
-
clone()方法不需要理会,是Object类中继承的方法。
-
ensureCapacity()方法,重点,底层重要代码。
扩容方法,当数组长度不足时,扩容至数组现长度的1.5倍。



☆底层内存数组的旧数组地址换成新数组地址
-
ArrayList中实现List接口时,在他的父类AbstractList中也实现了接口,创始人承认此事务,但是无伤大雅也就没删。
-
底层是Object类型数组。
-
还有一个size,指数组中有效长度。5,6点是重要属性。
-
在idea点击构造方法,构造方法调用时,给底层数组elementData数组初始化长度为initialCapacity。
-
在使用无参构造器时,会直接进入有参构造器并且将底层数组长度初始化为10.

-
ArraysList内存分析

-
add方法代码

2. ArrayList实现类JDK1.8源码解析
-
1.8源码底层依旧是一个Object[]数组和一个size字段,这两个是重要属性
-
在jdk1.8中 使用空构造器时,ArrayList al = new ArrayList();时底层解析如下:

DEFAULTCAPACITY_EMPTY_ELEMENTDATA是什么?继续往里查:

可以看到,最终这一串字母返回的是一个空的Object[]数组。
-
add()方法:

首先进入ensureCapacityInternal()方法,确定容量是否够用。

进入ensureCapacityInternal()内部查看如何计算最小容量,可以看到计算容量方法即calculateCapacity()方法根据10和传入的minCapacity取最大值返回最小容量。之后进入ensureExplicitCapacity()内部。

根据最小容量和当前有效长度进行对比进行扩容。
-
add和1.7的区别
在1.7中,10的默认容量在new的时候就确定了,而1.8在第一次扩容的时候才加入10的容量。
六、Vector实现类
已经被淘汰,现在主要作为面试题提问,例如:Vector和ArrayList的区别是什么。
主要区别
- Vector线程安全,效率低
- Vector扩容是两倍扩容,而ArrayList是1.5倍扩容
- 在方法上和初始化上和jdk1.7的ArrayList是高度相似,初始化容量也是10
七、泛型
1. 引入
-
什么是泛型:
泛型就相当于标签形式:<>
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。
Collection,List ,, ArrayList 这个 就是类型参数,即泛型。 -
在没有泛型的时候使用集合:
public class Demo06 { public static void main(String[] args) { ArrayList al = new ArrayList(); al.add(18); al.add("18"); al.add(18); al.add(18); for (Object obj : al) { System.out.println(obj); } } }在使用时,集合大多数存一样数据类型的数据,而现在可以存入不一样引用类型的数据,不方便管理!
-
加入泛型的优点:正在编译的时候就会对这个类型进行检查,不是泛型对应的类型就不可以添加入这个集合。
-
在jdk1.5之后,集合中使用泛型
ArrayList<String> als = new ArrayList<String>(); als.add("a"); als.add("ab"); als.add("abc"); als.add("abcd"); for (String str : als) {//在遍历时,可以不使用Object,因为已经对泛型进行检查 System.out.println(str); } -
泛型总结

2. 自定义泛型结构,泛型类、泛型接口、泛型方法
① 构建泛型类:
public class Test<T> {//在类后加<>并且其中随意填写一个字母,这个类就会是拥有泛型的类
}
public class Test<T> {//T可以出现在类的任何地方
int age;
String name;
T thing;
public void doSomething(T thing){
System.out.println(thing);
}
}
泛型如何将类型“泛”出去
class Demo{
public static void main(String[] args) {
Test<String> t = new Test<>();//当泛型T确定为String,在对象中所有的T 都变成了String,这也就是泛型的“泛”
t.thing = "一些事情";
t.doSomething("做某事");
}
}
如果在实例化类的时候不确定泛型,则泛型为Object类型。
继承情况:
若指定了父类的泛型,则子类则不需要指定父类的泛型,如下:
class Test1 extends Test<Integer>{
//...
}
若没有明确父类的泛型,子类则需要和父类拥有同一个符号的泛型,如下:
class Test1<T> extends Test<T>{
//...
}
若明确了父类的泛型,子类也可以自己再加入一个泛型,如下:
class Test1<B> extends Test<String>{
//...
}
☆ 泛型接口和泛型类相似,自行探索。
② 泛型细节
-
泛型可以定义多个
class Test1<B,C> extends Test<String>{//此处B,C都是泛型 //... } -
泛型类构造器的写法:

-
泛型的引用类型不可以相互赋值,如下:

-
泛型如果不在实例化的时候确定,就会默认变成Object,称为擦除。
-
泛型类中的静态方法不能使用该类的泛型,因为静态方法再编译时就已经存在,而泛型只有再实例化的时候才确定。
-
不能使用T[]的创建

③ 泛型方法:
不是带泛型的方法就是泛型方法
方法中自行定义方法才是泛型方法
定义泛型方法:方法前需要指定泛型
public <F> void function(F a){
//...
}
泛型方法中的泛型实在调用方法是确定的:
Test<String> t = new Test<>();
t.thing = "一些事情";
t.doSomething("做某事");
t.function(1);//此时方法中的泛型全部变成int类型
t.function(5.99);//此时方法中的泛型全部变成float类型
t.function("hello");//此时方法中的泛型全部变成String类型
PS:泛型方法可以是静态方法。
3. 泛型参数存在继承关系的情况

泛型只在编译过程对类型作出限制,objList和sList是没有继承关系的,底层都是Object数组,是并列关系
4. 自定义泛型结构:通配符
为什么需要通配符:Demo1
public void xw(List<Object> list){//报错,本质上都是List类型,编译时不认为其满足重载条件 }public void xw(List<String> list){}public void xw(List<Integer> list){}
什么是通配符:Demo2
public static void main(String[] args) {
List<Object> list = new ArrayList<>();
List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
List<?> list3 = null;
list3 = list;
list3 = list1;
list3 = list2;//实现了多态情况下List<Object>不能等于List<String>的情况 <?>就是通配符。
}
小结:
- 在没有通配符的时候,Demo1的xw方法相当于方法的重复定义,会报错
- 引入通配符后,在Demo2。
//发现:A类B类是继承关系,但是G<A>和G<B>是并列关系,不能继承。加入通配符<?>后,G<?>就变成了G<A>和G<B>的父类
使用通配符:Demo3
package com.xiaowei9s.container;
import java.util.ArrayList;
import java.util.List;
public class Demo08 {
public static void main(String[] args) {
List<Object> list = new ArrayList<>();
List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
List<?> list3 = null;
list3 = list;
list3 = list1;
list3 = list2;//实现了多态情况下List<Object>不能等于List<String>的情况
}
/*public void xw(List<Object> list){
}
public void xw(List<String> list){
}
public void xw(List<Integer> list){
}*/
public void xw(List<?> list){
//...
}
}
自行体会API中通配符的应用
通配符使用细节:
数据写入:
//list.add("abc"); -->报错,不能随意加入数据!
list.add(null);
数据读出:
Object o = list.get(0);
5. 泛型受限
List<Object> list1 = new ArrayList<>();
List<Person> list2 = new ArrayList<>();
List<Student> list3 = new ArrayList<>();//开始使用泛型受限:泛型的上限
List<? extends Person> list;//必须继承于Person,不能是比Person还高的父类
list = list1;//报错
list = list2;list = list3;//<?>
//list123是并列关系
List<Object> list1 = new ArrayList<>();
List<Person> list2 = new ArrayList<>();
List<Student> list3 = new ArrayList<>();
//开始使用泛型受限:泛型的下限
List<? super Person> list4;
//?必须是Person的父类,不能是继承于Person的子类
list4 = list1;
list4 = list2;
list4 = list3;//报错<?>通配符
八、LinkedList实现类
在API文档中查看基本的方法,基本的方法这儿就不说了。
1. 基本使用
demo:
package com.xiaowei9s.container;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
public class Demo10 {
public static void main(String[] args) {
/*
LinkedList常用方法
增加
addFirst(E e) addLast(E e)
offer(E e) offerFirst(E e) offerLast(E e)
删除
poll() pollFirst() pollLast()
removeFirst() removeLast()
查看
element()
getFirst() getLast()
indexOf(Object o) lastIndexOf(Object o)
peek() peekFirst() peekLast()
修改
判断
*/
//创建LinkedList
LinkedList<String> llist = new LinkedList<>();
llist.add("1");
llist.add("2");
llist.add("3");
llist.add("4");
llist.add("5");
System.out.println(llist);
//增加
llist.addFirst("0");
llist.addLast("6");
System.out.println(llist);
llist.offer("7");//加在尾部
llist.offerFirst("8");//加在头部
llist.offerLast("9");//加在尾部
System.out.println(llist);
//删除
System.out.println(llist.poll());//删除头部
System.out.println(llist.pollFirst());//删除头部
System.out.println(llist.pollLast());//删除尾部
//remove系列的方法和poll一样的移除效果
System.out.println(llist);
//remove 和 poll系列方法的区别
//llist.clear();
System.out.println(llist);
System.out.println(llist.poll());//如果集合是空不报错,删除空,健壮性!推荐使用
//System.out.println(llist.remove());//如果集合是空报错-->异常:Exception in thread "main" java.util.NoSuchElementException
//集合的遍历
System.out.println("--------------集合遍历---------------");
//普通for循环
for (int i = 0; i < llist.size(); i++) {
System.out.println(llist.get(i));
}
System.out.println("增强for");
for (String s : llist) {
System.out.println(s);
}
System.out.println("迭代器");
/*Iterator<String> iterator = llist.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}*/
//这种方法好,能节省内存
for (Iterator<String> iterator = llist.iterator();iterator.hasNext();){
System.out.println(iterator.next());
}
}
}
2. LinkedList底层原理
LinkedList数据结构:
物理结构:跳转结构
逻辑结构:线性标(链表)Ps:在LinkedList中使用的是双向链表,如下图:

3. 模拟LinkedList底层源码
https://www.bilibili.com/video/BV1Pv411h7Xs?p=19&spm_id_from=pageDriver查看思路,这边就不写出代码了,没有效果
4. LinkedList底层源码解析
泛型 - size属性:有效长度
- Node
first 和 last,首节点和尾部节点 - 在Node中有三个属性,前驱节点也就是前面的节点,后继节点,也就是连在当前节点后一个的节点,当前节点元素E item。
- 空构造器LinkedList()
- linkLast(E e)方法,将元素加入尾部,新建一个节点,将节点前驱设为当前last节点,节点后继设为null,然后将链表的last属性赋值为这个新建的节点,如果当前链表的尾部节点为null,说明链表为空,则把新加入的元素也设置为first属性,最后将原来的最后的节点的后继节点设为新的节点,最后size加一。
- 寻找指定元素的方法:node(),分为两部分,第一个部分从头指针往中间走,第二部分从尾部指针往中间走,如果说一昧的从头往后找,则后半段的寻找效率就不如从尾指针往中间找。
- 更多的代码请自行探索了...
九、iterator()、iterator、iterable的关系(面试题)

hasNext()和next()实现:

十、ListIterator迭代器
1. 引入:并发修改异常
package com.xiaowei9s.container;
import java.util.*;
public class Demo11 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
list.add("ee");
Iterator it = list.iterator();
while(it.hasNext()){
if (it.next().equals("cc")){
list.add("kk");
}
}
}
}
执行代码,发现报错:

原因:list和it两个变量同时操纵集合,引发异常,即不允许这样操作。
解决办法:所有事情让一个变量做,引入listIterator。
package com.xiaowei9s.container;
import java.util.*;
public class Demo11 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
list.add("ee");
ListIterator it = list.listIterator();
while(it.hasNext()){
if ("cc".equals(it.next())){
it.add("kk");//listIterator执行操作add,因为只有listIterator进行操作,所以不会引发并发修改异常
}
}
System.out.println(list);
}
}
PS:listIterator不仅拥有hasNext方法,还拥有hasPrevious方法,利用这个方法,可以实现逆向遍历,读者请自行尝试。
十一、Set接口 (唯一、无序)
无序:相对于List接口无序,这个无序不等于随机
打开API文档,自行学习常规方法
发现:没有和索引相关的方法。
遍历方式:迭代器,增强for循环。
十二、HashSet实现类
放入引用类型:
package com.xiaowei9s.container;
import java.util.HashSet;
public class Demo12 {
public static void main(String[] args) {
HashSet<Integer> set =new HashSet<>();
set.add(1);
set.add(1);
set.add(2);
set.add(3);
set.add(2);
set.add(3);
set.add(5);
System.out.println(set.size());//4
System.out.println(set);//1,2,3,5
}
}
放入自定义引用类型:
package com.xiaowei9s.container;
import java.util.HashSet;
public class Demo13 {
public static void main(String[] args) {
HashSet<Student1> s = new HashSet<>();
s.add(new Student1("xiaowei",12));
s.add(new Student1("xiaowei12",12));
s.add(new Student1("xiaowei1",12));
s.add(new Student1("xiaowei2",12));
s.add(new Student1("xiaowei12",12));
s.add(new Student1("xiaowei1",12));
System.out.println(s.size());//6
System.out.println(s);
}
}
class Student1{
public Student1(String name, int age) {
this.name = name;
this.age = age;
}
String name;
int age;
@Override
public String toString() {
return "Student1{" +
"name='" + name + '\'' +
", 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;
}
}
上面自定义的引用类型不满足唯一、无序的特点。
1. HashSet底层原理图

至此,我们就知道了为何我们自定义的引用类型中为什么值相同的对象却还是能存入hashSet中,因为没有重写hashCode和equals方法。
2. 疑问(看完HashMap源码后讲解)
- 底层数组长度是多少?
- 数组的类型是什么?
- hashCode和equals方法真的调用了吗?验证
- 底层表达式是什么?
- 同一个位置的数据(如上图中的19和11)是向前还是向后放?
十三、LinkedHashSet实现类
特点:有序,按照输入顺序进行输出。
在HashSet的底层数组的基础上,多了一个拥有顺序的链表。
十四、TreeSet实现类
1. 比较器的使用
以int类型为案例:
比较的思路:将比较的数据做差,然后返回一个int类型的数据,将这个int数值进行 =0 >0 <0进行比较。
以String类型比较:
实现了Comparable接口,实现了comparaTo方法,用这个方法进行比较。
比较double类型的数据:
转换成相对应的包装类,包装类中实现了Comparable接口,可以用于比较
内部比较器:
class Student1 implements Comparable<Student1>{//实现接口
public Student1(String name, int age) {
this.name = name;
this.age = age;
}
String name;
int age;
@Override
public String toString() {
return "Student1{" +
"name='" + name + '\'' +
", 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 int compareTo(Student1 o) {//重写了接口中的方法
return this.age-o.age;
}
}
外部比较器:
package com.xiaowei9s.container;
import java.util.Comparator;
import java.util.HashSet;
public class Demo13 {
public static void main(String[] args) {
HashSet<Student1> s = new HashSet<>();
s.add(new Student1("xiaowei",12));
s.add(new Student1("xiaowei12",12));
Student1 student1 = new Student1("xiaowei12", 12);
Student1 student2 = new Student1("xiaowe", 14);
System.out.println(new Bijiaoqi().compare(student1,student2));//在Student1类外部实现比较器
System.out.println(s.size());
System.out.println(s);
}
}
class Student1{
public Student1(String name, int age) {
this.name = name;
this.age = age;
}
String name;
int age;
@Override
public String toString() {
return "Student1{" +
"name='" + name + '\'' +
", 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;
}
}
class Bijiaoqi implements Comparator<Student1>{//比较类:实现了Comparator接口
@Override
public int compare(Student1 o1, Student1 o2) {//重写compare方法
return o1.getAge()- o2.getAge();
}
}
外部比较器和内部比较器谁好,用谁?
外部好,原因:可拓展性高,多态,可解耦。
2. TreeSet基本概况
特点:唯一、有序(指按照升序而不是输入顺序);
原理:底层:二叉树
所以必须实现比较器。

放入String类型数据进入TreeSet:通过实现内部比较,让二叉树进行比较:

放入自定义引用类型:需要实现内部比较器:
class Student1 implements Comparable<Student1>{//实现接口
public Student1(String name, int age) {
this.name = name;
this.age = age;
}
String name;
int age;
@Override
public String toString() {
return "Student1{" +
"name='" + name + '\'' +
", 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 int compareTo(Student1 o) {//重写了接口中的方法
return this.age-o.age;
}
}
在TreeSet中使用外部比较器进行比较需要在TreeSet的构造函数中加入比较器实例:
package com.xiaowei9s.container;
import java.util.TreeMap;
import java.util.TreeSet;
public class Demo14 {
public static void main(String[] args) {
Bijiaoqi bijiaoqi = new Bijiaoqi();
//使用外部比较器
TreeSet<Student1> ts = new TreeSet<>(bijiaoqi);//加入了比较器
}
}
实际中常常使用外部比较器,可拓展性强
3. TreeSet的遍历
在TreeSet中的遍历实际上是二叉树的的升序遍历,如图:

十五、Map基本使用<K,V>
1. Map基本使用、常用方法
package com.xiaowei9s.container;
import java.util.*;
public class Demo15 {
public static void main(String[] args) {
/*
Map接口的常用方法
增加 put(K key, V value)
删除 clear() remove(Object key)
修改
查找 entrySet() get(Object key)
keySet() size() values()
判断 containKey(Object key) containValue(Object value)
equals(Object o) isEmpty()
*/
//创建一个集合:无序、唯一
Map<String, Integer> m = new HashMap<>();
m.put("xw",123123);
m.put("xw1",12223);
m.put("xw2",121243);
System.out.println(m.put("xw", 16623));//输出的是123123,为什么是上一个数据?
m.put("xw1",1297623);
m.put("xw5",127623);
System.out.println(m.size());
System.out.println(m);
//m.clear();//清空
m.remove("xw5");
System.out.println(m);
System.out.println(m.containsKey("xw1"));
System.out.println(m.containsKey("33"));
System.out.println(m.containsValue(11297623));
Map<String, Integer> m2 = new HashMap<>();
m2.put("xw",123123);
m2.put("xw1",12223);
m2.put("xw2",121243);
System.out.println(m2.put("xw", 16623));//输出的是123123,为什么是上一个数据?
m2.put("xw1",1297623);
m2.put("xw5",127623);
System.out.println(m2.size());
System.out.println(m2);
//m.clear();//清空
m2.remove("xw5");
System.out.println(m.equals(m2));//equals进行了重写,比较的是Map内的内容而不是地址。
System.out.println(m==m2);
System.out.println(m.isEmpty());
System.out.println(m.get("xw2"));
System.out.println("----------------------------");
//keySet()对集合中的key进行查看
Set<String> set = m.keySet();
for (String s : set) {
System.out.println(s);
}
System.out.println("----------------------------");
//values()对集合中的value进行查看
Collection<Integer> values = m.values();
for (Integer value : values) {
System.out.println(value);
}
System.out.println("----------------------------");
//entrySet()
Set<Map.Entry<String, Integer>> entries = m.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
System.out.println(entry.getKey()+"---"+entry.getValue());
}
}
}
2. Map特点
无序、唯一
十六、Hashtable和LinkedHashMap
1. 必要条件
必须重写hashCode和equals方法(用于计算哈希值以决定地址)
2. Hashtable
- 使用起来和Map基本一样。
- 时期上HashMap JDK1.2、Hashtable JDK1.0。
- Map效率高 线程不安全 table效率低 线程安全。
- table的key不能存入空值,Map可以。
3. LinkedHashMap实现类
- 底层维护了一个链表
- 唯一、有序(按照输入顺序)
十七、TreeMap实现类
1. 特点
唯一 有序(升序或降序)
2. 原理
二叉树,key遵循二叉树特点
key对应的数据类型在内部一定要实现比较器(内部比较器和外部比较器二选一)
3. Demo
基本使用:
package com.xiaowei9s.container;
import java.util.Map;
import java.util.TreeMap;
public class Demo16 {
public static void main(String[] args) {
Map m = new TreeMap<String,Integer>();
m.put("xw",111);
m.put("xw2",141);
m.put("xw3",124);
m.put("xw4",161);
m.put("xw",131);
System.out.println(m.size());
System.out.println(m);
}
}
自定义引用类型使用比较器:
package com.xiaowei9s.container;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
public class Demo17 {
public static void main(String[] args) {
Map<Student2,Integer> m = new TreeMap<>(new Comparator<Student2>() {//实现比较器
@Override
public int compare(Student2 o1, Student2 o2) {
return o1.getAge()- o2.getAge();
}
});
m.put(new Student2("xw",18,179.2),1001);
m.put(new Student2("xw1",14,172.1),1002);
m.put(new Student2("xw2",17,165.8),1003);
m.put(new Student2("xw3",23,188.5),1004);
m.put(new Student2("xw4",11,191.2),1005);
System.out.println(m);
}
}
class Student2{
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;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
String name;
int age;
double height;
public Student2(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
@Override
public String toString() {
return "Student2{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
}
十八、HashMap简单原理介绍
1. 原理图

7上8下:jdk1.7是尾插发,1.8是头插法
键用于计算哈希值,确定元素在底层主数组的位置,在经过算法计算后,会经历哈希碰撞或者哈希冲突,如果元素的键一致,则进行替代,如果不一致进行链表追加(追加原则7上8下)。
2. 源码解析
重要属性:
//重要属性
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 初始化赋给数组的长度
static final int MAXIMUM_CAPACITY = 1 << 30; //定义了一个很大很大的数,长度最大值
static final float DEFAULT_LOAD_FACTOR = 0.75f;//负载因子 加载因子
transient Node<K,V>[] table;//底层主数组
transient int size;//有效长度
int threshold;//没赋值,默认0 数组扩容的边界值(门槛值)
final float loadFactor;//用以接收前面的负载因子,实际的加载因子
构造器:jdk1.6

put方法:jdk1.6





十九、经典面试题
1. 装填因子加载因子负载因子为什么是0.75
- 装填因子为1,空间利用率高,但是容易发生碰撞,产生链表概率高,查询效率变低。
- 装填因子为0.5,空间利用率低,碰撞概率低,产生链表机率低。
- 取中间值0.75合适。
2. 主数组的长度必须是2的幂次方
-
h&(lenght-1)等效于h%lenght,等效的前提就是lenght是2的整数倍
-
防止哈希冲突
验证整数倍:
lenght:8
hash:3 00000011
lenght:8 00000111
与运算 00000011 -->位置 3
hash 2 00000010
lenght-1 00000111
与运算 00000010 -->位置2
未冲突!
验证不是整数倍:
lenght:9
hash 3 00000011
lenght-1 00001000
与运算 00000000 -->位置0
hash 2 00000010
lenght-1 00001000 -->位置0
冲突!
二十、HashSet底层原理
1. 重要属性
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
2. 构造器
public HashSet() {
map = new HashMap<>();//底层就是使用了HashMap方法实现
}
3. 重要方法
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
二十一、TreeMap底层原理
1. 原理图

2. 源码
重要属性:
private final Comparator<? super K> comparator;//外部比较器,未赋值,为null
private transient Entry<K,V> root;//树的根节点,有键、值、左子树根、右子树根、颜色
private transient int size = 0;//集合中元素数量
构造器:
public TreeMap() {//如果使用空构造器,那就不使用外部比较器
comparator = null;
}
public TreeMap(Comparator<? super K> comparator) {//如果使用有参构造器,那就使用外部比较器
this.comparator = comparator;
}
重要方法:
public V put(K key, V value) {//创建对象时确定泛型
Entry<K,V> t = root;//如果创建的是第一个泛型,则t就是null了
//如果是第一个元素走进if
if (t == null) {
//自己和自己比
compare(key, key); // type (and possibly null) check
//根节点确定为root
root = new Entry<>(key, value, null);
//size变成1
size = 1;
modCount++;
return null;//返回空
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;//将外部比较器赋值给cpr
if (cpr != null) {//不是外部比较器
do {
parent = t;
cmp = cpr.compare(key, t.key);//比较key值
if (cmp < 0)//小了,往左走
t = t.left;
else if (cmp > 0)//大了,往右走
t = t.right;
else//找到了,key一样,改变值
return t.setValue(value);//set值
} while (t != null);//找不到相同键值则循环到叶子退出
}
else {//是外部比较器 同上
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
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
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);//封装好节点
if (cmp < 0)
parent.left = e;//如果比当前叶子节点小则放到其左
else
parent.right = e;//如果比当前叶子节点大则放到其右
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
二十二、TreeSet底层原理
构造器:
public TreeSet() {//底层使用TreeMap
this(new TreeMap<E,Object>());
}
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
private transient NavigableMap<E,Object> m;//底层容器
add方法
public boolean add(E e) {//增加方法实际上是对Map进行put
return m.put(e, PRESENT)==null;
}
二十三、Collections工具类
package com.xiaowei9s.container;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Demo19 {
public static void main(String[] args) {
//Collections工具类
//Collections不支持实例化(因为构造器私有化)
//类似Math类,都是静态方法
ArrayList<String> as = new ArrayList<>();
as.add("aa");
as.add("bb");
as.add("cc");
as.add("dd");
as.add("ee");
//addAll一次加入多个元素
Collections.addAll(as,"aa","bb","cc","dd","ee");
Collections.addAll(as,new String[]{"vv","ww","qq"});
System.out.println(as);
//二分查找binarySearch()必须在有序的集合中使用-->排序sort()
Collections.sort(as);//是升序排序
System.out.println(as);
//二分查找binarySearch()
System.out.println(Collections.binarySearch(as, "aa"));
//copy()
ArrayList<String> as1 = new ArrayList<>();
Collections.addAll(as1,"11","22");
Collections.copy(as,as1);//用as1的内容顺序替换as
System.out.println(as);
System.out.println(as1);
}
}
知识来源 马士兵JAVA https://www.bilibili.com/video/BV1Pv411h7Xs?p=1

浙公网安备 33010602011771号