Java集合框架总结
本文对Java集合框架做了总结
这里推荐几篇关于集合的文章
https://blog.csdn.net/weixin_42533238/article/details/106651056
https://blog.csdn.net/ThinkWon/article/details/98844796
https://blog.csdn.net/feiyanaffection/article/details/81394745
先看看结构图,大致有个印象,方便后续学习
集合框架的概述
集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
此时的存储,注意指的是内存层面的存储,都不涉及持久化的存储(.txt,.jpg,数据库)
集合可以解决数组存储数据方面的弊端
Java容器
集合、数组都是用来对多个数据进行存储的结构,简称Java容器
数组
数组在存储多个数据方面的特点
1.数组一旦创建,它的长度就不能改变
2.数组一旦定义好,它的元素类型就确定了
数组在存储多个数据方面的缺点
1。数组一旦确定以后,它的长度就不能改变
2.数组中提供的方法很有限,对于添加、删除、修改、插入等一系列操作不方便,而且效率不高
3.获取数组中实际元素的个数的需求,数组没有现成的属性或方法可以用
4.数组存储数据的特点:有序、可重复,对于无序、不可重复的需求不能满足
集合框架
Java集合的体系分类
集合不能直接存储基本数据类型,也不能存储Java对象,只是存储java对象的内存地址
分为:Collection和Map两种体系
JDK提供的集合API位于java.util包内
Collection接口:
存的是一个一个的数据,就好比是一个个对象。
单列数据,定义了存取一组对象的方法的集合 没有直接提供它的实现类
Collection接口中的常用方法:
关于Arrays.asList()这个方法,其实有很多的坑,一不小心就会错
可以看看这篇博客Arrays.asList() 详解
向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重新equals方法
package com.atguigu.java;
import org.junit.Test;
import java.util.*;
/**
* 集合元素的遍历操作,使用迭代器Iterator接口
* 1.内部方法:hasNext 和 next
* 2.集合对象每次调用iterator()方法都得到一个全新的迭代器对象
* 默认游标都在集合的第一个元素之前
* 3.内部定义了remove(),可以在遍历的时候,删除集合中的元素,此方法不同于集合直接调用remove
*
*
* @author zengyihong
* @created 2021-04-25 15:06
*/
public class CollectionTest {
/**
*
* 结论:
* 向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重新equals方法
*/
@Test
public void test1() {
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(false);
coll.add( new String("Tom"));
coll.add(new Person("Jerry",20));
//contains(Object obj)判断当前集合是否包含obj
//我们在判断时会调用obj对象所在类的equals方法
boolean contains = coll.contains(123);
System.out.println(contains);
System.out.println(coll.contains(new String("Tom")));
System.out.println(coll.contains(new Person("Tom",20)));
//containsAll(Collection coll):判断形参coll中的所有元素是否都在当前集合中
Collection coll1= Arrays.asList(123,4567);
System.out.println(coll.containsAll(coll1));
}
@Test
public void test2(){
//remove(Object obj)
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(false);
coll.add( new String("Tom"));
coll.remove(123);
System.out.println(coll);
//removeAll(Collection coll1):从当前集合中移除集合coll1中的所有元素
//集合---->数组 :toArray()
Object[] array = coll.toArray();
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
//拓展:数组--->集合
List<String> list = Arrays.asList(new String[]{"aa", "bb", "cc"});
System.out.println(list);
List list=Arrays.asList(new Integer(123,456));
System.out.println(list.size()); // 1
//z
List list1 = Arrays.asList(new int[]{123, 456});
System.out.println(list1.size());//1
List list2 = Arrays.asList(new Integer[]{123, 456});
System.out.println(list2.size());//2
//iterator():返回Iterator接口的实例,用于遍历集合元素
}
@Test
public void test3(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(false);
coll.add( new String("Tom"));
//删除集合中的”Tom"
Iterator iterator=coll.iterator();
while (iterator.hasNext()){
Object obj = iterator.next();//接收一下集合中的元素
if ("Tom".equals(obj)){
iterator.remove();
}
}
//遍历集合
//这个时候指针已经到最后的元素,我们需要把指针重新指到第一个元素
iterator=coll.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
@Test
public void testListRemove() {
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
updateList(list);
System.out.println(list);//1,2
}
private static void updateList(List list) {
list.remove(2);//移除索引位置为2的元素
}
}
Collection接口中的contains方法源码分析
boolean contains(Object o) 判断集合是否包含某个对象
contains方法底层调用equals方法
contains方法是用来判断集合是否包含某个元素的方法,它在底层是调用equals方法进行比对的
equals方法返回true,就表示包含这个元素
放在集合里面的元素,如果没有重写equals方法,那么在比较的是,调用的是object的是equals方法
remove方法源码分析
boolean remove( )
底层调用equals方法
Iterator迭代器接口:
可以用于集合的遍历,但是Map不可以用这个来遍历
Iterator接口概述
Iterator方法
可以将iteritor看做是游标,指向所在元素的左边。
1.boolean hasNext(); //判断游标右边是否有元素
2.Object next(); //返回游标右边的元素并将游标移动到下一个位置
3.void remove(); //删除游标左边的元素,在执行完next
iterator中的remove方法
void remove()删除迭代器指向的当前元素
使用Iterator迭代器来遍历集合中的元素
方式一:
//先造一个集合
Collection coll=new ArrayList();
//给集合添加元素
coll.add(123);
coll.add("aa");
coll.add(new String("Tom"));
coll.add(false);
//然后造一个迭代器(即Iterator对象)
Iterator iterator=coll.iterator();
//接着调用next方法,有几个元素,就调用几次
//如果调用次数超过元素个数的话,会报异常 NoSuchElementException
iterator.next();
iterator.next();
iterator.next();
iterator.next();
方式二:
//先造一个集合
Collection coll=new ArrayList();
//给集合添加元素
coll.add(123);
coll.add("aa");
coll.add(new String("Tom"));
coll.add(false);
//然后造一个迭代器(即Iterator对象)
Iterator iterator=coll.iterator();
//然后使用循环语句,元素有几个,就调用几次next方法
for(int i=0;i<coll.length;i++){
iterator.next();
}
方式三:重点推荐
//先造一个集合
Collection coll=new ArrayList();
//给集合添加元素
coll.add(123);
coll.add("aa");
coll.add(new String("Tom"));
coll.add(false);
//然后造一个迭代器(即Iterator对象)
Iterator iterator=coll.iterator();
while(iterator.hasNext()){//判断是否还有下一个元素
//next() ①指针下移 ②把下移以后的集合位置上的元素返回
System.out.println(iterator.next());
}
集合的结构一旦发生改变,迭代器就一定要重新获取
迭代器执行原理
迭代器刚开始没有指向第一个元素
我们先获取一个迭代器对象
此时,相当于有一个指针
然后判断集合有没有下一个元素
有的话, 指针后移,指向元素,获得这个元素
使用迭代器的时候,可能出现下面这个异常 concurrentmodificationexception是什么异常
当我们已经获得了一个迭代器对象,然后要进行遍历,但是在遍历之前,对集合又进行了操作 这个时候就会出现上面的异常
如果我们获得迭代器对象,同时对集合进行操作,但是我们没有对迭代器进行遍历,这个时候,也不会报异常
使用foreach
循环来遍历集合中的元素
1.JDK5.0提供了foreach循环迭代访问Collection和数组
2。遍历操作不需要获取集合或数组的长度,不需要使用索引访问元素
3.遍历集合的底层调用Iteraor完成操作
使用foreach循环的语法
1.for(集合元素的类型 局部变量 : 集合对象)
2. for(数组元素的类型 局部变量 : 数组对象)
//方式一:普通for循环
String[] arr=new String[]{"MM","MM","MM"};
// for (int i = 0; i < arr.length; i++) {
// arr[i]="GG";
// }
//方式二:增强for循环
//内部仍然调用迭代器
for (String s:arr){
s="GG";
}
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
分析讨论:
一、使用普通for循环:
我们直接对字符串数组中的元素进行操作,改变元素所指的常量池中的值
二、使用加强for循环:
我们用一个变量来指向字符串数组中的所有元素,然后对这个新变量进行操作数据,并不会影响原本字符串数组中所储存的数据
Collection的子接口
List接口
存储有序的、可重复的数据--->动态数组,替换原有的数组
List接口的实现类
- ArrayList:作为List接口的主要实现类;线程不安全,效率高;底层使用Object[] elementData存储
- LinkedList:对应频繁的插入、删除操作,使用此类效率比ArrayList高,底层使用双向链表存储
- Vector:List接口的古老实现类,线程安全的,效率低;底层使用Object[] elementData存储
三者的相同点:三个类都实现了List接口,都是存储有序的、可重复的数据
ArrayList
1.JDK7情况下
ArrayList list=new ArrayList();//底层创建了一个长度为10的Object[]数组elementData
List.add(123)//elementData[0]=new Integer(123)
...
List.add(11)//如果此次的添加导致底层elementData数组容量不够,则扩容。
默认情况下,扩容为原来的1.5倍,同时需要把原来的数组复制到新的数组中
结论:建议开发中使用带参的构造器 : ArrayList list=new ArrayList(int capacity)可以减少扩容次数,效率可以高一点
2.JDK8中ArrayList的变化
ArrayList list=new ArrayList()//底层Object[]elementData初始化为{},并没有创建长度为10的数组
ArrayList.add(123)//第一次调用add()时,底层才创建了长度为10的数组,并把数据123添加到elementData中
后续的添加和扩容操作与JDK7没有差异,扩容为原来的1.5倍
3.总结:JDK7中的ArrayList的创建类似于单例的饿汉式,而JDK8中的ArrayList的对象的创建类似于单例的懒汉式,延迟数组的创建,节省内存
LinkedList
底层是双向链表结构
链表的优点:
链表上的元素在空间存储上,内存地址不联系,在随机增删元素的时候,不需要大量移动元素
但是查找效率低,每一次查找都要从头结点开始
LinkedList集合没有初始化容量最初这个链表中没有任何元素,first和larst都是null
LinkedList list=new LinkedList();//内部声明了Node类型的first和last属性,默认值为null
List.add(123);//把123封装到Node中,创建了Node对象
其中Node定义为:体现了LinkedList的双向链表的说法
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
Vector
JDK7和JDK8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组,在扩容方面,默认扩容为原来数组长度的2倍
底层也是数组
创建一个Vector集合,默认初始化容量10
扩容后为原来容量的2倍
Vector所有方法都是线程同步的,都带有synchronized
线程安全,效率低,少用
List接口的方法
List除了从Collection集合继承的方法外,List 集合里添加了一些根据索引来
操作集合元素的方法。
1.void add(int index, Object ele):在index位置插入ele元素
2. boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
3.Object get(int index):获取指定index位置的元素
4. int indexOf(Object obj):返回obj在集合中首次出现的位置
5. int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
6. Object remove(int index):移除指定index位置的元素,并返回此元素
7. Object set(int index, Object ele):设置指定index位置的元素为ele
8. List subList(int fromIndex, int toIndex):返fromIndex到toIndex位置的子集合
List常用方法
1.增:add(Object obj)
2.删:remove(int index)/remove(Object obj)
3.改:set(int index,Object obj)
4.查:get(int index)
5.插:add(int index,Object obj)
6.长度:size()
7.遍历:①Iterator迭代器 ②增强for循环 ③普通for循环 for(int i=0;i<list.size();i++){
System.out.println(list.get(i))
}
remove方法的易错题
@Test
public void testListRemove() {
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
updateList(list);
System.out.println(list);//[1,2]
}
private static void updateList(List list) {
list.remove(2);
}
目的:区别remove(int index)/remove(Object obj)
当我们使用remove方法,里面传入的是一个整型数据,默认认为 我们要移除该索引位置的元素
如果我们想要移除对应元素,应该进行装箱,把这个数据转换为包装类
例如 : List.remove(new Integer(2)) 把元素2删除
List.remove(2)) 把索引位置为2的地方的元素删除
Set接口
存储无序的,不可重复的数据---->高中讲的集合 Set接口中没有额外定义方法,使用的是Collection接口中的方法
要求:向Set接口中添加的数据,其所在的类一定要重写hashCode()和equals()
重写的hashCode()和equals()尽可能保持一致性,相同的对象具有相同的散列码
Set接口的特性
无序性
以HashSet为例: 不等于随机性, 每次遍历的顺序都是固定的。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的
HashSet底层是数组,它的长度为16
不可重复性
保证添加的元素按照equals方法判断时,不能返回true,如果返回true,则认为两个对象相同,即相同的元素只能添加一个
添加元素的过程:以 HashSet 为例
我们向HashSet添加元素a时,首先调用HashCode()方法,计算元素a的哈希值,这个哈希值在通过散列函数计算出HashSet在底层数组中的存放位置(即为:索引位置),判断数组此位置上面是否有元素:
如果此位置上面没有元素,则元素a添加成功。---->情况1
如果此位置上面有其他元素b(或以链表形式存在的多个元素),则比较元素a和元素b的Hash值:
如果Hash值不同,则元素a添加成功---->情况2
如果Hash值相同,进而需要调用元素a所在类的equals()方法:
equals()返回true,元素a添加失败
equals()返回false,则元素a添加成功---->情况3
对于添加成功的情况2和情况3而言:元素a和已经存在指定索引位置上的数据以链表的形式存储
jdk7:元素a放到数组中,指向原来的元素
jdk8:原来的元素在数组中,指向元素a
总结:七上八下(在jdk8中新的元素放到上面)
HashSet底层:数组+链表
添加元素的要求
向Set接口中添加的元素,其所在的类一定要重写equals()方法,和HashCode()方法
重写方法的要求
重写的equals()方法和HashCode()方法尽可能保持一致性:相等的对象必须具有相等的散列码(即哈希值)
重写两个方法的小技巧:对象中用作equals()方法比较的属性:都应该用来计算哈希值
尽量使得当两个对象的属性都相等时,它们的哈希值也相等
当它们的哈希值相等时,它们的属性也相等
Set接口的实现类
HashSet
重写hashCode()方法的基本原则 在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值。
当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode() 方法的返回值也应相等。 对象中用作
equals() 方法比较的 Field,都应该用来计算 hashCode 值。
LinkedHashSet
1.作为HashSet的子类,在添加数据的同时,每一个数据还维护两个引用,用来记录此数据前一个数据和后一个数据
2. LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置, 但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入 顺序保存的。
3. LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全 部元素时有很好的性能。
4. LinkedHashSet 不允许集合元素重复 优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet
5.遍历内部数据的时候,可以按照添加的顺序进行遍历
TreeSet
不能放相同的数据
TreeSet和 TreeMap 采用红黑树的存储结构 特点:有序,查询速度比List快
放入TreeSet中的数据要是同一个类new出来的,我们可以按照对象的某一些属性进行排序
默认是从小到大排序
向TreeSet中添加的数据,要求是相同类的对象
自然排序
实现Comparable接口
重写compareTo方法
自然排序中,比较两个对象是否相同的标准为:compareTo()返回0,不再是equals()
定制排序
实现Comparator接口
重写compare方法
Map接口
Map实现类的结构
/---Map:存储双列数据,存储key-value数据 ---类似高中讲的函数
/---HashMap:作为Map的主要实现类,线程不安全,效率高,可以存储key和value都是null的数据
/---LinkedHashMap:HashMap的子类,在原有的基础上加一对指针,指向前一个元素和后一个元素,形成链表结构,能够保证在遍历Map元素时,可以按照我们添加的顺序进行遍历
对于频繁的遍历操作,此类指向效率高于HashMap
/---TreeMAp:可以保证按照添加的key-value进行排序,实现排序遍历,按照key来排序
此时考虑key的自然排序或定制排序
底层使用红黑树
/---Hashtable:作为古老的实现类,线程安全,效率低,不能存储null(key和value中有一个是null都不行)
/---Properties:常用来处理配置文件。key和value都是String类型
HashMap的底层: 数组+链表(jdk7之前)
数组+链表+红黑树(jdk8)
Map结构的理解
我们知道Map类似于高中讲的函数,所以Map中的key不能重复
1.Map中的key:无序的、不可重复的,使用Set存储所有的key --->Key所在的类要重写equals方法和HashCode方法(以HashMap为例) 因为存储的key可能是我们自定义的类
2.Map中的value:可重复的、无序的,使用Collection存储所有的value---->value所在的类要重写equals方法
3.一个键值对:key-value构成了一个Entry对象,在计算机中Map存储是存一个Entry对象,而不是存储两个值key和value,而是把这两个数据封装在一起
4.Map中的entry:无序的,不可重复的,使用Set存储所有的entry
事实上Map里面放的是一个一个的数据,这一个个的数据是Entry,Entry里面有两个属性,一个key,一个value,里面放的数据无序不可重复,因为 key无序不可重复,一个key对应一个value
HashMap的底层实现原理
以JDK7为例
HashMap map=new HashMap();
在实例化以后,底层创建了长度为16的一维数组Entry[] table
map.put(key1,value1)
首先调用key1所在类的HashCode方法来计算key1的哈希值,然后根据某种算法计算以后,得到在Entery数组中的存储位置。
如果此位置数据为空,此时的key1-value1添加成功—情况1
如果此位置数据不为空(说明此位置存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数值的哈希值:
如果key1的哈希值和已经存在的数据的哈希值不相同,此时的key1-value1添加成功—情况2
如果key1和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较,调用key1所在类的equals方法,比较:
如果equals方法返回false,此时key1-value1添加成功–情况3
如果equals方法返回true:使用value1去替换相同key的数据值value2
补充:关于情况2和情况3:
此时key1-value1和原来的数据以链表的形式存在
在不断添加的过程中,会涉及到扩容问题:当超出临界值(而且其存放的位置非空) 默认的扩容方式:扩容为原来的2倍,并把原来的数据复制过来
JDK8.0和JDK7的不同
1. new HashMap();//底层没有创建一个长度为16的数组
2. JDK8底层的数组:Node[],不是Entry[]
3. JDK8首次调用put方法时,底层创建长度为16的数组
4. JDK7底层结构只有数组+链表 JDK8中底层结构:数组+链表+红黑树 当数组的某一个位置上的元素以链表形式存在的数据个数=8,且当前数组的长度>64,此时索引位置上的所有数据改为使用红黑树存储,便于查找数据
加载因子:0.75
LinkedHashMap
它能够让输出的顺序和我们添加的顺序一样,是因为底层有before,after属性,用来记录添加Entry的前一个和下一个是谁,记录添加的元素顺序
Map接口常用方法
@Test
public void test1(){
Map map=new HashMap<>();
map.put("AA",123);
map.put("BB",12 );
map.put( 156,78);
//修改
map.put("BB",33);
System.out.println(map);
Map map1=new HashMap();
map1.put("cc",40);
map1.put(78,"tim");
map.putAll(map1);
System.out.println(map);
//remove 如果没有找到对应的key,返回null
Object value = map.remove("cc");
System.out.println(value);
System.out.println(map);
//clear 清空元素
map.clear();
System.out.println(map.size());//0
}
Map的遍历方法
@Test
public void test2(){
Map map=new HashMap<>();
map.put("AA",123);
map.put("BB",12 );
map.put( 156,78);
/**
* 遍历所有的key
*/
Set set = map.keySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}
}
/**
* Map的遍历
*/
@Test
public void test2(){
Map map=new HashMap<>();
map.put("AA",123);
map.put("BB",12 );
map.put( 156,78);
/**
* 遍历所有的key
*/
Set set = map.keySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}
System.out.println();
/**
* 遍历所有的value集
*/
Collection values = map.values();
Iterator iterator1 = values.iterator();
while (iterator1.hasNext()) {
Object next = iterator1.next();
System.out.println(next);
}
System.out.println();
/**
* 遍历key 和 value
* 方式一:
*/
Set entrySet = map.entrySet();
Iterator iterator2 = entrySet.iterator();
while (iterator2.hasNext()){
//这个时候,obj是entry类型
Object obj=iterator2.next();
//entrySet集合中的元素都是entry
Map.Entry entry=(Map.Entry)obj;
System.out.println(entry.getKey()+"--->"+entry.getValue());
}
System.out.println();
/**
* 方式二:先遍历所有的key,通过key来获取value
*/
Set set1 = map.keySet();
Iterator iterator3=set1.iterator();
while (iterator3.hasNext()){
//获取key
Object next = iterator3.next();
System.out.println(next+"--->"+map.get(next));
}
Properties
Collections工具类
操作Collection和Map的工具类
package com.atguigu.java;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
*
* CollectionsTest是操作Colletion和Map的工具类
*
* 面试题:Collection和Collections的区别
*
* @author zengyihong
* @created 2021-09-18 14:52
*/
public class CollectionsTest {
/**
*reverse(list); 反转List中元素的顺序
*shuffle(list):对List集合元素进行随机排序
*sort(list):根据元素的自然排序对指定list集合元素按照升序排序
*swap(list,int,int):将指定List集合中的i处元素和j处元素进行互换
*
*
*
*
*Object max(Collection):根据元素的自然排序,返回给定集合中的最大元素
*Object max(Collection,Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素
*Object min(Collection)
*Object(Collection,Comparator)
*int frequency(Collection,Object):返回指定集合中指定元素的出现次数
*void copy(List dest,List src):将src中的内容复制到dest中
*boolean replaceAll(List list,Object newVal):使用新值替换List中的旧值
*
*
*Collections 类中提供了多个synchronizedXxx()方法,
*该方法可以将指定集合包装成线程同步的集合,从而可以解决
* 多线程并发访问集合时的线程安全问题
*
*
*/
@Test
public void test1(){
List list=new ArrayList();
list.add(123);
list.add(456);
list.add(10);
list.add(-56);
list.add(0);
System.out.println(list);
// Collections.reverse(list); 反转list中元素的顺序
//shuffle(list)对list集合元素进行随机排序
//Collections.shuffle(list);
//报异常:java.lang.IndexOutOfBoundsException: Source does not fit in dest
// List dest=new ArrayList();
//
// Collections.copy(dest,list);
List dest= Arrays.asList(new Object[list.size()]);
Collections.copy(dest,list);
System.out.println(dest);
}
}
- 点赞
- 收藏
- 关注作者
评论(0)