Java集合容器详解:ArrayList、LinkedList和HashMap、HashTable及其区别

news2024/9/23 7:33:35

文章目录

    • 一、简介
    • 二、ArrayList详解
      • 2.1 动态数组
      • 2.2 扩容机制
      • 2.3 特点
      • 2.4 操作
    • 三、LinkedList详解
      • 3.1 双向链表结构
      • 3.2 双向链表结构
      • 3.3 操作
    • 四、HashMap详解
      • 4.1 概述
      • 4.2 内部实现
        • 4.2.1 哈希表结构
        • 4.2.2 散列冲突解决
        • 4.2.3 扩容机制
      • 4.3 版本差异
      • 4.4 实操
    • 五、HashTable
      • 5.1 概述
      • 5.2 内部实现
      • 5.3 特点
    • 六、区别与总结
      • 6.1 ArrayList和LinkedList的区别
      • 6.2 HashMap和HashTable的区别
      • 6.3 总结



一、简介


  Java集合框架是Java提供的一组用于存储和操作数据的类和接口。其中,ArrayList、LinkedList和HashMap、HashTable是常用的集合容器,它们在不同的场景中具有重要性和广泛应用。


二、ArrayList详解


  ArrayList内部使用数组作为数据存储结构,数组可以通过下标直接访问元素的内存地址,因此访问的效率很高。通过下标访问元素的时间复杂度为O(1),即常数时间。

2.1 动态数组

  数组是一块连续的内存空间,数组建立后就无法修改数组长度。ArrayList为了能够动态扩容数组,在每次扩容时都创建一个新的数组,再把旧的数组中的元素添加到新数组中。通过不断更换更大容量的数组来实现动态数组。
在这里插入图片描述

2.2 扩容机制

  在数组容量不足时,需要创建更大的新数组,这个过程叫做“扩容”。
  一开始创建ArrayList对象,并为添加元素时,ArrayList的容量为0。再添加第一个元素后,初始容量变为默认的10,也就是可以存储下标0-9的的数据。

  即使可以存10个元素,在存储数据时也要按下标顺序存,不允许按照下标跳着存数据,例如数组为空,直接在下标1存数据,不是从下标0开始存数据,这样会报错。


什么时候扩容?
  每次添加元素都会判断是否需要扩容,如果需要,会先扩容,再插入元素。扩容时,新数组的容量是原数组容量的1.5倍。

2.3 特点

  • 随机访问高效
      由于ArrayList使用数组作为内部数据结构,可以通过下标直接访问元素的内存地址,因此随机访问的效率很高。通过索引访问元素的时间复杂度为O(1),即常数时间。

  • 查询和修改操作
    ArrayList提供了丰富的查询和修改操作方法。可以根据索引查询元素,也可以根据索引修改元素。这使得ArrayList在需要频繁访问和修改元素的场景中非常适用。

2.4 操作

  因为底层数据结构是数组,在操作数组时会有些步骤,下图是场景操作的步骤,数组的优点是按下标读取快,但是操作数据就有些复杂。
在这里插入图片描述

  下面是一个使用ArrayList的示例代码:

import java.util.ArrayList;

public class ArrayListExample {
    public static void main(String[] args) {
        // 创建ArrayList
        ArrayList<String> list = new ArrayList<>();

        // 添加元素
        list.add("苹果");
        list.add("香蕉");
        list.add("橙子");

        // 遍历元素
        for (String fruit : list) {
            System.out.println(fruit);
        }

        // 根据索引访问元素
        String firstFruit = list.get(0);
        System.out.println("第一个水果:" + firstFruit);

        // 根据索引修改元素
        list.set(1, "草莓");
        System.out.println("修改后的水果列表:" + list);
    }
}




三、LinkedList详解


3.1 双向链表结构

  LinkedList基于双向链表实现,可以高效地进行插入和删除操作,并提供了遍历和索引操作的能力。内部使用双向链表作为数据存储结构。每个Node(节点)包含一个元素和两个指针,分别指向前一个节点和后一个节点。通过这种结构,LinkedList可以在O(1)的时间复杂度内进行插入和删除操作。
在这里插入图片描述

3.2 双向链表结构

