Java核心(四)你不知道的数据集合

数据容器关系图

导读:Map竟然不属于Java集合框架的子集?队列也和List一样属于集合的三大子集之一?更有队列的正确使用姿势,一起来看吧!

Java中的集合通常指的是Collection下的三个集合框架List、Set、Queue和Map集合,Map并不属于Collection的子集,而是和它平行的顶级接口。Collection下的子集的关系如文章开头图片所示。

本文的重点将会围绕: 集合的使用、性能、线程安全、差异性、源码解读等几个方面进行介绍。

本文涉及的知识点,分为两部分:

第一部分,Collection所有子集:

  • List => Vector、ArrayList、LinkedList
  • Set => HashSet、TreeSet
  • Queue

第二部分,Map => Hashtable、HashMap、TreeMap、ConcurrentHashMap。

一、List

我们先来看List、Vector、ArrayList、LinkedList,它们之间的继承关系图,如下图:

关系图

可以看出Vector、ArrayList、LinkedList,这三者都是实现集合框架中的List,也就是所谓的有序集合,因此具体功能也比较近似,比如都提供按照位置进行定位、添加或者删除的操作,都提供迭代器以遍历其内容等。但因为具体的设计区别,在行为、性能、线程安全等方面,表现又有很大不同。

来看它们的主要方法,如下图:

List方法

常用方法:

  • size 集合个数
  • add()/add(int, E) 添加末尾/添加指定位置
  • get(int) 获取
  • remove 删除
  • clear 清空

1.1 Vector

Vector是Java早期提供的 线程安全的动态数组, 如果不需要线程安全,并不建议选择,毕竟同步是有额外开销的。Vector 内部是使用对象数组来保存数据,可以根据需要自动的增加容量,当数组已满时,会创建新的数组,并拷贝原有数组数据。

看源代码可以知道,我们Vector是通过 synchronized 实现线程安全的:

public synchronized boolean add(E e) {modCount++;ensureCapacityHelper(elementCount + 1);elementData[elementCount++] = e;return true;
}

Vector动态增加容量,源码查看:

private void grow(int minCapacity) {// overflow-conscious codeint oldCapacity = elementData.length;int newCapacity = oldCapacity + ((capacityIncrement > 0) ?capacityIncrement : oldCapacity);if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);elementData = Arrays.copyOf(elementData, newCapacity);
}

capacityIncrement变量是what?答案如下:

public Vector(int initialCapacity, int capacityIncrement) {super();if (initialCapacity < 0)throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);this.elementData = new Object[initialCapacity];this.capacityIncrement = capacityIncrement;
}

Vector动态增加容量总结: 由上面的源码可知,如果初始化Vector的时候指定了动态容量扩展大小,就增加指定的动态大小,如果未指定,则扩展一倍的容量。

1.2 ArrayList

ArrayList 是应用更加广泛的动态数组,它本身不是线程安全的,所以性能要好很多。

ArrayList的使用与Vector类似,但有着不同的动态扩容机制,如下源码:

private void grow(int minCapacity) {// overflow-conscious codeint oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1);if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// minCapacity is usually close to size, so this is a win:elementData = Arrays.copyOf(elementData, newCapacity);
}

其中“>> 1”是位运算相当于除2,所有ArrayList扩容是动态扩展50%.

1.3 LinkedList

LinkedList 顾名思义是 Java 提供的双向链表,所以它不需要像上面两种那样调整容量,它也不是线程安全的,它包含一个非常重要的内部类:Entry。Entry是双向链表节点所对应的数据结构,它包括的属性有:当前节点所包含的值,上一个节点,下一个节点。

1.4 Vector、ArrayList、LinkedList区别

Vector、ArrayList、LinkedList的区别,可以从以下几个维度进行对比:

1.4.1 底层实现的区别

Vector、ArrayList 内部使用数组进行实现,LinkedList 内部使用双向链表实现。

1.4.2 读写性能方面的区别

ArrayList 对元素 非末位 的增加和删除都会引起内存分配空间的动态变化,因此非末位的操作速度较慢,但检索速度很快。

LinkedList 基于链表方式存放数据,增加和删除元素的速度较快,但是检索速度较慢。

1.4.3 线程安全方面的区别

Vector 使用了synchronized 修饰了操作方法是线程安全的,而 ArrayList、LinkedList 是非线程安全的。

如果需要使用线程安全的List可以使用CopyOnWriteArrayList类。

二、Map

Hashtable、HashMap、TreeMap 都是最常见的一些 Map 实现,是以键值对的形式存储和操作数据的容器类型。

它们之间的关系,如下图:

