第 21 章 Java常用的集合类(随笔)

 1.Java 集合类介绍

Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射。Collection 接口又有 3 种子类型,List、Set 和 Queue,再下面是一些抽象类,最后是具体实现类,常用的有 ArrayListLinkedListHashSet、LinkedHashSet、HashMap、LinkedHashMap 等等。

集合框架是一个用来代表和操纵集合的统一架构。所有的集合框架都包含如下内容:

  • 接口:是代表集合的抽象数据类型。例如 Collection、List、Set、Map 等。之所以定义多个接口,是为了以不同的方式操作集合对象

  • 实现(类):是集合接口的具体实现。从本质上讲,它们是可重复使用的数据结构,例如:ArrayList、LinkedList、HashSet、HashMap。

  • 算法:是实现集合接口的对象里的方法执行的一些有用的计算,例如:搜索和排序,这些算法实现了多态,那是因为相同的方法可以在相似的接口上有着不同的实现。

2. 常用的 Java集合 --List集合

List接口继承了Collection接口,可使用Collection中的所有方法,此外List接口还定义了两个重要的方法,get和set。

get(int index):获得指定索引位置的元素

set(int index, Object obj):将集合中指定索引位置的对象修改为指定对象。

List接口元素是有序的,且允许集合中的元素重复。

1)List接口的实现类

List接口常用的实现类有ArrayList和LinkedList。

ArrayList:ArrayList类实现了可变的数组,可根据索引快速访问集合中的元素,但向指定索引位置插入及删除对象速度较慢。

LinkedList:LinkedList类采用链表结构保存对象。这种结构优点是插入和删除对象效率高,但对于随机访问集合中的对象效率较低。

链表并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的地址。链表可分为单向链表和双向链表。

一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的链接。

一个双向链表有三个整数值: 数值、向后的节点链接、向前的节点链接。

与 ArrayList 相比,LinkedList 的增加和删除的操作效率更高,而查找和修改的操作效率较低。

因此总结一下:

以下情况使用 ArrayList :

  • 频繁访问列表中的某一个元素。
  • 只需要在列表末尾进行添加和删除元素操作。

以下情况使用 LinkedList :

  • 你需要通过循环迭代来访问列表中的某些元素。
  • 需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作。

2)实例化

ArrayList和LinkedList实例化:

List<E> list=new ArrayList<>();

List<E> list2=new LinkedList<>();

来看代码例子:

import java.util.*;
import java.util.List;

public class Muster{
    public static void main(String args[]) {
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        int i = (int)(Math.random()*list.size());
        System.out.println("随机获取数组中的元素:" +list.get(i));
        list.remove(2);
        System.out.println("将索引2的元素移除后,数组中剩余的元素是: ");
        for (String j : list) {
            System.out.println(j);
        }

        list.set(2,"e");
        System.out.println("插入元素后的数组:" +list);
    }
}

结果:
随机获取数组中的元素:b
将索引2的元素移除后,数组中剩余的元素是: 
a
b
d
插入元素后的数组:[a, b, e]

3. 常用的 Java集合 --Set集合

Set集合不允许存储重复元素,且没有索引,若要查找Set集合中的元素需要遍历集合,Set集合常用的方法如下:

public interface Set<E> extends Collection<E> {

    A:添加功能
    boolean add(E e);
    boolean addAll(Collection<? extends E> c);

    B:删除功能
    boolean remove(Object o);
    boolean removeAll(Collection<?> c);
    void clear();

    C:长度功能
    int size();

    D:判断功能
    boolean isEmpty();
    boolean contains(Object o);
    boolean containsAll(Collection<?> c);
    boolean retainAll(Collection<?> c); 

    E:获取Set集合的迭代器:
    Iterator<E> iterator();

    F:把集合转换成数组
    Object[] toArray();
    <T> T[] toArray(T[] a);
    
    //判断元素是否重复,为子类提高重写方法
    boolean equals(Object o);
    int hashCode();
}

Set接口常用的实现类主要有HashSet类和TreeSet类,这里重点讲下HashSet,先了解下HashSet的几个重载的构造方法

