目录
集合
1.集合概述
1.1 什么是集合
什么是集合?有什么用?
集合实际上就是一个容器可以来容纳其他类型的数据,集合可以放不同类型的元素
集合容量是无限的可以自动扩容。
集合为什么在开发中使用较多? 集合是一个容器,是一个载体,可以一次容纳多个对象,在实际开发中,假设连接数据库,数据库当中有十条记录,那么假设把这10条记录查询出来,在java程序中会将10条数据封装成10个java对象,然后将10个java对象放到某一个集合当中,将集合传到前端,然后遍历集合,将一个数据,一个数据展现出来。
理解为运输数据的
1.2 集合
集合不能直接存储基本数据类型,另外集合也不能最直接存储java对象,集合当中存储的都是java对象的内存地址(或者说集合中存储的是引用)
注意:
集合在java中本身是一个容器,是一个对象。
集合中任何时候存储的都是引用
1.3 数据结构
在java中每一个不同的集合,底层会对应不同的数据结构。往不同的集合中储存元素等于将数据放到了不同的数据结构中。
什么是数据结构?
数据存储的结构就是数据结构,不同的数据结构储存方式不同。
例如: 数组 二叉树 链表 哈希表 以上这些都是常见的数据结构
java中已经将数据结构实现了,已经写好了这些常用的集合类,只需要掌握怎么用、在什么情况下选择哪一种合适的集合去使用即可。
1.4 集合所在的包
集合在java JDK中的哪个包下?
所有的集合类和集合接口,都在在java.util.*
包下。
new ArrayList();创建一个集合,底层是数组。 new LinkedList();创建一个集合对象,底层是链表。 new TreeSet();创建一个集合对象,底层是二叉树。
1.5 集合的继承结构图
Map:
1.6 集合两大类
在java中集合分为两大类:
-
一类是单个方式存储元素:
单个方式存储元素,这一类集合中超级父接口:java.util.Collection;
-
一类是以键值对(key value )的方式存储元素
以键值对的方式存储元素,这一类集合中超级父接口: java.util.Map;
总结(所有实现类)
ArrayList:底层是数组。 Linked:底层是双向列表。 Vector:底层是数组,线程安全的,效率较低,使用较少 HashSet:底层是HashMap,放到HashSet集合中的元素等同于放到HashMap集合key部分了。 TreeSet:底层是TreeMap,放到TreeSet集合中的元素等同于放到TreeMap集合key部分了。 HashMap:底层是哈希表。 Hashtable:底层也是哈希表,只不过线程安全的,效率较低,使用较少。 Properties:是线程安全的,并且key和value只能存储字符串String TreeMap:底层是二叉树。TreeMap集合的key可以自动按照大小顺序排序。
-
List集合存储元素的特点:
有序可重复
有序:存进去的顺序和取出的顺序相同,每一个元素都有下标。
可重复:存进去1,可以在存储一个1
-
Set(Map)集合存储元素的特点:
无序不可重复
无序:存进去的顺序和取出的顺序不一定相同,另外Set集合中元素没有下标。
不可重复:存进去1,不能再存1了。
-
SortedSet(SortedMap)集合存储元素的特点:
首先是无序不可重复的,但是SortedSet集合中的元素是可排序的。
无序:存进去的顺序和取出的顺序不一定相同,另外Set集合中元素没有下标。
不可重复:存进去1,不能再存1了。
可排序:可以按照大小顺序排列。
Map集合的key就是一个Set集合。
往Set集合中放数据,实际上放到了Map集合的key部分。
2.Collection接口
1.Collection中能存放什么元素?
-
没有使用”泛型“之前,Collection中可以存储Object的所有子类
-
使用了"泛型"之后,Collection中只能存放某个具体类型的子类
集合后期我们会学习“泛型”的语法(集合中不能直接存储基本数据类型,java对象,只是存储java对象的内存地址)
2.1Collection常用方法
boolean add(Object e) //像集合中添加元素 int size() ;//获取元素个数 void clear();//清空集合 boolean contains(Object o);//判断当前集合中是否包含元素o,包含返回true不包含返回false boolean remove(Object o)//删除集合中某个元素 boolean isEmpty()//判断该集合中元素的个数是否为0 Object[] toArray() //调用这个方法可以把集合转换成数组
package com.test01; import java.util.ArrayList; import java.util.Collection; public class test01 { public static void main(String[] args) { System.out.println("helloworld"); //接口是抽象的不能实例化,所以使用多态 Collection c =new ArrayList(); //添加元素add() c.add("123"); c.add(12345);//自动装箱(java5的新特性),实际上是放进去了一个对象的内存地址。Integer x = new Integrt(1200); c.add(789);//自动装箱 c.add(new Student()); c.add(true); //获取元素个数 size() System.out.println(c.size());//5 //清空集合 clear() c.clear(); System.out.println(c.size());//0 c.add("c"); c.add("l"); c.add("j"); //判断是否包含c contains() boolean flag1 = c.contains("c"); boolean flag2 =c.contains("d"); System.out.println("判断是否包含某个元素:contains()"); System.out.println(flag1);//true System.out.println(flag2);//false System.out.println(c.size()); //删除元素 remove() System.out.println("删除元素:remove()"); c.remove("c"); System.out.println(c.size()); //判断集合是否为空isEmpty()、clear() System.out.println("判断元素是否为空:isEmpty()"); System.out.println(c.isEmpty()); c.clear();//清空集合 System.out.println(c.isEmpty());//true //集合转换成数组 toArray() c.clear(); c.add(123); c.add("hello"); c.add("world"); c.add("chen"); //转换成数组 Object[] o = c.toArray(); for(int i=0;i<o.length;i++){ System.out.println(o[i]); } //转换成数组 Object[] objs = c.toArray(); for(int i = 0; i<objs.length;i++){ //遍历数组 object o = objs[i]; System.out.println(o); toString方法在String和Integer中都重写了。 } } } class Student{ }
2.2迭代器(重点)
迭代器(迭代集合:遍历集合中的元素) 继承的Iterable中的方法
package com.test01; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; public class test02 { public static void main(String[] args) { //以下迭代方式/遍历方式,是所有Collection通用的一种方式, //在Map集合中不能用。在所有的Collection以及子类中使用。 //创建集合对象 Collection c =new HashSet(); c.add(123); c.add(456); c.add(789); c.add(new test()); //第一步:获取集合对象的迭代器对象 Iterator it=c.iterator(); //第二步:通过获取的迭代器对象开始迭代 /** *如果接下来还有对象可以迭代,hasNext就返回true,如果没有元素可以迭代了就返回false(数据结构中单链表的方式) * next:获取下一个元素 */ while(it.hasNext()){ System.out.println(it.next()); //自动装箱:Integer等和String都重写了toString方法,而自定义的类没有重写所以输出的是地址 } } } class test{ //没有重写toString方法 }
2.2.1图解
该画法不太精确,因为集合里面实际上存的是地址
hasNext方法: boolean hadNext = it.hasNext(); //这个方法返回false表示没有更多的元素可以迭代了。返回true,表示还有元素可以迭代 hasNext方法: //这个方法让迭代器前进一位,并且将指向的元素返回(拿到)。
2.2.2案例
package com.test01; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; public class test03 { public static void main(String[] args) { //创建集合对象 Collection ao =new ArrayList(); //添加元素 ao.add(123); ao.add(456); ao.add(789); ao.add(159); //创建迭代对象 Iterator ii =ao.iterator(); // while(ii.hasNext()){ Object o = ii.next();//注:调用一次next就会使元素后移一个 if(o instanceof Integer){ System.out.println("Interger类型");//存进去什么类型,输出还是什么类型 } System.out.println(o);//只不过会在输出的时候变成字符串类型,因为这里println会调用toString方法 } System.out.println("---------------------------------"); //HashSat无序不重复 //无序:存进去和取出的顺序不一定相同,不可重复 Collection cc =new HashSet(); cc.add(1); cc.add(2); cc.add(3); cc.add(6); cc.add(8); cc.add(1); cc.add(1); cc.add(1); cc.add(1); Iterator iii= cc.iterator(); while(iii.hasNext()){ System.out.println(iii.next()); } } }
2.2.3contions()方法
判断集合中是否包含某个对象o 如果包含返回true,不包含返回false
底层是调用了equals()方法
package com.Swing; import java.util.ArrayList; import java.util.Collection; /** * Created with IntelliJ IDEA. * * @Author: Mr.chen * @Date: 2022/09/13/15:52 * @Description: */ public class ContionsTest { public static void main(String[] args) { Collection t= new ArrayList(); String s1 = new String("123"); //0x1111 t.add(s1);//放进去了一个"123" String s2 = new String("456");//0x2222 t.add(s2); //输出个数 System.out.println(t.size()); String s3 = new String("123");//0x5555 System.out.println(s3.equals(s1));//字符串的比较会调用equals方法。 System.out.println(t.contains(s3));//true ,判断集合中是否含有"abc" //t集合中包含s3 } }
contains方法是用来判断集合中是否包含某个元素的方法,他在底层调用了equals方法进行比对(String的equals方法比较的是内容),所以equals方法返回true就表示包含了这个元素。
Object方法的equals方法用双等号,比较的是内存地址。类中没有重写equals方法就调用的是Object里的equals方法。
-
放在集合里面的元素要重写equals方法。
2.2.4remove()方法
remove方法底层也调用了equals方法
package com.Swing; import java.util.ArrayList; import java.util.Collection; /** * Created with IntelliJ IDEA. * * @Author: Mr.chen * @Date: 2022/09/13/16:37 * @Description: */ public class RemoveTest { public static void main(String[] args) { Collection t = new ArrayList(); Integer i1= new Integer(123); t.add(i1); System.out.println(t.size());//1 Integer i2 = new Integer(123); System.out.println(t.remove(i2));//true System.out.println(t.size());//0 } }
2.2.5迭代器是快照
//迭代器就像是快照,只会迭代出快照里的元素,如果要修改元素那么就必须重新快照(即重新迭代)或者采用Iterator里面的remove方法来移除元素
当集合的结构发生改变时,迭代器必须重新获取
如果还是用以前老的迭代器,会出现异常:java.util.ConcurrentModifirionException
package com.Collection; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; /** * Created with IntelliJ IDEA. * * @Author: Mr.chen * @Date: 2022/09/13/17:06 * @Description: */ public class Test01 { public static void main(String[] args) { Collection c= new ArrayList(); c.add(123); c.add(456); c.add(789); Iterator i = c.iterator(); while(i.hasNext()){ Object o= i.next(); c.remove(123); System.out.println(o); } /** *删除元素后,集合结构发生改变,应该重新获取迭代器对象。 * Exception in thread "main" java.util.ConcurrentModificationException */ } }
总结:在迭代集合元素的过程中(迭代器对象创建完成后),不能调用集合对象的remove方法删除元素,及不能改变集合的结构
因为:直接通过集合去删除元素没有通知迭代器(导致迭代器的快照和原集合状态不同)
图解
直接使用集合删
使用迭代器删
底层会不断检查快照余真实结构的关系,如果发生了变化就会报错
解决方法: //使用迭代器来删 Iterator i = c.iterator(); while(i.hasNext()){ Object o= i.next(); i.remove();//删除的一定是迭代器当前返回的对象,获取之后就立马删掉 System.out.println(o);//能打印出来是因为,集合保存的是地址,删掉的是地址,但是内容是保存到常量池当中的。 } System.out.println(c.size());//0
Iterator对象和Collection对象的remove方法是不一样的。
Collection的删除:直接通过集合去删除元素,没有通知迭代器,导致迭代器的快照与结构不同。就会报错 Iterator的删除: 把快照删了的时会把集合中的元素删掉(自动更新),及同时发生改变。不会出现异常
集合状态发生变化时,重新获取迭代器
3.List接口
特点:有序可重复,有下标
有序:List集合中的元素有下标,从0开始,以1递增
可重复:存储一个1,还可以存储1
3.1List接口常用方法
boolean add(Object e) //像集合中添加元素,默认加在末尾 void add(int index, Object element)//在指定位置上加入元素(第一个参数是下标) 效率较低不常用 Object get(int index);//根据下标取元素,List集合特有的遍历方式 //下标从0开始 int indexOf(Objcect o)//获取指定对象第一次出现的索引(下标) int lastIndexOf(object o)//获取指定对象最后一次出现的索引(下标) Object remove(int index) //删除指定下标的元素 Object set(int index,Object element)//修改指定位置的元素
package com.Collection; import java.util.ArrayList; import java.util.List; /** * Created with IntelliJ IDEA. * * @Author: Mr.chen * @Date: 2022/09/13/17:44 * @Description: */ public class List01 { public static void main(String[] args) { //创建List对象 List list = new ArrayList<>(); //添加元素 list.add(0,0); list.add(1); list.add(2); list.add(3); //List添加的独特方式(不常用) list.add(4,1); //遍历 for(int i = 0;i <list.size();i++){ System.out.println(list.get(i)); } //获取第一次出现的索引 System.out.println(list.indexOf(1));//1 //获取最后一次出现的索引 System.out.println(list.lastIndexOf(1));//4 } }
3.2ArrayList集合
底层是数组
1.Arraylist初始化容量是10(jdk13 底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量是10)
2.Arraylist底层是Object[]数组
3.指定初始化容量:
List list1 =new Arraylist(20);
默认初始化容量:List list2=new List();
数组的长度4.ArrayList集合的扩容
原容量的1.5倍,ArrayList集合底层是数组,怎么优化?
尽可能少的扩容,因为数组扩容的效率比较低,建议在使用ArrayLIst集合的时候估计元素的个数给定一个初始化容量。
5.数组的优点:检索效率比较高。(每个元素占用空间大小相同,内存地址是连续的,知道首元素内存地址,然后知道下标,通过数学表达式计算出元素的内存地址没所以检索效率最高)
6.数组的缺点:随即增删元素效率比较低,但像数组末尾添加元素效率很高
另外数组无法存储大数据量。
7.面试:这么多的集合中用哪一个 : ArrayList集合, 因为往数组末尾添加元素效率不受影响。另外检索,查找某个元素时比较多。
8.ArrayList集合是非线程安全的(不是线程安全的集合)
package com.Collection; import java.util.ArrayList; /** * Created with IntelliJ IDEA. * * @Author: Mr.chen * @Date: 2022/09/13/17:58 * @Description: */ public class ArrayListTest { public static void main(String[] args) { ArrayList arrayListTest = new ArrayList(); System.out.println(arrayListTest.size()); //可以指定容量 ArrayList arrayListTest1 = new ArrayList(20); System.out.println(arrayListTest.size()); //size()获取的是集合当前元素的个数,不是数组的容量。 } }
//二进制位运算 //100(二进制) 二转化成十: 00000100(4) 右移一位 00000010(2) [4/2] //原先是4,现在增长2,增长之后是6,增长之后是增长之前的1.5倍(6是4的1.5倍)。
3.2.1ArrayList创建对象的三种方式
package com.Collection; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; /** * Created with IntelliJ IDEA. * * @Author: Mr.chen * @Date: 2022/09/13/18:29 * @Description:命名三种方式 */ public class ArrayListTest02 { public static void main(String[] args) { //第一种 ArrayList arrayList1 = new ArrayList(); //第二种,指定初始化容量 ArrayList arrayList2 = new ArrayList(100); //第三种:HashSet转化成ArrayList HashSet hashSet= new HashSet(); hashSet.add(123); hashSet.add(456); hashSet.add(789); hashSet.add(741); ArrayList arrayList3 = new ArrayList(hashSet); for(int i =0;i<hashSet.size();i++){ System.out.println(arrayList3.get(i)); } } }
//将Arraylist转化为Hastset package com.test01; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; public class test05 { public static void main(String[] args) { /*Collection col =new ArrayList(); String s1 =new String("123"); col.add(s1); String s2 =new String("123"); col.remove(s2); System.out.println(col.size());*/ //创建Hashset集合 Collection d =new HashSet(); d.add(123); d.add(456); d.add(789); //指定初始化容量 List f =new ArrayList(100); //通过这个方法可以将Hashset集合转换成List集合 List f1 =new ArrayList(d); for(int i=0;i<f1.size();i++){ System.out.println(f1.get(i)); } } }
3.3链表:
两部分组成:一部分是数据 一部分是内存地址
3.3.1单项链表
3.3.2链表优缺点
链表优点:
随机增删元素的效率较高(因为增删元素不会涉及到大量元素位移的问题)
由于链表上的元素在空间存储上内存地址不连续,所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高。在以后的开发中,如果遇到随机增删集合中元素的业务比较多时,建议使用LinkList。
链表缺点:
查询效率较低(每一次查找某个元素的时候都需要从头节点开始往下遍历)
不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头结点开始遍历,知道找到位置。所以LinkedList集合检索查找的效率较低。
ArrayList:把检索发挥到极致(末尾检索效率还是很高的)
LinkListl:把随机增删发挥到极致
3.3.3双向链表
三部分组成:上一个对象内存地址,数据,下一个对象内存地址
p686LinkedList源码分析
初始化容量为null
3.4LinkedList集合
1.LinkedList集合底层采用了双向链表数据结构
2.LinkedList集合是双向链表,对于链表数据结构来说,随即增删效率较高检索效率较低。
3.链表中的元素在空间存储上内存地址不连续。
3.3.5Vector集合
1.底层也是一个数组
2.初始化容量是十
3.增么扩容的?
Vector扩容之后是原容量的2倍。10-->20--->40--->80
4.ArrayList扩容后是原容量的1.5倍。
5.Vector中所有的方法都是线程同步的,都带有synchtronized关键字,是线程安全的。(效率比较低,使用较少)
6.怎么将一个线程不安全的集合转换成线程安全的集合
使用集合工具类java.util.Collections
java.util.Collection 借口
java.util.Collections 工具类
List coll =new ArrayList();//非线程安全 //变成线程安全 Collections.synchronizedList(coll);
4.泛型
4.1式例
未使用泛型前
import org.w3c.dom.ls.LSOutput; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; /** * 未使用泛型前 */ public class test06 { public static void main(String[] args) { //准备猫对象、鸟对象 Cat c= new Cat(); Bird b =new Bird(); // 不适用泛型,创建集合对象 Collection cl= new ArrayList(20); //将对象添加到集合当中去 cl.add(c); cl.add(b); //迭代器 Iterator ie = cl.iterator(); //迭代器获取元素 while(ie.hasNext()){ Object j = ie.next();//没使用泛型,得到的是Object对象 if(j instanceof Animal){ Animal animal =(Animal) j; animal.move(); if(animal instanceof Cat){ Cat cc =(Cat)animal; cc.eat(); } else if(animal instanceof Bird){ Bird bb =(Bird)animal; bb.eat(); } } } } } class Animal{ public void move(){ System.out.println("动物在移动!"); } } class Cat extends Animal{ public void eat(){ System.out.println("毛在捕老鼠"); } } class Bird extends Animal{ public void eat(){ System.out.println("鸟儿吃虫子!"); } }
使用泛型后
import org.w3c.dom.ls.LSOutput; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; /** * 未使用泛型前 */ public class test06 { public static void main(String[] args) { //指定collection集合只允许存储Animal类型的数据 //用泛型来指定集合中存储的数据类型 Collection<Animal> collection =new ArrayList<>(); //创建猫、鸟对象 Cat c1 =new Cat(); Bird b1 =new Bird(); //添加元素 collection.add(c1); collection.add(b1); //使用泛型,这个迭代器迭代的类型是Animal,不能存储其他元素 Iterator<Animal> iterator = collection.iterator(); while(iterator.hasNext()){ //直接通过迭代器获取了Animal类型 Animal animal =iterator.next();//使用泛型直接得到Animal类型,不需要向下强制转型 animal.move(); if(animal instanceof Cat){ Cat cc =(Cat)animal; cc.eat(); } else if(animal instanceof Bird){ Bird bb =(Bird)animal; bb.eat(); } } } } class Animal{ public void move(){ System.out.println("动物在移动!"); } } class Cat extends Animal{ public void eat(){ System.out.println("毛在捕老鼠"); } } class Bird extends Animal{ public void eat(){ System.out.println("鸟儿吃虫子!"); } }
-
不使用泛型需要强制转换
-
创建集合的时候需要指定泛型的类型,迭代的时候也需要指定泛型的类型。
1.JDK5.0之后推出的新特性
2.泛型这种语法机制,只在程序编译阶段起作用,只是给编译器参考的(运行阶段泛型没用!)
3.泛型的优点:
第一:集合中存储的元素类型统一了。
第二:从集合中取出的元素类型是泛型指定的类型,不需要进行大量的"向下转型"!
4.泛型的缺点:
导致集合中存储的元素缺乏多样性
大多是业务中,集合中元素的类型还是统一的。所以这种泛型特性被大家认可。
4.2类型自动推断
JDK之后引入了:自动类型推断机制。(又称为钻石表达式)
自动类型推断机制(钻石表达式)JDK8之后 Collection<Animal> collection =new ArrayList<Animal>(); Collection<Animal> collection =new ArrayList<>();
4.3自定义泛型
自定义泛型的时候,<>尖括号中的是一个标识符,随便写。
java源码中经常出现:<E> \ <T>
E是Element首字母
T是Type的首字母
package com.test01; import java.util.ArrayList; import java.util.List; public class test06 <E(标识符随便写)> { public static void main(String[] args) { Test<String> t =new Test();//指定是String类型 t.dosome("123");//只能传String List<String> l =new ArrayList<>(); Test<Integer> ti =new Test<>(); ti.dosome(123);//只能传入Integer类型 } } class Test<E>{ Test () {} Test(E e){ System.out.println("helloworld"); } public void dosome(E e){ System.out.println("做点什么"); } public E dosome(E e){ return e; } }
package com.Collection; /** * Created with IntelliJ IDEA. * * @Author: Mr.chen * @Date: 2022/09/13/20:14 * @Description:泛型测试 */ public class GenericTest<随便写标识符> { static class DoSome<随便写标识符> { void DoSome(随便写标识符 o) { System.out.println(o.toString()); } } public static void main(String[] args) { DoSome<String> doSome=new DoSome<>(); doSome.DoSome("456"); } }
4.4增强for循环(foreach)
语法: for( 元素类型 变量名:数组或集合){System.out.println(元素变量名)}
package com.test01; /* 增强for */ public class test07 { public static void main(String[] args) { int[]arr={1,2,3,4,5,6}; for(int i =0;i<arr.length;i++){ System.out.println(arr[i]); } //增强for foreach /** * 语法: * for( 元素类型 变量名:数组或集合){System.out.println(变量名)} */ System.out.println("=============================="); for (int data:arr) { System.out.println(data); } } }
三种输出方式
package com.test01; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class test08 { public static void main(String[] args) { List<String> l = new ArrayList<>(); l.add("123"); l.add("456"); l.add("789"); l.add("159"); Iterator<String> i = l.iterator(); while(i.hasNext()){ System.out.println(i.next()); } for(int a =0;a< l.size();a++){ System.out.println(l.get(a)); } for(String add:l){ System.out.println(add); } } }
5.Set接口
5.1HashSet集合
package com.test01; import java.util.HashSet; import java.util.Set; public class test09 { public static void main(String[] args) { Set<String> e=new HashSet<>(); e.add("123"); e.add("789"); e.add("123"); e.add("123"); e.add("1594"); e.add("1594"); for(String add:e){ System.out.println(add); } } }
特点:
1.存储时顺序和取出的顺序不同
2.不可重复
3.放到HashSet集合中的元素实际上是放到HashMap集合的key部分了。
初始化容量:16,初始化容量建议是2的倍数。扩容:扩容之后是原容量的2倍。
5.2TreeSet集合
特点:1.无序不可重复,但是存储的元素可以自动按照大小顺序排序!
1.TreeSet集合底层实际上是一个TreeMap 2.TreeMap集合底层是一个二叉树 3.放到Tree集合中的元素,等同于放到TreeMap集合Key部分了。 4.TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序
称为:可排序集合。升序!
package com.test01; import java.util.HashSet; import java.util.Set; import java.util.TreeSet; public class test09 { public static void main(String[] args) { Set<String> s =new TreeSet<>(); s.add("A"); s.add("C"); s.add("H"); s.add("L"); s.add("R"); s.add("P"); s.add("L"); for(String ss:s){ System.out.println(ss); } } } //结果 从小到大自动排序(按字典的排序方法排序) A C H L P R
package com.Collection; import java.util.Set; import java.util.TreeSet; /** * Created with IntelliJ IDEA. * * @Author: Mr.chen * @Date: 2022/09/14/9:29 * @Description: TreeSet自动排序 */ public class treeSetTest { public static void main(String[] args) { Set<String> set = new TreeSet<>(); set.add("zhangsan"); set.add("lisi"); set.add("wangwu"); set.add("zhaoliu"); set.add("lisan"); set.add("zhaosi"); for(String ss :set){ System.out.println(ss); } /** 按照字母大小一个一个比较 * lisan * lisi * wangwu * zhangsan * zhaoliu * zhaosi */ } }
5.2.1自定义类型不能用TreeSet
原因: 没有实现
java.lang.Conparable
接口
package com.Collection; import java.util.Set; import java.util.TreeSet; /** * Created with IntelliJ IDEA. * * @Author: Mr.chen * @Date: 2022/09/15/10:56 * @Description: */ public class TreeTest { public static void main(String[] args) { /** * Exception in thread "main" java.lang.ClassCastException: * class com.Collection.Customer cannot be cast to class java.lang.Comparable */ Customer customer0= new Customer(12); Customer customer1= new Customer(12); Customer customer2= new Customer(12); Customer customer3= new Customer(12); Customer customer4= new Customer(12); Set<Customer> customers = new TreeSet<>(); customers.add(customer0); customers.add(customer1); customers.add(customer2); customers.add(customer3); customers.add(customer4); for(Customer customer :customers){ System.out.println(customer); } } } class Customer{ public int age; public Customer(int age) { this.age = age; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
5.2.2实现比较
两种方法让自定义类型用TreeSet能实现比较
5.2.2.1重写compareTo
继承Comparable接口
package com.test02; import java.util.Set; import java.util.TreeSet; /** * 书写比较规则 比较规则自己决定 */ public class test002 { public static void main(String[] args) { users u0 =new users(123); users u1 =new users(456); users u2 =new users(789); Set<users> tree =new TreeSet<>(); tree.add(u0); tree.add(u1); tree.add(u2); for(users set:tree){ System.out.println(set); } /** * 结果: * users{age=123} * users{age=456} * users{age=789} */ } } class users implements Comparable<users>{ private int age; public users(int age) { this.age = age; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } //重写compareTo方法 //需要在这里编写比较的逻辑,按照什么进行比较! //k.compareTo(t.key) //拿着参数k和集合中的每一个k进行比较,返回值可能是>0 <0 =0 //比较规则是又程序员指定的,例如按照年龄升序或者年龄降序 @Override public int compareTo(users o){ //c1.compare(c2) /** * this是c1 * o就是c2 * c1和c2比较就是this和o比较 */ /*int age1=this.age; int age2 =o.age; if(age1==age2){ return 0; }else if(age1-age2<0){ return -1; }else if(age1>age2){ return 1; }*/ return this.age-o.age; } //重写toString方法 @Override public String toString() { return "users{" + "age=" + age + '}'; } }
案例
package com.Collection; import java.util.Set; import java.util.TreeSet; /** * Created with IntelliJ IDEA. * * @Author: Mr.chen * @Date: 2022/09/15/11:19 * @Description:先按照年龄比,再按照名字排序 */ public class TreeTests { public static void main(String[] args) { Set<Teacher> teachers = new TreeSet<>(); teachers.add(new Teacher(12,"rdf")); teachers.add(new Teacher(18,"xdf")); teachers.add(new Teacher(14,"zdf")); teachers.add(new Teacher(22,"ydf")); teachers.add(new Teacher(22,"xdf")); for(Teacher teacher :teachers){ System.out.println(teacher); } } } class Teacher implements Comparable<Teacher>{ int age; String name; public Teacher(int age, String name) { this.age = age; this.name = name; } @Override public int compareTo(Teacher s) { if(s.age==this.age){ return this.name.compareTo(s.name);//注意这里是this-s(二叉树的特点) }else{ return this.age-s.age;//用当前元素去减之间已经放好的元素,中序遍历:左根右,左小右大 } } @Override public String toString() { return "Teacher{" + "age=" + age + ", name='" + name + '\'' + '}'; } }
5.2.2.2实现Comparator接口
单独编写一个比较器
比较器实现java.utli.Comparator接口
Comparable是Java.lang包下的
Comparator是java.util包下的
案例
package com.Collection; import java.util.Comparator; import java.util.Set; import java.util.TreeSet; /** * Created with IntelliJ IDEA. * * @Author: Mr.chen * @Date: 2022/09/15/15:00 * @Description:让Animals自定义类型可以进行比较 */ public class TreeSetTest001 { public static void main(String[] args) { //创建Set集合的时候,需要使用这个比较器 //Set<Animals> aniamls = new TreeSet<>();这样不行没有传入比较器 //给构造方法传一个比较器 Set<Animals> animals = new TreeSet<>(new AnimalComparator()); //匿名内部类的方式 Set<Animals> animal1 = new TreeSet<>(new Comparator<Animals>() { @Override public int compare(Animals o1, Animals o2) { return o1.age-o2.age; } }); animals.add(new Animals(123)); animals.add(new Animals(14)); animals.add(new Animals(13)); for(Animals Animal :animals){ System.out.println(Animal); } } } class Animals{ int age; public Animals(int age) { this.age = age; } @Override public String toString() { return "Animals{" + "age=" + age + '}'; } } //单独在这里编写一个比较器 //比较器实现java.util.Comparator接口。 //Comparable是java.lang包下的。(Comparator是java,util包下的) class AnimalComparator implements Comparator<Animals> { @Override public int compare(Animals o1, Animals o2) { //指定比较规则 //按照年龄排序 return o1.age-o2.age; } }
5.2.2.3如何选择
当比较规则不会发生改变的时候,或者说当比较规则只有一个的时候,建议实现Comparable接口。
如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Compararoe接口。
-
comparator接口的设计符合OCP原则
6.Map接口
java.util.Map接口中的常用方法
键值对:Map<Integer,Students> map =new HashMap();
1.Map和Collection没有继承关系。
2.Map集合以key和value的方式存储数据类型:键值对
key和value都是引用数据类型。
key和value都是存储对象的内存地址。
key起到主导的地位,value是key的一个附属品。
3.常用方法:
Set keySet():获取Map集合的所有的key(所有的key是一个set集合) get(Object key) :通过key来获取value Collection values():获取Map集合中所有的value,返回Collection
Map.Entry<K,V> Map集合通过entrySet()方法转化成的这个Set集合,Set集合中的元素类型是Map.Entry<K,V> Map.Entry和String一样,都是一种类型的名字,只不过:Map.Entry是Map中的静态内部类。 <Map是个类名,Entry是Map的静态内部类>
package com.Collection; import com.sun.jdi.Value; import java.util.HashSet; import java.util.Set; import java.util.TreeSet; /** * Created with IntelliJ IDEA. * * @Author: Mr.chen * @Date: 2022/09/13/22:10 * @Description: */ public class StaticTest { //静态内部类 public static class MyClass{ //静态内部类中的静态方法 public static void InnerClass(){ System.out.println("静态内部类的静态方法"); } //静态内部类中的实例方法 public void m2(){ System.out.println("静态内部类的实例方法"); } } public static void main(String[] args) { //Set集合中存储的对象是:MyClass.InnerClass类型 Set<StaticTest.MyClass> set0 = new HashSet<>(); //Set集合中存储的是字符串对象 Set<String> set1 = new HashSet<>(); Set<MyClass_plus.fun<Integer,String >> set2 =new HashSet<>(); } } class MyClass_plus{ public static class fun<k,v>{ } }
6.1静态内部类(补充)
package com.Collection; /** * Created with IntelliJ IDEA. * * @Author: Mr.chen * @Date: 2022/09/13/22:10 * @Description: */ public class StaticTest { //声明一个静态内部类 private static class InnerClass{ //静态方法 public static void m1() { System.out.println("静态内部类的静态方法执行!"); } //实例方法 public void m2(){ System.out.println("静态内部类中的实例方法执行!"); } } public static void main(String[] args) { //类名叫做:MyClass.InnerClass StaticTest.InnerClass.m1(); //创建静态内部类对象 StaticTest.InnerClass I1 =new StaticTest.InnerClass(); I1.m2(); } }
6.2常用方法
Map接口常用的方法:
package com.Collection; import java.util.Collection; import java.util.HashMap; import java.util.Map; /** * Created with IntelliJ IDEA. * * @Author: Mr.chen * @Date: 2022/09/13/22:58 * @Description:获取values */ public class MapTest { public static void main(String[] args) { Map<Integer,String> map = new HashMap<>(); map.put(1,"chen"); map.put(2,"liang"); map.put(3,"jiang"); //获取values Collection<String> collection=map.values(); for(String str:collection){ System.out.println(str); } } }
6.3Map遍历(非常重要)
第一种遍历方式:
//第一种访问方式 package com.test01; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; public class test10 { public static void main(String[] args) { Map<Integer,String> m= new HashMap<>(); m.put(1,"A"); m.put(2,"B"); m.put(3,"C"); m.put(4,"D"); m.put(5,"E"); Set<Integer> set =m.keySet(); Iterator<Integer> i =set.iterator(); /** * 拿到key,通过key来获取value */ while(i.hasNext()){ Object o = i.next(); //m.get(o)通过key访问value System.out.println(o+"="+m.get(o)); } for(Integer keys: m.keySet()){ System.out.println(keys+"="+m.get(keys)); } } }
第二种遍历方式:
package com.test01; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; public class test11 { public static void main(String[] args) { Map<Integer,String> s =new HashMap<>(); s.put(0,"123"); s.put(1,"456"); s.put(2,"789"); s.put(3,"753"); s.put(4,"159"); //通过调用 entrySet()把Map集合转化成Set集合 Set<Map.Entry<Integer,String>> e =s.entrySet(); //Set集合中元素的类型是Map.Entry //迭代器 Iterator<Map.Entry<Integer,String>> iterator =e.iterator(); while(iterator.hasNext()) { Map.Entry<Integer,String> node =iterator.next(); System.out.println(node.getKey()+"="+node.getValue()); } System.out.println("==================================================="); //使用for增强不需要用迭代器 for(Map.Entry<Integer,String> nodes:e){ System.out.println(nodes.getKey()+"="+nodes.getValue()); } } }
6-1HashMap集合
1.HashMap集合底层是哈希表(散列表)的数据结构
2.哈希表(散列表)是一个怎样的数据结构呢?
哈希表是一个数组和单项列表的结合体
数组:在查询方面效率很高,随机增删方面效率很低
单项列表:在随机增删方面效率较高,在查询方面效率较低
哈希表将以上的两种数据结构融合在一起,充分发挥他们各自的优点
3.HashMap集合底层的源代码
Node[] nodes; public class Node{ Object k; Object v; Node next; }
public Class HashMap{ //HashMap底层实际上就是一个数组。(一维数组) Node<K,V>[]table; //静态的内部类HashMap.Node static class Node<K,V>{ final int hash;//哈希值(哈希值的执行结果,hash值通过哈希函数/算法,可以转换称数组的下标。) final K key;//存储到Map集合中的key V value;//存储到Map集合中的value Node<K,V> next;//下一个节点的内存地址 } } 哈希表/散列表:一维数组,这个数组中每一个元素是一个单项链表。(数组和链表的结合体)
1.HashMap的put,get方法(必须掌握)
2.HashMap集合的key部分特点:
无序,不可重复。
为什么无序?因为不一定挂到那个单向链表上。
不可重复是怎么保证的?equals方法来保证HashMap集合的key不可重复。
如果key重复了,value会覆盖
-
放在HashMap集合key部分的元素其实就是放到HashSet集合中了。所以HashMap集合中的元素也需要同时重写hashCode()+equals()方法。
同一个链表上的hash一定相同,因为他们的数组下标是一样的,但同一个链表上k和k的equals方法肯定返回的是false,都不相等
3.散列分布不均匀
4.重写hashset和equals方法
package com.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * Created with IntelliJ IDEA. * * @Author: Mr.chen * @Date: 2022/09/14/0:35 * @Description: */ public class MapTest02 { public static void main(String[] args) { Map<Integer,String> map = new HashMap(); map.put(1111,"1111"); map.put(2222,"2222"); map.put(3333,"3333"); map.put(4444,"4444"); map.put(4444,"4455"); Set<Map.Entry<Integer,String>> set= map.entrySet(); for(Map.Entry<Integer,String> node : set){ System.out.println(node.getKey()+"="+node.getValue());//key重复value会被覆盖,且输出无序 } /** * 3333=3333 * 1111=1111 * 4444=4455 * 2222=2222 */ } }
放在HashSet集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法
-
HashMap集合的默认初始化容量是16,默认加载因子是0.75(默认加载因子是:当HashMap集合底层数组容量达到75%的时候,数组开始扩容。)
-
重点:HashMap集合初始化容量必须是2的倍数,这也是官方推荐的
这是因为达到散列均匀,为了提高HashMap集合的存储效率所必需的
5.equals的调用情况
向Map集合中存,以及Map集合中取,都是先调用key的hashCode方法,然后再调用equals方法。
equals方法可能调用,也可能不调用
拿put(k,v)举例,什么时候equals不会调用? k.hashCode()方法返回哈希值。 哈希值经过hash算法转换成数组的下标。 数组下标位置上如果是null,equals不需要执行。 拿get(k)举例,什么时候equals不会被调用? k.hashCode()方法返回哈希值 哈希值经过哈希算法转换成数组的下标。 数组下标位置上如果是null,equals不需要执行。
总结:hashCode()返回的哈希值就是数组的下标
如果一个类的equals方法
重写了,那么hashCode()
方法必须重写。并且equals方法
返回如果是true
,hashCode方法
返回的值必须一样。
package com.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * Created with IntelliJ IDEA. * * @Author: Mr.chen * @Date: 2022/09/14/8:20 * @Description:equals重写的时候hashCode方法也要重写。同时生成 */ public class SetTest { public static void main(String[] args) { Student stu1 = new Student("张三"); Student stu2 = new Student("张三"); boolean bool=stu1.equals(stu2); System.out.println(bool); Map<Integer,Student> map = new HashMap<>(); //特点,无序不可重复(内容不能重复) map.put(1,stu1); map.put(1,stu2); System.out.println(map.size());//1 Set<Map.Entry<Integer,Student>>set=map.entrySet(); for(Map.Entry<Integer,Student> node : set){ System.out.println(node.getKey()+"="+node.getValue()); //1=com.Collection.Student@bd308 只有一个 } } }
同时重写hashCode()和equals()方法
原因:equals方法返回true表示两个对象相同,是在同一个单向链表上比较,那么对于同一个单向链表上的节点来说,他们的哈希值(数组下标)都是相同的。所以hashCode返回的值也应该相同
6.终极结论
放在HashMap
集合key
部分的,以及放在HashSet
集合中的元素,equals()和hashCode()
方法必须同时重写,用IDEA同时生成(shift+insert)
7.哈希表总结
对于哈希表数据结构来说:
-
如果o1和o2的哈希值相同,一定是放到同一个单项列表上
-
如果o1和o2的哈希值不同,但由于哈希算法执行结束之后转换的数组下标有可能相同,此时发生了”哈希碰撞“。
哈希值相同一定在同一个链表上
HashMap扩容之后的容量是原容量的2倍
package com.test01; import java.util.*; public class test12 { public static void main(String[] args) { Students stu1 =new Students(12); Students stu2 = new Students(12); //创建Set集合 Set<Students> set =new HashSet<>(); set.add(stu1); set.add(stu2); System.out.println(stu1.equals(stu2));//重写之后返回true System.out.println(set.size());//1 //同时重写equals和hashCode方法后相同的只返回一次 //创建Map集合 Map<Integer,Students> map =new HashMap(); map.put(1,stu1); map.put(1,stu2); //key重复,value覆盖 System.out.println(map.size());//1 } } class Students{ private int age; private String name; public Students(){} public Students(int age) { this.age = age; } public int getAge() { return age; } public String getName() { return name; } public void setAge(int age) { this.age = age; } public void setName(String name) { this.name = name; } //重写equals()和hashCode()方法 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Students students = (Students) o; return age == students.age && Objects.equals(name, students.name); } @Override public int hashCode() { return Objects.hash(age, name); } }
8.key和value为空
HashMap()的key和value都可以为空(面试)
package com.Collection; import java.util.HashMap; import java.util.Map; /** * Created with IntelliJ IDEA. * * @Author: Mr.chen * @Date: 2022/09/14/9:07 * @Description:验证map集合的key能否为空 */ public class MapTest03 { public static void main(String[] args) { Map map = new HashMap(); //key为null map.put(null,123); System.out.println(map.size());//1 map.put(null,456); System.out.println(map.size());//1 //value为null map.put(1,null); //map集合的key可以为空,但是只能又一个,因为会被覆盖。 System.out.println(map.get(null)); System.out.println(map.get(1));//null } }
6-2Hashtable集合
Hashtable的key和value都不能为空(空指针异常)
Hashtable方法都带有synchronized:线程安全的。
线程安全有其他的方案,这个Hashtable对线程的处理导致效率较低,使用较少了
Hashtable和HashMap一样底层都是哈希表数据结构
Hashtable的初始化容量为11,默认加载因子是0.75
Hashtable的扩容是:原容量*2+1
6-3Properties集合
目前只需要掌握properties属性类对象
-
proprerties是一个Map集合,继承Hashtable,properties的key和value都是String类型。
-
Properties被称为属性类对象。
-
Properties是线程安全的
package com.test01; import java.util.HashMap; import java.util.Map; import java.util.Properties; public class test13 { public static void main(String[] args) { Map M =new HashMap(); M.put(null,null); System.out.println(M.size());//1 System.out.println(M.get(null));//null M.put(null,100); System.out.println(M.get(null));//100 Properties pro = new Properties(); //Properties的两个方法(存、取) pro.setProperty("name0","chen"); pro.setProperty("name1","liang"); pro.setProperty("name2","jiang"); //通过key获取value String s =pro.getProperty("name0"); String s2 =pro.getProperty("name1"); String s3 =pro.getProperty("name2"); System.out.println(s); System.out.println(s2); System.out.println(s3); /*chen liang jiang */ } }
6-4TreeMap集合
1.TreeSet集合底层实际上是一个TreeMap 2.TreeMap集合底层是一个二叉树 3.放到Tree集合中的元素,等同于放到TreeMap集合Key部分了。 4.TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序
二叉树结构
7.集合工具类
Collections
java.util.Collextion: 集合接口
java.util.Collections:集合工具类
-
排序
-
线程安全
package com.Collection; import java.util.*; /** * Created with IntelliJ IDEA. * * @Author: Mr.chen * @Date: 2022/09/15/16:01 * @Description:集合工具类 */ public class CollectionsTest { public static void main(String[] args) { //List是非线程安全的 List<Integer> list = new ArrayList(); //将List变为线程安全的 Collections.synchronizedList(list); //排序 list.add(465); list.add(789); list.add(159); Collections.sort(list); for(Integer lists :list){ System.out.println(lists); } List<Tutor> lists = new ArrayList(); lists.add(new Tutor(123)); lists.add(new Tutor(543)); lists.add(new Tutor(541)); //对list元素经行排序,必须保证List集合中的元素实现了Comparable接口。 Collections.sort(lists); for(Object list_1 :lists){ System.out.println(list_1); } //对Set集合进行排序 //将Set转成List集合 Set<String> set = new TreeSet<>(); set.add("abc"); set.add("acd"); set.add("sbs"); set.add("abb"); //将Set集合转成List集合 List<String> LIST = new ArrayList<>(set); Collections.sort(LIST); for(String s : LIST){ System.out.println(s); } } } class Tutor implements Comparable<Tutor>{ int age; public Tutor(int age) { this.age = age; } @Override public int compareTo(Tutor o) { return this.age-o.age; } @Override public String toString() { return "Tutor{" + "age=" + age + '}'; } }
注意:Set集合可以转成List集合