Java入门到入土(集合篇)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: Java集合篇的基本介绍、功能以及解析

前言
   初出茅庐
      Collection集合特点
      Map集合特点
   牛刀小试
      List集合用法
         迭代器原理
      Set集合用法
      Map集合用法
   追根溯源
      List集合解析
      Set集合解析
      Map集合解析
结束语

前言

Java中用来批量存储数据的方式有两种。一种是数组,而另一种就是较为高级的集合。数组在Java前段基础部分讲过很多,这里先回顾一下,数组有几个很明显的特点,1、严格规定数据单一存放类型。2、长度在创建时就固定不可更改(在不使用额外空间下)。3、其底层数据存放地址是连续的。集合其实和数组很像,但又不完全是,两者还是有区别的,通俗的理解就是集合是数组的Plus版本。

初出茅庐

当我们要去学一个未知的东西时,首先并不是如何使用它,而是先要了解它,知道它是什么,它的继承了什么,又实现了什么,它的大体结构是什么样的。那么我画了一张结构树,这里包含了我们Java基础所必须要了解并掌握的几个常用的集合类。
屏幕截图 2023-07-08 185419.png
你也可以自己通过快捷键Ctrl+n进行类的查找,并且通过找到父类或实现类进行了解类的结构。
屏幕截图 2023-07-08 183331.png
屏幕截图 2023-07-08 182958.png
大体关系就如上图所说,一个Collection接口和一个Map接口包揽了几乎所有的常用集合,但是在这两个接口下又有不同的子类接口进行实现,更加准确的分类了不同功能和特性的集合实现类,譬如:Collection下就有List、Set和Queue这三个子接口,分别对应不同的功能的集合实现类,开发者更具需求进行逐个实现即可。

Collection接口特点

List接口旗下的集合里的元素是有序且可重复的,采用索引值进行存储和定位元素
Set接口旗下的集合里的元素是无序且不可重复的,采用hash码进行存储元素
Queue接口不常用,这里暂不做介绍

Map接口特点

map集合和Collection旗下的集合存储元素方式不同,map采用key-value,一键一值对应的方式来存储元素。key和value均为Object类型,灵活性较强

牛刀小试

List集合

先演示List接口下常用的ArrayList类的一些基本用法

        ArrayList<String> list = new ArrayList<>();

        //添加元素入集合
        list.add("第一个元素");
        list.add("第二个元素");

        //获取元素
        String s1 = list.get(0);
        String s2 = list.get(1);

        //遍历list集合内所有的元素
        //第一种常规根据长度进行逐一遍历的方式
        int size = list.size(); //获取list集合大小,就是元素个数
        for (int i = 0; i <size; i++) {     //使用索引逐一按顺序输出
            String s = list.get(i);
            System.out.println(s);
        }

        //第二种使用迭代器进行遍历输出
        Iterator<String> iterator = list.iterator();    //获取一个迭代器对象
        while (iterator.hasNext()){                     //判断集合中当前位置下一个是否存在元素
            String next = iterator.next();              //获取当前位置的下一个元素
            System.out.println(next);
        }

        //删除元素
        list.remove(0); //根据索引值删除元素
        list.remove("第二个元素");   //根据元素值删除元素
        System.out.println(list.size()); //验证是否删除成功

List集合的典型特征就是有序且可重复,所以可以通过索引来取值。遍历也有两种不同的方式,都可以达到相同的效果。一种通过集合的大小逐一遍历内部的元素。另一种通过创建对应的迭代器对象,逐一迭代元素。

迭代器原理

关于迭代器工作原理,由下图解释:

屏幕截图 2023-07-08 215342.png

Set集合

下面演示Set集合的其中一个常用的HashSet实现类的一些基本的用法

        HashSet<String> set = new HashSet<>();

        //向HashSet里存放值
        set.add("甲");
        set.add("乙");

        //由于Set是无序所以没有索引,只能用迭代器迭代出来
        Iterator<String> iterator1 = set.iterator();
        while (iterator1.hasNext()){
            String next = iterator1.next();
            System.out.println(next);
        }

        //移除值
        set.remove("甲");
        set.remove("乙");
        System.out.println(set.size());

Set由于是无序的,所以就不会像List集合那样存在索引可以取值,只能使用迭代器进行取值,因为其是通过每存一个元素就计算其的哈希码算出存放位置,所以也证明了其内部的元素不可能重复,因为相同的值哈希码必然相同。

