Java集合概述——Collection、Map


Java集合是一种非常重要的数据存储容器,数组也能够存储数据,但使用数组存储对象时有一些弊端:

  • 数组初始化以后,长度就不可变了,不便于扩展
  • 数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高。 同时无法直接获取存储元素的个数
  • 数组存储数据的特点单一 (只能存放有序、可重复数据)

集合可以动态地存储多个对象的引用,并且提供多个操作方法,便于对集合内数据进行操作。
Java 集合可分为 Collection 和 Map 两种体系。

1. Collection接口

Collection接口可用于存放单列数据,JDK不提供此接口的任何直接实现。而是提供更具体的子接口——List接口(存储有序、可重复的数据)、Set接口(存储无序、不可重复的数据)。
在 Java5 之前,Java 集合会丢失容器中所有对象的数据类型,把所有对象都 当成 Object 类型处理;从 JDK 5.0 增加了泛型以后,Java 集合可以记住容器中对象的数据类型。

1.1 Collection接口方法

● add(Object obj)
● addAll(Collection coll):添加
● int size():获取有效元素个数
● void clear():清空集合
● boolean isEmpty():是否为空集合
● boolean contains(Object obj):是否包含某个元素
● boolean containsAll(Collection c):是否包含c集合元素
● boolean remove(Object obj):删除指定元素(仅删除找到的第一个元素)
● boolean removeAll(Collection coll):取两个集合的差集
● boolean retainAll(Collection c):取两个集合的交集,存至当集合中
● boolean equals(Object obj):判断是否相等
● Object[] toArray():转成对象数组
● iterator():返回迭代器对象,用于集合遍历

1.2 遍历集合元素

遍历集合元素有两种方法,一是使用Iterator迭代器接口,二是使用增强for循环(也可以遍历数组)。

1.2.1 Iterator迭代器

Iterator对象称为迭代器是一种设计模式,用于遍历集合中的元素,在不暴露元素内部细节的情况下,可以提供某些方法去访问集合中的各个元素。
Iterator 仅用于遍历集合,Iterator 本身并不提供承装对象的能力。如果需要创建 Iterator 对象,则必须有一个被迭代的集合。
集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合 的第一个元素之前。

Iterator iterator = ts.iterator();
while (iterator.hasNext()){
    System.out.println(iterator.next());
}

hasNext()方法判断是否还有下一个元素,next()方法将指针下移,并返回下移后集合位置上的元素。
此外, Iterator可以删除集合的元素,但是是遍历过程中通过迭代器对象的remove方法,不是集合对象的remove方法。如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法, 再调用remove都会报IllegalStateException异常。

1.2.2 增强for循环

Java 5.0 提供了 foreach 循环迭代访问 Collection和数组。 由于其底层还是调用Iterator完成操作的,因此它也不需获取Collection或数组的长度,无需使用索引访问元素。
其结构为:for (要遍历的元素类型 遍历后自定义元素名称 : 要遍历的结构名称) {}

String[] str = new String[5]; 
for (String myStr : str) {  
	System.out.println(myStr);
} 

1.3 List接口

与数组类似,List集合类中存储的元素有序、可重复,每个元素都有其对应的顺序索引。由于数组的局限性,所以我们常用List代替数组。List的常用实现类有:ArrayList、LinkedList和Vector。

1.3.1 ArrayList

ArrayList 是 List 接口的典型实现类,是线程不安全的,因此执行效率高。ArrayList是对象引用的一个”变长”数组,在JDK7.0之前,ArrayList类似于饿汉式,直接创建一个初始长度为10的数组;在JDK8.0之后,ArrayList类似于懒汉式,一开始创建一个长度为0的数组,当添加第一个元 素时再创建一个始容量为10的数组。

1.3.2 LinkedList