在这里插入图片描述

  • 插入和删除操作高效
    由于LinkedList使用双向链表结构,插入和删除操作非常高效。在插入和删除元素时,只需要修改相邻节点的指针,不需要像数组那样进行元素的移动和复制。

  • 遍历和索引操作
    LinkedList可以通过遍历访问每个元素,也可以通过索引访问特定位置的元素。但是,由于链表没有数组那样的随机访问能力,索引操作的效率较低,并且遍历建议不要使用for循环,使用迭代器效率更高。

3.3 操作

在这里插入图片描述

示例代码:

import java.util.LinkedList;

public class LinkedListExample {
    public static void main(String[] args) {
        // 创建LinkedList
        LinkedList<String> list = new LinkedList<>();

        // 添加元素
        list.add("苹果");
        list.add("香蕉");
        list.add("橙子");

        // 遍历元素
        for (String fruit : list) {
            System.out.println(fruit);
        }

        // 在指定位置插入元素
        list.add(1, "草莓");
        System.out.println("插入后的水果列表:" + list);

        // 删除指定位置的元素
        list.remove(2);
        System.out.println("删除后的水果列表:" + list);
    }
}




四、HashMap详解


4.1 概述

  HashMap是基于哈希表实现的集合容器,具有高效的键值存储和查找能力。它适用于需要高效存储和查找键值对的场景,例如缓存数据、数据索引和键值对存储等。通过使用HashMap,我们可以方便地操作和管理键值对,提高代码的效率和灵活性。

4.2 内部实现

在这里插入图片描述

4.2.1 哈希表结构

  HashMap内部使用哈希表作为数据存储结构。哈希表由一个数组和链表组成。数组的每个元素称为桶(bucket),每个桶可以存储一个或多个键值对。通过哈希函数将键映射到桶的索引(数组的下标),实现快速的存储和查找。

4.2.2 散列冲突解决

  由于哈希函数的映射不是一对一的,可能会出现多个键映射到同一个桶的情况,称为散列冲突。
  jdk1.8版本之前单纯使用链表解决哈希冲突,1.8以后使用链表+红黑树来解决。当链表长度超过阈值8时,链表会转换为红黑树,提高查找效率。当红黑树节点个数低于6时,又转变会链表结构。

4.2.3 扩容机制

  HashMap的底层是数组,数组无法修改长度,本质也是创建一个新数组,再把数据挪进去。

什么时候需要扩容?
  根据泊松算法,当数组的数据存储达到容量的70%-80%左右,产生哈希冲突的概率会大大增加,所以HashMap设置了一个负载因子——0.75,也就是说当数组的数据量达到容量的75%后就该马上扩容了。HashMap在每次插入新数据的时候都会判断是否需要扩容,和ArrayList扩容时间一样。最大扩到2的31次方-1(int类型最大值)。

  HashMap的扩容流程如下:

  1. HashMap初始容量是16,当元素数量达到负载因子(0.75)与容量的乘积时,即元素数量超过了扩容阈值,需要进行扩容操作。

  2. 创建一个新的数组,其长度是原数组的两倍。新数组的长度一般选择为原数组长度的两倍,这是经验性的选择,可以在时间和空间效率之间做一个折中。

  3. 遍历原数组中的每个元素,重新计算它们在新数组中的位置,并将其移动到新数组中的对应位置。这一步是为了保持元素之间的相对顺序不变。

  4. 在移动元素时,如果发现新数组中的某个位置已经被占用,则使用链表或红黑树(在JDK 8及以后的版本中)来解决哈希冲突。这是因为在扩容后,原本哈希冲突的元素可能被分散到新数组的不同位置,需要重新处理冲突。

  5. 扩容完成后,HashMap的容量会增加为原来的两倍,并且负载因子的值保持不变。

4.3 版本差异

特性HashMap 1.7HashMap 1.8
处理哈希冲突的方式链表链表 + 红黑树
扩容机制先扩容再插入先插入再扩容
扩容后先重新计算所有元素hash值,更换桶位置不用重新计算hash,新的下标为原索引+原来的容量

4.4 实操

  HashMap适用于需要高效的键值存储和查找的场景。

  • 缓存数据:HashMap可以用作缓存数据的容器,提供快速的查找和插入能力。
  • 数据索引:由于HashMap提供了快速的查找操作,可以用于构建数据索引,加快数据的检索速度。
  • 键值对存储:如果需要将一组键值对进行存储和管理,并且对快速查找的需求较高,可以选择使用HashMap。

在这里插入图片描述
代码示例:

import java.util.HashMap;

