9、Java集合
9、Java集合
9.1 集合框架概述
集合,数组都是对多个数据进行存储操作的结构,简称Java容器;
数组在存储多个数据方面的缺点:
一旦初始化后,长度就不可修改;
数组中提供的方法有限,增删改等操作不便,效率不高;
数组存储数据的特点:有序,可重复。(无法满足 无序,不可重复的需求)
集合框架:
Java集合可分为 Collection 和 Map 两种体系:
- Collection接口:单列数据,定义了存取一组对象的方法的集合
List:元素有序,可重复的集合
Set: 元素无序,不可重复的集合- Map接口:双列数据,保存具有映射关系 “key-value对” 的集合
Collection: -List: ArrayList,LinkedList,Vecter -Set: HashSet,LinkedHashSet,TreeSet Map: HashMap,LinkedHashMap,TreeMap,Hashtable,Properties
Collection:
Map:
![]()
9.2 Collection接口
集合Collection中存储的非基本数据类型对象 (自定义类的对象),都需要重写equals()方法
常用方法:
add(Object obj)
addAll(Collection coll)
int size()
void clear()
boolean isEmpty()
//包含
boolean contains(Object obj) //调用了 obj.equals() 传入对象的equals方法
boolean containsAll(Collection c)
//删除
boolean remove(Object obj) //删除找到的第一个元素
boolean removeAll(Collection coll)//求差集
//交集
boolean retainAll(Collection c) //求交集 --修改了集合本身
//比较
boolean equals(Object obj) //两集合元素是否相同--(根据子类看是否按顺序比较)
hashCode() //返回当前对象哈希值
//集合--->数组
Object[] toArray()
//数组--->集合
Arrays.asList(new Integer[]{1,2}); //调用Arrays类的静态方法asList()
//迭代器iterator(): 返回Iterator接口的实例,用于遍历集合元素
Iterator iterator = xx.iterator();
Iterator迭代器接口
Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。
Iterator 仅用于遍历集合,它本身并不是容器
Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,可以返回一个Iterator接口对象
集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
内部的方法:hasNext() , next() , remove
//hasNext():判断是否还有下一个元素 while(iterator.hasNext()){ //next():指针下移,返回下移后该集合位置上的元素 sout(iterator.next()); //remove():可以在遍历的时候,删除集合中的元素 iterator.remove(); }
JDK5.0新增 增强型for循环 -->foreach循环
//内部调用的就是 Iterator迭代器 for(Object obj : 集合对象){ sout(obj); }
List接口
List接口,存储有序的,可重复的数据;---> "动态"数组
- ArrayList:作为List接口的主要实现类;线程不安全,效率高;底层使用Object[]
- LinkedList:对于频繁的插入,删除操作,使用此类效率更高;底层使用双向链表
- Vector:作为List接口的古老实现类;线程安全,效率低;底层使用Object[]
面试题:ArrayList,LinkedList,Vector三者的异同?
同:都实现了List接口,存储数据的特点是:有序,可重复数据;
不同:见上
ArrayList源码分析:
JDK7 情况下:
ArrayList list = new ArrayList(); //底层默认创建长度为10的Object[]数组elementData
list.add(123); // elementData[0] = new Integer(123);
当容量不够时,(默认情况下)扩容为原来的1.5倍,同时将原有数据复制到新数组中
建议使用带参的构造器: new ArrayList(int capacity) 指定数组大小JDK8 中ArrayList的变化:
new ArrayList( ); //底层Object[] elementData初始化为 {},并没有创建长度为10的数组
list.add(123); //第一次调用add( ) 时,底层才创建长度为10的数组小结:
JDK7中的ArrayList的对象的创建类似于单例的饿汉式,而JDK8中的对象创建类似于懒汉式,延迟了数组的创建,节省内存。
LinkedList源码分析:
new LinkedList(); //内部声明了Node类型的 first和last 属性,默认值为null
list.add(123); // 创建一个Node对象,将123封装到Node中
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源码分析:
new Vector(); 底层默认创建长度为10的数组
(默认) 扩容为原来数组长度的2倍;
List接口常用方法:
增:add( Object obj )
删:remove ( int 下标) / remove ( Object 元素 )
改:set ( int index , Object ele )
查:get ( int index )
插:add( int index , Object ele)
长度:size( )
遍历:Iterator迭代器,增强for循环,普通for循环
Iterator iterator = list.iterator(); while(iterator.hasNext()){ sout(iterator.next()); }
面试题:List中,下标与元素 重复时,remov( ) 删除的是哪一个?
list.add(1); list.add(2); list.add(3); list.remove(2); //int类型 默认使用的是下标index,通过下标删除 list.remove(new Integer(2));//Object对象类型 使用的是删除元素(int不会自动装箱)
Set接口
set接口,存储无序的,不可重复的数据;
- HashSet:作为Set接口的主要实现类;线程不安全;可以存储null值
- LinkedHashSet:作为HashSet的子类;遍历其内部时可以按照添加的顺序遍历
- TreeSet:可以按照添加对象的指定属性,进行排序 (添加元素类型必须一样)
对于Set接口:(本质是调用的是HashMap)
无序性:不等于随机性,存储数据的地址是根据数据的哈希值存放,而不是按数组索引顺序
不可重复性:保证添加的元素按照equals()判断时,不能返回true;
Set接口中没有定义额外的方法,都是Collection接口中的方法
要求:向Set中添加的数据,其所在的类一定要重写hashCode() 和 equals();
重写的hashCode() 和 equals() 尽可能保持一致性:相等的对象必须具有相等的散列码
HashSet 添加元素的过程: (先hashCode() ,再equals() )
hashCode()
计算元素的哈希值,根据某种算法(散列函数)-->得到在元素在底层数组的存放位置
- 判断该位置没有数据,加入。
- 该位置有数据(一个或多个链表形式元素),但两元素的哈希值不一致,以链表的形式存放在该位置
- 该位置有数据,两元素的哈希值也一致,调用元素的
equals()
进行判断,返回false,添加至链表。链表形式的存放情况:(七上八下)
JDK7:加入元素 放入数组中,指向原链表
JDK8:加入的元素 添加到链表后面。HashSet底层:数组 + 链表
![]()
LinkedHashSet:
LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用(记录前后数据)
优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet
TreeSet:
向TreeSet中添加的数据,要求是相同类的对象
TreeSet底层使用 红黑树存储(顺序二叉树)
![]()
两种排序方式:自然排序(实现Comparable接口) 和 定制排序(Comparator)
自然排序:比较两对象是否相同,compareTo() 返回0,不再是equals()
定制排序:比较两对象是否相同,compare返回0,不再是equals()
public int compareTo(Object o){ if(o instanceOf myClass){//当前类 myClass myclass = (myClass)o; return this.XX.comparaTo(myclass.XX); //比较类的属性 }else{ thorw new RuntimeException("输入类型不匹配"); } } new Comparator(){ public int compare(Object o1,Object o2){ if(o1 instanceof myClass && o2 instanceof myClass){ myClass c1 = (myClass)o1; myClass c2 = (myClass)o2; //return Integer.compare(c1.age,c2.age); return 包装类.compare(c1.XXX,c2.XXX); } } }
小结:
去除List中的重复数据:
//注意,list中的数据 必须要重写equals,hashCode方法 public list getList(List list){ Hashset set = new HashSet(); set.addAll(list); return new ArrayList(set); }
面试题:关于HashSet的修改
HashSet set = new HashSet(); Person p1 = new Person(1001,"AA"); //Person对象已经重写了hashCode(),equals() Person p2 = new Person(1002,"BB"); set.add(p1); set.add(p2); sout(set); //p1(1001,AA),p2(1002,BB) p1.name = "CC"; //1.修改p1后,remove(p1), 此时索引的p1位置发生了改变,实际上没有remove掉p1 set.remove(p1); System.out.println(set); // p1(1001,CC),p2(1002,BB) //2.hashCode索引到该位置没有元素,可以直接添加 set.add(new Person(1001,"CC")); System.out.println(set); //p1(1001,CC),p2(1002,BB),(1001,CC) //3.hashCode索引到p1位置,进一步用equals()比较 不一致--->可以添加 set.add(new Person(1001,"AA")); System.out.println(set); //p1(1001,CC),P2(1002,BB),(1001,CC),(1001,AA)
9.3 Map接口
双列数据,存储key-value对的数据
- HashMap:Map的主要实现类;线程不安全,效率高;可以存储null的key和value
- LinkedHashMap:HashMap的子类,可以按照添加顺序实现遍历;利于进行频繁的遍历操作
- TreeMap:可以按照添加对象的指定属性(key),进行排序;底层使用红黑树
- Hashtable:作为古老的实现类;线程安全,效率低;不能存储null的key和value
- Properties:常用来处理配置文件,key和value都是String类型
Map结构:
key:无序的,不可重复的,使用Set存储所有key ---> key所在的类要重写 hashCode,equals
value:无序的,可重复的,使用Collection存储所有的value ---> value所在类要重写equals
entry:无序的,不可重复的,使用Set存储;(一个键值对key-value构成一个Entry对象)
Map的底层实现原理:
- JDK7中:
Hash map = snew HashMap(); //底层创建一个长度为16的Entry类型数组 Entry[] table;
添加元素:
调用key的hashCode()计算哈希值,(散列函数)算法 --> 得到在Entry数组的存放位置
- 存放位置为空,直接加入;
- 该位置有数据(一个或多个链表形式元素),比较key的哈希值,哈希值不重复,添加(链表添加--七上八下)
- 哈希值一致,调用key的equals()方法,比较key的值,equals返回false,添加
- equals返回true,key值重复, 替换value
扩容问题:数组长度> size()*加载因子(默认0.75)时,(默认为) 扩容为原来的2倍,并将原有数据赋值过来;
- JDK8 相较于JDK7的不同:
在new HashMap() 时并没有创建数组,而是在put()第一个元素的时候创建一个长度为16的数组;
底层数组的类型是 Node[] ,而非Entry[]
JDK7底层结构只有:数组 + 链表;
JDK8底层结构:数组+链表+红黑树:
当数组的某一索引位置上的链表长度 >8 且 当前数组长度 >64 时,此索引位置上的数据改用红黑树存储。
链表长度 > 8 ,且 数组长度 < 64 时,对数组扩容源码中的重要常量:
DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
DEFAULT_LOAD_FACTOR :HashMap的默认加载因子,0.75
threshold:扩容的临界值, = 容量*加载因子:16 * 0.75 = 12
TREEIFY_THRESHOLD :Bucket中链表长度大于该默认值,转化为红黑树:8
UNTREEIFY_THRESHOLD :Bucket中红黑树存储的Node小于该默认值,转化为链表
MIN_TREEIFY_CAPACITY :桶中的Node被树化时最小的hash表容量。64
LinkedHashMap底层(了解):
本质上还是调用HashMap,只是加了两个(befor,after)属性
static class Entry<K,V> extends HashMap.Node<K,V> { Entry<K,V> before, after; Entry(int hash, K key, V value, Node<K,V> next) { super(hash, key, value, next); } } //HashMap的内部类 static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; }
TreeMap:
TreeMap中的key,必须要是相同类的对象;
与TreeSet一样,底层采用 红黑树结构,有两种排序方式(按照key进行排序)
自然排序:比较两对象是否相同,compareTo() 返回0,不再是equals()
定制排序:比较两对象是否相同,compare返回0,不再是equals()
常用方法
增加/修改:put
删除:remove
查询:get
长度:size
遍历:keySet / values / entrySet
添加 、 删除、修改操作 :
Object put (Object key,Object value): 将指定key-value添加到(或修改)当前map对象中
void putAll (Map m): 将m中的所有key-value对存放到当前map中
Object remove (Object key): 移除指定key的key-value对,并返回value
void clear(): 清空当前map(内部)中的所有数据(并不是map=null)
元素 查询的操作:
Object get (Object key):获取指定key对应的value
boolean containsKey(Object key):是否包含指定的key
boolean containsValue(Object value):是否包含指定的value(遇到找到的第一个就直接返回)
int size():返回map中key-value对的个数
boolean isEmpty():判断当前map是否为空
boolean equals(Object obj):判断当前map和参数对象obj是否相等
元视图操作的方法:(遍历)
Set keySet():返回所有key构成的Set集合
Collection values():返回所有value构成的Collection集合
Set entrySet():返回所有key-value对构成的Set集合获取到Conllection集合后,就可以使用 itrator迭代器、增强for循环 进行遍历
//遍历方式一: Set set = map.keySet(); //获取key(Set)集 Iterator iterator = set.iterator(); while(iterator.hasNext(){ Object k = iterator.next; Object v = set.get(k); //可以通过get() 得到value sout(k + v); } //遍历方式二: Set entrySet = map.entrySet(); //获取Entry(Set)集 Iterator it2 = entrySet.iterator(); while(it2.hasNext()){ Map.Enty entry = it2.next(); //获取Entry元素 entry.getKey(); //通过Entry得到 key,value entry.getValue(); }
Properties类
-
Properties 类 是Hashtable的子类,常用来处理配置文件 (.properties文件)
-
由于属性文件里的key,value都是字符串类型,key和value都是String类型
-
常用方法:setProperty(String key,String value) , getProperty(String key)
Properties properties = new Properties();
//若出现中文乱码,需要设置编辑器 编码为utf-8
FileInputStream fis = new FileInputStream("db.properties");
properties.load(fis); //加载流对于的配置文件
//注意配置文件中不能有多余空格
String name = properties.getProperty("name");
String pwd = properties.getProperty("pwd");
System.out.println(name + pwd);
9.4 Collections工具类
- Collections 是一个操纵 Set,List,Map等集合的工具类
排序操作:为 (均为static 方法)
reverse (List):反转 List 中元素的顺序
shuffle (List):对 List 集合元素进行随机排序
sort (List):根据元素的自然顺序对指定 List 集合元素按升序排序
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
查找、替换
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object):返回指定集合中指定元素的出现次数
void copy(List dest,List src):将src中的内容复制到dest中
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List 对象的所有旧值
//对于cope()方法: //创建的dest 需要有 >src的指定空间大小 List dest = new ArrayList(); //dest的size不够,会报异常 X List dest = Arrays.asList(new Object[list.size()]); //标准写法 √ Collections.copy(dest,list);
Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix