集合汇总
1.集合的理解与好处
1.1数组
-
长度开始时必须指定,而且一旦指定,不能更改
-
保存的必须为同一类型的元素
-
使用数组进行增加、删除元素比较麻烦
//写出Person数组的扩容示意代码 Person[] pers=new Person[1];//定义长度为1 pers[0]=new Person(); //增加新的Person对象? Person[] pers2=new Person[pers.length+1];//新创建数组 for(){}//拷贝pers数组的元素到pers2 pers2[pers2.length-1]=new Person();//添加新的对象集合
1.2集合
- 可以动态保存任意多个对象,使用比较方便
- 可以提供一系列方便的操作对象的方法:add、remove、set、get等
- 使用集合添加、删除新元素的示意代码-简洁了
2.集合的框架体系
单列集合
双列集合【键值对】
3.Collection接口和常用方法
3.1Collection接口实现类的特点
public interface Collection<E> extends Iterable<E>
- Collection实现子类可以存放多个元素,每个元素可以是Object
- 有些Collection的实现类,可以存放重复的元素,有些不可以
- 有些Collection的实现类,有些是有序的(List),有些不是有序(Set)
- Collection接口没有直接的实现子类,是通过它的子接口Set和List来实现的
3.2Collection遍历方式
-
Iterator迭代器
-
Iterator对象称为迭代器,主要用于遍历Collection集合中的元素
-
所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器
-
Iterator仅用于遍历集合,Iterator本身并不存放对象
-
提示:在调用iterator.next()方法之前,必须要调用iterator.hasNext()进行检查,若不调用,且下一条记录无效,则直接调用iterator.next()会抛出NoSuchElementException异常。
-
//迭代器执行原理 Iterator iterator = coll.iterator();//得到一个集合的迭代器 //hasNext()://判断是否还有下一个元素 while(iterator.hasNext()){ //next();//①指针下移,②将下移以后集合位置上的元素返回 Object next=iterator.next();//返回下一个元素,类型是Object System.out.println(iterator.next()); } //生成while快捷键itit
-
-
增强for循环
-
增强for循环,可以代替iterator迭代器,特点:增强for就是简化版的iterator,本质是一样的,增强for循环只能用于遍历集合和数组
-
增强for循环,底层仍然是迭代器;可以debug进入for里面
-
基本用法
//基本用法 for(元素类型 元素名:集合名或数组名){ 访问元素 } //增强for,用于集合上 Collection col =new ArrayList(); col.add(new Book(){"三国演义","罗贯中",20.2}); col.add(new Book(){"西游记","吴承恩",18.9}); for(Object book:col){ System.out.println(book); } //增强for,用于数组上 int[] nums={1,23,5,67,45}; for(int i:nums){ System.out.println(i); }
-
4.List接口和常用方法
4.1 List接口基本介绍
- List接口是Collection接口的子接口
- List集合类中元素有序(即添加顺序和取出顺序一致且可重复)
- List集合中的每个元素都有其对应的顺序索引,即支持索引【索引从0开始】
- List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
4.2List常用方法
以ArrayList模拟List中的方法
add()、addAll()方法
public static void main(String[] args) {
List list=new ArrayList();
list.add("hytour");
list.add("cct");
//1.在指定index位置插入元素,在hytour和cct之间插入bsm
list.add(1,"bsm");
System.out.println(list);//[hytour, bsm, cct]
//2.在指定位置添加集合
List list2=new ArrayList();
list2.add("刀剑笑");
list2.add("韦大宝");
list.addAll(1,list2);//在hytour与bsm之间添加list2集合
System.out.println(list);//[hytour, 刀剑笑, 韦大宝, bsm, cct]
}
get()方法
public static void main(String[] args) {
//get属于 List 接口中提供方法
List list=new ArrayList();
list.add("hytour");
list.add("cct");
//获取索引为1的元素
System.out.println(list.get(1));//cct
//如果获取元素时,索引超过了集合的索引长度,则直接抛出异常
Object obj=list.get(2);//抛出异常
System.out.println(obj);
}
indexOf()、lastIndexOf()方法
public static void main(String[] args) {
//indexOf、lastIndexOf属于 List 接口中提供方法
List list=new ArrayList();
list.add("刀剑笑");
list.add("银蛟龙");
list.add("韦大宝");
list.add("银蛟龙");
list.add("张三");
list.add("银蛟龙");
list.add("李四");
//银蛟龙首次出现的位置,如果此集合不包含该元素,则返回 -1
System.out.println(list.indexOf("银蛟龙"));//1
System.out.println(list.indexOf("jh"));//-1
//银蛟龙最后一次出现的位置,如果此集合不包含该元素,则返回 -1
System.out.println(list.lastIndexOf("银蛟龙"));//5
System.out.println(list.lastIndexOf("rx"));//-1
}
set()方法
public static void main(String[] args) {
//set属于 List 接口中提供方法
List list = new ArrayList();
list.add("小三");
list.add("小四");
list.add("小五");
System.out.println(list);//[小三, 小四, 小五]
//将指定index位置的元素指定为当前值,将index=1位置的值设置为“刀剑笑”,但是返回值还是原来的值
Object obj = list.set(1, "刀剑笑");
System.out.println(obj);//小四
System.out.println(list);//[小三, 刀剑笑, 小五]
}
subList()方法
public static void main(String[] args) {
//subList属于 List 接口中提供方法
List list=new ArrayList();
list.add(1);//index=0
list.add(2);//index=1
list.add(3);//index=2
list.add(4);//index=3
list.add(5);//index=4
list.add(6);//index=5
list.add(7);//index=6
list.add(8);//index=7
list.add(9);//index=8
list.add(10);//index=9
//List subList(int fromIndex,int toIndex);返回从fromIndex到toIndex位置的子集合
//注意返回的子集合 fromIndex <=subList < toIndex
List new_list=list.subList(3,6);
System.out.println(new_list);//[4, 5, 6]
}
isEmpty()方法
//===========声明List变量并初始化,添加元素==========================
List list = new ArrayList();
list.add("刀剑笑");
list.add("银蛟龙");
list.add(true);
list.add(10);
System.out.println(list.size());//4
System.out.println(list.isEmpty());//false,如果集合添加了元素,则返回false
//==========声明List变量并初始化,未添加元素===========================
List list2 = new ArrayList();
System.out.println(list2.size());//0
System.out.println(list2.isEmpty());//true,如果集合未添加任何元素,则返回true
//==========声明List变量未初始化===========================
List list3=null;//定义集合list3未进行初始化操作
System.out.println(list3.size());//如果list3是null,直接调用list3.size()则会报异常
System.out.println(list3.isEmpty());//如果list3是null,直接调用list3.isEmpty()则会报异常
clear()方法
List list = new ArrayList();
list.add("刀剑笑");
list.add("银蛟龙");
list.add(true);
list.add(10);
System.out.println(list.size());//4
System.out.println(list.isEmpty());//false,如果集合添加了元素,则返回false
list.clear();//list进行clear之后,是一个空集合,不是null
System.out.println(list.size());//0
System.out.println(list);//[]
System.out.println(list.isEmpty());//true
addAll()方法
List list = new ArrayList();
list.add("xiaosan");
list.add("刀剑笑");
list.add(123);
System.out.println(list);//[xiaosan, 刀剑笑, 123]
List list2 = new ArrayList();
list2.add("三国演义");
list2.add("西游记");
list2.add(123);
list.addAll(list2);//将集合list2的所有元素都添加到集合list中
System.out.println(list);//[xiaosan, 刀剑笑, 123, 三国演义, 西游记, 123]
contains()方法
public static void main(String[] args) {
List list = new ArrayList();
list.add("热血江湖");
list.add("CF");
//contains list存在则返回true,list不存在则返回false 【严格区分大小写】
boolean res = list.contains("Cf");
System.out.println(res);//false
boolean res2 = list.contains("CF");
System.out.println(res2);//true
}
containsAll()方法
//boolean containsAll(Collection c) 判断集合中是否包含集合 c 中的所有元素
List list = new ArrayList();
list.add("xiaosan");
list.add("刀剑笑");
list.add(123);
System.out.println(list);//[xiaosan, 刀剑笑, 123]
List list2 = new ArrayList();
list2.add("三国演义");
list2.add("西游记");
list2.add(123);
list.addAll(list2);
System.out.println(list);//[xiaosan, 刀剑笑, 123, 三国演义, 西游记, 123]
//==========list3中的元素“红楼梦”,不在list中,所以返回false==========
List list3 = new ArrayList();
list3.add("三国演义");
list3.add("西游记");
list3.add("红楼梦");
System.out.println(list3);//[三国演义, 西游记, 红楼梦]
System.out.println(list.containsAll(list3));//false 如果list3的所有元素都在list中,则返回true;只要list3中有一个元素不在list中,则返回false
//==========list3中的元素全部都在list中,所以返回true==========
List list3 = new ArrayList();
list3.add("三国演义");
list3.add("西游记");
System.out.println(list3);//[三国演义, 西游记]
System.out.println(list.containsAll(list3));//true 如果list3的所有元素都在list中,则返回true;只要list3中有一个元素不在list中,则返回false
remove()方法
public static void main(String[] args) {
List list =new ArrayList();
list.add("三国演义");
list.add("西游记");
list.add("红楼梦");
list.add("水浒传");
//删除指定index位置的元素,并返回此元素
Object obj=list.remove(0);//删除索引为0的元素
System.out.println(obj);//三国演义
System.out.println(list);//[西游记, 红楼梦, 水浒传]
//删除指定的元素,删除成功返回true,否则返回false
Object obj2=list.remove("红楼梦");
System.out.println(obj2);//true
System.out.println(list);//[西游记, 水浒传]
//====================================================
//如果集合中包含多个相同的元素,根据指定元素删除时,删除的只是第一次出现的元素
List list =new ArrayList();
list.add("三国演义");
list.add("西游记");
list.add("水浒传");
list.add("红楼梦");
list.add("水浒传");
list.add("热血江湖");
System.out.println(list);//[三国演义, 西游记, 水浒传, 红楼梦, 水浒传, 热血江湖]
list.remove("水浒传");
System.out.println(list);//[三国演义, 西游记, 红楼梦, 水浒传, 热血江湖]
}
removeAll()方法
//boolean removeAll(Collection c) 从集合中删除所有在集合 c 中出现的元素(相当于把调用该方法的集合减去集合 c)。如果该操作改变了调用该方法的集合,则该方法返回 true。
List list=new ArrayList();
list.add(123);
list.add("小三");
list.add("西游记");
list.add("三国演义");
list.add("刀剑笑");
list.add("三国演义");
System.out.println(list);//[123, 小三, 西游记, 三国演义, 刀剑笑, 三国演义]
//===========list2集合中的元素都在list集合中========
List list2=new ArrayList();
list2.add("三国演义");
list2.add("刀剑笑");
System.out.println(list2);//[三国演义, 刀剑笑]
//如果list2中的元素,在list中出现多个,则list中的多个元素都会被删除,比如:三国演义
System.out.println(list.removeAll(list2));//true
System.out.println(list);//[123, 小三, 西游记]
//===========list3集合中的元素不都在list集合中========
List list3=new ArrayList();
list3.add("三国演义");
list3.add("刀剑笑");
list3.add("红楼梦");
System.out.println(list3);//[三国演义, 刀剑笑, 红楼梦]
//如果list3中的元素,部分出现在list集合中,则在list中的元素会被删除,如果出现多个,则多个都会被删除,比如:三国演义
System.out.println(list.removeAll(list3));//true
System.out.println(list);//[123, 小三, 西游记]
//===========list4集合中的元素都不在list集合中,则返回false========
List list4=new ArrayList();
list4.add("热血江湖");
list4.add("CF");
System.out.println(list4);
//如果list4中有一个元素在list集合中,则返回true;如果list4中的所有元素都不在list集合中,则返回false
System.out.println(list.removeAll(list4));//false
System.out.println(list);//[123, 小三, 西游记, 三国演义, 刀剑笑, 三国演义]
retainAll()方法
public static void main(String[] args) {
//boolean retainAll(Collection c) 从集合中删除集合 c 里不包含的元素(相当于把调用该方法的集合变成该集合和集合 c 的交集),
// 如果该操作改变了调用该方法的集合,则该方法返回 true。
List list = new ArrayList();
list.add("三国演义");
list.add("红楼梦");
list.add("西游记");
list.add("水浒传");
System.out.println(list);//[三国演义, 红楼梦, 西游记, 水浒传]
List list2 = new ArrayList();
list2.add("三国演义");
list2.add("西游记");
System.out.println(list2);//[三国演义, 西游记]
boolean res = list.retainAll(list2);
System.out.println(res);//true
System.out.println(list);//[三国演义, 西游记] 因为c集合中不包含“红楼梦"、"水浒传" 所以会把这两项删除了
}
4.3List的三种遍历方式
List的三种实现[ArrayList、LinkedList、Vector]
//这是一个程序入口
public static void main(String[] args) {
List list=new ArrayList();
list.add(123);
list.add("门柱");
list.add("热血江湖");
list.add("西游记");
list.add("person");
//====================迭代器循环==============================
Iterator iterator = list.iterator();
//快捷键itit
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println(obj);
}
//====================增强for循环==============================
//快捷键大写字母I
for (Object obj:list) {
System.out.println(obj);
}
//====================普通for循环==============================
//快捷键fori
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
4.4集合冒泡排序
public class Book{
public String name;
public String author;
public double price
}
//要求价格由小到大排序
public static void sort(List list){
int listSize = list.size();
for(int i = 0;i < listSize-1;i++){
for(int j=0;j<listSize-1-i;j++){
Book book1 = (Book)list.get(j);
Book book2 = (Book)list.get(j+1);
if(book1.getPrice()>book2.getPrice()){
list.set(j,book2);
list.set(j+1,book1);
}
}
}
}
5.ArrayList底层结构和源码分析
5.1ArrayList的注意事项
- ArrayList可以添加任何元素,包括null,可以加入多个null
- ArrayList是由数组来实现数据存储的
- ArrayList是线程不安全的(执行效率高),在多线程情况下,不建议使用ArrayList
5.2ArrayList的底层操作机制源码分析
-
ArrayList中维护了一个Object类型的数组elementData
transient Object[] elementData;//transient 修饰的元素不会被序列话,比如ArrayList被序列化时,elementData属性不会被序列化
-
当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第一次添加元素时,则扩容elementData为10,如果再次扩容,则扩容elementData为1.5倍。
0->10->10 * 1.5=15->15 * 1.5 = 22 ->22 * 1.5 =33
-
当创建ArrayList对象时,如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍
ArrayList arrayList = new ArrayList(5);//elementData容量初始大小为5
-
数组保留原先数据扩容Arrays.copyOf
int arr = {1,2,3,4,5}; arr = Arrays.copyOf(arr,10);//将arr扩容,由长度为5扩容到长度为10 //此时arr的内容为 {1,2,3,4,5,0,0,0,0,0}
-
具体源码跟踪可参考
6.Vector底层结构和源码剖析
6.1Vctor的基本介绍
-
Vector的定义
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
-
Vector底层也是一个对象数组
protected Object[] elementData;
-
Vector是线程同步的,即线程安全的,Vector类的操作方法带有synchronized关键字,比如:get
public synchronized E get(int index) { if (index >= elementCount) throw new ArrayIndexOutOfBoundsException(index); return elementData(index); }
-
在开发中,需要线程同步安全时,考虑是用Vector
6.2Vector与ArrayList的比较
底层结构 | 版本 | 线程安全(同步)效率 | 扩容倍数 | |
---|---|---|---|---|
ArrayList | 可变数组 | jdk1.2 | 不安全,效率高 | 如果有参构造1.5倍;如果是无参,1.第一次10,2.从第二次开始按1.5倍扩容 |
Vector | 可变数组 | jdk1.0 | 安全,效率不高 | 如果是无参,默认是10,满后就按2倍扩容;如果有参数指定大小,则每次直接按2倍扩容 |
7.LinkedList底层结构
7.1LinkedList底层操作机制
- 可以添加任意元素(元素可以重复),包括null
- 线程不安全,没有实现同步
- LinkedList底层维护了一个双向链表
- LinkedList中维护了两个属性first和last分别指向首节点和尾节点
- 每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个节点,通过next指向后一个节点,最终实现双向链表
- LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高
7.2源码分析
-
内部结构
//定义了一个Node类对象 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; } } //定义了主要属性 transient int size = 0; transient Node<E> first; transient Node<E> last;
-
添加元素,add()方法
//执行初始化操作 LinkedList linkedList = new LinkedList(); //内部执行 public LinkedList(){} //此时linkedLast的属性 first=null last=null //执行添加元素操作 linkedList.add(1); //内部执行 public boolean add(E e) { linkLast(e); return true; } void linkLast(E e) { final Node<E> l = last; final Node<E> newNode = new Node<>(l, e, null); last = newNode; if (l == null) first = newNode; else l.next = newNode; size++; modCount++; }
-
删除元素,remove()方法
linkedList.remove();//默认删除第一个元素 linkedList.remove(index);//删除指定索引的元素 linkedList.remove(Object);//删除指定的元素 //=================================源码分析===================================== linkedList.remove(index);//删除指定索引的元素 //内部执行 public E remove(int index) { checkElementIndex(index);//检查索引是否正常 return unlink(node(index));//1.根据索引获取到元素 2.调用删除方法 } //检查索引是否正常 private void checkElementIndex(int index) { if (!isElementIndex(index))//判断是否为链表索引 throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } //判断是否为链表索引 private boolean isElementIndex(int index) { return index >= 0 && index < size; } //根据索引获取到链表中的元素 Node<E> node(int index) { // assert isElementIndex(index); //非常巧妙的 中间分组算法 if (index < (size >> 1)) {//size >> 1 =>size/2 Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; //如果index=5,循环只能执行到i=4,此时x.next就相当于是i=5的元素 return x; } else { Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } } //调用删除方法 E unlink(Node<E> x) { // assert x != null; final E element = x.item; final Node<E> next = x.next; final Node<E> prev = x.prev; //prev == null说明x是第一个元素,如果要删除第一个元素,则需要把first指向第二个元素,即x.next if (prev == null) { first = next; } else { prev.next = next;//前一个元素的后一个结点,指向x的下一个结点,把x这个要删除的结点跳过 x.prev = null;//当前要删除的x结点,就不需要指向前一个结点了 } //next == null 说明x是最后一个元素,如果要删除最后一个元素,则需要把last指向倒数第二个元素,即x.prev if (next == null) { last = prev; } else { next.prev = prev;//后一个元素的前一个结点,指向x的前一个结点,把x这个要删除的结点跳过 x.next = null;//当前要删除的x结点,就不需要指向后一个结点了 } x.item = null;//将当前要删除的x结点,置为null size--;//链表元素总个数减一 modCount++;//修改次数+1 return element; }
-
修改元素,set()方法
//LinkedList修改某个结点对象 linkedList.set(2,"xiaosi"); //循环方式,因为LinkedList实现了List接口,遍历方式 //1.迭代器循环->快捷键itit Iterator iterator = linkedList.iterator(); while (iterator.hasNext()) { Object obj = iterator.next(); System.out.println(obj); } //2.增强for循环->快捷键大写字母I for (Object obj :linkedList) { System.out.println(obj); } //3.普通for循环 for (int i = 0; i < linkedList.size(); i++) { Object obj=linkedList.get(i); System.out.println(obj); }
-
获取元素,get()方法
//得到LinkedList的第二个对象 Object obj=linkedList.get(1);
-
LInkedList循环
//循环方式,因为LinkedList实现了List接口,遍历方式 //1.迭代器循环->快捷键itit Iterator iterator = linkedList.iterator(); while (iterator.hasNext()) { Object obj = iterator.next(); System.out.println(obj); } //2.增强for循环->快捷键大写字母I for (Object obj :linkedList) { System.out.println(obj); } //3.普通for循环 for (int i = 0; i < linkedList.size(); i++) { Object obj=linkedList.get(i); System.out.println(obj); }
7.3模拟双向链表
//定义一个Node类,Node 对象 表示双向链表的一个结点
public class Node{
public Object item;//真正存放数据
public Node next;//指向后一个结点
public Node pre;//指向前一个结点
public Node(Object name){
this.item = name;
}
public String toString(){
return "Node name="+item;
}
}
public static void main(String[] args){
//模拟一个简单的双向链表
Node jack = new Node("jack");
Node tom = new Node("tom");
Node xiaosan = new Node("xiaosan");
//连接三个结点,形成双向链表
//jack -> tom ->xiaosan
jack.next = tom;
tom.next = xiaosan;
//xiaosan -> tom -> jack
xiaosan.pre = tom;
tom.pre = jack;
//定义双向链表的头结点
Node first = jack;//让first引用指向jack
//定义双向链表的尾结点
Node last = xiaosan;//让last引用指向xiaosan
//从头到尾进行遍历
while(true){
if(first == null){
break;
}
//输出first信息
System.out.println(first);
first = first.next;//需要重点理解
}
//从尾到头进行遍历
while(true){
if(last == null){
break;
}
//输出last信息
System.out.println(last);
last = last.pre;//需要重点理解
}
//在tom和xiaosan之间,插入一个对象 smith
//1.先创建一个Node结点,name就是smith
Node smith = new Node("smith");
//2.下面就把smith加入到双向链表了
smith.next=xiaosan;
smith.pre=tom;
xiaosan.pre = smith;
tom.next = smith;
}
8.ArrayList和LinkedList比较
底层结构 | 增删效率 | 改查效率 | 线程安全 | |
---|---|---|---|---|
ArrayList | 可变数组 | 较低,数组扩容 | 较高 | 线程不安全 |
LinkedList | 双向链表 | 较高,通过链表追加 | 较低 | 线程不安全 |
如何选择ArrayList和LinkedList:
- 如果改查的操作较多,选择ArrayList
- 如果增删的操作多,选择LinkedList
9.Set接口和常用方法
9.1Set接口基本介绍
- 无序(添加和取出的顺序不一样),没有索引
- 不允许重复元素,最多包含一个null
- 注意:取出的顺序虽然不是添加的顺序,但是取出的顺序是固定的
9.2Set接口的常用方法
- 和List接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection接口一样
9.3Set接口的遍历方式
- 可以使用迭代器
- 增强for循环
- 不能使用索引的方式来获取【不能使用普通for循环】
10.Set接口实现类-HashSet
10.1HashSet全面说明
-
HashSet实现了Set接口
-
HahSet实际上是HashMap
public HashSet(){ map = new HashMap<>(); }
-
可以存放null值,但是只能存放一个null
-
HashSet不能保证元素是有序的,取决于hash后,再确定索引的结果
-
不能有重复元素/对象
10.2HashSet常用方法
HashSet set =new HashSet();
//在执行add方法后,会返回一个boolean值;如果添加成功,返回true,否则返回false
boolean res = set.add("rxjh");//true
boolean res1 = set.add("rxjh");//false
set.add(new Dog("tom"));//true
set.add(new Dog("tom"));//true
set.add(new String("jack"));//true
set.add(new String("jack"));//false
10.3模拟数组+链表
//结点,存储数据,可以指向下一个结点,从而形成链表
class Node{
Object item;//存放数据
Node next;//指向下一个结点
public Node(Object item,Node next){
this.item=item;
this.next=next;
}
}
//模拟一个HashMap的底层
public static void main(String[]args){
//1.创建一个数组,数组的类型是Node[]
Node [] table = new Node[16];
//2.创建结点
Node join=new Node("join",null);
table[2]=join;//将join放在table表的索引为2的位置
Node jack= new Node("jack",null);
join.next=jack;//将jack结点挂载到join
Node rose= new Node("rose",null);
jack.next=rose;//将rose结点挂载到jack
Node lucy = new Node("lucy",null);
table[3]=lucy;//将lucy放到table表的索引为3的位置
}
10.4HashSet底层机制
- HashSet底层是HashMap,HashMap底层是(数组+链表+红黑树)【单项链表】
- 添加一个元素时,先得到hash值-会转成->索引值
- 找到存储数据表table,看这个索引位置是否已经存放的有元素
- 如果没有直接添加
- 如果有,调用equals比较,如果相同就放弃添加,如果相同则添加到最后
- 在java8中,如果一个链表的元素个数到TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
public static void main(String[] args) {
HashSet hashSet=new HashSet();
hashSet.add("java");
hashSet.add("php");
hashSet.add("java");
}
//===============================源码=============================
//HashSet类中,维护变量信息
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
//1.执行构造器 new HashSet();
public HashSet() {
map = new HashMap<>();//底层是HashMap
}
//2.执行add("java");
public boolean add(E e) { //E为泛型,e就是添加的元素,第一个add时,e就是java字符串
return map.put(e, PRESENT)==null;
}
//3.以下代码已经进入到了HashMap类里面==============
public V put(K key, V value) {//key就是添加的java字符串;value就是PRESENT对象,起一个占位作用,为了使HashSet使用到HashMap,value放的统一都是object
return putVal(hash(key), key, value, false, true);
}
//计算hash值,就是table的索引值
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//实际添加元素核心代码【重点核心】
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;//定义了辅助变量
//table就是HashMap的一个数组,类型是Node[]
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;//table表的第一次扩容,n=16
//(1)根据key计算的hash,去计算key应该存放到table表的哪个索引位置,并把这个位置的对象,赋给p
//(2)如果p为null,表示还没有存放元素,就创建一个Node放在tabl[i]这个位置
if ((p = tab[i = (n - 1) & hash]) == null)//(n-1)即索引从0-15
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样
//(1)准备加入的key和p指向的Node结点的key是同一个对象
//(2)p指向的Node结点的key的equals()和准备加入的key比较后相同
//如果满足这两个条件中的任意一个,就不加入
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//判断p是不是一个红黑树,如果是,则调用putTreeVal,来进行添加
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//如果table对应的索引位置,已经是一个链表,就使用for循环比较
for (int binCount = 0; ; ++binCount) {//这个for循环没有限制条件,其实是个死循环
//依次和链表中的每一个元素比较后,都不相同,则加入到该链表的最后
if ((e = p.next) == null) {//如果第一次就满足的话,说明链表中就一个元素
p.next = newNode(hash, key, value, null);
//注意:把元素添加到链表后,立即判断 该链表是否已经达到8个结点,如果达到就调用treeifyBin()对当前这个链接进行树化,转成红黑树【在转成红黑树时,还进行一个判断,如果table长度小于64不会进行树化,只是进行table数组扩容】
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//依次和该链表的每一个元素比较过程中,如果有相同情况,就直接break
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
//for中把p.next给了e,相当于e是p指向的下一个结点;此处又把e给了p相当于p向下移动了一个结点
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//size 就是没加入一个结点的Node(k,v,h,next) 无论结点是加在table中某个索引的第一个元素,还是某个索引的链表上,只要加入Node元size就会++【只要size到达12就会扩容】
if (++size > threshold)
resize();
afterNodeInsertion(evict);//目的是让Hashmap得子类去实现的
return null;//添加之后,返回null
}
10.5HashSet扩容机制和转成红黑树机制
- HashSet底层是HashMap,第一次添加时,table数组扩容到16,临界值(threshold)是16*加载因子(loadFactor)是0.75=12
- 如果table数组使用到了临界值12,就会扩容到16*2=32---->32 * 2=64,新的临界值就是32 * 0.75 =24---->64 * 0.75 =48,依次类推
- 在java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制
//如何能for循环将所有的数据添加到同一个链表上 1.保证hash相同 2.key值不同
class A{
private int n;
public A(int n){
this.n = n;
}
@Override
public int hashCode(){
return 100;//A的所有对象,hash都返回100
}
}
public static void main(String[] args){
HashSet hashSet = new HashSet();
for (int i=1;i<=12;i++){
hashSet.add(new A(i));//equals()不同
}
}
//for循环8次,即i=8时,此时所有的元素都在table[4]这个位置形成一个链表,链表的个数为8个
//for再循环一次,即i=9时,链表元素大于8,触发树化操作,但是table的长度没有到64【此时table的长度为32,16->32】,所以只对table的长度进行了扩容,链表没有树化【此时这个链表上的数据为9个,链表在table的索引位置变成了table[8]】
//for再循环一次,即i=10时,链表元素大于8,触发树化操作,但是table的长度没有到64【此时table的长度为64,32->64】,所以只对table的长度进行了扩容,链表没有树化【此时这个链表上的数据为10个,链表在table的索引位置变成了table[32]】
//for再循环一次,即i=11时,链表元素大于8,触发树化操作,同时满足了数组长度不小于64且链表长度大于等于8.则对该链表进行树化
11.Set接口实现类-LinkedHashSet
11.1LinkedHashSet全面说明
- LinkedHashSet是HashSet的子类
- LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双向链表【LinkedHashMap是HashMap的一个子类】
- LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的【插入的顺序与查询的顺序相同】
- LinkedHashSet不允许添加重复元素
11.2LinkedHashSet底层机制
12.TreeSet
与HashSet最大的区别是,TreeSet可以排序【默认情况下,不做任何处理TreeSet也是无序的】
12.1当使用TreeSet的无参构造器,创建TreeSet时,仍然是无序的
public static void main(String[] args) {
TreeSet treeSet = new TreeSet();
treeSet.add("tom");
treeSet.add("lili");
treeSet.add("jack");
treeSet.add("a");
System.out.println(treeSet);//[a, jack, lili, tom]
}
12.2使用TreeSet的一个构造器可以传入一个比较器(匿名内部类)并指定排序规则
public static void main(String[] args) {
TreeSet treeSet = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//调用String的compareTo方法进行字符串大小的比较
return ((String)o1).compareTo((String)o2);
}
});
treeSet.add("tom");
treeSet.add("lili");
treeSet.add("jack");
treeSet.add("a");
System.out.println(treeSet);//[a, jack, lili, tom]
}
12.3使用Treeset添加一个对象
//=====================================发生异常情况========================================================
class Person{}
public static void main(String[] args) {
TreeSet treeSet1 = new TreeSet();
treeSet1.add(new Person());//会抛出异常,原因是Person没有实现Comparator接口
}
//=====================================正常情况========================================================
class Person implements Comparable{
@Override
public int compareTo(Object o) {
return 0;
}
}
public static void main(String[] args) {
TreeSet treeSet1 = new TreeSet();
treeSet1.add(new Person());//正常添加
}
13.Map接口和常用方法
13.1Map实现类的特点
- Map用于保存具有映射关系的数据:key-value
- Map中的key和value可以是任何应用类型的数据,会封装到HashMap$Node对象中
- Map中key不允许重复【原因和HashSet一样】
- Map中的value可以重复
- Map的key可以为null,value也可以为null,注意key为null只能有一个,value为null可以有多个
- 常用String类作为Map的key
- key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value
//如果添加相同key的数据,后添加的key的数据会把先添加的key的数据覆盖掉
Map map=new HashMap();
map.put("no1","刀剑笑");
map.put("no2","银蛟龙");
map.put("no1","韦大宝");
//最终结果只有两个元素,即{no2=银蛟龙,no1=韦大宝}
13.2常用方法
public static void main(String[] args){
//map常用方法
Map map=new HashMap();
//put:添加元素
map.put("no1","韦大宝");
//remove:根据键删除映射关系
map.remove("no1");
//get:根据键获取值
Object val=map.get("no1");//韦大宝
//size:获取元素个数
int size = map.size();
//isEmpty:判断个数是否为0
boolean bol=map.isEmpty();
//clear:清除k-v
map.clear();//void类型,无返回值
//containsKey():查找键是否存在
boolean no11 = map.containsKey("no1");//true
//containsValue():查找值是否存在
boolean nxd = map.containsValue("nxd");//true
}
13.3遍历方式
创建map并输入map数据
Map map=new HashMap();
map.put("吴承恩","西游记");
map.put("罗贯中","三国演义");
map.put("施耐庵","水浒传");
map.put("曹雪芹","红楼梦");
System.out.println(map);//{吴承恩=西游记, 罗贯中=三国演义, 曹雪芹=红楼梦, 施耐庵=水浒传}
获取map所有的key
//获取map所有的key
Set keySet = map.keySet();
System.out.println(keySet);//[吴承恩, 罗贯中, 曹雪芹, 施耐庵]
获取map所有的value
//获取map所有的value
Collection values = map.values();
System.out.println(values);//[西游记, 三国演义, 红楼梦, 水浒传]
通过keySet循环
//①先取出所有的key,通过key取出对应的value
Set keySet=map.keySet();
System.out.println("==================keySet()方式-增强for======================");
//增强for循环【快捷键大写I】
for (Object key :keySet) {
//吴承恩-西游记
//罗贯中-三国演义
//曹雪芹-红楼梦
//施耐庵-水浒传
System.out.println(key+"-"+map.get(key));
}
System.out.println("==================keySet()方式-迭代器======================");
//迭代器循环【快捷键itit】
Iterator iterator = keySet.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key+"-"+map.get(key));
}
System.out.println("==================keySet()方式-普通for循环======================");
//普通for循环【不支持】
for (int i = 0; i < keySet.size(); i++) {
//System.out.println(keySet.get(i));//keySet没有get()方法,不能进行普通for循环
}
通过values循环
//②取出所有的values
Collection values = map.values();
System.out.println("==================values()方式-增强for======================");
for (Object value :values) {
//西游记
//三国演义
//红楼梦
//水浒传
System.out.println(value);
}
System.out.println("==================values()方式-迭代器======================");
Iterator iterator1 = values.iterator();
while (iterator1.hasNext()) {
//西游记
//三国演义
//红楼梦
//水浒传
Object value = iterator1.next();
System.out.println(value);
}
System.out.println("==================values()方式-普通for循环======================");
//普通for循环【不支持】
for (int i = 0; i < values.size(); i++) {
//System.out.println(values.get(i));//values没有get()方法,不能进行普通for循环
}
通过EntrySet循环
//③通过EntrySet来获取k-v
Set entrySet = map.entrySet();
//迭代器循环
Iterator iterator2 = entrySet.iterator();
while (iterator2.hasNext()) {
Object entry = iterator2.next();
//System.out.println(next.getClass());//HashMap$Node没有getKey()、getValue()方法,HashMap$Node 实现->Map.Entry(getKey,getValue)
//向下转型
Map.Entry m=(Map.Entry)entry;
System.out.println(m.getKey()+"-"+m.getValue());
}
//增强for循环
for (Object entry :entrySet) {
//将entry转成Map.Entry【entry是没有getKey()方法、getValue()方法的】
Map.Entry m=(Map.Entry)entry;
System.out.println(m.getKey()+"-"+m.getValue());
}
//普通for循环【不支持】
for (int i = 0; i < entrySet.size(); i++) {
//Object entry=entrySet.get(i);//entrySet没有get()方法,不能进行普通for循环
}
14.Map接口实现类-HashMap
14.1HashMap小结
- HashMap是Map接口使用频率最高的实现类
- HashMap是以key-val 键值对方式来存储数据(HashMap$Node类型)
- key不能重复,但是值可以重复,允许使用null键和null值;注意key为null只能有一个,value为null可以有多个
- 如果添加相同的key,则会覆盖原来的key-val,等同于修改(key不会替换,val会替换)
- 与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的(jdk7.0数组+链表,jdk8.0的hashMap底层 数组+链表+红黑树)
- HashMap没有实现同步,因此是线程不安全的,方法没有做同步互斥的操作,没有synchronized
14.2HashMap底层机制
14.3HashMap扩容机制
扩容机制和HashSet相同
-
HashMap底层维护了Node类型的数组table,默认为null
transient Node<K,V>[] table;
-
当创建对象时,将加载因子(loadfactor)初始化为0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f; public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted }
-
当添加key-val时,通过key的hash值得到在table的索引。然后判断该索引处是否有元素,如果没有元素则直接添加。如果该索引处有元素,继续判断该元素的key是否和准备加入的key相等,如果相等则直接替换val;如果不相等,则需要判断是树结构还是链表结构,做出相应处理。如果添加时发现容量不够,则需要扩容。
-
第一次添加元素,则需要扩容table容量为16,临界值(threshold)为12(16*0.75)
-
以后再扩容,则需要扩容table容量为原来的2倍(即32),临界值为原来的2倍(即24),以此类推
-
在java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),该链表就会进行树化(红黑树)【该链表这句需要验证】
15.Map接口实现类-HashTable
15.1基本介绍
- 存放的元素是键值对:即K-V
- HashTable的键和值都不能为null,否则会抛出NullPointerException
- HashTable使用方法基本上和HashMap一样
- HashTable是线程安全的(synchronized),HashMap是线程不安全的
15.2基本使用
public static void main(String[] args) {
Hashtable hashtable = new Hashtable();
hashtable.put("join",100);//ok
//hashtable.put(null,100);//NullPointerException 键和值都不能为null
//hashtable.put("join",null);//NullPointerException 键和值都不能为null
hashtable.put("lic",100);//ok
hashtable.put("lic",100);//替换
}
15.3HashTable与HashMap对比
版本 | 线程安全(同步) | 效率 | 允许null键null值 | |
---|---|---|---|---|
HashMap | 1.2 | 不安全 | 高 | 可以 |
HashTable | 1.0 | 安全 | 较低 | 不可以 |
16.Map接口实现类Properties
16.1基本介绍
- Properties类继承自HashTable类并且实现了Map接口,也是使用一种键值对的形式来保存数据
- 使用特点与HashTable类似
- Properties还可以用于从xxx.properties文件中,加载数据到Properties类对象,并进行读取和修改
16.2常用方法
public static void main(String[] args) {
Properties properties = new Properties();
properties.put("join", 100);//ok
//properties.put(null,100);//NullPointerException
//properties.put("join",null);//NullPointerException
properties.put("lic", 100);//ok
properties.put("lic", 88);//替换
System.out.println(properties);//{lic=88, join=100}
//删除
properties.remove("lic");
System.out.println(properties);//{join=100}
//查询
System.out.println(properties.get("join"));//100
properties.put("lucy", 110);
System.out.println(properties.getProperty("lucy"));//null【这个返回null,需要注意】
}
17.Map接口实现类-TreeMap
17.1小结
-
TreeMap是TreeSet的底层实现
-
TreeMap可以按照key进行排序【默认情况下,不做任何处理的TreeMap也是无序的】
-
键不能为null,值可以null且可以有多个
-
当使用TreeMap的无参构造器,创建TreeMap时,仍然是无序的
public static void main(String[] args) { TreeMap treeMap = new TreeMap(); treeMap.put("jack","杰克"); treeMap.put("tom","汤姆"); treeMap.put("kiss","kkkk"); treeMap.put("smith","斯密斯"); System.out.println(treeMap);//{jack=杰克, kiss=kkkk, smith=斯密斯, tom=汤姆} }
-
使用TreeMap的一个构造器可以传入一个比较器(匿名内部类)并指定排序规则
public static void main(String[] args) { TreeMap treeMap = new TreeMap(new Comparator() { @Override public int compare(Object o1, Object o2) { //按照key由小到大排序 //return ((String)o1).compareTo((String) o2);//如果返回0的话,value做替换操作 //按照key的长度大小排序 return ((String)o1).length()-((String)o2).length(); //如果返回0的话,value做替换操作 } }); treeMap.put("jack","杰克"); treeMap.put("tom","汤姆"); treeMap.put("kiss","kkkk"); treeMap.put("smith","斯密斯"); System.out.println(treeMap);//{tom=汤姆, jack=kkkk, smith=斯密斯} //重点解释:为什么key为kiss的元素值没有添加进来,key为jack的元素值被替换为了key为kiss的值? //因为构造器中,指定的匿名内部类,是按照key字符串长度大小进行排序的,如果判断key的字符串长度相等的话,则值做替换操作 }
-
TreeMap的key必须实现Comparator接口
//=====================================发生异常情况======================================================== class Person{} public static void main(String[] args) { TreeMap treeMap = new TreeMap(); treeMap.put(new Person(),"213");//使用匿名person对象作为key直接抛出异常,原因是Person没有实现Comparator接口 } //=====================================正常情况======================================================== class Person implements Comparable { @Override public int compareTo(Object o) { return 0; } } public static void main(String[] args) { TreeMap treeMap = new TreeMap(); treeMap.put(new Person(),"213");//正常添加 }
18.集合汇总
如何选择集合实现类
- 先判断存储的类型(一组对象【单列】或一组键值对【双列】)
- 一组对象【单列】:Collection接口
- 允许重复:List
- 增删多:LinkedList【底层维护了一个双向链表】
- 改查多:ArrayList【底层维护了Object类型的可变数组】
- 不允许重复Set
- 无序:HashSet【底层是HashMap,维护了一个哈希表 即(数组+链表+红黑树)】
- 排序:TreeSet
- 插入和取出顺序一致:LinkedHashSet【底层是LinkedHashMap的底层是HashMap】,维护数组+双向链表
- 允许重复:List
- 一组键值对【双列】:Map
- 键无序:HashMap【底层是:哈希表 jdk7:数组+链表 jdk8:数组+链表+红黑树】
- 键排序:TreeMap
- 键插入和取出顺序一致:LinkedHashMap【底层是:数组+双向链表】
- 读取文件 :ProPerties
19.Collections工具类
19.1基本介绍
- Collections是一个操作Set、List和Map等集合的工具类
- Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作
19.2排序操作(均为静态方法)
- reverse(List):反转List中元素的顺序
- shuffle(List):对List集合元素进行随机排序
- sort(List):根据元素的自然顺序对指定List集合元素按升序排序
- sort(List,Comparator):根据指定的Comparator产生的顺序对List集合元素进行排序
- swap(List,int,int):将指定List集合中的i处元素和j处元素进行交换
public static void main(String[] args) {
List list = new ArrayList();
list.add("tom");
list.add("smith");
list.add("king");
list.add("milan");
System.out.println(list);//[tom, smith, king, milan]
//reverse(List):反转List中元素的顺序
Collections.reverse(list);
System.out.println(list);//[milan, king, smith, tom]
//shuffle(List):对List集合元素进行随机排序
Collections.shuffle(list);//[milan, smith, tom, king] 每次顺序都不一样
System.out.println(list);
Collections.shuffle(list);//[tom, milan, smith, king] 每次顺序都不一样
System.out.println(list);
//sort(List):根据元素的自然顺序对指定List集合元素按升序排序
Collections.sort(list);
System.out.println("自然排序:"+list);//自然排序:[king, milan, smith, tom]
//希望按照字符串的长度大小排序
Collections.sort(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//if(o1 instanceof String) 可以加上这样的判断
return ((String)o1).length()-((String)o2).length();
}
});
System.out.println("按照字符长度大小排序:"+list);//按照字符长度大小排序:[tom, king, milan, smith]
//swap(List,int,int):将指定List集合中的i处元素和j处元素进行交换
System.out.println("交换前="+list);//交换前=[tom, king, milan, smith]
Collections.swap(list,0,1);
System.out.println("交换后="+list);//交换后=[king, tom, milan, smith]
}
19.2查找、替换
- Object max(Collection):根据元素的自然顺序,返回给定集合中最大元素
- Object max(Collection Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素
- Object min(Collection)
- Object min(Collection Comparator)
- int frequency(Collection,Object):返回指定集合中指定元素的出现次数
- void copy(List dest,List src):将src中的内容复制到dest中
- boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象的所有旧值
public static void main(String[] args) {
List list = new ArrayList();
list.add("tom");
list.add("smith");
list.add("king");
list.add("milan");
System.out.println(list);//[tom, smith, king, milan]
//Object max(Collection):根据元素的自然顺序,返回给定集合中最大元素
System.out.println("自然顺序最大元素=" + Collections.max(list));//自然顺序最大元素=tom
//Object max(Collection Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素
//比如,我们要返回长度最大的元素
Object maxObject = Collections.max(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String) o1).length() - ((String) o2).length();
}
});
System.out.println("长度最大的元素=" + maxObject);//长度最大的元素=smith
//int frequency(Collection,Object):返回指定集合中指定元素的出现次数
int tom_num = Collections.frequency(list, "tom");
System.out.println("tom出现的次数=" + tom_num);//tom出现的次数=1
//void copy(List dest,List src):将src中的内容复制到dest中
ArrayList dest = new ArrayList();
//Collections.copy(dest,list);
//System.out.println("dest="+dest);//异常:Source does not fit in dest
//为了完成一个完整拷贝,我们需要先给dest赋值,大小和list.size()一样
for (int i = 0; i < list.size(); i++) {
dest.add("");
}
//拷贝
Collections.copy(dest,list);
System.out.println("dest="+dest);//dest=[tom, smith, king, milan]
//boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象的所有旧值
list.add("tom");
System.out.println("list替换前:"+list);//list替换前:[tom, smith, king, milan, tom]
//list中有tom就替换成汤姆
Collections.replaceAll(list,"tom","汤姆");
System.out.println("list替换后:"+list);//list替换后:[汤姆, smith, king, milan, 汤姆]
}