LinkedList使用了双向链表,内部没有声明数组,而是定义了Node类型的first和last,用于记录首末元素。同时,定义内部类Node,作为LinkedList中保存数据的基本结构,Node中定义了三个变量——element、prev、next分别用于记录元素数据、前一个元素的位置以及下一个元素的位置。
由于使用了双向链表结构,因此对于需要频繁的进行插入、删除操作的情况,使用LinkedList效率较高,并且是线程不安全的。

1.3.3 Vector

Vector是线程安全的,因此执行速度较慢。

异同点ArrayListLinkedListVector
线程安全不安全不安全安全
底层存储数组双向链表数组
扩容机制扩容1.5倍链表不需扩容扩容2倍

1.4 Set接口

Set接口是Collection的子接口,set接口没有提供额外的方法,用于存储无序、不可重复的数据。

1.4.1 HashSet

HashSet 是 Set 接口的典型实现,是线程不安全的,不能保证元素的排列顺序,其中的集合元素可以为null。
HashSet 集合判断两个元素相等的标准:两个对象通过 hashCode() 方法比较相 等,并且两个对象的 equals() 方法返回值也相等。因此对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Object obj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。
当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法 来得到该对象的 hashCode 值,然后根据 hashCode 值,通过某种散列函数决定该对象 在 HashSet 底层数组中的存储位置。如果两个元素的hashCode()值相等,会再继续调用equals方法,如果equals方法结果 为true,添加失败;如果为false,那么会保存该元素,但是该数组的位置已经有元素了, 那么会在该位置上通过链表的方式继续链接。
底层也是数组,初始容量为16,当如果使用率超过0.75,(16*0.75=12) 就会扩大容量为原来的2倍。

1.4.1.1 LinkedHashSet

LinkedHashSet 是 HashSet 的子类,它根据元素的 hashCode 值来决定元素的存储位置, 但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能。

1.4.2 TreeSet

TreeSet 是 SortedSet 接口的实现类,TreeSet 可以确保集合元素处于排序状态,其有两种排序方法——自然排序、定制排序。需要注意的是:因为只有相同类的两个实例才会比较大小,所以向 TreeSet 中添加的应该是同 一个类的对象。 TreeSet底层使用红黑树结构存储数据,其特点是数据存储有序,因此查询速度比List快。
自然排序:
默认情况下,TreeSet 采用自然排序,会调用集合元素的 compareTo(Object obj) 方法来比较元 素之间的大小关系,然后将集合元素按升序(默认情况)排列 。
如果试图把一个对象添加到 TreeSet 时,则该对象的类必须实现 Comparable 接口,并且在该类内部实现compareTo(Object obj) 方法。对于 TreeSet 集合而言,它判断两个对象是否相等的唯一标准是:两个对象通 过 compareTo(Object obj) 方法。

定制排序:
如果元素所属的类没 有实现Comparable接口,或不希望按照升序(默认情况)的方式排列元素或希望按照 其它属性大小进行排序,则考虑使用定制排序。定制排序,通过Comparator接口来实现。需要将实现Comparator接口的实例作为形参传递给TreeSet的构 造器,并要重写compare(T o1,T o2)方法(如果方法返回正整数,则表 示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2)。

2. Map接口

Map与Collection并列存在,用于保存具有映射关系的数据:key-value ,其中key 用Set来存放,不允许重复; value 用Collection存储,可以重复,但key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到 唯一确定的 value 。

2.1 Map常用方法

🔺 添加、删除、修改操作:
● 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中的所有数据
🔺 元素查询的操作:
● 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集合

2.2 HashMap

允许使用null键和null值。
所有的key构成的集合是Set:无序的、不可重复的。判断两个 key 相等的标准是:两个 key 通过 equals() 方法返回 true,同时hashCode 值也相等,因此key所在的类要重写: equals()和hashCode() 。
所有的value构成的集合是Collection:无序的、可以重复的。判断两个 value相等的标准是:两个 value 通过 equals() 方法返回 true。因此value所在的类需要重写:equals()。
所有的entry(key-value)构成的集合是Set:无序的、不可重复的 。

