• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

索静丨LLH

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

java-集合

第十一章 集合

什么是算法和数据结构

【1】算法:

(1)可以解决具体问题 :例如   1+2+3+4+。。。+99+100

解题流程=算法

(2)有设计解决的具体的流程

算法1: 1+2=3  3+3=6 6+4=10.....加到100  --》5050

算法2:(1+100)*50=101*50=5050-->高斯算法

(3)有评价这个算法的具体的指标 --》时间复杂度  空间复杂度(从数学角度考虑)

 

---------------------------------------------------------------------

 

【2】数据结构:就是在计算机的缓存,内存,硬盘  如何组织管理数据的。重点在结构上,是按照什么结构来组织管理我们的数据。

 

数据结构分为:

(1)逻辑结构 :--》思想上的结构--》卧室,厨房,卫生间 ---》线性表(数组,链表),图,树,栈,队列

(2)物理结构 :--》真实结构--》钢筋混凝土+牛顿力学------》紧密结构(顺序结构),跳转结构(链式结构)

 

【3】紧密结构(顺序结构),跳转结构(链式结构) 

以线性表为例:

线性表的逻辑结构如图所示:

 

线性表特点:

 

线性表是n个类型相同数据元素的有限序列,通常记作a0,a1,,,ai-1,ai,ai+1,,,,,an-1)。

1.相同数据类型

  在线性表的定义中,我们看到从a0到an-1的n个数据元素是具有相同属件的亓素。

  比如说可以都是数字,例如(12,23,45,56,45);

  也可以是宇符,例如(A,B,....Z)

  当然也可以是具有更复杂结构的数据元素,例如学生、商品、装备等。

  相同数据类型意味着在内存中存储时,每个元素会占用相同的内存空间,便于后续的查询定位。

2.序列(顺序性)

  在线性表的相邻数据元素之间存在若序偶关系,

  即ai-1是ai的直接前驱,则ai是ai-1的直接后续,

  同时ai又是ai+1的直接前驱,ai+1是ai的直接后续。

  唯一没有直接前驱的元素a0 一端称为表头,唯一没有后续的元素an-1一端称为表尾。

  除了表头和表尾元素外,任何一个元素都有且仅有一个直接前驱和直接后继。

3.有限

  线件表中数据元素的个数n定义为线性表的长度, n是个有限值。

  当n=0时线性表为空表,

  在非空的线性表中每个数据元索在线性表中都有唯一确定的序号,

  例如a0的序号是0 ,ai的序号是i。

  在一个具有n>0个数据元素的线性表中,数据元素序号的范围是[O, n-1]。

 逻辑结构和物理结构的关系:

线性表逻辑结构,对应的真实结构如果是紧密结构---》典型就是  数组:

 

线性表逻辑结构,对应的真实结构如果是跳转结构---》典型就是  链表:

优点:删除元素,插入元素效率高

缺点:查询元素效率低 

 

 

 

集合的引入

【1】数组,集合都是对多个数据进行存储操作的,简称为容器。

PS:这里的存储指的是内存层面的存储,而不是持久化存储(.txt,.avi,.jpg,数据库)。

 

【2】数组:特点:

(1)数组一旦指定了长度,那么长度就被确定了,不可以更改。

int[] arr = new int[6];

(2)数组一旦声明了类型以后,数组中只能存放这个类型的数据。数组中只能存放同一种类型的数据。

int[] arr,String[] s,double[] d.....

【3】数组:缺点:

(1)数组一旦指定了长度,那么长度就被确定了,不可以更改。

(2)删除,增加元素  效率低。

(3)数组中实际元素的数量是没有办法获取的,没有提供对应的方法或者属性来获取

(4)数组存储:有序,可重复 ,对于无序的,不可重复的数组不能满足要求。

 

【4】正因为上面的缺点,引入了一个新的存储数据的结构---》集合

 

【5】集合一章我们会学习很多集合,为什么要学习这么多集合呢?

因为不同集合底层数据结构不一样。集合不一样,特点也不一样

 

 

简要集合结构图

 

集合应用场合

前端后端数据库交互:

 

当需要将相同结构的个体整合到一起的时候,需要集合。

实际应用场合:

 

 

 

 

Colletion接口

Colletion接口常用方法

 1 package com.llh;
 2 
 3 import java.util.ArrayList;
 4 import java.util.Arrays;
 5 import java.util.Collection;
 6 import java.util.List;
 7 
 8 public class Test01 {
 9     //这是main方法,程序的入口
10     public static void main(String[] args) {
11         /*
12         Collection接口的常用方法:
13         增加:add(E e) addAll(Collection<? extends E> c)
14         删除:clear() remove(Object o)
15         修改:
16         查看:iterator() size()
17         判断:contains(Object o)  equals(Object o) isEmpty()
18          */
19         //创建对象:接口不能创建对象,利用实现类创建对象:
20         Collection col = new ArrayList();
21         //调用方法:
22         //集合有一个特点:只能存放引用数据类型的数据,不能是基本数据类型
23         //基本数据类型自动装箱,对应包装类。int--->Integer
24         col.add(18);
25         col.add(12);
26         col.add(11);
27         col.add(17);
28         System.out.println(col/*.toString()*/);
29         List list = Arrays.asList(new Integer[]{11, 15, 3, 7, 1});
30         col.addAll(list);//将另一个集合添加入col中
31         System.out.println(col);
32         //col.clear();清空集合
33         System.out.println(col);
34         System.out.println("集合中元素的数量为:"+col.size());
35         System.out.println("集合是否为空:"+col.isEmpty());
36         boolean isRemove = col.remove(15);
37         System.out.println(col);
38         System.out.println("集合中数据是否被删除:"+isRemove);
39         Collection col2 = new ArrayList();
40         col2.add(18);
41         col2.add(12);
42         col2.add(11);
43         col2.add(17);
44         Collection col3 = new ArrayList();
45         col3.add(18);
46         col3.add(12);
47         col3.add(11);
48         col3.add(17);
49         System.out.println(col2.equals(col3));
50         System.out.println(col2==col3);//地址一定不相等  false
51         System.out.println("是否包含元素:"+col3.contains(117));
52     }
53 }

 

Collection集合的遍历

迭代器简要原理图:

 

 

 1 package com.llh;
 2 
 3 import java.util.ArrayList;
 4 import java.util.Collection;
 5 import java.util.Iterator;
 6 
 7 public class Test02 {
 8     //这是main方法,程序的入口
 9     public static void main(String[] args) {
10         Collection col = new ArrayList();
11         col.add(18);
12         col.add(12);
13         col.add(11);
14         col.add(17);
15         col.add("abc");
16         col.add(9.8);
17         //对集合遍历(对集合中元素进行查看)
18         //方式1:普通for循环
19         /*for(int i= 0;i<col.size();i++){
20             col.
21         }*/
22         //方式2:增强for循环
23         for(Object o:col){
24             System.out.println(o);
25         }
26         System.out.println("------------------------");
27         //方式3:iterator()
28         Iterator it = col.iterator();
29         while(it.hasNext()){
30             System.out.println(it.next());
31         }
32     }
33 }

 

 

List接口

 

List接口的常用方法和遍历方式

 1 package com.llh;
 2 
 3 import com.sun.org.apache.xerces.internal.dom.PSVIAttrNSImpl;
 4 import java.util.ArrayList;
 5 import java.util.Iterator;
 6 import java.util.List;
 7 
 8 public class Test03 {
 9     //这是main方法,程序的入口
10     public static void main(String[] args) {
11         /*
12         List接口中常用方法:
13         增加:add(int index, E element)
14         删除:remove(int index)  remove(Object o)
15         修改:set(int index, E element)
16         查看:get(int index)
17         判断:
18          */
19         List list = new ArrayList();
20         list.add(13);
21         list.add(17);
22         list.add(6);
23         list.add(-1);
24         list.add(2);
25         list.add("abc");
26         System.out.println(list);
27         list.add(3,66);
28         System.out.println(list);
29         list.set(3,77);
30         System.out.println(list);
31         list.remove(2);//在集合中存入的是Integer类型数据的时候,调用remove方法调用的是:remove(int index)
32         System.out.println(list);
33         list.remove("abc");
34         System.out.println(list);
35         Object o = list.get(0);
36         System.out.println(o);
37         //List集合 遍历:
38         //方式1:普通for循环:
39         System.out.println("---------------------");
40         for(int i = 0;i<list.size();i++){
41             System.out.println(list.get(i));
42         }
43         //方式2:增强for循环:
44         System.out.println("---------------------");
45         for(Object obj:list){
46             System.out.println(obj);
47         }
48         //方式3:迭代器:
49         System.out.println("---------------------");
50         Iterator it = list.iterator();
51         while(it.hasNext()){
52             System.out.println(it.next());
53         }
54     }
55 }

 

ArrayList实现类(JDK1.7)

【1】在idea中切换JDK的方法:

  

【2】ArrayList实现List接口的失误:

集合创始人 承认了这个失误,但是在后续的版本中没有删除,觉得没必要: 

 

【3】底层重要属性:

 

在JDK1.7中:在调用构造器的时候给底层数组elementData初始化,数组初始化长度为10:

 

对应内存:

 

调用add方法:

1         ArrayList al = new ArrayList();
2         System.out.println(al.add("abc"));
3         System.out.println(al.add("def"));
 

当数组中的10个位置都满了的时候就开始进行数组的扩容,扩容长度为 原数组的1.5倍:

 

 

 

 

ArrayList实现类(JDK1.8)

【1】JDK1.8底层依旧是Object类型的数组,size:数组中有效长度:

 

【2】ArrayList al = new ArrayList();调用空构造器:

  

【3】add方法:

 

 

 

 

 

 

 

 

Vector实现类

【1】底层Object数组,int类型属性表示数组中有效长度:

 

【2】Vector v=new Vector();调用构造器:

  

 

【3】add方法:

  

泛型

引入

【1】什么是泛型(Generic):

泛型就相当于标签

形式:<>  

集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,

JDK1.5之 后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。

Collection<E>, List<E>, ArrayList<E> 这个<E>就是类型参数,即泛型。

 

【2】没有泛型的时候使用集合:

 1 package com.llh;
 2 
 3 import java.util.ArrayList;
 4 
 5 public class Test01 {
 6     //这是main方法,程序的入口
 7     public static void main(String[] args) {
 8         //创建一个ArrayList集合,向这个集合中存入学生的成绩:
 9         ArrayList al = new ArrayList();
10         al.add(98);
11         al.add(18);
12         al.add(39);
13         al.add(60);
14         al.add(83);
15         al.add("丽丽");
16         //对集合遍历查看:
17         for(Object obj:al){
18             System.out.println(obj);
19         }
20     }
21 }

 

如果不使用泛型的话,有缺点:

一般我们在使用的时候基本上往集合中存入的都是相同类型的数据--》便于管理,所以现在什么引用数据类型都可以存入集合,不方便!

 

【3】JDK1.5以后开始使用泛型,集合中使用泛型:

 1 package com.llh;
 2 
 3 import java.util.ArrayList;
 4 
 5 public class Test01 {
 6     //这是main方法,程序的入口
 7     public static void main(String[] args) {
 8         //创建一个ArrayList集合,向这个集合中存入学生的成绩:
 9         //加入泛型的优点:在编译时期就会对类型进行检查,不是泛型对应的类型就不可以添加入这个集合。
10         ArrayList<Integer> al = new ArrayList<Integer>();
11         al.add(98);
12         al.add(18);
13         al.add(39);
14         al.add(60);
15         al.add(83);
16         /*al.add("丽丽");
17         al.add(9.8);*/
18         //对集合遍历查看:
19         /*for(Object obj:al){
20             System.out.println(obj);
21         }*/
22         for(Integer i:al){
23             System.out.println(i);
24         }
25     }
26 }

 

【4】泛型总结:

(1)JDK1.5以后

(2)泛型实际就是 一个<>引起来的 参数类型,这个参数类型  具体在使用的时候才会确定具体的类型。

 

  

(3)使用了泛型以后,可以确定集合中存放数据的类型,在编译时期就可以检查出来。

(4)使用泛型你可能觉得麻烦,实际使用了泛型才会简单,后续的遍历等操作简单。

(5)泛型的类型:都是引用数据类型,不能是基本数据类型。

(6)ArrayList<Integer> al = new ArrayList<Integer>();在JDK1.7以后可以写为:

ArrayList<Integer> al = new ArrayList<>();  --<>  ---钻石运算符

 

自定义泛型结构

 

泛型类,泛型接口