map

  • Hashtable 是早期 Java 类库提供的一个哈希表实现,本身是同步的,不支持 null 键和值,由于同步导致的性能开销,所以已经很少被推荐使用。
  • HashMap 是应用更加广泛的哈希表实现,行为上大致上与 HashTable 一致,主要区别在于 HashMap 不是同步的,支持 null 键和值等。通常情况下,HashMap 进行 put 或者 get 操作,可以达到常数时间的性能,所以它是绝大部分利用键值对存取场景的首选,比如,实现一个用户 ID 和用户信息对应的运行时存储结构。
  • TreeMap 则是基于红黑树的一种提供顺序访问的 Map,和 HashMap 不同,它的 get、put、remove 之类操作都是 O(log(n))的时间复杂度,具体顺序可以由指定的 Comparator 来决定,或者根据键的自然顺序来判断。

HashMap 的性能表现非常依赖于哈希码的有效性,请务必掌握 hashCode 和 equals 的一些基本约定,比如:

  • equals 相等,hashCode 一定要相等;
  • 重写了 equals 也要重写 hashCode;
  • hashCode 需要保持一致性,状态改变返回的哈希值仍然要一致;
  • equals 的对称、反射、传递等特性;

线程安全: Hashtable是线程安全的,HashMap和TreeMap是非线程安全的。HashMap可以使用ConcurrentHashMap来保证线程安全。

三、Set

Set有两个比较常用的子集:HashSet、TreeSet.

HashSet内部使用的是HashMap实现的,看源代码可知:

public HashSet() {map = new HashMap<>();
}

HashSet也并不是线程安全的,HashSet用于存储无序(存入和取出的顺序不一定相同)元素,值也不能重复。

HashSet可以去除重复的值,如下代码:

public static void main(String[] args) {Set set = new HashSet();set.add("orange");set.add("apple");set.add("banana");set.add("grape");set.add("banana");System.out.println(set);
}

编译器不会报错,执行的结果为:[orange, banana, apple, grape],去掉了重复的“banana”选项。但排序是无序的,如果要实现有序的存储就要使用TreeSet了。

public static void main(String[] args) {Set set = new TreeSet();set.add("orange");set.add("apple");set.add("banana");set.add("grape");set.add("banana");System.out.println(set);
}

输出的结果是:[apple, banana, grape, orange]

同样,我们查看源码发现,TreeSet的底层实现是TreeMap,源码如下:

public TreeSet() {this(new TreeMap<E,Object>());
}

TreeSet也是非线程安全的。

四、Queue

Queue(队列)与栈是相对的一种数据结构。只允许在一端进行插入操作,而在另一端进行删除操作的线性表。栈的特点是后进先出,而队列的特点是先进先出。队列的用处很大,但大多都是在其他的数据结构中,比如,树的按层遍历,图的广度优先搜索等都需要使用队列做为辅助数据结构。

Queue的直接子集,如下图:

queue

其中最常用的就是线程安全类:BlockingQueue.

4.1 Queue方法

  • 添加:add(e) / offer(e)
  • 移除:remove() / poll()
  • 查找:element() / peek()

注意:

  1. 避免add()和remove()方法,而是要使用offer()和poll()添加和移除元素。后者操作失败不会报错,前者会抛出异常;
  2. element() / peek() 都为查询第一个元素,不会删除集合,但element()查询失败会抛出异常,peek()不会。

4.2 Queue使用

Queue<String> queue =  new LinkedList<String>();
queue.offer("a");
queue.offer("b");
queue.offer("c");
queue.offer("d");
System.out.println(queue);
queue.poll();
System.out.println(queue);
queue.poll();
queue.poll();
queue.poll();
System.out.println(queue.peek());
// System.out.println(queue.element()); // element 查询失败会抛出异常
System.out.println(queue);

4.3 其他队列

ArrayBlockingQueue 底层是数组,有界队列,如果我们要使用生产者-消费者模式,这是非常好的选择。

LinkedBlockingQueue 底层是链表,可以当做无界和有界队列来使用,所以大家不要以为它就是无界队列。

SynchronousQueue 本身不带有空间来存储任何元素,使用上可以选择公平模式和非公平模式。

PriorityBlockingQueue 是无界队列,基于数组,数据结构为二叉堆,数组第一个也是树的根节点总是最小值。

ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。

LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。

PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。

DelayQueue:一个使用优先级队列实现的无界阻塞队列。

SynchronousQueue:一个不存储元素的阻塞队列。

LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。

LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列

五、扩展:String的线程安全

关于String、StringBuffer、StringBuilder的线程安全

String是典型的Immutable(不可变)类,被声明为final所有属性也都是final,所有它是不可变的,所有拼加、截取等动作等会产生新的String对象。

StringBuffer是为了解决上面的问题,而诞生的,提供了append方法实现了对字符串的拼加,append方法使用了synchronized实现了线程安全。