map集合

下面是map集合,通过map接口下的一个常用的HashMap实现类进行基本的用法进行演示说明。

        Map<String,Integer> map = new HashMap<String,Integer>();

        //添加元素到map集合中
        map.put("第一个数",1);
        map.put("第二个数",2);

        //第一种方式
        Set<String> set1 = map.keySet();
        for (String s : set1){
            System.out.println("key="+s+",value="+map.get(s));
        }

        //第二种
        for (Map.Entry<String,Integer> entry: map.entrySet()) {
            System.out.println("key="+entry.getKey()+",value="+entry.getValue());
        }

        //第三种
        // 通过Map.entrySet使用iterator遍历key和value
        Iterator<Map.Entry<String, Integer>> entries = map.entrySet().iterator();
        while (entries.hasNext()) {
            Map.Entry<String, Integer> entry = entries.next();
            System.out.println(entry);
        }

        //移除元素
        map.remove("第一个数");
        map.remove("第二个数");
        System.out.println(map.size());

map集合比前面的两种集合略微复杂一些,但是功能也会更加强大和高效。map存放元素是以键值对的形式进行存放,一个键对应一个值,键和值分别通过Set集合和List集合进行设计并存放。其迭代方式有三种,一种是通过先取key值,再通过key值,取出value值。第二种通过获取map内部的一个封闭接口拿到所有的key-value值的对象,再逐一获取对应的key和value值。第三种其实就是把foreach遍历换成了iterator迭代器遍历。原理和第二种类似。

追根溯源

集合的大概结构和基本的用法我们都已经清楚了,下面我们看看如此强大的集合对象究竟是怎么形成的。通过点开集合的源码我们就可以一目了然其内部构造了。

ArrayList解析

首先看它的底层初始构造,我们打开类的搜索项,搜索ArrayList类,
屏幕截图 2023-07-09 194729.png

我们可以看到在创建ArrayList集合对象底层其实创建一个数组,也就是说集合底层就是个数组,如果但是集合和数组有什么区别呢?
屏幕截图 2023-07-09 195238.png
屏幕截图 2023-07-09 195834.png
image.png
一开始先创建一个默认容量为10的数组进行存储,如果存储的数据量超过10的话,就会进行扩容,扩容量为1.5倍(oldCapacity >> 1)。普通数组长度是固定,但是List集合对象长度是可以变化的。下面我们看他如何取出数据
屏幕截图 2023-07-09 201501.png
屏幕截图 2023-07-09 201310.png
屏幕截图 2023-07-09 201450.png
当它get取这个索引所指向的值时,会先进行checkIndex方法进行判断该索引是否越界,越界就抛出越界异常,否则就通过数组取出该索引的值。

Set解析

Set常用接口的实现类HashSet的底层其实是一个Map集合,谈到了map集合下面就会介绍,可以先看map集合的解析,看完后再回来看这里就清楚了。Set结构与List是完全不同的
屏幕截图 2023-07-09 202003.png

再观察他的存值特点
屏幕截图 2023-07-09 202214.png
屏幕截图 2023-07-09 202145.png
屏幕截图 2023-07-09 202129.png
set存储数据时用到了hash方法计算哈希码的方式进行存储,这样set集合就不会出现重复的元素了,但是同样也带来没有索引值取数据困难的局面。

Map解析

这里主讲map集合的底层创建以及扩容机制。map是一个内部比较复杂集合,因为他的功能比较多结构精巧。他的底层实现用到了负载因子,根据计算阈值来创建,公式为阈值=负载因子*容量,这负载因子默认为0.75,
屏幕截图 2023-07-10 152044.png
屏幕截图 2023-07-10 151917.png
我们可以看到这个对象初始化只是把负载因子赋值了,我们下面再看它的添加元素方法
屏幕截图 2023-07-10 152647.png
这里通过把key和value作为参数进行传到真正实现存储的putVal方法
image.png

我们可以看到这里创建了两个数组进行存储,并且下面两步将需要储存的传来的参数key和value进行存储到刚创建的数组中进行保存
屏幕截图 2023-07-10 153006.png

