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:
image-20220329170741975

Map:

image-20220329170841913

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()计算元素的哈希值,根据某种算法(散列函数)-->得到在元素在底层数组的存放位置

  1. 判断该位置没有数据,加入。
  2. 该位置有数据(一个或多个链表形式元素),但两元素的哈希值不一致,以链表的形式存放在该位置
  3. 该位置有数据,两元素的哈希值也一致,调用元素的 equals() 进行判断,返回false,添加至链表。

链表形式的存放情况:(七上八下)
 JDK7:加入元素 放入数组中,指向原链表
 JDK8:加入的元素 添加到链表后面。

HashSet底层:数组 + 链表

image-20220402143740505

LinkedHashSet:

LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用(记录前后数据)

优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet


TreeSet:

向TreeSet中添加的数据,要求是相同类的对象

TreeSet底层使用 红黑树存储(顺序二叉树)

image-20220402173836272

两种排序方式:自然排序(实现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对象)
image-20220402165312464

Map的底层实现原理:

  • JDK7中:

 Hash map = snew HashMap(); //底层创建一个长度为16Entry类型数组 Entry[] table;

 添加元素:

调用key的hashCode()计算哈希值,(散列函数)算法 --> 得到在Entry数组的存放位置

  1. 存放位置为空,直接加入;
  2. 该位置有数据(一个或多个链表形式元素),比较key的哈希值,哈希值不重复,添加(链表添加--七上八下)
  3. 哈希值一致,调用key的equals()方法,比较key的值,equals返回false,添加
  4. 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() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题

posted @   simp1e1  阅读(39)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示