public class HashMapExample {
    public static void main(String[] args) {
        // 创建HashMap
        HashMap<String, Integer> map = new HashMap<>();

        // 添加键值对
        map.put("苹果", 10);
        map.put("香蕉", 5);
        map.put("橙子", 8);

        // 获取键对应的值
        int appleCount = map.get("苹果");
        System.out.println("苹果的数量:" + appleCount);

        // 遍历键值对
        for (String fruit : map.keySet()) {
            int count = map.get(fruit);
            System.out.println(fruit + "的数量:" + count);
        }

        // 判断键是否存在
        boolean containsKey = map.containsKey("橙子");
        System.out.println("是否包含橙子:" + containsKey);

        // 删除键值对
        map.remove("香蕉");
        System.out.println("删除香蕉后的水果列表:" + map);
    }
}




五、HashTable


5.1 概述

  HashTable是Java中的一个集合容器,它是线程安全的数据结构。

5.2 内部实现

  • 哈希表结构
    HashTable内部使用哈希表作为数据存储结构。哈希表由一个数组和链表组成,每个桶可以存储一个或多个键值对。
  • 散列冲突解决
    HashTable使用开放地址法(Open Addressing)解决散列冲突。当发生冲突时,它会尝试将键映射到下一个空闲的桶,直到找到合适的位置。

5.3 特点

  • 线程安全性
    HashTable是线程安全的集合容器,它在操作上提供了同步机制,保证多线程环境下的安全性。它使用synchronized关键字来实现同步,但这也导致了性能的降低。

  • 性能
    由于HashTable提供了线程安全性,它在多线程环境下的性能较差。在单线程环境下,HashMap的性能通常优于HashTable。

import java.util.Arrays;

class HashTable {
    private static final int TABLE_SIZE = 10;
    private Entry[] table;

    HashTable() {
        table = new Entry[TABLE_SIZE];
    }

    // 哈希函数
    private int hashFunction(int key) {
        return key % TABLE_SIZE;
    }

    // 插入键值对
    public void put(int key, String value) {
        int index = hashFunction(key);
        Entry entry = new Entry(key, value);

        if (table[index] == null) {
            table[index] = entry;
        } else {
            Entry current = table[index];
            while (current.next != null) {
                current = current.next;
            }
            current.next = entry;
        }
    }

    // 获取键对应的值
    public String get(int key) {
        int index = hashFunction(key);
        Entry current = table[index];

        while (current != null) {
            if (current.key == key) {
                return current.value;
            }
            current = current.next;
        }

        return null;
    }

    // 删除键值对
    public void remove(int key) {
        int index = hashFunction(key);
        Entry current = table[index];
        Entry previous = null;

        while (current != null) {
            if (current.key == key) {
                if (previous == null) {
                    table[index] = current.next;
                } else {
                    previous.next = current.next;
                }
                return;
            }
            previous = current;
            current = current.next;
        }
    }

    // 打印哈希表内容
    public void printTable() {
        for (int i = 0; i < TABLE_SIZE; i++) {
            Entry current = table[i];
            System.out.print(i + ": ");
            while (current != null) {
                System.out.print("(" + current.key + ", " + current.value + ") ");
                current = current.next;
            }
            System.out.println();
        }
    }

    // 哈希表的节点
    private static class Entry {
        int key;
        String value;
        Entry next;

        Entry(int key, String value) {
            this.key = key;
            this.value = value;
            this.next = null;
        }
    }
}

public class Main {
    public static void main(String[] args) {
        HashTable hashTable = new HashTable();

        // 插入键值对
        hashTable.put(1, "Value 1");
        hashTable.put(11, "Value 11");
        hashTable.put(21, "Value 21");
        hashTable.put(2, "Value 2");

        // 打印哈希表
        hashTable.printTable();

        // 获取键对应的值
        System.out.println("Value for key 1: " + hashTable.get(1));
        System.out.println("Value for key 11: " + hashTable.get(11));

        // 删除键值对
        hashTable.remove(11);

        // 打印哈希表
        hashTable.printTable();
    }
}




六、区别与总结


6.1 ArrayList和LinkedList的区别

