【Java集合框架 一】Java集合类概述

简介: 【Java集合框架 一】Java集合类概述

Java中的集合主要分为以下集合类:Map、List、Set、Queue和JUC包下供多线程环境下使用的以上几种集合类,JUC包下的相关类我在另一篇JUC下集合类相关的Blog详细介绍过,本篇Blog重点介绍Java单线程环境下的几种集合类。我们知道数组是很常用的一种的数据结构,我们用它可以满足很多的功能,但有时我们会遇到如下这样的问题:

  • 我们需要该容器的长度是不确定的,在添加数据时也不确定需要添加多少。
  • 我们需要它能自动排序,添加数据进行是有序的
  • 我们需要存储以键值对方式存在的数据。

这些数组类都满足不了,于是我们使用与数组类似的数据结构——集合类,集合类在Java中有很重要的意义,保存临时数据,管理对象,泛型,Web框架等,很多都大量用到了集合类。以下是一张集合类图:

Collection接口是集合类的根接口,Java中没有提供这个接口的直接的实现类。但是却让其被继承产生了三个接口,就是Set、List和Queue。我们重点关注Set和List这两个集合类。Map是Java.util包中的另一个接口,它和Collection接口没有关系,是相互独立的,但是都属于集合类的一部分

List集合

实现List接口的类有三个,ArrayList&Vector&LinkedList,它们都有插入元素有序、可重复的特征

ArrayList集合类

ArrayList集合的类继承关系如下图所示

基本使用方法如下:

public static void main(String[] args) {
        /* 新建一个ArrayList */
        ArrayList<String> list = new ArrayList<>();
        System.out.println("初始化大小:" + list.size());
        /* 添加元素 */
        list.add("tml1");
        list.add("tml2");
        list.add("tml3");
        list.add("tml4");
        System.out.println("当前容量:" + list.size());
        /* 将ArrayList的大小和实际所含元素的大小设置一致 */
        list.trimToSize();
        System.out.println("遍历--------------");
        /* 遍历 */
        for (String string : list) {
            System.out.println(string);
        }
        System.out.println("插入后遍历-----------");
        /* 在指定位置插入元素 */
        list.add(2, "tml5");
        for (String string : list) {
            System.out.println(string);
        }
        System.out.println("清空后遍历-----------");
        /* 清空list */
        list.clear();
        /* 遍历 */
        for (String string : list) {
            System.out.println(string);
        }
    }

打印结果如下:

初始化大小:0
当前容量:4
遍历--------------
tml1
tml2
tml3
tml4
插入后遍历-----------
tml1
tml2
tml5
tml3
tml4
清空后遍历-----------

LinkedList集合类

LinkedList类的继承关系如下图所示,可以看到其实现了队列Queue的接口。

Vector集合类

Vector集合的继承关系如下图所示:

List下集合类对比

ArrayList和Vector的特征比较

  1. vector是线程同步的,所以它也是线程安全的,而arraylist线程不安全。如果不考虑到线程安全因素,一般用arraylist效率比较高。
  2. 如果集合中的元素的数目大于目前集合数组的长度时,vector增长率为目前数组长度的100%,而arraylist增长率为目前数组长度的50%。如果在集合中使用数据量比较大的数据,用vector有一定的优势
  3. 如果查找一个指定位置的数据,vector和arraylist使用的时间是相同的,如果频繁的访问数据,这个时候使用vector和arraylist都可以。而如果移动一个指定位置会导致后面的元素都发生移动,这个时候就应该考虑到使用linkedlist,因为指针结构,移动一个指定位置的数据时其它元素不需要移动。

ArrayList和LinkedList的特征比较

  • 相同点:都实现了Collection接口
  • 不同点:ArrayList基于数组,具有较高的查询速度,而LinkedList基于双向循环链表,具有较快的添加或者删除的速度,二者的区别,其实就是数组和列链表的区别。

ArrayList&Vector&LinkedList性能比较

  • ArrayList 和Vector是采用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,都允许直接序号索引元素,但是插入数据要涉及到数组元素移动等内存操作,所以索引数据快,插入数据慢
  • Vector由于使用了synchronized方法(线程安全)所以性能上比ArrayList要差
  • LinkedList使用双向链表实现存储,按序号索引数据需要进行向前或向后遍历,查询性能较差,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快

