【韩顺平讲Java】Java集合专题 -ArrayList HashMap HashSet List Map TreeMap
01----集合的理解和好处
- 可以动态保存任意多个对象,比较方便。
- 提供了一系列操作对象的方法:
add()
,remove()
,set()
,get()
。
02----集合的框架体系
- 单列集合
- 双列集合
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cotMTIEA-1640566300154)(E:[HanShunping] Java Collection\笔记\01.png)]
03----集合的常用方法
import java.util.ArrayList;
import java.util.List;
public class CollectionMethod {
public static void main(String[] args) {
List list = new ArrayList();
//add 添加元素
list.add("jack");
list.add(10);
list.add(true);
//remove 删除元素
list.remove(0); //删除第一个元素
list.remove(true);
System.out.println(list);
//contains 查找元素是否存在
System.out.println(list.contains("jack"));
//size 获取元素个数
System.out.println(list.size());
//isEmpty 判断是否为空
System.out.println(list.isEmpty());
//clear 清空
list.clear();
//addAll 添加多个元素
List list2 = new ArrayList();
list2.add("wang");
list2.add(100);
list.addAll(list2);
//cintainsAll 查找多个元素是否都存在
list.containsAll(list2);
//removeAll 删除多个元素
list.removeAll(list2);
}
}
04----迭代器遍历
iterator.hasNext()
:判断是否有下一个元素。
import java.util.ArrayList;
import java.util.Iterator;
public class CollectionIterator {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("Wang");
arrayList.add("Zhao");
arrayList.add("Qian");
//生成迭代器
Iterator iterator = arrayList.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
05----增强 for
循环
增强 for
底层仍然是迭代器。
for(Object obj : arrayList){
System.out.println(obj);
}
07----List
接口方法
- 有序可重复(添加,取出顺序一致)
- 支持索引
- 可根据序号存取容器中的元素
-
ArrayList
线程不安全,效率高 -
Vector
线程安全 -
LinkedList
线程不安全
add(int index, Object ele)
:在 index 位置插入 ele 元素
addAll(int index, Collection eles)
:从 index 位置开始将 eles 中所有元素添加进去
get(int index)
:获取指定位置的元素
indexOf(Object obj)
:返回 obj 首次出现的位置
lastIndexOf()
:
remove(int index)
:删除指定位置的元素,并返回此元素
set(int index, Object ele)
:设置指定位置的元素为 ele
subList(int fromIndex, int toIndex)
:返回从 fromIndex 到 toIndex 位置的子集合
12----ArrayList
底层结构和源码分析
-
ArrayList
中维护了一个Object
类型的数组elementData
,transient Object[] elementData
。 - 当创建
ArrayList
对象时,如果使用的是无参构造器,则初始elementData
容量为0,第一次添加数据时扩容elementData
为10,如需要再次扩容,则按照 1.5倍 增加。 - 当使用指定大小的构造器时,则初始
elementData
容量为指定大小,如需要扩容,则按照 1.5倍 增加。
16----Vector
底层结构和源码分析
-
Vector
中维护了一个Object
类型的数组elementData
,transient Object[] elementData
。 - 当创建
Vector
对象时,如果使用的是无参构造器,则默认elementData
容量为10,如需要扩容,则按照 2倍 增加。 - 当使用指定大小的构造器时,则初始
elementData
容量为指定大小,如需要扩容,则按照 2倍 增加。
17----LinkedList
底层结构和源码分析
- LinkedList
的底层操作机制
-
LinkedList
底层维护了一个双向链表 - 维护了两个属性
first
和last
分别指向首节点和尾节点 - 每个节点
Node对象
里面又维护了prev
,next
,item
三个属性,其中通过prev
指向前一个,next
指向后一个节点,最终实现双向链表 - 所以
LinkedList
的元素的添加和删除不是通过数组完成的,相对说效率较高
20----Set
接口和常用方法
- 无序不可重复
- 没有索引
21----HashSet
-
HashSet
实现了Set
接口 -
HashSet
实际上是HashMap
- 可以存放
null
值,但只能有一个 -
HashSet
不保证元素是有序的,取决于hash
后,再确定索引结果 - 元素,对象不可重复
经典Java
面试题:
Set set = new HashSet();
set.add(new Dog("tom")); //Ok
set.add(new Dog("tom")); //Ok
set.add(new String("hsp")); //Ok
set.add(new String("hsp")); //false
22----数组链表模拟
Node[] table = new Node[16];
Node john = new Node("john", null);
table[2] = john;
Node jack = new Node("jack", null);
john.next = jack; //jack挂载到john后
class Node{
Object item;
Node next; //单向链表
public Node(Object item, Node next){
this.item = item;
this.next = next;
}
}
23----HashSet
扩容机制
-
分析
HashSet
的添加元素底层是如何实现的(hash()+equals()
)-
HashSet
底层是HashMap
- 添加一个元素时,先得到
hash
值,然后转成索引值 - 找到存储数据表
table
,看这个索引值位置是否已经存放有元素 - 如果没有,则直接存入
- 如果有,调用
equals
比较,如果相同就放弃添加,若果不同,则向后添加 - 在
java8
中,如果一条链表的元素个数超过默认8,且table的大小大于等于默认64时,则进行树化(红黑树)
-
-
分析
HashSet
的扩容和转成红黑树的机制-
HashSet
底层是HashMap
,第一次添加时,table
数组扩容到16,临界值是16*0.75(加载因子)=12 - 如果
table
数组到了临界值12,就会扩容到16×2(2倍扩容)=32,新的临界值就是24,以此类推 - 在
java8
中,只有一条链表的元素个数超过默认8,且table
的大小大于等于默认64时,才会进行树化(红黑树),否则仍采用数组扩容机制
-
-
加载因子为何是0.75?
- 加载因子越大
hash
表(table
)数据密度越大,发生碰撞的几率越高,数组(table
)中的链表越容易长,造成查询或插入时的比较次数增多,性能会下降。 - 加载因子越小,就越容易触发扩容,数据密度也越小,意味着发生碰撞的几率越小,数组中的链表也就越短,查询和插入时比较的次数也越小,性能会更高。但是会浪费一定的内存空间。而且经常扩容也会影响性能。
- 源码中有:在理想情况下,使用随机哈希码,节点出现的频率在
hash
桶中遵循泊松分布,当桶中元素到达8个的时候,概率已经变得非常小,也就是说用0.75作为加载因子,每个碰撞位置的链表长度超过8个是几乎不可能的。
- 加载因子越大
28----HashSet
最佳实战
import java.util.HashSet;
import java.util.Objects;
public class HashSetExc {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add(new Employee("milan",18));
hashSet.add(new Employee("jackie",28));
hashSet.add(new Employee("milan",18));
// 若果不重写equals方法,则三个对象是不同对象都能被添加
// 重写后名字年龄相同的对象视为相同对象,则第三个对象无法添加
System.out.println("hashSet == "+ hashSet);
}
}
class Employee{
private String name;
private int age;
public Employee() {
}
public Employee(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 boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return age == employee.age && Objects.equals(name, employee.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
33----Map
接口特点
-
Map
用于保存具有映射关系的数据:Key-Value
-
Map
中的Key
和Value
可以是任何引用类型的数据 -
Map
中的Key
不允许重复,原因和HashSet
一样 -
Map
中的Value
可以重复 -
Map
中的Key
可以为null
,value
也可以为null
,注意key
为null
只能有一个 - 常用
String
类作为Map
中的key
-
key
和value
存在单向一一对应关系,即通过指定key
总能找到对应的value
-
Map
的k-v
存放在Node
中
35----Map
的常用方法
-
put
:添加 -
remove
:根据key
删除映射关系 -
get
:根据key
获取值 -
size
:获取元素个数 -
isEmpty
:判断是否为空 -
clear
:清除 -
containsKey
:查找key
是否存在
39----HashMap
扩容机制
- 扩容机制和
HashSet
相同-
HashMap
底层维护了Node
类型的数组table
,默认为null
- 当创建
HashMap
对象时,将加载因子(loadfactor
)初始化为0.75 - 当添加
k-v
时,通过key
的hash
值得到在table
的索引,然后判断该索引处是否有元素,如果么有则直接添加;如果有则继续判断该元素的key
是否和准备加入的元素key
相等,如果相等则替换value
,如果不相等需要判断是树结构还是链表结构做出相应处理。如果添加时发现容量不够,则需要扩容 - 第一次添加元素则需要扩容
table
容量为16,临界值12 - 以后再扩容,按照***2倍***增加
- 在
java8
中,如果一条链表的元素个数超过默认8,且table
的大小大于等于默认64时,则进行树化(红黑树)
-
42----HashTable
基本介绍
- 存放的元素是键值对:
k-v
- 键和值都不能为
null
- 使用方法与
HashMap
基本一致 -
HashMap
线程不安全,HashTable
线程安全(加了同步synchronized
)
43----HashTable
扩容
- 底层有数组
Hashtable$Entry[]
,初始化大小为11 - 临界值:
11*0.75=8
- 当大于临界值时,按照***
2倍+1
***的方式扩容
HashMap
与HashTable
的对比:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W4RRxSla-1640566300157)(https://www.bilibili.com/video/BV1YA411T76k?p=43)]
45----开发中如何选择集合实现类
- 先判断存储的类型(一组对象【单列】或一组键值对【双列】)
- 一组对象:
Collection
接口- 允许重复:
List
(有序可重复)- 增删多:
LinkedList
【底层维护了一个双向链表】 - 改查多:
ArrayList
【底层维护了Object
类型的可变数组】
- 增删多:
- 不许重复:
Set
(无序不可重复)- 无序:
HashSet
【底层是HashMap
,维护了哈希表,即数组+链表+红黑树】 - 排序:
TreeSet
- 插入和取出顺序一致:
LinkedHashSet
【维护了数组+双向链表】
- 无序:
- 允许重复:
- 一组键值对:
Map
- 键无序:
HashMap
【底层是哈希表(jdk1.7
:数组+链表,jdk1.8
:数组+链表+红黑树)】 - 键排序:
TreeMap
- 键插入和取出顺序一致:
LinkedHashMap
- 读取文件:
Properties
- 键无序:
46----TreeSet
介绍
- 当使用无参构造器
new TreeSet()
时,仍然是无序的 - 使用
TreeSet
提供的一个构造器,可以传入比较器,从而使得其有序
47----TreeMap
介绍
与TreeSet
基本一致,区别在于TreeMap
存入的是键值对k-v
- 当使用无参构造器
new TreeSet()
时,仍然是无序的 - 使用
TreeSet
提供的一个构造器,可以传入比较器,从而使得其有序
48----Collections
工具类
-
Collections
是一个操作Set,List,Map
等集合的工具类 - 提供了一系列方法
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
public class Collections_ {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("Jack");
arrayList.add("Tom");
arrayList.add("Moon");
arrayList.add("McGrady");
System.out.println(arrayList);
//reverse(List):翻转其中元素
Collections.reverse(arrayList);
System.out.println(arrayList);
//shuffle(List):随机排序
Collections.shuffle(arrayList);
System.out.println(arrayList);
//sort(List):根据元素自然顺序排序
Collections.sort(arrayList);
System.out.println(arrayList);
//sort(List,Comparator):根据指定的比较器对其排序
Collections.sort(arrayList, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).length() - ((String)o2).length();
}
});
System.out.println("按字符串长度排序:"+arrayList);
//swap(List,int,int):交换i和j处的元素
Collections.swap(arrayList,0,3);
System.out.println(arrayList);
//Object max(Collection):根据元素自然排序,返回集合中最大元素
//Object max(Collection,Comparator):
//Object min(Collection):
//Object min(Collection,Comparator):
//int frequency(Collection,Object):返回指定元素的出现次数
//void copy(List dest,List src):将src内容复制到dest中,注意dest必须大于等于src的长度
//boolean replaceAll(List list, Object oldVal, Object newVal):用新值替换list中的旧值
}
}
50----作业
作业一:
import java.util.ArrayList;
@SuppressWarnings({"all"})
public class Homework01 {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add(new News("新冠确诊病例超千万,数百万印度教信徒赴恒河\"圣浴\"引民众担忧"));
arrayList.add(new News("男子突然想起2个月前钓的鱼还在网兜里,捞起一看赶紧放生"));
int size = arrayList.size();
//倒序遍历
for (int i = size - 1; i >= 0; i--) {
//System.out.println(arrayList.get(i));
News news = (News)arrayList.get(i);
System.out.println(processTitle(news.getTitle()));
}
}
//专门写一个方法,处理现实新闻标题 process处理
public static String processTitle(String title) {
if(title == null) {
return "";
}
if(title.length() > 15) {
return title.substring(0, 15) + "..."; //[0,15)
} else {
return title;
}
}
}
/**
* 按要求实现:
* (1) 封装一个新闻类,包含标题和内容属性,提供get、set方法,重写toString方法,打印对象时只打印标题;
* (2) 只提供一个带参数的构造器,实例化对象时,只初始化标题;并且实例化两个对象:
* 新闻一:新冠确诊病例超千万,数百万印度教信徒赴恒河“圣浴”引民众担忧
* 新闻二:男子突然想起2个月前钓的鱼还在网兜里,捞起一看赶紧放生
* (3) 将新闻对象添加到ArrayList集合中,并且进行倒序遍历;
* (4) 在遍历集合过程中,对新闻标题进行处理,超过15字的只保留前15个,然后在后边加“…”
* (5) 在控制台打印遍历出经过处理的新闻标题;
*/
class News {
private String title;
private String content;
public News(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "News{" +
"title='" + title + '\'' +
'}';
}
}
作业二:
import java.util.ArrayList;
import java.util.Iterator;
@SuppressWarnings({"all"})
public class Homework02 {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
Car car = new Car("宝马", 400000);
Car car2 = new Car("宾利",5000000);
//1.add:添加单个元素
arrayList.add(car);
arrayList.add(car2);
System.out.println(arrayList);
//* 2.remove:删除指定元素
arrayList.remove(car);
System.out.println(arrayList);
//* 3.contains:查找元素是否存在
System.out.println(arrayList.contains(car));//F
//* 4.size:获取元素个数
System.out.println(arrayList.size());//1
//* 5.isEmpty:判断是否为空
System.out.println(arrayList.isEmpty());//F
//* 6.clear:清空
//System.out.println(arrayList.clear(););
//* 7.addAll:添加多个元素
System.out.println(arrayList);
arrayList.addAll(arrayList);//2个宾利
System.out.println(arrayList);
//* 8.containsAll:查找多个元素是否都存在
arrayList.containsAll(arrayList);//T
//* 9.removeAll:删除多个元素
//arrayList.removeAll(arrayList); //相当于清空
//* 使用增强for和 迭代器来遍历所有的car , 需要重写 Car 的toString方法
for (Object o : arrayList) {
System.out.println(o);//
}
System.out.println("===迭代器===");
Iterator iterator = arrayList.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}
}
}
/**
* 使用ArrayList 完成对 对象 Car {name, price} 的各种操作
* 1.add:添加单个元素
* 2.remove:删除指定元素
* 3.contains:查找元素是否存在
* 4.size:获取元素个数
* 5.isEmpty:判断是否为空
* 6.clear:清空
* 7.addAll:添加多个元素
* 8.containsAll:查找多个元素是否都存在
* 9.removeAll:删除多个元素
* 使用增强for和 迭代器来遍历所有的car , 需要重写 Car 的toString方法
*/
class Car {
private String name;
private double price;
public Car(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
作业三:
import java.util.*;
@SuppressWarnings({"all"})
public class Homework03 {
public static void main(String[] args) {
Map m = new HashMap();
m.put("jack", 650);//int->Integer
m.put("tom", 1200);//int->Integer
m.put("smith", 2900);//int->Integer
System.out.println(m);
m.put("jack", 2600);//替换,更新
System.out.println(m);
//为所有员工工资加薪100元;
//keySet
Set keySet = m.keySet();
for (Object key : keySet) {
//更新
m.put(key, (Integer)m.get(key) + 100);
}
System.out.println(m);
System.out.println("=============遍历=============");
//遍历 EntrySet
Set entrySet = m.entrySet();
//迭代器
Iterator iterator = entrySet.iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry)iterator.next();
System.out.println(entry.getKey() + "-" + entry.getValue());
}
System.out.println("====遍历所有的工资====");
Collection values = m.values();
for (Object value : values) {
System.out.println("工资=" + value);
}
}
}
/**
* 按要求完成下列任务
* 1)使用HashMap类实例化一个Map类型的对象m,键(String)和值(int)分别用于存储员工的姓名和工资,
* 存入数据如下: jack—650元;tom—1200元;smith——2900元;
* 2)将jack的工资更改为2600元
* 3)为所有员工工资加薪100元;
* 4)遍历集合中所有的员工
* 5)遍历集合中所有的工资
*/
作业四:试分析HashSet
和TreeSet
分别如何实现去重?
-
HashSet
的去重机制:hashcode()+equals()
,底层先通过存入的对象运算得到一个hash
值,通过hash
值得到对应的索引,如果发现table
索引值所在的位置没有数据,就直接存放;如果有数据,就使用equals()
比较,比较后如果不相同就加入,否则不加入。 -
TreeSet
的去重机制:如果传入了一个Comparator
匿名对象,就使用实现的compare()
去重,如果方法返回0,就认为是相同的元素,不添加;如果没有传入Comparator
匿名对象,则以添加的对象实现的Comparable
接口的compareTo()
方法去重。
import com.hspedu.map_.TreeMap_;
import java.util.TreeMap;
import java.util.TreeSet;
@SuppressWarnings({"all"})
public class Homework04 {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet();
treeSet.add("hsp");
treeSet.add("tom");
treeSet.add("king");
treeSet.add("hsp");//加入不了, 因为String的compareTo()方法比较的是内容
System.out.println(treeSet);
}
}
作业五:
//import java.util.TreeSet;
//@SuppressWarnings({"all"})
//public class Homework05 {
// public static void main(String[] args) {
// TreeSet treeSet = new TreeSet();
// //分析源码
// //add 方法,因为 TreeSet() 构造器没有传入Comparator接口的匿名内部类
// //所以在底层 Comparable<? super K> k = (Comparable<? super K>) key;
// //即 把 Perosn转成 Comparable类型
// treeSet.add(new Person());//ClassCastException.
// treeSet.add(new Person());//ClassCastException.
// treeSet.add(new Person());//ClassCastException.
// treeSet.add(new Person());//ClassCastException.
// treeSet.add(new Person());//ClassCastException.
//
// System.out.println(treeSet);
//
// }
//}
//
//class Person implements Comparable{
//
// @Override
// public int compareTo(Object o) {
// return 0;
// }
//}
作业六:
import java.util.HashSet;
import java.util.Objects;
@SuppressWarnings({"all"})
public class Homework06 {
public static void main(String[] args) {
HashSet set = new HashSet();//ok
Person p1 = new Person(1001,"AA");//ok
Person p2 = new Person(1002,"BB");//ok
set.add(p1);//ok
set.add(p2);//ok
p1.name = "CC";
set.remove(p1); //找不到1001,CC的hash值所表示的索引,因为AA-》CC,不改变1001,AA的hash值
System.out.println(set);//2
set.add(new Person(1001,"CC"));
System.out.println(set);//3
set.add(new Person(1001,"AA"));
System.out.println(set);//4
}
}
class Person {
public String name;
public int id;
public Person(int id, String name) {
this.name = name;
this.id = id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return id == person.id &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, id);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", id=" + id +
'}';
}
}
//===========================结果=================================
/*[Person{name='BB', id=1002}, Person{name='CC', id=1001}]
[Person{name='BB', id=1002}, Person{name='CC', id=1001}, Person{name='CC', id=1001}]
[Person{name='BB', id=1002}, Person{name='CC', id=1001}, Person{name='CC', id=1001}, Person{name='AA', id=1001}]
*/