ArrayList和LinkedList都是Java中的集合容器,它们的区别如下:

  • 内部实现:ArrayList基于动态数组,LinkedList基于双向链表。
  • 随机访问:ArrayList通过索引可以快速访问元素,时间复杂度为O(1);LinkedList需要遍历链表才能访问元素,时间复杂度为O(n)。
  • 插入和删除:ArrayList插入和删除元素时需要移动后续元素,时间复杂度为O(n);LinkedList插入和删除元素只需修改前后节点的指针,时间复杂度为O(1)。
  • 适用场景:ArrayList适用于频繁访问和遍历元素的场景;LinkedList适用于频繁插入和删除元素的场景。

6.2 HashMap和HashTable的区别

HashMap和HashTable都是Java中的集合容器,它们的区别如下:

  • 线程安全性:HashMap不是线程安全的,HashTable是线程安全的。
  • 空键和空值:HashMap允许空键和空值,HashTable不允许空键和空值。
  • 性能:HashMap在单线程环境下的性能通常优于HashTable,因为HashTable使用synchronized关键字实现同步,导致性能较差。
  • 迭代器:HashMap的迭代器是快速失败的(fail-fast),而HashTable的迭代器不是。

6.3 总结

  • ArrayList适用于频繁访问和遍历元素的场景,LinkedList适用于频繁插入和删除元素的场景。
  • HashMap适用于非线程安全的场景,HashTable适用于线程安全的场景。
  • HashMap在性能上通常优于HashTable,但HashTable提供了线程安全性。
  • 在选择集合容器时,根据具体的需求和场景综合考虑线程安全性、性能和功能特点。

  根据这些内容,可以选择适合自己需求的集合容器,并理解它们的特点和用法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/855176.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

安卓:LitePal操作数据库

目录 一、LitePal介绍 常用方法&#xff1a; 1、插入数据&#xff1a; 2、更新数据&#xff1a; 3、删除数据&#xff1a; 4、查询数据&#xff1a; 二、LitePal的基本用法&#xff1a; 1、集成LitePal&#xff1a; 2、创建LitePal配置文件&#xff1a; 3、创建模型类…

Vue+Vue Router+TailwindCss+Daisyui部署

一、构建Vue项目 > npm init vuelatest > cd <your-project-name> > npm install > npm run dev 二、设置IDEA JS版本 三、安装Tailwindcss Install Tailwind CSS with Vite - Tailwind CSS npm install -D tailwindcss postcss autoprefixer npx tai…

Linux下匿名管道简单模拟进程间通信

Linux下匿名管道简单模拟进程间通信 文章目录 Linux下匿名管道简单模拟进程间通信在这里插入图片描述1.引言2.具体实现2.1创建管道2.2创建子进程 && 通信(子进程写入)2.3关闭对应fd 3.结果 1.引言 ​ ​ 首先&#xff0c;管道是一种半双工的单向进程间通信方式&#…

有哪些简单的AI绘画软件?

随着人工智能技术的不断发展&#xff0c;越来越多的人工智能绘画软件出现了。人工智能绘画软件利用人工智能技术&#xff0c;通过计算机自动生成或辅助生成艺术作品。人工智能绘画软件通常集成了深度学习、计算机视觉和自然语言处理技术&#xff0c;可以模拟人类的创作过程&…

【数据结构与算法】十大经典排序算法-插入排序

&#x1f31f;个人博客&#xff1a;www.hellocode.top &#x1f3f0;Java知识导航&#xff1a;Java-Navigate &#x1f525;CSDN&#xff1a;HelloCode. &#x1f31e;知乎&#xff1a;HelloCode &#x1f334;掘金&#xff1a;HelloCode ⚡如有问题&#xff0c;欢迎指正&#…

【Shell】基础语法(三)

文章目录 一、Shell基础语法1. 位置参数和特殊变量2. 输入输出3. 管道4. 文件重定向5. 函数6. 脚本调试方法 二、Shell高级和正则表达式1. sort命令2. uniq命令3. wc命令4. grep命令5. find命令6. xargs7. sed命令8. crontab 一、Shell基础语法 1. 位置参数和特殊变量 $0 …

循环队列详解

1. 循环队列 1.1 概念及结构 循环队列是一种特殊类型的队列数据结构&#xff0c;也被称为”唤醒缓冲器“。它在数组的基础上实现了循环利用空间的功能。在循环队列中&#xff0c;队尾和队头之间形成了一个循环&#xff0c;当队尾指针“追上”队头指针时&#xff0c;队列不再继…

IDEA设置Tabs多行显示的方法

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 前言 在开发的时候、不知不觉我们就会打开很多代码页。如果打开的页面…

