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 }