我们可以看到在添加元素时,在没有创建实例时初始化后,put方法里会判断容量是否为空,调用resize方法第一次赋值容量赋值为16,阈值赋值为0.75*16=12。每当超过阈值就会进行扩容,之后每次扩容都是两倍。总之容量和阈值之间保持0.75的比例
以上的是无参构造器实现及扩容机制,下面看有参构造器的实现:
屏幕截图 2023-07-10 154712.png
屏幕截图 2023-07-10 154951.png

有参构造器构造集合时,以上该种构造器就不会用默认的系统容量,则会使用开发者所定义的参数容量,但会使用系统默认的负载因子,下面会判断是否超过最大容量,超出则赋予系统默认最大支持的容量。并且在最后一行给阈值进行了赋值

屏幕截图 2023-07-10 161337.png
屏幕截图 2023-07-10 161354.png

注意:初始容量设置为2的倍数的话是自定义容量,如果不是则容量是系统根据以上算法算出离设定参数最近的2的n倍的容量。但是最后编译器仍会把我们设置的容量屏蔽,假设设置的初始容量为8,那么阈值就是8*0.75=6,此时扩容就会造成内存浪费,一般系统把容量定义为16。

结束语

以上就是Java集合篇的三大常用集合的归纳、常用功能和解析,除此之外还有很多其他特色功能的集合,以后也会根据需求撰写更多的相关内容。

相关文章
|
2月前
|
存储 安全 Java
Java 集合框架中的老炮与新秀:HashTable 和 HashMap 谁更胜一筹?
嗨,大家好,我是技术伙伴小米。今天通过讲故事的方式,详细介绍 Java 中 HashMap 和 HashTable 的区别。从版本、线程安全、null 值支持、性能及迭代器行为等方面对比,帮助你轻松应对面试中的经典问题。HashMap 更高效灵活,适合单线程或需手动处理线程安全的场景;HashTable 较古老,线程安全但性能不佳。现代项目推荐使用 ConcurrentHashMap。关注我的公众号“软件求生”,获取更多技术干货!
43 3
|
1月前
|
自然语言处理 Java
Java中的字符集编码入门-增补字符(转载)
本文探讨Java对Unicode的支持及其发展历程。文章详细解析了Unicode字符集的结构,包括基本多语言面(BMP)和增补字符的表示方法,以及UTF-16编码中surrogate pair的使用。同时介绍了代码点和代码单元的概念,并解释了UTF-8的编码规则及其兼容性。
101 60
|
2月前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
89 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
2月前
|
存储 缓存 安全
Java 集合江湖:底层数据结构的大揭秘!
小米是一位热爱技术分享的程序员,本文详细解析了Java面试中常见的List、Set、Map的区别。不仅介绍了它们的基本特性和实现类,还深入探讨了各自的使用场景和面试技巧,帮助读者更好地理解和应对相关问题。
51 5
|
2月前
|
监控 架构师 Java
Java虚拟机调优的艺术:从入门到精通####
本文作为一篇深入浅出的技术指南,旨在为Java开发者揭示JVM调优的神秘面纱,通过剖析其背后的原理、分享实战经验与最佳实践,引领读者踏上从调优新手到高手的进阶之路。不同于传统的摘要概述,本文将以一场虚拟的对话形式,模拟一位经验丰富的架构师向初学者传授JVM调优的心法,激发学习兴趣,同时概括性地介绍文章将探讨的核心议题——性能监控、垃圾回收优化、内存管理及常见问题解决策略。 ####
|
3月前
|
存储 缓存 安全
Java 集合框架优化:从基础到高级应用
《Java集合框架优化:从基础到高级应用》深入解析Java集合框架的核心原理与优化技巧,涵盖列表、集合、映射等常用数据结构,结合实际案例,指导开发者高效使用和优化Java集合。
62 4
|
3月前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
3月前
|
存储 Java 开发者
在 Java 中,如何遍历一个 Set 集合?
【10月更文挑战第30天】开发者可以根据具体的需求和代码风格选择合适的遍历方式。增强for循环简洁直观,适用于大多数简单的遍历场景;迭代器则更加灵活,可在遍历过程中进行更多复杂的操作;而Lambda表达式和`forEach`方法则提供了一种更简洁的函数式编程风格的遍历方式。
|
3月前
|
Java
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式。本文介绍了 Streams 的基本概念和使用方法,包括创建 Streams、中间操作和终端操作,并通过多个案例详细解析了过滤、映射、归并、排序、分组和并行处理等操作,帮助读者更好地理解和掌握这一重要特性。
55 2
|
3月前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。