【1】泛型类的定义和实例化:

 1 package com.llh;
 2 
 3 /**
 4  * GenericTes就是一个普通的类
 5  * GenericTest<E> 就是一个泛型类
 6  * <>里面就是一个参数类型,但是这个类型是什么呢?这个类型现在是不确定的,相当于一个占位
 7  * 但是现在确定的是这个类型一定是一个引用数据类型,而不是基本数据类型
 8  */
 9 public class GenericTest<E> {
10     int age;
11     String name;
12     E sex;
13     public void a(E n){
14     }
15     public void b(E[] m){
16     }
17 }
18 class Test{
19     //这是main方法,程序的入口
20     public static void main(String[] args) {
21         //GenericTest进行实例化:
22         //(1)实例化的时候不指定泛型:如果实例化的时候不明确的指定类的泛型,那么认为此泛型为Object类型
23         GenericTest gt1 = new GenericTest();
24         gt1.a("abc");
25         gt1.a(17);
26         gt1.a(9.8);
27         gt1.b(new String[]{"a","b","c"});
28         //(2)实例化的时候指定泛型:---》推荐方式
29         GenericTest<String> gt2 = new GenericTest<>();
30         gt2.sex = "男";
31         gt2.a("abc");
32         gt2.b(new String[]{"a","b","c"});
33 
34     }
35 }

 

【2】继承情况:

(1)父类指定泛型:

 1 class SubGenericTest extends GenericTest<Integer>{
 2 }
 3 class Demo{
 4     //这是main方法,程序的入口
 5     public static void main(String[] args) {
 6         //指定父类泛型,那么子类就不需要再指定泛型了,可以直接使用
 7         SubGenericTest sgt = new SubGenericTest();
 8         sgt.a(19);
 9     }
10 }

 

(2)父类不指定泛型:

如果父类不指定泛型,那么子类也会变成一个泛型类,那这个E的类型可以在创建子类对象的时候确定:

1 class SubGenericTest2<E> extends GenericTest<E>{
2 
3 }
1 class Demo2{
2     //这是main方法,程序的入口
3     public static void main(String[] args) {
4         SubGenericTest2<String> s = new  SubGenericTest2<>();
5         s.a("abc");
6         s.sex = "男";
7     }
8 }

 

【3】应用场合:

 

 

【4】细节:

(1)泛型类可以定义多个参数类型

 

(2)泛型类的构造器的写法:

 

(3)不同的泛型的引用类型不可以相互赋值:

  

(4)泛型如果不指定,那么就会被擦除,反应对应的类型为Object类型:

 

(5)反省类中的静态方法不能使用类的泛型:

 

(6)不能直接使用E[]的创建:

  

泛型方法
 1 package com.llh;
 2 
 3 /**
 4  * 1.什么是泛型方法:
 5  * 不是带泛型的方法就是泛型方法
 6  * 泛型方法有要求:这个方法的泛型的参数类型要和当前的类的泛型无关
 7  * 换个角度:
 8  * 泛型方法对应的那个泛型参数类型 和  当前所在的这个类 是否是泛型类,泛型是啥  无关
 9  * 2.泛型方法定义的时候,前面要加上<T>
10  *     原因:如果不加的话,会把T当做一种数据类型,然而代码中没有T类型那么就会报错
11  * 3.T的类型是在调用方法的时候确定的
12  * 4.泛型方法可否是静态方法?可以是静态方法
13  */
14 public class TestGeneric<E> {
15     //不是泛型方法 (不能是静态方法)
16     public static void a(E e){
17     }
18     //是泛型方法
19     public static <T>  void b(T t){
20     }
21 }
22 class Demo{
23     //这是main方法,程序的入口
24     public static void main(String[] args) {
25         TestGeneric<String> tg = new TestGeneric<>();
26         tg.a("abc");
27         tg.b("abc");
28         tg.b(19);
29         tg.b(true);
30     }
31 }

 

泛型参数存在继承关系的情况

  

通配符

【1】在没有通配符的时候:

下面的a方法,相当于方法的重复定义,报错

1 public class Test {
2     /*public void a(List<Object> list){
3     }
4     public void a(List<String> list){
5     }
6     public void a(List<Integer> list){
7     }*/
8 }

 

【2】引入通配符:

 1 public class Demo {
 2     //这是main方法,程序的入口
 3     public static void main(String[] args) {
 4         List<Object> list1 = new ArrayList<>();
 5         List<String> list2 = new ArrayList<>();
 6         List<Integer> list3 = new ArrayList<>();
 7         List<?> list = null;
 8         list = list1;
 9         list = list2;
10         list = list3;
11     }
12 }

 

发现: A 和 B是子类父类的关系,G<A>和G<B>不存在子类父类关系,是并列的

加入通配符?后,G<?>就变成了 G<A>和G<B>的父类

 

【3】使用通配符:

 1 package com.llh;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 
 6 public class Test {
 7     /*public void a(List<Object> list){
 8     }
 9     public void a(List<String> list){
10     }
11     public void a(List<Integer> list){
12     }*/
13     public void a(List<?> list) {
14         //内部遍历的时候用Object即可,不用?
15         for (Object a : list) {
16             System.out.println(a);
17         }
18     }
19 }
20 
21 class T {
22     //这是main方法,程序的入口
23     public static void main(String[] args) {
24         Test t = new Test();
25         t.a(new ArrayList<Integer>());
26         t.a(new ArrayList<String>());
27         t.a(new ArrayList<Object>());
28     }
29 }

 

【4】查看API中应用位置:

 

使用通配符后的细节
1. 
 1 public class Test {
 2     public void a(List<?> list){
 3         //1.遍历:
 4         for(Object a:list){
 5             System.out.println(a);
 6         }
 7         //2.数据的写入操作 :
 8         //list.add("abc");-->出错,不能随意的添加数据
 9         list.add(null);
10         //3.数据的读取操作:
11         Object s = list.get(0);
12     }
13 }
14 class T{
15     //这是main方法,程序的入口
16     public static void main(String[] args) {
17         Test t = new Test();
18         t.a(new ArrayList<Integer>());
19         t.a(new ArrayList<String>());
20         t.a(new ArrayList<Object>());
21     }
22 }

 

泛型受限
 1 package com.llh;
 2 
 3 import com.llh1.Person;
 4 
 5 import java.util.ArrayList;
 6 import java.util.List;
 7 
 8 public class Test {
 9     //这是main方法,程序的入口
10     public static void main(String[] args) {
11         //a,b,c三个集合是并列的关系:
12         List<Object> a = new ArrayList<>();
13         List<Person> b = new ArrayList<>();
14         List<Student> c = new ArrayList<>();
15         /*开始使用泛型受限:泛型的上限
16         List<? extends Person>:
17         就相当于:
18         List<? extends Person>是List<Person>的父类,是List<Person的子类>的父类
19          */
20         List<? extends Person> list1 = null;
21         /*list1 = a;
22         list1 = b;
23         list1 = c;*/
24         /*开始使用泛型受限:泛型的下限
25         List<? super Person>
26         就相当于:
27         List<? super Person>是List<Person>的父类,是List<Person的父类>的父类
28          */
29         List<? super Person> list2 = null;
30         list2 = a;
31         list2 = b;
32         list3 = c;
33     }
34 }

 

LinkedList实现类的使用

 1 package com.llh;
 2 
 3 import java.util.Iterator;
 4 import java.util.LinkedList;
 5 
 6 public class Test {
 7     //这是main方法,程序的入口
 8     public static void main(String[] args) {
 9         /*
10         LinkedList常用方法:
11         增加 addFirst(E e) addLast(E e)
12              offer(E e) offerFirst(E e) offerLast(E e)
13         删除 poll()
14             pollFirst() pollLast()  ---》JDK1.6以后新出的方法,提高了代码的健壮性
15             removeFirst() removeLast()
16         修改
17         查看 element()
18              getFirst()  getLast()
19              indexOf(Object o)   lastIndexOf(Object o)
20              peek()
21              peekFirst() peekLast()
22         判断
23          */
24         //创建一个LinkedList集合对象:
25         LinkedList<String> list = new LinkedList<>();
26         list.add("aaaaa");
27         list.add("bbbbb");
28         list.add("ccccc");
29         list.add("ddddd");
30         list.add("eeeee");
31         list.add("bbbbb");
32         list.add("fffff");
33         list.addFirst("jj");
34         list.addLast("hh");
35         list.offer("kk");//添加元素在尾端
36         list.offerFirst("pp");
37         list.offerLast("rr");
38         System.out.println(list);//LinkedList可以添加重复数据
39         System.out.println(list.poll());//删除头上的元素并且将元素输出
40         System.out.println(list.pollFirst());
41         System.out.println(list.pollLast());
42         System.out.println(list.removeFirst());
43         System.out.println(list.removeLast());
44         System.out.println(list);//LinkedList可以添加重复数据
45         /*list.clear();//清空集合
46         System.out.println(list);*/
47         /*System.out.println(list.pollFirst());*/
48         /*System.out.println(list.removeFirst());报错:Exception in thread "main" java.util.NoSuchElementException*/
49         //集合的遍历:
50         System.out.println("---------------------");
51         //普通for循环:
52         for(int i = 0;i<list.size();i++){
53             System.out.println(list.get(i));
54         }
55         System.out.println("---------------------");
56         //增强for:
57         for(String s:list){
58             System.out.println(s);
59         }
60         System.out.println("---------------------");
61         //迭代器:
62         /*Iterator<String> it = list.iterator();
63         while(it.hasNext()){
64             System.out.println(it.next());
65         }*/
66         //下面这种方式好,节省内存
67         for(Iterator<String> it = list.iterator();it.hasNext();){
68             System.out.println(it.next());
69         }
70     }
71 }
 
LinkedList简要底层原理图

 

模拟LinkedList源码
 1 public class MyLinkedList {
 2     //链中一定有一个首节点:
 3     Node first;
 4     //链中一定有一个尾节点:
 5     Node last;
 6     //计数器:
 7     int count = 0;
 8     //提供一个构造器:
 9     public MyLinkedList(){
10     }
11     //添加元素方法:
12     public void add(Object o){
13         if(first == null){//证明你添加的元素是第一个节点:
14             //将添加的元素封装为一个Node对象:
15             Node n = new Node();
16             n.setPre(null);
17             n.setObj(o);
18             n.setNext(null);
19             //当前链中第一个节点变为n
20             first = n;
21             //当前链中最后一个节点变为n
22             last = n;
23         }else{//证明已经不是链中第一个节点了
24             //将添加的元素封装为一个Node对象:
25             Node n = new Node();
26             n.setPre(last);//n的上一个节点一定是当前链中的最后一个节点last
27             n.setObj(o);
28             n.setNext(null);
29             //当前链中的最后一个节点的下一个元素 要指向n
30             last.setNext(n);
31             //将最后一个节点变为n
32             last = n;
33         }
34         //链中元素数量加1
35         count++;
36     }
37     //得到集合中元素的数量:
38     public int getSize(){
39         return count;
40     }
41     //通过下标得到元素:
42     public Object get(int index){
43         //获取链表的头元素:
44         Node n = first;
45         //一路next得到想要的元素
46         for(int i=0;i<index;i++){
47             n = n.getNext();
48         }
49         return n.getObj();
50     }
51 }
52 class Test{
53     //这是main方法,程序的入口
54     public static void main(String[] args) {
55         //创建一个MyLinkedList集合对象:
56         MyLinkedList ml = new MyLinkedList();
57         ml.add("aa");
58         ml.add("bb");
59         ml.add("cc");
60         System.out.println(ml.getSize());
61         System.out.println(ml.get(0));
62     }
63 }

 

debug验证数据添加成功:

  

LinkedList源码解析

【1】JDK1.7和JDK1.8的LinkedList的源码是一致的

【2】源码:

 1 public class LinkedList<E>{//E是一个泛型,具体的类型要在实例化的时候才会最终确定
 2         transient int size = 0;//集合中元素的数量
 3         //Node的内部类
 4         private static class Node<E> {
 5         E item;//当前元素
 6         Node<E> next;//指向下一个元素地址
 7         Node<E> prev;//上一个元素地址
 8         Node(Node<E> prev, E element, Node<E> next) {
 9             this.item = element;
10             this.next = next;
11             this.prev = prev;
12         }
13     }
14         transient Node<E> first;//链表的首节点
15         transient Node<E> last;//链表的尾节点
16         //空构造器:
17         public LinkedList() {
18     }
19         //添加元素操作:
20         public boolean add(E e) {
21         linkLast(e);
22         return true;
23     }
24         void linkLast(E e) {//添加的元素e
25         final Node<E> l = last;//将链表中的last节点给l 如果是第一个元素的话 l为null
26                 //将元素封装为一个Node具体的对象:
27         final Node<E> newNode = new Node<>(l, e, null);
28                 //将链表的last节点指向新的创建的对象:
29         last = newNode;
30                 
31         if (l == null)//如果添加的是第一个节点
32             first = newNode;//将链表的first节点指向为新节点
33         else//如果添加的不是第一个节点 
34             l.next = newNode;//将l的下一个指向为新的节点
35         size++;//集合中元素数量加1操作
36         modCount++;
37     }
38         //获取集合中元素数量
39         public int size() {
40         return size;
41     }
42         //通过索引得到元素:
43         public E get(int index) {
44         checkElementIndex(index);//健壮性考虑
45         return node(index).item;
46     }
47         
48     Node<E> node(int index) {
49         //如果index在链表的前半段,那么从前往后找
50         if (index < (size >> 1)) {
51             Node<E> x = first;
52             for (int i = 0; i < index; i++)
53                 x = x.next;
54             return x;
55         } else {//如果index在链表的后半段,那么从后往前找
56             Node<E> x = last;
57             for (int i = size - 1; i > index; i--)
58                 x = x.prev;
59             return x;
60         }
61     }
62 }

 