2.2.1 HashMap存储结构(JDK8.0之前)

HashMap的内部存储结构其实是数组和链表的结合。当实例化一个HashMap时, 系统会创建一个长度为Capacity的Entry数组,这个数组中可以存放元素的位置我们称之为“桶”(bucket),每个bucket中存储一个元素,即一个Entry对象,但每一个Entry对象可以带一个引 用变量,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Entry链。 而且新添加的元素作为链表的head。

添加元素的过程:
首先计算要添加元素entry1中key的哈希值,此哈希值经过处理以后,得到在底层Entry[]数 组中要存储的位置i。如果位置i上没有元素,则entry1直接添加成功。如果位置i上 已经存在entry2(或还有链表存在的entry3,entry4),则需要通过循环的方法,依次 比较entry1中key和其他的entry。如果彼此hash值不同,则直接添加成功。如果 hash值相同,继续比较二者是否equals。如果返回值为true,则使用entry1的value 去替换equals为true的entry的value。如果遍历一遍以后,发现所有的equals返回都 为false,则entry1仍可添加成功。entry1指向原有的entry元素。

扩容:
当HashMap中的元素个数超过数组大小*loadFactor(默认为0.75)时 , 就会进行数组扩容 ,扩大1倍。然后重新计算每个元素在数组中的位置, 这是一个非常消耗性能的操作。

2.2.2 HashMap存储结构(JDK8.0之后)

JDK8.0之后,HashMap的内部存储结构其实是数组+链表+树的结合。当实例化一个 HashMap时,会初始化initialCapacity和loadFactor,先不创建数组,而是在put第一对映射关系时,系统会创建一个长度为initialCapacity的Node数组。

每个bucket中存储一个元素,即一个Node对象,但每一个Node对象可以带 一个引用变量next,用于指向下一个元素,因此,在一个桶中,就有可能 生成一个Node链。也可能是一个一个TreeNode对象,每一个TreeNode对象 可以有两个叶子结点left和right,因此,在一个桶中,就有可能生成一个 TreeNode树。而新添加的元素作为链表的last,或树的叶子结点。

扩容和树形化:

JDK8.0的扩容机制与7.0大致一样。

当HashMap中的其中一个链的对象个数如果达到了8个,此时如果capacity没有达到64,那么HashMap会先扩容解决,如果已经达到了64,那么这个链会变成 树,结点类型由Node变成TreeNode类型。如果当映射关系被移除后, 下次resize方法时判断树的结点个数低于6个,也会把树再转为链表。

2.2.3 LinkedHashMap

LinkedHashMap 是 HashMap 的子类。在HashMap存储结构的基础上,使用了一对双向链表来记录添加元素的顺序,与LinkedHashSet类似,LinkedHashMap 可以维护 Map 的迭代 顺序:迭代顺序与 Key-Value 对的插入顺序一致

2.3 TreeMap

TreeMap 可以保证所有的 Key-Value 对处于有序状态。 底层使用红黑树结构存储数据 ,通过给Key实现Comparable接口和创建TreeMap时传入Comparator 对象的方式分别实现元素的自然排序和定制排序。具体实现过程与TreeSet类似。

2.4 Hashtable

Hashtable是个古老的 Map 实现类,JDK1.0就提供了。不同于HashMap, Hashtable是线程安全的。 Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构,查询 速度快,很多情况下可以互用。 与HashMap不同,Hashtable 不允许使用 null 作为 key 和 value 。

2.4.1 Properties

Properties 类是 Hashtable 的子类,该对象用于处理属性文件, Properties 里的 key 和 value 都是字符串类型。

3. Collections工具类

与操作数组的工具类Arrays类似,操作集合也有一个工具类Collections。

🔺排序操作:
● 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 对象的所有旧值

集合相关的基础内容就介绍到这里,欢迎批评指正!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值