依据自己的需求选择合适的集合使用。

Set集合

实现Set接口的类有四个,HashSet&TreeSet&LinkedHashSet,它们都有插入元素无序、不可重复的特征,

HashSet集合类

HashSet内部使用Map保存数据,即将HashSet的数据作为Map的key值保存,这也是HashSet中元素不能重复的原因。而Map中保存key值前,会去判断当前Map中是否含有该key对象,内部是先通过key的hashCode,确定有相同的hashCode之后,再通过equals方法判断是否相同。HashSet集合的类继承关系如下图所示:

TreeSet集合类

TreeSet的继承关系实现如下图所示

LinkedHashSet集合类

LinkedHashSet的继承关系实现如下图所示

Set下集合类对比

HashSet&TreeSet&LinkedHashSet比较三者的联系和区别如下:

  • HashSet,为快速查找而设计的Set。存入HashSet的对象必须实现hashCode()和equals(),底层由HashMap实现。如果覆盖了equals方法,需要重写hashCode方法
  • TreeSet是 提供排序功能的Set,底层为树结构 。相比较HashSet其查询速度低,如果只是进行元素的查询,我们一般使用HashSet
  • LinkedHashSet,具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序),于是在使用迭代器遍历Set时,结果会按元素插入的次序显示,底层由LinkedHashMap实现

我们可以看的出,Set的很多实现方式都是通过组合的方式使用了Map,举个例子:

HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

Map集合

Map是Java.util包中的另一个接口,它和Collection接口没有关系,是相互独立的,但是都属于集合类的一部分。Map包含了key-value对。Map不能包含重复的key,但是可以包含相同的value,Map集合中存储的是键值对,键不能重复,值可以重复。根据键得到值,对map集合遍历时先得到键的set集合,对set集合进行遍历,得到相应的值。

HashMap集合类

在插入值的过程中HashMap可以插入null的key或value,插入的时候,检查是否已经存在相同的key,如果不存在,则直接插入,如果存在,则用新的value替换旧的value

HashMap有两种遍历方式,第一种方式:KeySet,第二种方式entrySet

KeySet遍历

将Map中所有的键存入到set集合中。因为set具备迭代器。所有可以迭代方式取出所有的键,再根据get方法。获取每一个键对应的值。 keySet迭代后只能通过get取key 。取到的结果会乱序,因为取得数据行主键时,使用了HashMap.keySet方法,而这个方法返回的Set结果,里面的数据是乱序排放的。

Map map = new HashMap();
map.put("key1","lisi1");
map.put("key2","lisi2");
map.put("key3","lisi3");
map.put("key4","lisi4");  
//获取迭代器
Iterator it = map.keySet().iterator();
//通过迭代器获取遍历set,然后取值
while(it.hasNext()){
   Object key = it.next();
   System.out.println(map.get(key));
}

返回结果为

lisi1
lisi2
lisi3
lisi4

entrySet遍历

返回此映射中包含的映射关系的 Set 视图(一个关系就是一个键-值对),就是把(key-value)作为一个整体一对一对地存放到Set集合当中的。Map.Entry表示映射关系。entrySet迭代后可以e.getKey,e.getValue两种方法来取key和value。返回的是Entry接口。

public static void main(String[] args) {
        Map map = new HashMap();
        map.put("key1","lisi1");
        map.put("key2","lisi2");
        map.put("key3","lisi3");
        map.put("key4","lisi4");
       //将map集合中的映射关系取出,存入到set集合
        Iterator it = map.entrySet().iterator();
        while(it.hasNext()){
            Map.Entry e =(Map.Entry) it.next();
            System.out.println("键"+e.getKey () + "的值为" + e.getValue());
        }
    }

返回结果为:

键key1的值为lisi1
键key2的值为lisi2
键key3的值为lisi3
键key4的值为lisi4

LinkedHashMap集合类

以下为LinkedHashMap集合类的继承关系图:

TreeMap集合类

以下为TreeMap集合类的继承关系图:

HashTable集合类

以下为HashTable集合类的继承关系图:

Map下集合类对比