StringBuilder是JDK 1.5 新出的特性,作为StringBuffer的性能补充,StringBuffer的append方法使用了synchronized实现了线程的安全,但同时也带来了性能开销,在没有线程安全的情况下可以优先使用StringBuilder。

六、总结

List 也就是我们前面介绍最多的有序集合,它提供了方便的访问、插入、删除等操作。

Set 是不允许重复元素的,这是和 List 最明显的区别,也就是不存在两个对象 equals 返回 true。我们在日常开发中有很多需要保证元素唯一性的场合。

Queue/Deque 则是 Java 提供的标准队列结构的实现,除了集合的基本功能,它还支持类似先入先出(FIFO, First-in-First-Out)或者后入先出(LIFO,Last-In-First-Out)等特定行为。这里不包括 BlockingQueue,因为通常是并发编程场合,所以被放置在并发包里。

Map 是广义 Java 集合框架中的另外一部分,Map 接口存储一组键值对象,提供key(键)到value(值)的映射。

七、参考资料

《码出高效:Java开发手册》

Java核心技术36讲:http://t.cn/EwUJvWA

Oracle docs:https://docs.oracle.com/javase/tutorial/collections/interfaces/queue.html

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

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

相关文章

【Jetson-Nano】2.Tensorflow object API和Pytorch的安装

文章目录 1、Tensorflow多版本安装 1.1 Protobuf 安装1.2 安装依赖包及tensorflow1.151.3 安装其它常用库1.4 测试python包是否安装成功1.5 TensorRT和Opencv的安装1.6 pycuda和onnx安装1.7 Tensorflow2.3安装2、Pytorch安装 2.1 安装pytroch和torchvision2.2 安装环境验证参考…

Java核心(五)深入理解BIO、NIO、AIO

导读&#xff1a;本文你将获取到&#xff1a;同/异步 阻/非阻塞的性能区别&#xff1b;BIO、NIO、AIO 的区别&#xff1b;理解和实现 NIO 操作 Socket 时的多路复用&#xff1b;同时掌握 IO 最底层最核心的操作技巧。 BIO、NIO、AIO 的区别是什么&#xff1f; 同/异步、阻/非阻…

pyqt5让主窗口居中显示(显示在显示器的中间位置)

原文&#xff1a;https://blog.csdn.net/zzx188891020/article/details/105940024 课程重点&#xff1a; 就是让窗口居中显示 # QDesktopWidget import sys from PyQt5.QtWidgets import QDesktopWidget,QMainWindow,QApplication from PyQt5.QtGui import QIconclass Cente…

Basic4android v3.50 发布

这次发布的主要是debug 的增强。说实话&#xff0c;在这一方面B4a 比delphi做的要好。希望delphi 在新的版本里面 能进一步加强。 Im happy to release Basic4android v3.50. This update brings major improvements to the debugging features of Basic4android. With this up…

荔枝派 Nano 全志 F1C100s 编译运行 Linux ubuntu并升级gcc

首先是荔枝派的官方文档&#xff0c;写的不是很细&#xff0c;应当说我们必须明确几点&#xff1a; 出厂时 SPI Flash 自带了一个 U-BootLinux Kernel&#xff08;出厂的时候可能烧过了&#xff09;&#xff0c;可直接拿来用。如果希望自己烧固件&#xff0c;才需要后续步骤必…

Java提高班(六)反射和动态代理(JDK Proxy和Cglib)

反射和动态代理放有一定的相关性&#xff0c;但单纯的说动态代理是由反射机制实现的&#xff0c;其实是不够全面不准确的&#xff0c;动态代理是一种功能行为&#xff0c;而它的实现方法有很多。要怎么理解以上这句话&#xff0c;请看下文。 一、反射 反射机制是 Java 语言提…

C++ STL 四种智能指针

文章目录 0.前言1.unique_ptr2.auto_ptr3.shared_ptr 3.1 简介3.2 通过辅助类模拟实现 shared_ptr4.weak_ptr 4.1 简介4.2 用法4.3 作用5.如何选择智能指针参考文献0.前言 C 标准模板库 STL&#xff08;Standard Template Library&#xff09; 一共给我们提供了四种智能指针&…

快速傅里叶变换应用之二 hdu 4609 3-idiots

快速傅里叶变化有不同的应用场景&#xff0c;hdu4609就比较有意思。题目要求是给n个线段&#xff0c;随机从中选取三个&#xff0c;组成三角形的概率。 初始实在没发现这个怎么和FFT联系起来&#xff0c;后来看了下别人的题解才突然想起来&#xff1a;组合计数问题可以用多项式…

基于sympy的python实现三层BP神经网络算法

#!/usr/bin/python # -*- coding: utf-8 -*- """ 写一个三层的BP神经网络&#xff08;3&#xff0c;2&#xff0c;1&#xff09;,3是输入数据的维度&#xff0c;隐层设置节点数为2&#xff0c;1是因为每个观测的target都是一个标量即只有一个数&#xff1b; 1.随…

