一、集合的概念
在给大家分享集合框架之前,先对这篇文章做一个简单的开篇。这是我在学习千峰Java过程中,自己总结的一些个人认为比较重要的知识点,万分感谢千峰教育的教育奉献!在学习过程中,慢慢养成了记笔记的好习惯,我始终认为将技术记到脑子里,落到笔记中才是属于我的,希望我可以一直坚持下去,也希望大家也可以试着去系统的学习,并记录在自己的笔记中,这个好习惯会让我们终身受益!
开冲!
集合是对象的容器,定义了对多个对象进行操作的常用方法。可以实现数组的功能!本篇文章涉及集合框架的底层实现,希望大家上车后认真学习!
集合和数组的区别
1、数组的长度是固定的,集合的长度不固定
2、数组可以存储基本类型和引用类型,集合只能存储引用类型
二、集合中常见的接口
Collection接口
1、Collection接口体系结构
Collection接口是所有集合接口的父接口,在这里大家先有个系统的认知,后面详细学习常见的集合接口的使用
在这里提几点使用Collection接口的常见问题:
迭代器使用过程中,不能同时使用collection 的remove方法,会报并发修改异。但是可以使用迭代器自带的删除方法
List接口与实现类
List接口
1、List接口认识
List集合是有序、有下标、元素可重复的
2、常见方法
(1)返回list集合中某元素下标,如果该集合中不存在这个元素,则返回-1
这个方法经常用来做if判断,常用作判断如果某个元素存在,则执行某个操作的场景
list.indexOf(list中某元素);
(2)add方法
往List集合中添加数据,这里需要注意的是,add中传递的参数,存在自动装箱的过程,比如存储一个int类型的数据,实际上存储的是Integer的类型,其他的基本数据类型也是遵循这个规则
拓展:Iterator接口和ListIterator接口
Iterator作用和方法
假如有一个集合,内容为: 1,2,3,4,5
迭代器的光标就是在数据1的左边或者右边,不会再数据上,这样在执行hashNext方法的时候,如果光标在集合的最后面,就直接返回false;
当光标在1的前面,此时hashNext方法返回true,才会执行next方法,此时光标就会到数据1的后面,返回数据1
2.具体的方法使用
(1)迭代器正常遍历集合
运行结果:
(2)完全版迭代器:可以一边遍历一边进行删除
运行结果:
(3)简易版迭代器
运用了增强for循环
运行结果:
ListIterator作用和方法
列表迭代器
除了支持正向的遍历获取集合种的数据,还支持反向遍历出列表中的数据
正反向遍历方法:
@Test
public void test01(){
AdvertisementMessage message1 = new AdvertisementMessage();
AdvertisementMessage message2 = new AdvertisementMessage();
AdvertisementMessage message3 = new AdvertisementMessage();
AdvertisementMessage message4 = new AdvertisementMessage();
message1.setMenuCode("1231");
message2.setMenuCode("2");
message3.setMenuCode("3");
message4.setMenuCode("4");
List<AdvertisementMessage> advertisementMessages = new ArrayList<>();
advertisementMessages.add(message1);
advertisementMessages.add(message2);
advertisementMessages.add(message3);
advertisementMessages.add(message4);
ListIterator<AdvertisementMessage> listIterator = advertisementMessages.listIterator();
while (listIterator.hasNext()){ //正向遍历
System.out.println(listIterator.next());
}
while (listIterator.hasPrevious()){ //反向遍历,注意,仅限当前指针指向个集合元素的后面,才能往前遍历,如果本身光标在前面,是无法向后遍历的!!!!!!
int i = listIterator.previousIndex();
AdvertisementMessage previous = listIterator.previous();
System.out.println(i+":"+previous.toString());
}
}
List接口实现类
一、List实现类ArrayList
1、ArrayList特点
底层是数组机构的实现,查询快,增删慢
2、常见的方法
(1)添加元素用add
(2)删除元素用remove
(3)遍历元素有3种方案
·增强for循环遍历
·Iterator迭代器
·列表迭代器ListIterator
这里只演示列表迭代器
(4)判断集合中是否包含某个元素contains方法
(5)判断集合是否为空
(6)查找某元素,返回元素下标
二、List实现类Vector
1、Vector集合的特点
底层是数组结构实现,查询快,增删慢
2、常见方法
(1)添加元素
List<String> vectorList = new Vector();
vectorList.add("西瓜");
(2)删除元素
//删除某个元素
vectorList.remove("西瓜");
//清除所有的元素
vectorList.clear();
(3)遍历
遍历,对于Vector来说,有一个特有的枚举器,相当于List的Iterator迭代器
使用方法如下:
(4)判断某个元素是否存在于这个集合中
vectorList.contains("西瓜");
(5)判断Vector集合是否为空
vectorList。isEmpty();
三、List实现类LinkedList
1、LinkedList集合的特点
底层是通过链表的结构实现的,增删快,查询较慢
2、常见的方法
(1)创建集合的方法
LinkedList linkedList = new LinkedList<>();
(2)添加元素
linkedList.add();
(3)获取集合元素的数量
linkedList.size();
(4)删除集合中的元素
linkedList.remove();
(5)遍历集合
两种方案,一种是常规的for循环,一种是增强for循环
(6)判断元素是否存在和判断集合是否为空
linkedList.contains(元素);
linkedList.isEmpty();
(7)获取元素在该集合中的下标
linkedList.indexOf(元素);
拓展:
ArrayList和LinkedList的不同结构实现方式
泛型和工具类
一、认识泛型
1、什么是泛型
泛型是在jdk1.5引入的新特性,本质上就是参数化类型,把类型作为参数传递
2、常见的泛型形式
泛型类、泛型接口和泛型方法
3、语法
<T,......>其中,T成为类型占位符,表示一种引用类型
4、使用泛型的优势
(1)提高代码的重用性
(2)防止类型转换异常,提高代码的安全性
下面的示例中,如果不加上泛型,添加不同类型的数据,就会报异常
二、泛型类
1、泛型类语法
类名<T> T:是类型占位符,表示一种引用类型,如果编写多个需要用逗号隔开!
2、使用示例
(1)创建泛型类,并且可以使用泛型创建变量
(2)泛型可以作为方法的参数
(3)泛型作为方法的返回值
3、创建泛型类,使用方式
该实例中是泛型为String类型
三、泛型接口
1、泛型接口语法
public interface 接口名<T>
2、使用示例
(1)创建泛型接口
(2) 创建泛型接口中的抽象方法
该抽象方法是参数为泛型类型参数,返回值为该泛型类型
3、创建泛型接口实现类的两种方式
(1)实现类直接实现泛型接口中的方法
(2)实现类也是一个泛型类,由创建该泛型实现类的实例对象来定类型
四、泛型方法
1、泛型方法语法
泛型方法的泛型<T>是放在返回值类型前的,泛型方法的好处就是一个方法实现了当初的方法重载的功能,方法名相同,但是传递的参数类型不同
public <T> T 方法名(T t){
}
2、使用示例
创建一个普通的方法,在方法中可以创建泛型方法
调用的时候:
传递一个String的字符串,这时候这个方法的参数类型就变成了String类型,返回值类型也变成了String类型
当我传递一个Integer类型的数据,那么这个方法的参数类型和返回值类型就变成了Integer
五、泛型集合
1、泛型集合概念
参数化类型、类型安全的集合,它会强制集合中元素的类型必须一致
2、泛型集合特点
(1)编译时即可检查数据的正确性,而非运行时抛出异常
(2)访问时,不必进行类型的转换(拆箱)
(3)不同的泛型之间不能相互赋值,泛型且不存在多态
3、实际开发中,经常创建一个泛型为某个对象的List集合,这样这个集合中就只能存这一种类型的数据,存其他类型的数据就会报错!
Set接口与实现类
Set接口
一、Set接口特点
无序、无下标、元素不能重复
二、Set实现类
1、HashSet
(1)基于HashCode实现元素不重复
(2)当存入元素的哈希码相同时,会调用equals方法,如果结果为true,则拒绝后者存入
2、TreeSet
基于排序顺序实现元素不重复
Set接口实现类
hashSet
一、hashSet理解
HashSet是一种基于HashCode计算元素存放位置的方法。当存入元素的哈希码相同时,会调用equals进行确认,如果为true,则拒绝后者存入,如果HashhCode相同,equals为false,则在相同的位置上进行链表存储。
HashSet的存储结构是哈希表,由数组+链表+红黑树构成
二、存储过程
1、根据hashCode计算保存的位置,如果此位置为空,则直接保存,如果不为空,则执行第二步
2、再执行equals方法,如果equals方法为true,则认为是重复的,否则将在这个位置上形成链表
注意:
一般都是使用快捷键,自动重写hashCode和equals方法
重点:
重写hashCode为什么有一个31?
1、因为31是一个质数,这个数做运算,可以尽量减少散列的冲突,让hashCode计算出的位置尽量不一样
2、提高计算执行效率,因为有一个非常著名的公式:31*i=(i<<5)-i,假设i为2,其中这个左移5位就相当于6个2相乘
三、HashSet的常见的方法
下图是关于HashSet的常见的方法:
TreeSet
一、原理和实现方案
TreeSet是基于排列顺序实现元素不重复
实现方案:
TreeSet实现了SortedSet接口,对集合元素自动排序;
元素对象的类型必须实现Comparable接口,指定排序规则;
通过ComparaTo方法确定是否为重复元素
二、存储结构
红黑树
三、观察TreeSet底层原理,创建TreeSet就是通过调用无参构造,创建一个TreeMap对象
四、TreeSet常见方法
1、存储基本数据类型
2、存储引用数据类型
(1)add添加对象方法
通过观察源码,底层就是调用的TreeMap的put方法来进行添加的
但是直接创建一个Penson对象,调用add方法添加元素是会报错的,报类型转换异常,Person类不能转换成Comparable,报这个错误的原因是,TreeSet的存储原理是红黑树,二叉查找树,左边的节点一定小于右边的节点,新添加的元素一定需要和根节点进行比较,来确定是存在左孩子位置还是右孩子位置,所以就需要一个规则来比较
解决方法:
存的对象,也就是TreeSet中的元素实现Comparable接口,重写里面的抽象方法CompareTo
重写这个方法,如果最后的返回值为0,说明两个对象一样,如果返回的不等于0,那么就可以正常存储,重写规则开发人员可以自定义
(2)删除元素
同样是调用remove方法,但是如果在方法中创建一个和已经存储进去的元素姓名和年龄一样的对象,如果我们重写了compareTo方法,那么就会判定是同一个元素,该元素会被移除
(3)遍历treeSet中元素
使用增强FOR和迭代器!
(4)判断元素是否存在
调用cointains方法
3、Comparator接口
(1)认识该接口
该接口还是用于TreeSet中,用于实现定制比较,他可以通俗的理解成就是一个比较器,它可以让我们的集合中存储的引用数据类型,免去实现Comparable接口,直接在创建集合的时候将这个比较规则定义好
(2)使用方法
Map接口与实现类
Map接口
Map接口认识
一、Map接口特点
1、用于存储任意键值对(Key-value)
2、键:无序、无下标、不允许重复(唯一)
3、值:无序、无下标、允许重复
二、Map接口继承结构
Map接口使用
1、添加元素
调用put方法
注意:如果相继put两次相同key的数据,那么后一次的value将会覆盖上一次的value
2、删除元素
remove 方法
3、遍历Map集合
(1)使用keySet方法拿到集合中所有的key,该方法的返回值是一个Set集合,就可以直接通过增强for循环,遍历得到每一个key
(2)使用entrySet方法,直接返回一个键值对,类型为Set类型,里面是用到了map接口里面的Entry接口
比较两种遍历方式,EntrySet方法效率会更高点
4、判断集合中是否存在某个元素
(1)判断是否有某个key
(2)判断集合中是否存在某个value
Map接口实现类
HashMap
认识hashMap
一、了解HashMap
hashMap是在jdk1.2版本引进来的,线程不安全,运行效率快;
该集合允许用null作为key或是value
二、了解HashMap构造方法
调用无参构造,默认创建一个容量为16和默认加载因子(0.75)的空HashMap(默认加载因子:假设容量设置成了100,当存储的数据超过了75条,则就会自动扩容)
HashMap常见方法
一、存储结构
哈希表(数组+链表+红黑树)
他判断键值对中的key是否是重复的,假设key是引用对象,那么这个对象如果重写hashCode和equals方法,那么就会按照里面的规则判断键是不是重复的
二、常见方法
直接参考Map接口的:Map接口使用
这里只罗列Map接口中不存在的方法,剩下的方法和接口方法一致
HashMap源码分析
一、HashMap类常量
详细了解TREEIFY_THRESHOLD=8和UNTREEIFY_THRESHOLD=6:
这两个数据包括最后面数组的最大长度都是jdk1.8的新特性
这个是jdk1.8新增加的特性,一般我们的HashMap存储的时候是数组加链表的形式,存储的过程是新元素先通过HashCode判断在数组的哪个位置上,如果这个位置为空,那么直接存储;如果这个位置上铀元素了,那么就会调用equals方法,判断元素是不是同一个,如果是,那么拒绝存储,如果不是那么就会在数组的这个位置上,形成链表。
TREEIFY_THRESHOLD=8表示如果这个数组的长度大于了64,链表的长度大于了8,就会把链表变成红黑树,这样计算的效率就会大大提高。
UNTREEIFY_THRESHOLD=6表示链表的长度小于6的时候,就会由原来的红黑树变回成链表的结构
二、HashMap构造方法
1、无参构造
刚创建的集合,table=null,size=0,目的是节省空间,当添加第一个元素的时候,才会设置默认的容量
三、put方法
打开putVal,阅读里面的源码,发现当我们put一个元素的时候,数组容量就会被赋值为16,当元素个数大于阈值(16*0.75=12)时,会进行扩容,扩容后大小为原来的2倍,目的是减少调整元素的个个数
jdk1.8之前,链表是头插入,jdk1.8之后是尾插入
阅读里面的源码!
TreeMap
一、了解和认识
TreeMap实现了SortedMap接口,该接口是Map接口的子接口,TreeMap可以对key实现自动排序,类似于Set接口实现类中的TreeSet,可以参考上面的笔记
二、存储结构
红黑树
三、常见方法
1、添加元素
添加元素的时候,由于这个treeMap底层的存储结构是红黑树,所以如果要存引用数据类型,需要实现Comparable接口,重写CompareTo方法,可以参考TreeSet中重写的方式
2、删除元素
3、遍历元素
(1)使用keySet方法,获取所有的key,然后调用get方法,获得value
(2)entrySet方法,获取键值对
4、判断
还是调用containsKey或者containsValue方法
Collections工具类
1、对集合中的数据排序
当然了,也可以调用这个方法的重载方法,自定义排序规则
2、实现元素的复制
调用该工具类的copy方法
注意,调用这个copy方法的时候,新创建的集合的大小要和被拷贝的集合的大小一致,否则就会报数组下标越界的异常
3、实现集合中元素反转
调用reverse方法
4、打乱集合中的元素顺序
调用shuffle方法
数组和集合的相互转换
一、集合转换成数组
//创建一个集合
List<String> list = new arrayList<>();
list.add("张三");
list.add("李四");
String[] arr = list.toArray(new String[2]); //调用toArray方法,里面的参数为创建的带大小的数组
二、数组转换成集合
//创建数组
String[] names = {"张三","李四","王五"};
//调用Arrays工具类中的方法实现转换,注意:转换后的集合是一个受限制的集合,不能添加或者删除
List<String> list = Arrays.asList(names);