面试题:iterator(),Iterator,Iterable关系

【1】面试题:对应的关系:

 

【2】hasNext(),next()的具体实现:

  

 

【3】增强for循环  底层也是通过迭代器实现的:

 

 

ListIterator迭代器

【1】加入字符串:

 1 import java.util.ArrayList;
 2 import java.util.Iterator;
 3 import java.util.List;
 4 /**
 5  * @author : msb-zhaoss
 6  */
 7 public class Test2 {
 8     //这是main方法,程序的入口
 9     public static void main(String[] args) {
10         ArrayList<String> list = new ArrayList<>();
11         list.add("aa");
12         list.add("bb");
13         list.add("cc");
14         list.add("dd");
15         list.add("ee");
16         //在"cc"之后添加一个字符串"kk"
17         Iterator<String> it = list.iterator();
18         while(it.hasNext()){
19             if("cc".equals(it.next())){
20                 list.add("kk");
21             }
22         }
23     }
24 }

 

发现报错:

 

出错原因:就是迭代器和list同时对集合进行操作:

  

解决办法:事情让一个“人”做 --》引入新的迭代器:ListIterator

迭代和添加操作都是靠ListIterator来完成的:

 1 import java.util.ArrayList;
 2 import java.util.Iterator;
 3 import java.util.List;
 4 import java.util.ListIterator;
 5 /**
 6  * @author : msb-zhaoss
 7  */
 8 public class Test2 {
 9     //这是main方法,程序的入口
10     public static void main(String[] args) {
11         ArrayList<String> list = new ArrayList<>();
12         list.add("aa");
13         list.add("bb");
14         list.add("cc");
15         list.add("dd");
16         list.add("ee");
17         //在"cc"之后添加一个字符串"kk"
18         ListIterator<String> it = list.listIterator();
19         while(it.hasNext()){
20             if("cc".equals(it.next())){
21                 it.add("kk");
22             }
23         }
24         System.out.println(it.hasNext());
25         System.out.println(it.hasPrevious());
26         //逆向遍历:
27         while(it.hasPrevious()){
28             System.out.println(it.previous());
29         }
30         System.out.println(it.hasNext());
31         System.out.println(it.hasPrevious());
32         System.out.println(list);
33     }
34 }

 

Set接口

 

HashSet实现类的使用

【1】放入Integer类型数据:

 1 package com.llh;
 2 
 3 import java.util.HashSet;
 4 
 5 public class TestInteger {
 6     //这是main方法,程序的入口
 7     public static void main(String[] args) {
 8         //创建一个HashSet集合:
 9         HashSet<Integer> hs = new HashSet<>();
10         System.out.println(hs.add(19));//true
11         hs.add(5);
12         hs.add(20);
13         System.out.println(hs.add(19));//false 这个19没有放入到集合中
14         hs.add(41);
15         hs.add(0);
16         System.out.println(hs.size());//唯一,无序
17         System.out.println(hs);
18     }
19 }

 

【2】放入String类型数据:

 1 package com.llh;
 2 
 3 import java.util.HashSet;
 4 
 5 public class TestString {
 6     //这是main方法,程序的入口
 7     public static void main(String[] args) {
 8         //创建一个HashSet集合:
 9         HashSet<String> hs = new HashSet<>();
10         hs.add("hello");
11         hs.add("apple");
12         hs.add("banana");
13         hs.add("html");
14         hs.add("apple");
15         hs.add("css");
16         System.out.println(hs.size());
17         System.out.println(hs);
18     }
19 }

 

【3】放入自定义的引用数据类型的数据:

 1 package com.llh;
 2 
 3 import java.util.HashSet;
 4 
 5 public class TestStudent {
 6     //这是main方法,程序的入口
 7     public static void main(String[] args) {
 8         //创建一个HashSet集合:
 9         HashSet<Student> hs = new HashSet<>();
10         hs.add(new Student(19,"lili"));
11         hs.add(new Student(20,"lulu"));
12         hs.add(new Student(18,"feifei"));
13         hs.add(new Student(19,"lili"));
14         hs.add(new Student(10,"nana"));
15         System.out.println(hs.size());
16         System.out.println(hs);
17     }
18 }

 

上面自定义的类型不满足 唯一,无序的特点。为什么呢?

 

【4】HashSet原理图:(简要原理图)

 

【5】疑问:

1.数组的长度是多少。

2.数组的类型是什么?

3.hashCode,equals方法真的调用了吗?验证

4.底层表达式是什么?

5.同一个位置的数据 向前放  还是 向后放?

6.放入数组中的数据,是直接放的吗?是否封装为对象了?

 

LinkedHashSet使用

其实就是在HashSet的基础上,多了一个总的链表,这个总链表将放入的元素串在一起,方便有序的遍历:

(可以看到LinkedHashMap.Entry 继承自HashMap.Node 除了Node 本身有的几个属性外,额外增加了before after 用于指向前一个Entry 后一个Entry。也就是说,元素之间维持着一条总的链表数据结构。)

 

 

 

代码:

 1 package com.llh;
 2 
 3 import java.util.HashSet;
 4 import java.util.LinkedHashMap;
 5 import java.util.LinkedHashSet;
 6 
 7 public class TestInteger {
 8     //这是main方法,程序的入口
 9     public static void main(String[] args) {
10         //创建一个HashSet集合:
11         LinkedHashSet<Integer> hs = new LinkedHashSet<>();
12         System.out.println(hs.add(19));//true
13         hs.add(5);
14         hs.add(20);
15         System.out.println(hs.add(19));//false 这个19没有放入到集合中
16         hs.add(41);
17         hs.add(0);
18         System.out.println(hs.size());//唯一,无序
19         System.out.println(hs);
20     }
21 }

 

比较器的使用

【1】以int类型为案例:

比较的思路:将比较的数据做差,然后返回一个int类型的数据,将这个int类型的数值  按照 =0  >0  <0

1         int a = 10;
2         int b = 20;
3         System.out.println(a-b); // =0  >0  <0

 

【2】比较String类型数据:

String类实现了Comparable接口,这个接口中有一个抽象方法compareTo,String类中重写这个方法即可

1         String a = "A";
2         String b = "B";
3         System.out.println(a.compareTo(b));

 

【3】比较double类型数据:

1         double a = 9.6;
2         double b = 9.3;
3        /* System.out.println((int)(a-b));*/
4         System.out.println(((Double) a).compareTo((Double) b));

 

【4】比较自定义的数据类型:

(1)内部比较器:

 1 package com.llh;
 2 
 3 public class Student implements Comparable<Student>{
 4     private int age;
 5     private double height;
 6     private String name;
 7     public int getAge() {
 8         return age;
 9     }
10     public void setAge(int age) {
11         this.age = age;
12     }
13     public double getHeight() {
14         return height;
15     }
16     public void setHeight(double height) {
17         this.height = height;
18     }
19     public String getName() {
20         return name;
21     }
22     public void setName(String name) {
23         this.name = name;
24     }
25     public Student(int age, double height, String name) {
26         this.age = age;
27         this.height = height;
28         this.name = name;
29     }
30     @Override
31     public String toString() {
32         return "Student{" +
33                 "age=" + age +
34                 ", height=" + height +
35                 ", name='" + name + '\'' +
36                 '}';
37     }
38     @Override
39     public int compareTo(Student o) {
40         //按照年龄进行比较:
41         /*return this.getAge() - o.getAge();*/
42         //按照身高比较
43         /*return ((Double)(this.getHeight())).compareTo((Double)(o.getHeight()));*/
44         //按照名字比较:
45         return this.getName().compareTo(o.getName());
46     }
47 }

 

1 public class Test02 {
2     //这是main方法,程序的入口
3     public static void main(String[] args) {
4         //比较两个学生:
5         Student s1 = new Student(14,160.5,"alili");
6         Student s2 = new Student(14,170.5,"bnana");
7         System.out.println(s1.compareTo(s2));
8     }
9 }

 

 

(2)外部比较器:

 1 package com.llh;
 2 
 3 import java.util.Comparator;
 4 
 5 public class Student{
 6     private int age;
 7     private double height;
 8     private String name;
 9     public int getAge() {
10         return age;
11     }
12     public void setAge(int age) {
13         this.age = age;
14     }
15     public double getHeight() {
16         return height;
17     }
18     public void setHeight(double height) {
19         this.height = height;
20     }
21     public String getName() {
22         return name;
23     }
24     public void setName(String name) {
25         this.name = name;
26     }
27     public Student(int age, double height, String name) {
28         this.age = age;
29         this.height = height;
30         this.name = name;
31     }
32     @Override
33     public String toString() {
34         return "Student{" +
35                 "age=" + age +
36                 ", height=" + height +
37                 ", name='" + name + '\'' +
38                 '}';
39     }
40 }
41 class BiJiao01 implements Comparator<Student> {
42     @Override
43     public int compare(Student o1, Student o2) {
44         //比较年龄:
45         return o1.getAge()-o2.getAge();
46     }
47 }
48 class BiJiao02 implements Comparator<Student> {
49     @Override
50     public int compare(Student o1, Student o2) {
51         //比较姓名:
52         return o1.getName().compareTo(o2.getName());
53     }
54 }

 

 1 class BiJiao03 implements Comparator<Student> {
 2     @Override
 3     public int compare(Student o1, Student o2) {
 4         //在年龄相同的情况下 比较身高  年龄不同比较年龄
 5         if((o1.getAge()-o2.getAge())==0){
 6             return ((Double)(o1.getHeight())).compareTo((Double)(o2.getHeight()));
 7         }else{//年龄不一样
 8             return o1.getAge()-o2.getAge();
 9         }
10     }
11 }
 1 package com.llh;
 2 
 3 import java.util.Comparator;
 4 
 5 public class Test02 {
 6     //这是main方法,程序的入口
 7     public static void main(String[] args) {
 8         //比较两个学生:
 9         Student s1 = new Student(9,160.5,"alili");
10         Student s2 = new Student(14,170.5,"bnana");
11         //获取外部比较器:
12         Comparator bj1 = new BiJiao03();
13         System.out.println(bj1.compare(s1, s2));
14     }
15 }

 

【5】外部比较器和内部比较器 谁好呀?

答案:外部比较器,多态,扩展性好 

 

TreeSet实现类的使用

【1】存入Integer类型数据:(底层利用的是内部比较器)

 1 package com.llh;
 2 
 3 import java.util.TreeSet;
 4 
 5 public class Test01 {
 6     //这是main方法,程序的入口
 7     public static void main(String[] args) {
 8         //创建一个TreeSet:
 9         TreeSet<Integer> ts = new TreeSet<>();
10         ts.add(12);
11         ts.add(3);
12         ts.add(7);
13         ts.add(9);
14         ts.add(3);
15         ts.add(16);
16         System.out.println(ts.size());
17         System.out.println(ts);
18     }
19 }

 

特点:唯一,无序(没有按照输入顺序进行输出), 有序(按照升序进行遍历)

 

【2】原理:底层:二叉树(数据结构中的一个逻辑结构)

  

【3】放入String类型数据:(底层实现类内部比较器)

 

 1 package com.llh;
 2 
 3 import java.util.TreeSet;
 4 
 5 public class Test02 {
 6     //这是main方法,程序的入口
 7     public static void main(String[] args) {
 8         //创建一个TreeSet:
 9         TreeSet<String> ts = new TreeSet<>();
10         ts.add("elili");
11         ts.add("blili");
12         ts.add("alili");
13         ts.add("elili");
14         ts.add("clili");
15         ts.add("flili");
16         ts.add("glili");
17         System.out.println(ts.size());
18         System.out.println(ts);
19     }
20 }

 

【4】想放入自定义的Student类型的数据:

(1)利用内部比较器:

 1 public class Student implements Comparable<Student> {
 2     private int age;
 3     private String name;
 4     public int getAge() {
 5         return age;
 6     }
 7     public void setAge(int age) {
 8         this.age = age;
 9     }
10     public String getName() {
11         return name;
12     }
13     public void setName(String name) {
14         this.name = name;
15     }
16     public Student(int age, String name) {
17         this.age = age;
18         this.name = name;
19     }
20     @Override
21     public String toString() {
22         return "Student{" +
23                 "age=" + age +
24                 ", name='" + name + '\'' +
25                 '}';
26     }
27     @Override
28     public int compareTo(Student o) {
29         return this.getAge()-o.getAge();
30     }
31 }
 1 package com.llh;
 2 
 3 import java.util.TreeSet;
 4 
 5 public class Test03 {
 6     //这是main方法,程序的入口
 7     public static void main(String[] args) {
 8         //创建一个TreeSet:
 9         TreeSet<Student> ts = new TreeSet<>();
10         ts.add(new Student(10,"elili"));
11         ts.add(new Student(8,"blili"));
12         ts.add(new Student(4,"alili"));
13         ts.add(new Student(9,"elili"));
14         ts.add(new Student(10,"flili"));
15         ts.add(new Student(1,"dlili"));
16         System.out.println(ts.size());
17         System.out.println(ts);
18     }
19 }

 