HashMap和HashTable的联系和区别如下

  1. 二者都实现了Map接口,因此具有一系列Map接口提供的方法。
  2. 继承不同Hashtable extends Dictionary,而HashMap extends AbstractMap
  3. 同步级别不同,Hashtable 中的方法是同步的,而HashMap中的方法在缺省情况下是非同步的。在多线程并发的环境下,可以直接使用Hashtable,但是要使用HashMap的话就要自己增加同步处理了。
  • 使用Collections类的synchronizedMap方法包装一下。方法如下:public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) 返回由指定映射支持的同步(线程安全的)映射
  • 使用ConcurrentHashMap,它使用分段锁来保证线程安全
  1. 对key、value的null值要求不同
  • Hashtable中key和value都不允许出现null值
  • HashMap中,key可以为null,这样的键只有一个;可以有一个或多个key所对应的value为null。当get方法返回null值时,既可以表示 HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get方法来判断HashMap中是否存在某个键, 而应该用containsKey方法来判断
  1. 遍历方式的内部实现不同。Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式
  2. 哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值,而且用与代替求模
  3. 内部实现方式的数组的初始大小和扩容的方式不同,HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。
  4. Hashtable有contains方法而HashMap没有contains方法

这些就是一些比较突出的不同点,实际上他们在实现的过程中会有很多的不同,如初始化的大小、计算hash值的方式等等。毕竟这两个类包含了很多方法,有很重要的功能,综合推荐使用HashMap,因为它提供了比HashTable更多的方法,以及较高的效率,如果大家需要在多线程环境中使用,那么做一下同步即可。

HashMap和TreeMap的联系和区别如下

HashMap具有较高的速度(查询),TreeMap则提供了按照键进行排序的功能。

HashMap和LinkedHashMap的联系和区别如下

LinkedHashMap具有HashMap的查询速度,且内部使用链表维护元素的顺序(插入的次序),于是在使用迭代器遍历Map时,结果会按元素插入的次序显示

相关文章
|
8天前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
|
8天前
|
Java
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式。本文介绍了 Streams 的基本概念和使用方法,包括创建 Streams、中间操作和终端操作,并通过多个案例详细解析了过滤、映射、归并、排序、分组和并行处理等操作,帮助读者更好地理解和掌握这一重要特性。
17 2
|
8天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
13天前
|
存储 Java
判断一个元素是否在 Java 中的 Set 集合中
【10月更文挑战第30天】使用`contains()`方法可以方便快捷地判断一个元素是否在Java中的`Set`集合中,但对于自定义对象,需要注意重写`equals()`方法以确保正确的判断结果,同时根据具体的性能需求选择合适的`Set`实现类。
|
12天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
53 4
|
13天前
|
存储 Java 开发者
在 Java 中,如何遍历一个 Set 集合?
【10月更文挑战第30天】开发者可以根据具体的需求和代码风格选择合适的遍历方式。增强for循环简洁直观,适用于大多数简单的遍历场景;迭代器则更加灵活,可在遍历过程中进行更多复杂的操作;而Lambda表达式和`forEach`方法则提供了一种更简洁的函数式编程风格的遍历方式。
|
13天前
|
Java 开发者
|
12天前
|
存储 Java 开发者
Java中的集合框架深入解析
【10月更文挑战第32天】本文旨在为读者揭开Java集合框架的神秘面纱,通过深入浅出的方式介绍其内部结构与运作机制。我们将从集合框架的设计哲学出发,探讨其如何影响我们的编程实践,并配以代码示例,展示如何在真实场景中应用这些知识。无论你是Java新手还是资深开发者,这篇文章都将为你提供新的视角和实用技巧。
12 0
|
3月前
|
存储 安全 Java
【Java集合类面试二十五】、有哪些线程安全的List?
线程安全的List包括Vector、Collections.SynchronizedList和CopyOnWriteArrayList,其中CopyOnWriteArrayList通过复制底层数组实现写操作,提供了最优的线程安全性能。
|
3月前
|
Java
【Java集合类面试二十三】、List和Set有什么区别?
List和Set的主要区别在于List是一个有序且允许元素重复的集合,而Set是一个无序且元素不重复的集合。