JVM(二)Java虚拟机组成详解

导读&#xff1a;详细而深入的总结&#xff0c;是对知识“豁然开朗”之后的“刻骨铭心”&#xff0c;想忘记都难。 Java虚拟机&#xff08;Java Virtual Machine&#xff09;下文简称jvm&#xff0c;上一篇我们对jvm有了大体的认识&#xff0c;进入本文之后我们将具体而详细的…

PyCharm pyqt5 python串口通信封装类SerialCommunication

""" pyqt5串口通信文件SerialCommunication.py """ import binascii import os import serial import serial.tools.list_ports from PyQt5.QtGui import QPixmap# 全局变量&#xff0c;串口是否创建成功标志 Ret False # 串口列表串口号 port_…

Fiddler利用Xposed框架+JustTrustMe抓取手机APP数据

文章目录 1. Xposed安装2. JustTrustMe安装3. 确保Fiddler在模拟器里配置 此文只是针对Fiddler抓取APP数据失败情况下的方案&#xff0c;主要想解决的是安卓手机APP抓包HTTPS报文通过MITM代理后证书不被信任的问题。网上搜索出这是使用了SSL Pinning技术&#xff0c;网上可以搜…

互动直播的视频录制与合成—支持多人离线重入

实现的效果图&#xff1a; 上图合成了2个人视频&#xff0c;中途有1个人先离开之后又重新加入了房间。 一、业务场景 业务场景是这样的&#xff1a;多个用户&#xff08;2-4人&#xff09;直播的视频&#xff0c;合成为一个视频&#xff0c;这期间要满足2个条件&#xff1a;首…

Python界面 PyQT可视化开发(python3+PyQt5+Qt Designer)

前言 以前制作一个Python窗体界面&#xff0c;我都是用GUI窗口视窗设计的模块Tkinter一点一点敲出来的&#xff0c;今天朋友问我有没有Python窗体的设计工具&#xff0c;“用鼠标拖拖”就能完成窗体设计&#xff0c;我查了查相关资料&#xff0c;果然有一款好用的工具——Qt De…

JVM(三)对象的生死判定和算法详解

好的文章是能把各个知识点&#xff0c;通过逻辑关系串连起来&#xff0c;让人豁然开朗的同时又记忆深刻。 导读&#xff1a;对象除了生死之外&#xff0c;还有其他状态吗&#xff1f;对象真正的死亡&#xff0c;难道只经历一次简单的判定&#xff1f;如何在垂死的边缘“拯救”一…

【STM32】修改芯片型号后报 Error 的解决方案

原文&#xff1a;https://blog.csdn.net/xiuhua_wu/article/details/85237418 背景 前几天有个新需求&#xff0c;使用 STM32 的标准库&#xff08;STD&#xff09;做个产品的例程。之前已经做了个 HAL 的&#xff0c;但人家客户不干&#xff0c;非要 STD 的。拖了一周&#xf…

Python手写神经网络实现3层感知机

一、BP神经网络结构模型 BP算法的基本思想是&#xff0c;学习过程由信号的正向传播和误差的反向传播俩个过程组成&#xff0c;输入从输入层输入&#xff0c;经隐层处理以后&#xff0c;传向输出层。如果输出层的实际输出和期望输出不符合&#xff0c;就进入误差的反向传…

JVM(四)垃圾回收的实现算法和执行细节

全文共 1890 个字&#xff0c;读完大约需要 6 分钟。 上一篇我们讲了垃圾标记的一些实现细节和经典算法&#xff0c;而本文将系统的讲解一下垃圾回收的经典算法&#xff0c;和Hotspot虚拟机执行垃圾回收的一些实现细节&#xff0c;比如安全点和安全区域等。 因为各个平台的虚拟…

python-cx_oracle报错“DatabaseError: DPI-1047: 64-bit Oracle Client library cannot be loaded: “

问题的主要原因是python-cx_oracle加载的是32位的instantclient-basic&#xff0c;我们需要让他读到64位的。 弄清版本&#xff0c;最重要&#xff01;&#xff01;&#xff01; 首先安装配置时&#xff0c;必须把握一个点&#xff0c;就是版本一致&#xff01;包括&#xff1…

JVM(五)垃圾回收器的前世今生

全文共 2195 个字&#xff0c;读完大约需要 8 分钟。 如果垃圾回收的算法属于内存回收的方法论的话&#xff0c;那本文讨论的垃圾回收器就属于内存回收的具体实现。 因为不同的厂商&#xff08;IBM、Oracle&#xff09;&#xff0c;实现的垃圾回收器各不相同&#xff0c;而本文…