(2)通过外部比较器:

 1 package com.llh;
 2 
 3 import java.util.Comparator;
 4 
 5 public class Student  {
 6     private int age;
 7     private String name;
 8     public int getAge() {
 9         return age;
10     }
11     public void setAge(int age) {
12         this.age = age;
13     }
14     public String getName() {
15         return name;
16     }
17     public void setName(String name) {
18         this.name = name;
19     }
20     public Student(int age, String name) {
21         this.age = age;
22         this.name = name;
23     }
24     @Override
25     public String toString() {
26         return "Student{" +
27                 "age=" + age +
28                 ", name='" + name + '\'' +
29                 '}';
30     }
31 }
32 class BiJiao implements Comparator<Student>{
33     @Override
34     public int compare(Student o1, Student o2) {
35         return o1.getName().compareTo(o2.getName());
36     }
37 }
 1 package com.llh;
 2 
 3 import java.util.Comparator;
 4 import java.util.TreeSet;
 5 public class Test03 {
 6     //这是main方法,程序的入口
 7     public static void main(String[] args) {
 8         //创建一个TreeSet:
 9         //利用外部比较器,必须自己制定:
10         Comparator<Student> com = new BiJiao();
11         TreeSet<Student> ts = new TreeSet<>(com);//一旦指定外部比较器,那么就会按照外部比较器来比较
12         ts.add(new Student(10,"elili"));
13         ts.add(new Student(8,"blili"));
14         ts.add(new Student(4,"alili"));
15         ts.add(new Student(9,"elili"));
16         ts.add(new Student(10,"flili"));
17         ts.add(new Student(1,"dlili"));
18         System.out.println(ts.size());
19         System.out.println(ts);
20     }
21 }

 

实际开发中利用外部比较器多,因为扩展性好(多态)

 

换一种写法:

 1 package com.llh;
 2 
 3 import java.util.Comparator;
 4 import java.util.TreeSet;
 5 
 6 public class Test03 {
 7     //这是main方法,程序的入口
 8     public static void main(String[] args) {
 9         //创建一个TreeSet:
10         //利用外部比较器,必须自己制定:
11         /*Comparator<Student> com = new Comparator<Student>() {
12             @Override
13             public int compare(Student o1, Student o2) {
14                 return o1.getName().compareTo(o2.getName());
15             }
16         };*/
17         TreeSet<Student> ts = new TreeSet<>(new Comparator<Student>() {
18             @Override
19             public int compare(Student o1, Student o2) {
20                 return o1.getName().compareTo(o2.getName());
21             }
22         });//一旦指定外部比较器,那么就会按照外部比较器来比较
23         ts.add(new Student(10,"elili"));
24         ts.add(new Student(8,"blili"));
25         ts.add(new Student(4,"alili"));
26         ts.add(new Student(9,"elili"));
27         ts.add(new Student(10,"flili"));
28         ts.add(new Student(1,"dlili"));
29         System.out.println(ts.size());
30         System.out.println(ts);
31     }
32 }

 

【5】TreeSet底层的二叉树的遍历是按照升序的结果出现的,这个升序是靠中序遍历得到的:

 

 

Collection部分整体结构图

  

Map接口

 

常用方法

 1 package com.llh;
 2 
 3 import java.util.Collection;
 4 import java.util.HashMap;
 5 import java.util.Map;
 6 import java.util.Set;
 7 
 8 public class Test01 {
 9     //这是main方法,程序的入口
10     public static void main(String[] args) {
11         /*
12         增加:put(K key, V value)
13         删除:clear() remove(Object key)
14         修改:
15         查看:entrySet() get(Object key) keySet() size() values()
16         判断:containsKey(Object key) containsValue(Object value)
17             equals(Object o) isEmpty()
18          */
19         //创建一个Map集合:无序,唯一
20         Map<String,Integer> map = new HashMap<>();
21         System.out.println(map.put("lili", 10101010));
22         map.put("nana",12345234);
23         map.put("feifei",34563465);
24         System.out.println(map.put("lili", 34565677));
25         map.put("mingming",12323);
26         /*map.clear();清空*/
27         /*map.remove("feifei");移除*/
28         System.out.println(map.size());
29         System.out.println(map);
30         System.out.println(map.containsKey("lili"));
31         System.out.println(map.containsValue(12323));
32         Map<String,Integer> map2 = new HashMap<>();
33         System.out.println(map2.put("lili", 10101010));
34         map2.put("nana",12345234);
35         map2.put("feifei",34563465);
36         System.out.println(map2.put("lili", 34565677));
37         map2.put("mingming2",12323);
38         System.out.println(map==map2);
39         System.out.println(map.equals(map2));//equals进行了重写,比较的是集合中的值是否一致
40         System.out.println("判断是否为空:"+map.isEmpty());
41         System.out.println(map.get("nana"));
42         System.out.println("-----------------------------------");
43         //keySet()对集合中的key进行遍历查看:
44         Set<String> set = map.keySet();
45         for(String s:set){
46             System.out.println(s);
47         }
48         System.out.println("-----------------------------------");
49         //values()对集合中的value进行遍历查看:
50         Collection<Integer> values = map.values();
51         for(Integer i:values){
52             System.out.println(i);
53         }
54         System.out.println("-----------------------------------");
55         //get(Object key) keySet()
56         Set<String> set2 = map.keySet();
57         for(String s:set2){
58             System.out.println(map.get(s));
59         }
60         System.out.println("-----------------------------------");
61         //entrySet()
62         Set<Map.Entry<String, Integer>> entries = map.entrySet();
63         for(Map.Entry<String, Integer> e:entries){
64             System.out.println(e.getKey()+"----"+e.getValue());
65         }
66     }
67 }
 

TreeMap

【1】key的类型为String类型:

 1 package com.llh;
 2 
 3 import java.util.Map;
 4 import java.util.TreeMap;
 5 
 6 public class Test02 {
 7     //这是main方法,程序的入口
 8     public static void main(String[] args) {
 9         Map<String,Integer> map = new TreeMap<>();
10         map.put("blili",1234);
11         map.put("alili",2345);
12         map.put("blili",5467);
13         map.put("clili",5678);
14         map.put("dlili",2345);
15         System.out.println(map.size());
16         System.out.println(map);
17     }
18 }

 

【2】key的类型是一个自定义的引用数据类型:

(1)内部比较器:

 1 package com.llh;
 2 
 3 import java.util.Map;
 4 import java.util.TreeMap;
 5 
 6 public class Test03 {
 7     //这是main方法,程序的入口
 8     public static void main(String[] args) {
 9         Map<Student,Integer> map = new TreeMap<>();
10         map.put(new Student(19,"blili",170.5),1001);
11         map.put(new Student(18,"blili",150.5),1003);
12         map.put(new Student(19,"alili",180.5),1023);
13         map.put(new Student(17,"clili",140.5),1671);
14         map.put(new Student(10,"dlili",160.5),1891);
15         System.out.println(map);
16         System.out.println(map.size());
17     }
18 }
 
 1 public class Student implements Comparable<Student>{
 2     private int age;
 3     private String name;
 4     private double height;
 5     public int getAge() {
 6         return age;
 7     }
 8     public void setAge(int age) {
 9         this.age = age;
10     }
11     public String getName() {
12         return name;
13     }
14     public void setName(String name) {
15         this.name = name;
16     }
17     public double getHeight() {
18         return height;
19     }
20     public void setHeight(double height) {
21         this.height = height;
22     }
23     public Student(int age, String name, double height) {
24         this.age = age;
25         this.name = name;
26         this.height = height;
27     }
28     @Override
29     public String toString() {
30         return "Student{" +
31                 "age=" + age +
32                 ", name='" + name + '\'' +
33                 ", height=" + height +
34                 '}';
35     }
36     @Override
37     public int compareTo(Student o) {
38        /* return this.getAge()-o.getAge();*/
39         return this.getName().compareTo(o.getName());
40     }
41 }

 

(2)外部比较器:

 

 1 package com.llh;
 2 
 3 import java.util.Comparator;
 4 import java.util.Map;
 5 import java.util.TreeMap;
 6 
 7 public class Test03 {
 8     //这是main方法,程序的入口
 9     public static void main(String[] args) {
10         Map<Student,Integer> map = new TreeMap<>(new Comparator<Student>() {
11             @Override
12             public int compare(Student o1, Student o2) {
13                 return ((Double)(o1.getHeight())).compareTo((Double)(o2.getHeight()));
14             }
15         });
16         map.put(new Student(19,"blili",170.5),1001);
17         map.put(new Student(18,"blili",150.5),1003);
18         map.put(new Student(19,"alili",180.5),1023);
19         map.put(new Student(17,"clili",140.5),1671);
20         map.put(new Student(10,"dlili",160.5),1891);
21         System.out.println(map);
22         System.out.println(map.size());
23     }
24 }

 

Map部分整体结构图

  

源码部分

HashMap

代码展示特性
 1 package com.llh;
 2 
 3 import java.util.HashMap;
 4 
 5 public class Test {
 6     //这是main方法,程序的入口
 7     public static void main(String[] args) {
 8         //JDK1.7以后支持后面的<>中内容可以不写
 9         HashMap<Integer,String> hm = new HashMap<>();
10         System.out.println(hm.put(12,"丽丽"));
11         System.out.println(hm.put(7,"菲菲"));
12         System.out.println(hm.put(19,"露露"));
13         System.out.println(hm.put(12,"明明"));
14         System.out.println(hm.put(6,"莹莹"));
15         System.out.println("集合的长度:"+hm.size());
16         System.out.println("集合中内容查看:"+hm);
17     }
18 }

 

结果展示:

  

先演示原理

先演示原理图,再看源码,直接看的话,有的人接不上就蒙了:

相当于先看原理,然后从源码中验证这个原理是否正确:把图搞懂了,就是事倍功半的效果

原理如下:(JDK1.7)

  