private transient HashMap<E,Object> map;
//默认构造器
public HashSet() {
    map = new HashMap<>();
}
//将传入的集合添加到HashSet的构造器
public HashSet(Collection<? extends E> c) {
    map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
    addAll(c);
}
//明确初始容量和装载因子的构造器
public HashSet(int initialCapacity, float loadFactor) {
    map = new HashMap<>(initialCapacity, loadFactor);
}
//仅明确初始容量的构造器(装载因子默认0.75)
public HashSet(int initialCapacity) {
    map = new HashMap<>(initialCapacity);
}

通过上述代码可以看出HashSet的底层是通过HashMap实现的,HashMap实现了Set接口,同时还实现了序列化和可克隆化,而集合(Set)是不允许重复值的,所以HashSet是一个没有重复元素的集合,但不保证集合的迭代顺序,因此随着时间其集合内部元素的顺序可能会改变。同时由于HashSet是基于HashMap来实现的,所以允许空值,不是线程安全的。
HashMap的数据存储是通过数组+链表/红黑树实现的,存储数据首先通过hash函数计算在数组中存储的位置,如果该位置已经有值了,判断key是否相同,相同则覆盖,不相同则放到元素对应的链表中,如果链表长度大于8,就转化为红黑树,如果HashMap的存储容量不够,则需扩容,默认扩容因子为0.75,这里可以了解下HashMap的底层原理,这样可以更加深入理解HashSet。

4. 常用的 Java集合 --Map集合

Map集合是以键值对的方式存储元素,所有Map集合的Key是无序不可重复的,key和value都是引用数据类型,存的都是内存的地址。

4.1. Map集合的特点

1) key和value可以是任意的引用类型的数据
2) 一个映射不能包含重复的键(map集合的key值不能重复)
3) 每个键最多可以映射到一个值(每个键值对只有一个key值,一个value值)
4) 同样的值可以对应多个不同的键(不同的键值对可以拥有相同的value值)

4.2. Map集合的常用方法

1) get

方法全称为:Object get(Object key),可以查询map集合中key对应的value值,返回value值,使用方法如下:

public class Muster {
public static void main(String[] args){
        Map map = new HashMap();
        map.put(1,"海鸥");
        map.put(2,"乌鸦");
        map.put(3,"大鹏");
        Object s = map.get(3);
        System.out.println(s);
        }
}

结果:
大鹏

2) put

方法全称为:put(Object key,Object value),给map集合中添加键值对,使用方法如下:

public class Muster {
public static void main(String[] args){
        Map map = new HashMap();
        map.put(1,"海鸥");
        map.put(2,"乌鸦");
        map.put(3,"大鹏");
        System.out.println("map集合中的元素:"+map);
    }
}

结果:
map集合中的元素:{1=海鸥, 2=乌鸦, 3=大鹏}

3) map集合的遍历

map集合有两种常用遍历方法,第一种常用方法是根据键找值,借助Set keySet()遍历;第二种是借助迭代器,根据键值对对象找键和值,来看代码例子:

public class Muster {
public static void main(String[] args){
        Map map = new HashMap();
        map.put(1,"海鸥");
        map.put(2,"乌鸦");
        map.put(3,"大鹏");
        Object s = map.get(3);
//        System.out.println(s);
        System.out.println("map集合中的元素:"+map);
  /*
    借助Set<K> keySet()遍历
    1、获取Map集合中所有映射的键的Set集合
    2、遍历键的集合,根据每一个键获取对应的值
 */
        //方式一,用增强for循环
//        Set<Integer> keys = map.keySet();
//        for( Integer m : keys){
//                Integer key = m;
//                Object value = map.get(m);
//                System.out.println(key+"="+value);
//        }

        //方式二,用迭代器
        //为Set集合创建一个迭代器Iterator
        Iterator<Integer> it = map.keySet().iterator();
        while(it.hasNext()){
                Integer key = it.next();
                System.out.println(key+"="+map.get(key));
        }
     }
}

结果:
map集合中的元素:{1=海鸥, 2=乌鸦, 3=大鹏}
1=海鸥
2=乌鸦
3=大鹏

其它的常用方法例如clear,containsKey,containsValue,remove等方法就不一一介绍了。

4.3. Map集合的实现类