在线原型设计工具有好用的吗?就是这10个

随着设计工作的不断发展&#xff0c;原型设计在设计工作中越来越重要&#xff0c;而在线原型设计工具在减轻了设计师工作负担的同时也提高了设计师的工作效率&#xff0c;今天本文将为大家推荐10个能在线使用的原型设计工具&#xff0c;一起来看看吧&#xff01; 1、即时设计 …

CDC 数据复制:技术、权衡、见解

推荐&#xff1a;使用NSDT场景编辑器助你快速搭建可编辑的3D应用场景 在本文中&#xff0c;我将定义 CDC 数据复制&#xff0c;简要讨论最常见的用例&#xff0c;然后讨论常见技术及其权衡。最后&#xff0c;我将提供一些我作为数据集成公司Dataddo的首席执行官和创始人所学到…

使用logback异步打印日志

文章目录 一、介绍二、运行环境三、演示项目1. 接口2. 日志配置文件3. 效果演示4. 异步输出验证 四、异步输出原理五、其他参数配置六、源码分析1. 同步输出2. 异步输出 七、总结 一、介绍 对于每一个开发人员来说&#xff0c;在业务代码中添加日志是至关重要的&#xff0c;尤…

CSS:弹性盒子模型详解(用法 + 例子 + 效果)

目录 弹性盒子模型flex-direction 排列方式 主轴方向换行排序控制子元素缩放比例缩放是如何实现的&#xff1f; 控制子元素的对其方式justify-content 横向 对齐方式align-items 纵向 对齐方式 align-content 多行 对齐方式 弹性盒子模型 flex-direction 排列方式 主轴方向 f…

webshell免杀项目-Auto-JSPwebshell(五)

Auto-JSPwebshell/jsp免杀/webshell免杀/自动生成 项目地址&#xff1a; https://github.com/G0mini/Bypass 具体使用请参考&#xff1a; https://mp.weixin.qq.com/s/9-__B0MBRSXHla6O0KU7Gg

PCB制造中铜厚度的重要性

电子产品中的PCB是现代电子设备中不可或缺的一部分。在PCB制造过程中&#xff0c;铜厚度是一个非常重要的因素。正确的铜厚度可以保证电路板的质量和性能&#xff0c;同时也影响着电子产品的可靠性和稳定性。 一般我们常见的铜厚有17.5um&#xff08;0.5oz&#xff09;&#x…

SpringBootWeb案例-准备工作

目录 前言 准备工作 需求&环境搭建 需求 环境搭建 开发规范 Restful开发规范 统一的响应结果 开发流程 前言 根据过往的文章可以知道目前我已经学习完了前端、后端、数据库的基础知识&#xff0c;接下来通过一个基于SpringBoot工程开发的web项目案例。 准备工作 …

来讲一讲面试必问的异步FIFO设计!

异步FIFO设计可以说是数字IC设计工程师面试时必问的一个问题了&#xff0c;也是我们经常使用但是又往往被忽略的一个东西&#xff0c;今天就展开详细说一说不同深度&#xff08;2^N或者非2^N&#xff09;异步FIFO的设计思想&#xff1b; 一&#xff1a;2^N深度异步FIFO设计 1…

Unity开发笔记:截取指定位置含有UI的场景截图并输出

学习记录整理&#xff0c;自用&#xff0c;也希望能帮助到有相同需求的人。 如果直接截全图&#xff1a; string screenshotName "Assets/Textures/UI/20230803/2.png";ScreenCapture.CaptureScreenshot(screenshotName);截取指定位置含有UI的场景截图&#xff1a; …

Therac-25事故:软件缺陷引发的医疗灾难与教训

目录 引言 Therac-25&#xff1a;背景与功能 软件缺陷导致的灾难 Bug原理解析与编程人员的反思 教训与反思 结论 引言 在计算机科技的进步与应用领域&#xff0c;软件的质量和安全性至关重要。然而&#xff0c;历史上曾经发生过一系列令人震惊的事件&#xff0c;突显了软…

培训报名小程序报名功能完善

目录 1 修改数据源2 修改表单3 支付成功时修改状态4 创建报名成功页5 最终的效果总结 目前我们的报名功能已经搭建了一个基础版&#xff0c;后续需要展示用户已经报名的信息&#xff0c;需要添加一个状态来显示用户是否成功付费。 1 修改数据源 打开我们的报名数据源&#xff…