源码(JDK1.7版本)
  1 import java.io.Serializable;
  2 import java.util.AbstractMap;
  3 
  4 public class HashMap<K, V>
  5         extends AbstractMap<K, V> //【1】继承的AbstractMap中,已经实现了Map接口
  6         //【2】又实现了这个接口,多余,但是设计者觉得没有必要删除,就这么地了
  7         implements Map<K, V>, Cloneable, Serializable {
  8 
  9 
 10     //【3】后续会用到的重要属性:先粘贴过来:
 11     static final int DEFAULT_INITIAL_CAPACITY = 16;//哈希表主数组的默认长度
 12     //定义了一个float类型的变量,以后作为:默认的装填因子,加载因子是表示Hsah表中元素的填满的程度
 13     //太大容易引起哈西冲突,太小容易浪费  0.75是经过大量运算后得到的最好值
 14     //这个值其实可以自己改,但是不建议改,因为这个0.75是大量运算得到的
 15     static final float DEFAULT_LOAD_FACTOR = 0.75f;
 16     transient Entry<K, V>[] table;//主数组,每个元素为Entry类型
 17     transient int size;
 18     int threshold;//数组扩容的界限值,门槛值   16*0.75=12
 19     final float loadFactor;//用来接收装填因子的变量
 20 
 21     //【4】查看构造器:内部相当于:this(16,0.75f);调用了当前类中的带参构造器
 22     public HashMap() {
 23         this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
 24     }
 25 
 26     //【5】本类中带参数构造器:--》作用给一些数值进行初始化的!
 27     public HashMap(int initialCapacity, float loadFactor) {
 28         //【6】给capacity赋值,capacity的值一定是 大于你传进来的initialCapacity 的 最小的 2的倍数
 29         int capacity = 1;
 30         while (capacity < initialCapacity)
 31             capacity <<= 1;
 32         //【7】给loadFactor赋值,将装填因子0.75赋值给loadFactor
 33         this.loadFactor = loadFactor;
 34         //【8】数组扩容的界限值,门槛值
 35         threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
 36 
 37         //【9】给table数组赋值,初始化数组长度为16
 38         table = new Entry[capacity];
 39 
 40     }
 41 
 42     //【10】调用put方法:
 43     public V put(K key, V value) {
 44         //【11】对空值的判断
 45         if (key == null)
 46             return putForNullKey(value);
 47         //【12】调用hash方法,获取哈希码
 48         int hash = hash(key);
 49         //【14】得到key对应在数组中的位置
 50         int i = indexFor(hash, table.length);
 51         //【16】如果你放入的元素,在主数组那个位置上没有值,e==null  那么下面这个循环不走
 52         //当在同一个位置上放入元素的时候
 53         for (Entry<K, V> e = table[i]; e != null; e = e.next) {
 54             Object k;
 55             //哈希值一样  并且  equals相比一样
 56             //(k = e.key) == key  如果是一个对象就不用比较equals了
 57             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
 58                 V oldValue = e.value;
 59                 e.value = value;
 60                 e.recordAccess(this);
 61                 return oldValue;
 62             }
 63         }
 64         modCount++;
 65         //【17】走addEntry添加这个节点的方法:
 66         addEntry(hash, key, value, i);
 67         return null;
 68     }
 69 
 70     //【13】hash方法返回这个key对应的哈希值,内部进行二次散列,为了尽量保证不同的key得到不同的哈希码!
 71     final int hash(Object k) {
 72         int h = 0;
 73         if (useAltHashing) {
 74             if (k instanceof String) {
 75                 return sun.misc.Hashing.stringHash32((String) k);
 76             }
 77             h = hashSeed;
 78         }
 79         //k.hashCode()函数调用的是key键值类型自带的哈希函数,
 80         //由于不同的对象其hashCode()有可能相同,所以需对hashCode()再次哈希,以降低相同率。
 81         h ^= k.hashCode();
 82         // This function ensures that hashCodes that differ only by
 83         // constant multiples at each bit position have a bounded
 84         // number of collisions (approximately 8 at default load factor).
 85                 /*
 86                 接下来的一串与运算和异或运算,称之为“扰动函数”,
 87                 扰动的核心思想在于使计算出来的值在保留原有相关特性的基础上,
 88                 增加其值的不确定性,从而降低冲突的概率。
 89                 不同的版本实现的方式不一样,但其根本思想是一致的。
 90                 往右移动的目的,就是为了将h的高位利用起来,减少哈西冲突
 91                 */
 92         h ^= (h >>> 20) ^ (h >>> 12);
 93         return h ^ (h >>> 7) ^ (h >>> 4);
 94     }
 95 
 96     //【15】返回int类型数组的坐标
 97     static int indexFor(int h, int length) {
 98         //其实这个算法就是取模运算:h%length,取模效率不如位运算
 99         return h & (length - 1);
100     }
101 
102     //【18】调用addEntry
103     void addEntry(int hash, K key, V value, int bucketIndex) {
104         //【25】size的大小  大于 16*0.75=12的时候,比如你放入的是第13个,这第13个你打算放在没有元素的位置上的时候
105         if ((size >= threshold) && (null != table[bucketIndex])) {
106             //【26】主数组扩容为2倍
107             resize(2 * table.length);
108             //【30】重新调整当前元素的hash码
109             hash = (null != key) ? hash(key) : 0;
110             //【31】重新计算元素位置
111             bucketIndex = indexFor(hash, table.length);
112         }
113         //【19】将hash,key,value,bucketIndex位置  封装为一个Entry对象:
114         createEntry(hash, key, value, bucketIndex);
115     }
116 
117     //【20】
118     void createEntry(int hash, K key, V value, int bucketIndex) {
119         //【21】获取bucketIndex位置上的元素给e
120         Entry<K, V> e = table[bucketIndex];
121         //【22】然后将hash, key, value封装为一个对象,然后将下一个元素的指向为e (链表的头插法)
122         //【23】将新的Entry放在table[bucketIndex]的位置上
123         table[bucketIndex] = new Entry<>(hash, key, value, e);
124         //【24】集合中加入一个元素 size+1
125         size++;
126     }
127 
128     //【27】
129     void resize(int newCapacity) {
130         Entry[] oldTable = table;
131         int oldCapacity = oldTable.length;
132         if (oldCapacity == MAXIMUM_CAPACITY) {
133             threshold = Integer.MAX_VALUE;
134             return;
135         }
136         //【28】创建长度为newCapacity的数组
137         Entry[] newTable = new Entry[newCapacity];
138         boolean oldAltHashing = useAltHashing;
139         useAltHashing |= sun.misc.VM.isBooted() &&
140                 (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
141         boolean rehash = oldAltHashing ^ useAltHashing;
142         //【28.5】转让方法:将老数组中的东西都重新放入新数组中
143         transfer(newTable, rehash);
144         //【29】老数组替换为新数组
145         table = newTable;
146         //【29.5】重新计算
147         threshold = (int) Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
148     }
149 
150     //【28.6】
151     void transfer(Entry[] newTable, boolean rehash) {
152         int newCapacity = newTable.length;
153         for (Entry<K, V> e : table) {
154             while (null != e) {
155                 Entry<K, V> next = e.next;
156                 if (rehash) {
157                     e.hash = null == e.key ? 0 : hash(e.key);
158                 }
159                 //【28.7】将哈希值,和新的数组容量传进去,重新计算key在新数组中的位置
160                 int i = indexFor(e.hash, newCapacity);
161                 //【28.8】头插法
162                 e.next = newTable[i];//获取链表上元素给e.next
163                 newTable[i] = e;//然后将e放在i位置
164                 e = next;//e再指向下一个节点继续遍历
165             }
166         }
167     }
168 }
 
细节讲解:主数组的长度为2的倍数

【1】主数组的长度为2的倍数,

  

因为这个length的长度,会影响 key的位置:

key的位置的计算:

 

实际上这个算法就是:  h%length   ,但是取模的话  效率太低,所以用位运算效率会很高。

 

原因1:

和等效的前提就是  length必须是2的整数倍

原因2:如果不是2的整数倍,那么 哈西碰撞 哈西冲突的概率就高了很多

 

位运算 就  涉及  到  length是不是2的整数倍:

比如是2的整数倍:

 

并且这个得到的索引值,一定在 0-15之间(数组是16的时候):

 

当然如果你扩容后数组长度为 32,那么这个索引就在0-31之间

 

比如如果不是2的整数倍:

 

发现:如果不是2的整数倍,那么 哈西碰撞 哈西冲突的概率就高了很多

细节讲解:装填因子0.75的原因

如果装填因子是1, 那么数组满了再扩容,可以做到  最大的空间利用率 

但是这是一个理想状态,元素不可能完全的均匀分布,很可能就哈西碰撞产生链表了。产生链表的话 查询时间就长了。 

---》空间好,时间不好

 

那么有人说 ,把装填因子搞小一点,0.5,  如果是0.5的话,就浪费空间,但是可以做到 到0.5就扩容 ,然后哈西碰撞就少,

不产生链表的话,那么查询效率很高   

---》时间好,空间不好

 

所以在空间和时间中,

取中间值,平衡这个因素 就取值为 0.75

 

 

HashSet底层原理
 1 public class HashSet<E>{
 2     //重要属性:
 3     private transient HashMap<E,Object> map;
 4     private static final Object PRESENT = new Object();
 5     //构造器:
 6     public HashSet() {
 7         map = new HashMap<>();//HashSet底层就是利用HashMap来完成的
 8     }
 9         
10     public boolean add(E e) {
11         return map.put(e, PRESENT)==null;
12     }      
13 }

 

TreeMap

【1】原理大致介绍:

 

【2】源码:

 1 public class TreeMap<K,V>{
 2         //重要属性:
 3         //外部比较器:
 4         private final Comparator<? super K> comparator;
 5         //树的根节点:
 6         private transient Entry<K,V> root = null;
 7         //集合中元素的数量:
 8         private transient int size = 0;
 9         //空构造器:
10         public TreeMap() {
11         comparator = null;//如果使用空构造器,那么底层就不使用外部比较器
12     }
13         //有参构造器:
14         public TreeMap(Comparator<? super K> comparator) {
15         this.comparator = comparator;//如果使用有参构造器,那么就相当于指定了外部比较器
16     }
17         
18         public V put(K key, V value) {//k,V的类型在创建对象的时候确定了
19         //如果放入的是第一对元素,那么t的值为null
20         Entry<K,V> t = root;//在放入第二个节点的时候,root已经是根节点了
21                 //如果放入的是第一个元素的话,走入这个if中:
22         if (t == null) {
23                         //自己跟自己比
24             compare(key, key); // type (and possibly null) check
25                         //根节点确定为root
26             root = new Entry<>(key, value, null);
27                         //size值变为1
28             size = 1;
29             modCount++;
30             return null;
31         }
32                 
33         int cmp;
34         Entry<K,V> parent;
35         // split comparator and comparable paths
36                 //将外部比较器赋给cpr:
37         Comparator<? super K> cpr = comparator;
38                 //cpr不等于null,意味着你刚才创建对象的时候调用了有参构造器,指定了外部比较器
39         if (cpr != null) {
40             do {
41                 parent = t;
42                 cmp = cpr.compare(key, t.key);//将元素的key值做比较
43                                 //cmp返回的值就是int类型的数据:
44                                 //要是这个值《0 =0  》0
45                 if (cmp < 0)
46                     t = t.left;
47                 else if (cmp > 0)
48                     t = t.right;
49                 else//cpm==0
50                                 //如果key的值一样,那么新的value替换老的value  但是key不变 因为key是唯一的
51                     return t.setValue(value);
52             } while (t != null);
53         }
54                 //cpr等于null,意味着你刚才创建对象的时候调用了空构造器,没有指定外部比较器,使用内部比较器
55         else {
56             if (key == null)
57                 throw new NullPointerException();
58             Comparable<? super K> k = (Comparable<? super K>) key;
59             do {
60                 parent = t;
61                 cmp = k.compareTo(t.key);//将元素的key值做比较
62                 if (cmp < 0)
63                     t = t.left;
64                 else if (cmp > 0)
65                     t = t.right;
66                 else
67                     return t.setValue(value);
68             } while (t != null);
69         }
70         Entry<K,V> e = new Entry<>(key, value, parent);
71         if (cmp < 0)
72             parent.left = e;
73         else
74             parent.right = e;
75         fixAfterInsertion(e);
76         size++;//size加1 操作
77         modCount++;
78         return null;
79     }
80         
81         
82 }
83  static final class Entry<K,V> implements Map.Entry<K,V> {
84         K key;
85         V value;
86         Entry<K,V> left = null;
87         Entry<K,V> right = null;
88         Entry<K,V> parent;
89         boolean color = BLACK;
90  }

 

TreeSet源码
 1 import java.util.AbstractSet;
 2 import java.util.NavigableMap;
 3 import java.util.NavigableSet;
 4 import java.util.TreeMap;
 5 
 6 public class TreeSet<E> extends AbstractSet<E> 
 7         implements NavigableSet<E>, Cloneable, java.io.Serializable {
 8     //重要属性:
 9     private transient NavigableMap<E, Object> m;
10     private static final Object PRESENT = new Object();
11 
12     //在调用空构造器的时候,底层创建了一个TreeMap
13     public TreeSet() {
14         this(new TreeMap<E, Object>());
15     }
16 
17     TreeSet(NavigableMap<E, Object> m) {
18         this.m = m;
19     }
20 
21     public boolean add(E e) {
22         return m.put(e, PRESENT) == null;
23     }
24 }
 

Collections工具类

 1 package com.llh;
 2 
 3 import java.util.ArrayList;
 4 import java.util.Collections;
 5 
 6 public class Test01 {
 7     //这是main方法,程序的入口
 8     public static void main(String[] args) {
 9         //Collections不支持创建对象,因为构造器私有化了
10         /*Collections cols = new Collections();*/
11         //里面的属性和方法都是被static修饰,我们可以直接用类名.去调用即可:
12         //常用方法:
13         //addAll:
14         ArrayList<String> list = new ArrayList<>();
15         list.add("cc");
16         list.add("bb");
17         list.add("aa");
18         Collections.addAll(list,"ee","dd","ff");
19         Collections.addAll(list,new String[]{"gg","oo","pp"});
20         System.out.println(list);
21         //binarySearch必须在有序的集合中查找:--》排序:
22         Collections.sort(list);//sort提供的是升序排列
23         System.out.println(list);
24         //binarySearch
25         System.out.println(Collections.binarySearch(list, "cc"));
26         //copy:替换方法
27         ArrayList<String> list2 = new ArrayList<>();
28         Collections.addAll(list2,"tt","ss");
29         Collections.copy(list,list2);//将list2的内容替换到list上去
30         System.out.println(list);
31         System.out.println(list2);
32         //fill 填充
33         Collections.fill(list2,"yyy");
34         System.out.println(list2);
35     }
36 }

 

 常见基础集合汇总

 

数据结构分为:
(1)逻辑结构 :--》思想上的结构--》卧室,厨房,卫生间 ---》线性表(数组,链表),图,树,栈,队列
(2)物理结构 :--》真实结构--》钢筋混凝土+牛顿力学------》紧密结构(顺序结构),跳转结构(链式结构)


栈:

特点:后进先出(LIFO - last in first out):

 

 

 

实际应用:

(1)内存分析:形参,局部变量放入栈中。放入的那个区域的数据结构就是按照栈来做的。

(堆:利用完全二叉树的结构来维护一组数据)

 