Map集合的实现类主要有HashMap、HashTable、TreeMap 、 ConcurrentHashMap、LinkedHashMap、weakHashMap等等。这里重点讲一下HashMap。
JDK1.8版本中HashMap底层存储结构是数组+链表+红黑树形式,实现Map.Entry接口。它有如下特点:
1)HashMap是 Map 接口使用频率最高的实现类。
2)key和value值允许为null,与HashSet一样,不保证映射的顺序。
3)所有的key构成的集合Set都是无序、不可重复的。所以,key所在的类要重写:equals()和hashCode()
4)所有的value构成的集合Collection是无序的、但可以重复的。所以,value所在的类要重写:equals()
5)一个key-value构成一个entry,因此所有的entry构成的集合也是无序的、不可重复的
6)HashMap 判断两个 key 相等的标准是:两个 key 通过 equals() 方法返回 true,hashCode 值也相等。
7)HashMap 判断两个 value相等的标准是:两个 value 通过 equals() 方法返回 true。
HashMap的存储结构
JDK 7及以前版本:HashMap是数组+链表结构(即为链地址法)
JDK 8版本发布以后:HashMap是数组+链表+红黑树实现。


HashMap的扩容

当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的

长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容,而在

HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算

其在新数组中的位置,并放进去,这就是resize。

HashMap进行扩容的时机:

HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数(size)*填充因子(loadFactor) 时就会进行数组扩容。

loadFactor:
loadFactor的默认 值 (DEFAULT_LOAD_FACTOR)为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小(DEFAULT_INITIAL_CAPACITY)为16,那么当HashMap中元素个数超过16x0.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把进行扩容。

容量翻倍:
HashMap的扩容是扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。

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

总结:

JDK1.8相较于之前的变化:

HashMap map = new HashMap();//默认情况下,先不创建长度为16的数组

当首次调用map.put()时,再创建长度为16的数组

数组为Node类型,在jdk7中称为Entry类型

形成链表结构时,新添加的key-value对在链表的尾部(七上八下)

当数组指定索引位置的链表长度>8时,且map中的数组的长度> 64时,此索引位置上的所有key-value对使用红黑树进行存储。

面试题:

负载因子值的大小,对HashMap有什么影响?

负载因子的大小决定了HashMap的数据密度。

负载因子越大密度越大,发生碰撞的几率越高,数组中的链表越容易长,造成查询或插入时的比较次数增多,性能会下降。

负载因子越小,就越容易触发扩容,数据密度也越小,意味着发生碰撞的几率越小,数组中的链表也就越短,查询和插入时比较的次数也越小,性能会更高。但是会浪费一定的内容空间。而且经常扩容也会影响性能,建议初始化预设大一点的空间。

按照其他语言的参考及研究经验,会考虑将负载因子设置为0.7~0.75,此时平均检索长度接近于常数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java常用集合类包括 List、Set、Map、Queue 等,它们分别具有以下特点: 1. List:List 接口是有序的集合,可以根据索引位置访问元素。常用的实现类有 ArrayList 和 LinkedList。ArrayList 实现了可变大小的数组,查询效率高,修改效率较低;LinkedList 实现了链表,查询效率较低,插入和删除效率高。 2. Set:Set 接口是无序的集合,不允许有重复元素。常用的实现类有 HashSet 和 TreeSet。HashSet 通过哈希表实现,插入和查询效率较高;TreeSet 通过红黑树实现,可以按照元素的自然顺序或者自定义比较器进行排序。 3. Map:Map 接口是键值对的集合,每个键最多只能映射到一个值。常用的实现类有 HashMap 和 TreeMap。HashMap 通过哈希表实现,插入和查询效率较高;TreeMap 通过红黑树实现,可以按照键的自然顺序或者自定义比较器进行排序。 4. Queue:Queue 接口是一种先进先出(FIFO)的集合,常用的实现类有 LinkedList 和 PriorityQueue。LinkedList 实现了双端队列,可以在队头和队尾进行插入和删除操作;PriorityQueue 实现了优先队列,可以根据元素的自然顺序或者自定义比较器进行排序。 以上集合类都是线程不安全的,如果需要使用线程安全的集合类,可以使用对应的线程安全类,如 Vector、Hashtable、ConcurrentHashMap、ConcurrentLinkedQueue 等。 总的来说,Java集合类丰富、易用,可以满足各种不同的需求,是 Java 编程中不可或缺的一部分。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值