(2)网络浏览器多会将用户最近访问过的网址组织为一个栈。这样,用户每访问一个新页面,其地址就会被存放至栈顶;而用户每按下一次“后退”按钮,即可沿相反的次序访问此前刚访问过的页面。

 

(3)主流的文本编辑器也大都支持编辑操作的历史记录功能(ctrl + z:撤销,ctrl + y:恢复),用户的编辑操作被依次记录在一个栈中。一旦出现误操作,用户只需按下“撤销”按钮,即可取消最近一次操作并回到此前的编辑状态。

 1 package com.llh;
 2 
 3 import java.util.Stack;
 4 
 5 public class Test {
 6     //这是main方法,程序的入口
 7     public static void main(String[] args) {
 8         /*
 9         Stack是Vector的子类,Vector里面两个重要的属性:
10         Object[] elementData;底层依然是一个数组
11         int elementCount;数组中的容量
12          */
13         Stack s = new Stack();
14         s.add("A");
15         s.add("B");
16         s.add("C");
17         s.add("D");
18         System.out.println(s);//[A, B, C, D]
19         System.out.println("栈是否为空:" + s.empty());
20         System.out.println("查看栈顶的数据,但是不移除:" + s.peek());
21         System.out.println(s);
22         System.out.println("查看栈顶的数据,并且不移除:" + s.pop());
23         System.out.println(s);
24         s.push("D");//和add方法执行的功能一样,就是返回值不同
25         System.out.println(s);
26     }
27 }

比如ArrayList,HashMap,线程不安全,现在想把线程不安全的集合转换为线程安全的集合:

1 public class Test01 {
2     //这是main方法,程序的入口
3     public static void main(String[] args) {
4         //ArrayList为案例:从线程不安全  转为线程安全:
5         List list = Collections.synchronizedList(new ArrayList());
6     }
7 }

 

试试ArrayList的线程不安全:

 1 package com.llh;
 2 
 3 import java.util.ArrayList;
 4 import java.util.concurrent.ExecutorService;
 5 import java.util.concurrent.Executors;
 6 
 7 public class Demo {
 8     //这是main方法,程序的入口
 9     public static void main(String[] args) {
10         //创建一个ArrayList集合:
11         ArrayList list = new ArrayList();
12         //创建一个线程池:线程池定长100
13         ExecutorService es = Executors.newFixedThreadPool(100);
14         //并发向集合中添加10000个数据:
15         for (int i = 0; i < 10000; i++) {
16             //每个线程处理任务:run方法中的内容就是线程单元,任务,实际线程执行的部分
17             es.execute(new Runnable() {
18                 @Override
19                 public void run() {
20                     list.add("aaa");
21                 }
22             });
23         }
24         //关闭线程池:
25         es.shutdown();
26         //监控线程是否执行完毕:
27         while(true){
28             //线程都执行完以后返回true
29             if(es.isTerminated()){
30                 System.out.println("所有的子线程都执行完毕了!");
31                 //执行完毕以后看一下集合中元素的数量:
32                 System.out.println(list.size());
33                 if(list.size() == 10000){
34                     System.out.println("线程安全!");
35                 }else{
36                     System.out.println("线程不安全!");
37                 }
38                 //线程执行完以后,while循环可以停止:
39                 break;
40             }
41         }
42     }
43 }

 

结果: 

 

利用同步类容器解决:

 1 package com.llh;
 2 
 3 import java.util.ArrayList;
 4 import java.util.Collections;
 5 import java.util.List;
 6 import java.util.concurrent.ExecutorService;
 7 import java.util.concurrent.Executors;
 8 
 9 public class Demo {
10     //这是main方法,程序的入口
11     public static void main(String[] args) {
12         //创建一个ArrayList集合:
13         ArrayList oldlist = new ArrayList();
14         List list = Collections.synchronizedList(oldlist);
15         //创建一个线程池:线程池定长100
16         ExecutorService es = Executors.newFixedThreadPool(100);
17         //并发向集合中添加10000个数据:
18         for (int i = 0; i < 10000; i++) {
19             //每个线程处理任务:run方法中的内容就是线程单元,任务,实际线程执行的部分
20             es.execute(new Runnable() {
21                 @Override
22                 public void run() {
23                     list.add("aaa");
24                 }
25             });
26         }
27         //关闭线程池:
28         es.shutdown();
29         //监控线程是否执行完毕:
30         while(true){
31             //线程都执行完以后返回true
32             if(es.isTerminated()){
33                 System.out.println("所有的子线程都执行完毕了!");
34                 //执行完毕以后看一下集合中元素的数量:
35                 System.out.println(list.size());
36                 if(list.size() == 10000){
37                     System.out.println("线程安全!");
38                 }else{
39                     System.out.println("线程不安全!");
40                 }
41                 //线程执行完以后,while循环可以停止:
42                 break;
43             }
44         }
45     }
46 }

 

结果:

 

源码解析:

 

JDK5.0之后提供了多种并发类容器可以替代同步类容器,提升性能、吞吐量
ConcurrentHashMap替代HashMap、HashTable
ConcurrentSkipListMap替代TreeMap


简单原理:

 

 

并发情况下,验证提高性能:

ConcunrrentHashMap :

 1 public class Test {
 2     //这是main方法,程序的入口
 3     public static void main(String[] args) {
 4         //选择一个容器:
 5         ConcurrentHashMap<String,Integer> map = new ConcurrentHashMap<>();
 6         
 7         //创建10个线程:
 8         for (int i = 0; i < 10; i++) {
 9             new Thread(new Runnable() {
10                 @Override
11                 public void run() {
12                     long startTime = System.currentTimeMillis();
13                     for (int j = 0; j < 1000000; j++) {
14                         map.put("test" + j , j);
15                     }
16                     long endTime = System.currentTimeMillis();
17                     System.out.println("一共需要的时间:" + (endTime - startTime));
18                 }
19             }).start();
20         }
21     }
22 }

 

结果: 

 

Hashtable:

 1 package com.llh;
 2 
 3 import java.util.Hashtable;
 4 import java.util.concurrent.ConcurrentHashMap;
 5 
 6 public class Test {
 7     //这是main方法,程序的入口
 8     public static void main(String[] args) {
 9         //选择一个容器:
10         //ConcurrentHashMap<String,Integer> map = new ConcurrentHashMap<>();
11         Hashtable map = new Hashtable();
12         //创建10个线程:
13         for (int i = 0; i < 10; i++) {
14             new Thread(new Runnable() {
15                 @Override
16                 public void run() {
17                     long startTime = System.currentTimeMillis();
18                     for (int j = 0; j < 1000000; j++) {
19                         map.put("test" + j , j);
20                     }
21                     long endTime = System.currentTimeMillis();
22                     System.out.println("一共需要的时间:" + (endTime - startTime));
23                 }
24             }).start();
25         }
26     }
27 }

 

结果:

 

 HashMap:

 1 package com.llh;
 2 
 3 import java.util.Collections;
 4 import java.util.HashMap;
 5 import java.util.Hashtable;
 6 import java.util.Map;
 7 import java.util.concurrent.ConcurrentHashMap;
 8 
 9 public class Test {
10     //这是main方法,程序的入口
11     public static void main(String[] args) {
12         //选择一个容器:
13         //ConcurrentHashMap<String,Integer> map = new ConcurrentHashMap<>();
14         //Hashtable map = new Hashtable();
15         HashMap oldmap = new HashMap();
16         Map map = Collections.synchronizedMap(oldmap);
17         //创建10个线程:
18         for (int i = 0; i < 10; i++) {
19             new Thread(new Runnable() {
20                 @Override
21                 public void run() {
22                     long startTime = System.currentTimeMillis();
23                     for (int j = 0; j < 1000000; j++) {
24                         map.put("test" + j , j);
25                     }
26                     long endTime = System.currentTimeMillis();
27                     System.out.println("一共需要的时间:" + (endTime - startTime));
28                 }
29             }).start();
30         }
31     }
32 }

线程安全的HashMap:

 1 package com.llh;
 2 
 3 import java.util.Collections;
 4 import java.util.HashMap;
 5 import java.util.Hashtable;
 6 import java.util.Map;
 7 import java.util.concurrent.ConcurrentHashMap;
 8 
 9 public class Test {
10     //这是main方法,程序的入口
11     public static void main(String[] args) {
12         //选择一个容器:
13         //ConcurrentHashMap<String,Integer> map = new ConcurrentHashMap<>();
14         //Hashtable map = new Hashtable();
15         HashMap oldmap = new HashMap();
16         Map map = Collections.synchronizedMap(oldmap);
17         //创建10个线程:
18         for (int i = 0; i < 10; i++) {
19             new Thread(new Runnable() {
20                 @Override
21                 public void run() {
22                     long startTime = System.currentTimeMillis();
23                     for (int j = 0; j < 1000000; j++) {
24                         map.put("test" + j , j);
25                     }
26                     long endTime = System.currentTimeMillis();
27                     System.out.println("一共需要的时间:" + (endTime - startTime));
28                 }
29             }).start();
30         }
31     }
32 }

 

结果: 

 

总结:
ConcurrentHashMap:性能高,线程安全
Hashtable: 线程安全,性能低
HashMap:线程不安全,性能高
线程安全的HashMap:线程安全,性能低

 

【1】COW类并发容器,全称:Copy On Write容器,写时复制容器。(读写分离容器)

【2】原理:
向容器中添加元素时,先将容器进行Copy复制出一个新容器,然后将元素添加到新容器中,再将原容器的引用指向新容器。
并发读的时候不需要锁定容器,因为原容器没有变化,所以可以读取原容器中的值,使用的是一种读写分离的思想。

 

【3】这种设计的好处是什么呢?
注意上面的操作arr数组本身是无锁的,没有锁,在添加数据的时候,做了额外的复制,
此时如果有线程来读数据,那么读取的是老arr的数据,此时arr的地址还没有改呢,在我添加元素的过程中,
无论有多少个线程来读数据,都是读的原来的arr,不是新的arr
所以性能很高,读写分离。提高了并发的性能。如果再读再复制...

【4】注意:
CopyOnWrite容器只能保证数据的最终一致性,不能保证数据实时一致性。
所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。


【5】适合特定场合:
这个应用场景显而易见,适合读多写少的情况。如果一万个线程都添加操作,都在集合中添加数据,那数组不断复制,长度不断+1,
那JVM肯定一直往上飙升,你用的时候肯定要评估使用场景的。
由于每次更新都会复制新容器,所以如果数据量较大并且更新操作频繁则对内存消耗很高,建议在高并发读的场景下使用。


【6】主要讲解:
COW容器有两种一种是CopyonWriteArrayList,一种是CopyOnWriteArraySet
一个是替代ArrayList,一个是代替Set

 

 

【1】代码:

 1 package com.llh;
 2 
 3 import java.util.concurrent.CopyOnWriteArrayList;
 4 
 5 public class Test {
 6     //这是main方法,程序的入口
 7     public static void main(String[] args) {
 8         CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
 9         //添加方法:
10         list.add(1);
11         list.add(2);
12         list.add(3);
13         list.add(4);
14         System.out.println(list);//[1, 2, 3, 4]
15         list.add(3);//add方法无论元素是否存在,都可以添加进去--》添加重复的元素
16         System.out.println(list);//[1, 2, 3, 4, 3]
17         //adj. 缺席的;缺少的;心不在焉的;茫然的
18         list.addIfAbsent(33);//添加不存在的元素--》不可以添加重复的数据
19         System.out.println(list);//[1, 2, 3, 4, 3, 33]
20     }
21 }

 

【2】源码分析:

 1 public class CopyOnWriteArrayList<E>{
 2         //底层基于数组实现的
 3         private transient volatile Object[] array;
 4         
 5         public CopyOnWriteArrayList() {
 6         setArray(new Object[0]);
 7     }
 8         
 9         final void setArray(Object[] a) {
10         array = a; // array = new Object[0]
11     }
12         //add方法:
13         public boolean add(E e) {
14         final ReentrantLock lock = this.lock;
15         lock.lock();
16         try {
17                         //返回底层array数组,给了elements
18             Object[] elements = getArray();
19                         //获取elements的长度---》获取老数组的长度
20             int len = elements.length;
21                         //完成数组的复制,将老数组中的元素复制到新数组中,并且新数组的长度加1操作
22             Object[] newElements = Arrays.copyOf(elements, len + 1);
23                         //将e元素放入新数组最后位置
24             newElements[len] = e;
25                         //array数组的指向从老数组变为新数组
26             setArray(newElements);
27             return true;
28         } finally {
29             lock.unlock();
30         }
31     }
32         
33         
34         final Object[] getArray() {
35         return array;//返回底层数组
36     }
37         
38         
39         private boolean addIfAbsent(E e, Object[] snapshot) {
40         final ReentrantLock lock = this.lock;
41         lock.lock();
42         try {
43                         //取出array数组给current
44             Object[] current = getArray();
45             int len = current.length;
46             if (snapshot != current) {
47                 // Optimize for lost race to another addXXX operation
48                 int common = Math.min(snapshot.length, len);
49                                 //遍历老数组:
50                 for (int i = 0; i < common; i++)
51                                         //eq(e, current[i])将放入的元素和老数组的每一个元素进行比较,如果有重复的元素,就返回false,不添加了
52                     if (current[i] != snapshot[i] && eq(e, current[i]))
53                         return false;
54                 if (indexOf(e, current, common, len) >= 0)
55                         return false;
56             }
57                         //完成数组的复制,将老数组中的元素复制到新数组中,并且新数组的长度加1操作
58             Object[] newElements = Arrays.copyOf(current, len + 1);
59                         //将e元素放入新数组最后位置
60             newElements[len] = e;
61                         //array数组的指向从老数组变为新数组
62             setArray(newElements);
63             return true;
64         } finally {
65             lock.unlock();
66         }
67     }   
68 }

 

 【1】代码:

 

 1 public class Test02 {
 2     //这是main方法,程序的入口
 3     public static void main(String[] args) {
 4         //创建一个集合:
 5         CopyOnWriteArraySet<Integer> set = new CopyOnWriteArraySet<>();
 6         //在这里也体现出Set和List的本质区别,就在于是否重复
 7         //所以add方法直接不可以添加重复数据进去
 8         set.add(1);
 9         set.add(2);
10         set.add(2);
11         set.add(7);
12         System.out.println(set);//[1, 2, 7]
13         
14     }
15 }

 

 【2】源码:

 

 1 public class CopyOnWriteArraySet<E>{
 2         //CopyOnWriteArraySet底层基于CopyOnWriteArrayList
 3         private final CopyOnWriteArrayList<E> al;
 4         
 5         public CopyOnWriteArraySet() {
 6         al = new CopyOnWriteArrayList<E>();
 7     }
 8         
 9         //添加方法:
10         public boolean add(E e) {
11         return al.addIfAbsent(e);//底层调用的还是CopyOnWriteArrayList的addIfAbsent
12     }
13 }

【3】总结:

 

 由上面的源码看出,每次调用CopyOnWriteArraySet的add方法时候,其实底层是基于CopyOnWriteArrayList的addIfAbsent,

每次在addIfAbsent方法中每次都要对数组进行遍历,所以CopyOnWriteArraySet的性能低于CopyOnWriteArrayList

 

 

数据结构分为:
(1)逻辑结构 :--》思想上的结构--》卧室,厨房,卫生间 ---》线性表(数组,链表),图,树,栈,队列
(2)物理结构 :--》真实结构--》钢筋混凝土+牛顿力学------》紧密结构(顺序结构),跳转结构(链式结构)

队列:特点:先进先出 (FIFO)(first in first out) 

 

 他有两端,一端是让新元素进去,一端是让老元素出去

在需要公平且经济地对各种自然或社会资源做管理或分配的场合,无论是调度银行和医院的服务窗口,还是管理轮耕的田地和轮伐的森林,队列都可大显身手。

甚至计算机及其网络自身内部的各种计算资源,无论是多进程共享的 CPU 时间,还是多用户共享的打印机,也都需要借助队列结构实现合理和优化的分配。

双端队列:两端都可以进行进队,出队的队列:

(1)前端,后端都可以进出:

 (2)进行限制: 

(3)特殊情况,双端队列实现栈操作: 

栈和队列的物理结构实现 可以用线性表的数组,链表都可以

 队列Queue

 BlockingQueue介绍

 

 

总结:BlockingQueue继承Queue,Queue继承自Collection
所以Collection最基础的增删改查操作是有的,在这个基础上,多了Queue的特点,在这个基础上又多了阻塞的特点,最终形成了BlockingQueue


什么叫阻塞?

 

 

 

常用的API:添加

 

 

 put是阻塞的

查询:

 

 take是阻塞的

删除:

 源码中的注释的解释说明:

 【1】添加元素:

 1 package com.llh;
 2 
 3 import java.util.concurrent.ArrayBlockingQueue;
 4 import java.util.concurrent.TimeUnit;
 5 
 6 public class Test01 {
 7     //这是main方法,程序的入口
 8     public static void main(String[] args) throws InterruptedException {
 9         //创建一个队列,队列可以指定容量指定长度3:
10         ArrayBlockingQueue aq = new ArrayBlockingQueue(3);
11         //添加元素:
12         //【1】添加null元素:不可以添加null元素,会报空指针异常:NullPointerException
13         //aq.add(null);
14         //aq.offer(null);
15         //aq.put(null);
16         //【2】正常添加元素:
17         aq.add("aaa");
18         aq.offer("bbb");
19         aq.put("ccc");
20         System.out.println(aq);//[aaa, bbb, ccc]
21         //【3】在队列满的情况下,再添加元素:
22         //aq.add("ddd");//在队列满的情况下,添加元素 出现异常:Queue full
23         //System.out.println(aq.offer("ddd"));//没有添加成功,返回false
24         //设置最大阻塞时间,如果时间到了,队列还是满的,就不再阻塞了
25         //aq.offer("ddd",2, TimeUnit.SECONDS);
26         //真正阻塞的方法: put ,如果队列满,就永远阻塞 
27         aq.put("ddd");
28         System.out.println(aq);
29     }
30 }

【2】获取元素:

 1 package com.llh;
 2 
 3 import javax.sound.midi.Soundbank;
 4 import java.util.concurrent.ArrayBlockingQueue;
 5 import java.util.concurrent.TimeUnit;
 6 
 7 public class Test02 {
 8     //这是main方法,程序的入口
 9     public static void main(String[] args) throws InterruptedException {
10         //创建一个队列,队列可以指定容量指定长度3:
11         ArrayBlockingQueue aq = new ArrayBlockingQueue(3);
12         aq.add("aaa");
13         aq.add("bbb");
14         aq.add("ccc");
15         //得到头元素但是不移除
16         System.out.println(aq.peek());
17         System.out.println(aq);
18         //得到头元素并且移除
19         System.out.println(aq.poll());
20         System.out.println(aq);
21         //得到头元素并且移除
22         System.out.println(aq.take());
23         System.out.println(aq);
24         //清空元素:
25         aq.clear();
26         System.out.println(aq);
27         System.out.println(aq.peek());//null
28         System.out.println(aq.poll());//null
29         //设置阻塞事件,如果队列为空,返回null,时间到了以后就不阻塞了
30         //System.out.println(aq.poll(2, TimeUnit.SECONDS));
31         //真正阻塞:队列为空,永远阻塞
32         System.out.println(aq.take());
33     }
34 }

【3】源码:

 1 public class ArrayBlockingQueue<E> {
 2         //底层就是一个数组:
 3         final Object[] items;
 4         //取元素用到的索引,初始结果为0
 5         int takeIndex;
 6         //放元素用到的索引,初始结果为0
 7         int putIndex;
 8         //数组中元素的个数:
 9         int count;
10         
11         //一把锁:这个锁肯定很多方法中用到了,所以定义为属性,初始化以后可以随时使用
12     final ReentrantLock lock;
13     //锁伴随的一个等待吃:notEmpty
14     private final Condition notEmpty;
15     //锁伴随的一个等待吃:notFull
16     private final Condition notFull;
17         
18         //构造器:
19         public ArrayBlockingQueue(int capacity) {//传入队列指定的容量
20         this(capacity, false);
21     }
22         
23         public ArrayBlockingQueue(int capacity, boolean fair) {//传入队列指定的容量
24                 //健壮性考虑
25         if (capacity <= 0)
26             throw new IllegalArgumentException();
27                 //初始化底层数组
28         this.items = new Object[capacity];
29                 //初始化锁 和  等待队列
30         lock = new ReentrantLock(fair);
31         notEmpty = lock.newCondition();
32         notFull =  lock.newCondition();
33     }
34         
35         //两个基本方法:一个是入队,一个是出队  ,是其他方法的基础:
36         //入队:
37         private void enqueue(E x) {
38         // assert lock.getHoldCount() == 1;
39         // assert items[putIndex] == null;
40         final Object[] items = this.items;//底层数组赋给items
41                 //在对应的下标位置放入元素
42         items[putIndex] = x;
43         if (++putIndex == items.length) //++putIndex putIndex 索引 加1 
44             putIndex = 0;
45                 //每放入一个元素,count加1操作
46         count++;
47         notEmpty.signal();
48     }
49         
50         
51         //出队:
52         private E dequeue() {
53         // assert lock.getHoldCount() == 1;
54         // assert items[takeIndex] != null;
55         final Object[] items = this.items;//底层数组赋给items
56         @SuppressWarnings("unchecked")
57         E x = (E) items[takeIndex];//在对应的位置取出元素
58         items[takeIndex] = null;//对应位置元素取出后就置为null
59         if (++takeIndex == items.length)//++takeIndex 加1操作
60             takeIndex = 0;
61         count--;//每取出一个元素,count减1操作
62         if (itrs != null)
63             itrs.elementDequeued();
64         notFull.signal();
65         return x;//将取出的元素作为方法的返回值
66     }    
67 }

takeIndex和putIndex置为0的原因:

 【4】其他的添加或者获取的方法都是依托与这个入队和出队的基础方法

 【5】感受一下put和take的阻塞:

 

上面的while不可以换为if,因为如果notFull中的线程被激活的瞬间,有其他线程放入元素,那么队列就又满了
那么沿着await后面继续执行就不可以,所以一定要反复确定队列是否满的,才能放入元素

一个可选择的有边界的队列:意思就是队列的长度可以指定,也可以不指定

 

【1】添加元素:

 1 package com.llh;
 2 
 3 import java.util.concurrent.ArrayBlockingQueue;
 4 import java.util.concurrent.LinkedBlockingQueue;
 5 import java.util.concurrent.TimeUnit;
 6 
 7 public class Test01 {
 8     //这是main方法,程序的入口
 9     public static void main(String[] args) throws InterruptedException {
10         //创建一个队列,队列可以指定容量指定长度3:
11         LinkedBlockingQueue aq = new LinkedBlockingQueue(3);
12         //添加元素:
13         //【1】添加null元素:不可以添加null元素,会报空指针异常:NullPointerException
14         //aq.add(null);
15         //aq.offer(null);
16         aq.put(null);
17         //【2】正常添加元素:
18         aq.add("aaa");
19         aq.offer("bbb");
20         aq.put("ccc");
21         System.out.println(aq);//[aaa, bbb, ccc]
22         //【3】在队列满的情况下,再添加元素:
23         //aq.add("ddd");//在队列满的情况下,添加元素 出现异常:Queue full
24         //System.out.println(aq.offer("ddd"));//没有添加成功,返回false
25         //设置最大阻塞时间,如果时间到了,队列还是满的,就不再阻塞了
26         //aq.offer("ddd",2, TimeUnit.SECONDS);
27         //真正阻塞的方法: put ,如果队列满,就永远阻塞
28         aq.put("ddd");
29         System.out.println(aq);
30     }
31 }

【2】取出元素:

 1 package com.llh;
 2 
 3 import javax.sound.midi.Soundbank;
 4 import java.util.concurrent.ArrayBlockingQueue;
 5 import java.util.concurrent.LinkedBlockingQueue;
 6 import java.util.concurrent.TimeUnit;
 7 
 8 public class Test02 {
 9     //这是main方法,程序的入口
10     public static void main(String[] args) throws InterruptedException {
11         //创建一个队列,队列可以指定容量指定长度3:
12         LinkedBlockingQueue aq = new LinkedBlockingQueue();
13         aq.add("aaa");
14         aq.add("bbb");
15         aq.add("ccc");
16         //得到头元素但是不移除
17         System.out.println(aq.peek());
18         System.out.println(aq);
19         //得到头元素并且移除
20         System.out.println(aq.poll());
21         System.out.println(aq);
22         //得到头元素并且移除
23         System.out.println(aq.take());
24         System.out.println(aq);
25         //清空元素:
26         aq.clear();
27         System.out.println(aq);
28         System.out.println(aq.peek());//null
29         System.out.println(aq.poll());//null
30         //设置阻塞事件,如果队列为空,返回null,时间到了以后就不阻塞了
31         //System.out.println(aq.poll(2, TimeUnit.SECONDS));
32         //真正阻塞:队列为空,永远阻塞
33         System.out.println(aq.take());
34     }
35 }

【3】特点:
ArrayBlockingQueue : 不支持读写同时操作,底层基于数组的。
LinkedBlockingQueue:支持读写同时操作,并发情况下,效率高。底层基于链表。

【4】源码:
入队操作:

 出队操作:

 

 1 public class LinkedBlockingQueue<E>{
 2         //内部类Node就是链表的节点的对象对应的类:
 3         static class Node<E> {
 4         E item;//封装你要装的那个元素
 5         
 6         Node<E> next;//下一个Node节点的地址
 7         Node(E x) { item = x; }//构造器
 8     }
 9         //链表的长度
10         private final int capacity;
11         //计数器:
12         private final AtomicInteger count = new AtomicInteger();
13         //链表的头结点
14         transient Node<E> head;
15         //链表的尾结点
16         private transient Node<E> last;
17         //取元素用的锁
18         private final ReentrantLock takeLock = new ReentrantLock();
19         //等待池
20     private final Condition notEmpty = takeLock.newCondition();
21     //放元素用的锁
22     private final ReentrantLock putLock = new ReentrantLock();
23     //等待池
24     private final Condition notFull = putLock.newCondition();
25         
26         public LinkedBlockingQueue() {
27         this(Integer.MAX_VALUE);//调用类本类的空构造器,传入正21亿
28     }
29         
30         public LinkedBlockingQueue(int capacity) {
31                 //健壮性考虑
32         if (capacity <= 0) throw new IllegalArgumentException();
33                 //给队列指定长度  
34         this.capacity = capacity;
35                 //last,head指向一个新的节点,新的节点中 元素为null 
36         last = head = new Node<E>(null);
37     }
38         
39         
40         //入队:
41         private void enqueue(Node<E> node) {
42         last = last.next = node;
43     }
44         
45         //出队:
46         private E dequeue() {
47         Node<E> h = head;//h指向了head
48         Node<E> first = h.next;//first 指向head的next
49         h.next = h; // help GC   h.next指向自己,更容易被GC发现 被GC
50         head = first;//head的指向指为first
51         E x = first.item;//取出链中第一个元素,给了x
52         first.item = null;
53         return x;//把x作为方法的返回值
54     }
55 }

 

【5】put的阻塞:


阻塞的前提是 队列是固定长度的

 BlockingQueue

 这个特殊的队列设计的意义:

 

 测试1:先添加元素:

1 public class Test01 {
2     //这是main方法,程序的入口
3     public static void main(String[] args) {
4         SynchronousQueue sq = new SynchronousQueue();
5         sq.add("aaa");
6     }
7 }

直接报错:说队列满了,因为队列没有容量,理解为满也是正常的:

 测试2:put方法  阻塞:队列是空的,可以理解为队列满了,满的话放入元素 put 一定会阻塞:

1 public class Test01 {
2     //这是main方法,程序的入口
3     public static void main(String[] args) throws InterruptedException {
4         SynchronousQueue sq = new SynchronousQueue();
5         sq.put("aaa");
6     }
7 }

 测试3:先取  再放:

 1 package com.llh;
 2 
 3 import java.util.concurrent.SynchronousQueue;
 4 
 5 public class Test02 {
 6     //这是main方法,程序的入口
 7     public static void main(String[] args) {
 8         SynchronousQueue sq = new SynchronousQueue();
 9         //创建一个线程,取数据:
10         new Thread(new Runnable() {
11             @Override
12             public void run() {
13                 while(true){
14                     try {
15                         System.out.println(sq.take());
16                     } catch (InterruptedException e) {
17                         e.printStackTrace();
18                     }
19                 }
20             }
21         }).start();
22         //搞一个线程,往里面放数据:
23         new Thread(new Runnable() {
24             @Override
25             public void run() {
26                 try {
27                     sq.put("aaa");
28                     sq.put("bbb");
29                     sq.put("ccc");
30                     sq.put("ddd");
31                 } catch (InterruptedException e) {
32                     e.printStackTrace();
33                 }
34             }
35         }).start();
36     }
37 }

结果:

 测试4:poll方法: 

 1 package com.llh;
 2 
 3 import java.util.concurrent.SynchronousQueue;
 4 import java.util.concurrent.TimeUnit;
 5 
 6 public class Test02 {
 7     //这是main方法,程序的入口
 8     public static void main(String[] args) {
 9         SynchronousQueue sq = new SynchronousQueue();
10         //创建一个线程,取数据:
11         new Thread(new Runnable() {
12             @Override
13             public void run() {
14                 while(true){
15                     try {
16                         //设置一个阻塞事件:超出事件就不阻塞了
17                         Object result = sq.poll(5, TimeUnit.SECONDS);
18                         System.out.println(result);
19                         if(result == null){
20                             break;
21                         }
22                     } catch (InterruptedException e) {
23                         e.printStackTrace();
24                     }
25                 }
26             }
27         }).start();
28         //搞一个线程,往里面放数据:
29         new Thread(new Runnable() {
30             @Override
31             public void run() {
32                 try {
33                     sq.put("aaa");
34                     sq.put("bbb");
35                     sq.put("ccc");
36                     sq.put("ddd");
37                 } catch (InterruptedException e) {
38                     e.printStackTrace();
39                 }
40             }
41         }).start();
42     }
43 }

 

注意:取出元素 不能用peek,因为peek不会将元素从队列中拿走,只是查看的效果;

PriorityBlockingQueue

带有优先级的阻塞队列。
优先级队列,意味着队列有先后顺序的,数据有不同的权重。

无界的队列,没有长度限制,但是在你不指定长度的时候,默认初始长度为11,也可以手动指定,
当然随着数据不断的加入,底层(底层是数组Object[])会自动扩容,直到内存全部消耗殆尽了,导致 OutOfMemoryError内存溢出 程序才会结束。

 不可以放入null元素的,不允许放入不可比较的对象(导致抛出ClassCastException),对象必须实现内部比较器或者外部比较器。

测试1:添加null数据:

1 public class Test {
2     //这是main方法,程序的入口
3     public static void main(String[] args) {
4         PriorityBlockingQueue pq = new PriorityBlockingQueue();
5         pq.put(null);
6     }
7 }

 测试2:添加四个数据:

 1 public class Student implements Comparable<Student> {
 2     String name;
 3     int age;
 4     public Student() {
 5     }
 6     public Student(String name, int age) {
 7         this.name = name;
 8         this.age = age;
 9     }
10     @Override
11     public String toString() {
12         return "Student{" +
13                 "name='" + name + '\'' +
14                 ", age=" + age +
15                 '}';
16     }
17     @Override
18     public int compareTo(Student o) {
19         return this.age - o.age;
20     }
21 }
 1 package com.llh;
 2 
 3 import java.util.concurrent.PriorityBlockingQueue;
 4 
 5 public class Test02 {
 6     //这是main方法,程序的入口
 7     public static void main(String[] args) {
 8         PriorityBlockingQueue<Student> pq = new PriorityBlockingQueue<>();
 9         pq.put(new Student("nana",18));
10         pq.put(new Student("lulu",11));
11         pq.put(new Student("feifei",6));
12         pq.put(new Student("mingming",21));
13         System.out.println(pq);
14     }
15 }

结果:

 

发现结果并没有按照优先级顺序排列


测试3:取出数据:

 1 package com.llh;
 2 
 3 import java.util.concurrent.PriorityBlockingQueue;
 4 
 5 public class Test02 {
 6     //这是main方法,程序的入口
 7     public static void main(String[] args) throws InterruptedException {
 8         PriorityBlockingQueue<Student> pq = new PriorityBlockingQueue<>();
 9         pq.put(new Student("nana",18));
10         pq.put(new Student("lulu",11));
11         pq.put(new Student("feifei",6));
12         pq.put(new Student("mingming",21));
13         System.out.println("------------------------------------------");
14         System.out.println(pq.take());
15         System.out.println(pq.take());
16         System.out.println(pq.take());
17         System.out.println(pq.take());
18     }
19 }

 

从结果证明,这个优先级队列,并不是在put数据的时候计算谁在前谁在后
而是取数据的时候,才真正判断谁在前 谁在后

优先级 --》取数据的优先级

 DelayQueue

一、DelayQueue是什么
  DelayQueue是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。

 

当生产者线程调用put之类的方法加入元素时,会触发Delayed接口中的compareTo方法进行排序,也就是说队列中元素的顺序是按到期时间排序的,而非它们进入队列的顺序。排在队列头部的元素是最早到期的,越往后到期时间赿晚。

 

消费者线程查看队列头部的元素,注意是查看不是取出。然后调用元素的getDelay方法,如果此方法返回的值小0或者等于0,则消费者线程会从队列中取出此元素,并进行处理。如果getDelay方法返回的值大于0,则消费者线程wait返回的时间值后,再从队列头部取出元素,此时元素应该已经到期。

注意:不能将null元素放置到这种队列中。
二、DelayQueue能做什么
 1. 淘宝订单业务:下单之后如果三十分钟之内没有付款就自动取消订单。
 2. 饿了吗订餐通知:下单成功后60s之后给用户发送短信通知。

 3. 关闭空闲连接。服务器中,有很多客户端的连接,空闲一段时间之后需要关闭之。

 4. 缓存。缓存中的对象,超过了空闲时间,需要从缓存中移出。

 5. 任务超时处理。在网络协议滑动窗口请求应答式交互时,处理超时未响应的请求等。

  

案例:

 1 package com.llh;
 2 
 3 import java.util.concurrent.Delayed;
 4 import java.util.concurrent.TimeUnit;
 5 
 6 public class User implements Delayed {
 7     private int id;//用户id
 8     private String name;//用户名字
 9     private long endTime;//结束时间
10     public int getId() {
11         return id;
12     }
13     public void setId(int id) {
14         this.id = id;
15     }
16     public String getName() {
17         return name;
18     }
19     public void setName(String name) {
20         this.name = name;
21     }
22     public long getEndTime() {
23         return endTime;
24     }
25     public void setEndTime(long endTime) {
26         this.endTime = endTime;
27     }
28     public User(int id, String name, long endTime) {
29         this.id = id;
30         this.name = name;
31         this.endTime = endTime;
32     }
33     //只包装用户名字就可以
34     @Override
35     public String toString() {
36         return "User{" +
37                 "name='" + name + '\'' +
38                 '}';
39     }
40     @Override
41     public long getDelay(TimeUnit unit) {
42         //计算剩余时间 剩余时间小于0 <=0  证明已经到期
43         return this.getEndTime() - System.currentTimeMillis();
44     }
45     @Override
46     public int compareTo(Delayed o) {
47         //队列中数据 到期时间的比较
48         User other = (User)o;
49         return ((Long)(this.getEndTime())).compareTo((Long)(other.getEndTime()));
50     }
51 }

compareTo:看谁先被移除
getDelay :看剩余时间

 1 package com.llh;
 2 
 3 import java.util.concurrent.DelayQueue;
 4 import java.util.concurrent.Delayed;
 5 import java.util.concurrent.TimeUnit;
 6 
 7 public class TestDelayQueue {
 8     //创建一个队列:
 9     DelayQueue<User> dq = new DelayQueue<>();
10     //登录游戏:
11     public void login(User user){
12         dq.add(user);
13         System.out.println("用户:[" + user.getId() +"],[" + user.getName() + "]已经登录,预计下机时间为:" + user.getEndTime() );
14     }
15     //时间到,退出游戏,队列中移除:
16     public void logout(){
17         //打印队列中剩余的人:
18         System.out.println(dq);
19         try {
20             User user = dq.take();
21             System.out.println("用户:[" + user.getId() +"],[" + user.getName() + "]上机时间到,自动退出游戏");
22         } catch (InterruptedException e) {
23             e.printStackTrace();
24         }
25     }
26     //获取在线人数:
27     public int onlineSize(){
28         return dq.size();
29     }
30     //这是main方法,程序的入口
31     public static void main(String[] args) {
32         //创建测试类对象:
33         TestDelayQueue test = new TestDelayQueue();
34         //添加登录的用户:
35         test.login(new User(1,"张三",System.currentTimeMillis()+5000));
36         test.login(new User(2,"李四",System.currentTimeMillis()+2000));
37         test.login(new User(3,"王五",System.currentTimeMillis()+10000));
38         //一直监控
39         while(true){
40             //到期的话,就自动下线:
41             test.logout();
42             //队列中元素都被移除了的话,那么停止监控,停止程序即可
43             if(test.onlineSize() == 0){
44                 break;
45             }
46         }
47     }
48 }

 

双端队列

 1 package com.llh;
 2 
 3 import java.util.Collection;
 4 import java.util.Deque;
 5 import java.util.Iterator;
 6 import java.util.LinkedList;
 7 
 8 public class Test03 {
 9     //这是main方法,程序的入口
10     public static void main(String[] args) {
11         /*
12         双端队列:
13         Deque<E> extends Queue
14         Queue一端放 一端取的基本方法  Deque是具备的
15         在此基础上 又扩展了 一些 头尾操作(添加,删除,获取)的方法
16          */
17         Deque<String> d = new LinkedList<>() ;
18         d.offer("A");
19         d.offer("B");
20         d.offer("C");
21         System.out.println(d);//[A, B, C]
22         d.offerFirst("D");
23         d.offerLast("E");
24         System.out.println(d);//[D, A, B, C, E]
25         System.out.println(d.poll());
26         System.out.println(d);//[A, B, C, E]
27         System.out.println(d.pollFirst());
28         System.out.println(d.pollLast());
29         System.out.println(d);
30     }
31 }

 

posted on 2023-11-14 19:41  索静丨LLH  阅读(28)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3