百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

JAVA脱水学习-java集合介绍,常用集合类

bigegpt 2024-08-02 10:54 4 浏览

在处理数据的过程中经常会需要一个容器来存储某一类型的数据,Java 中的数组就是这样一种容器。但 Java 中的数组有其局限性,定义后的数组长度不可变,超出数组长度后就不能再存放数据了。而很多时候我们并不知道数据到底有多少,所以就需要有不定长的容器来存放数据,这就是集合,Java 中的集合都采用了泛型实现,可以存入任何类型的对象数据。

Java 中的数组:

package test;
import java.util.Arrays;
public class Arr
{
 public static void main(String[] args)
 {
 int[] arr = new int[2];
 arr[0] = 1;
 arr[1] = 2;
 // arr[2] = 3; // 编译出错
 System.out.println(Arrays.toString(arr)); // 输出:[1, 2]
 }
}

Java 中的集合主要分为四类:

  1. List 列表,有序,可重复
  2. Queue 队列,有序,可重复
  3. Set 集合,不可重复
  4. Map 映射,无序,键唯一,值不唯一

每种集合类型下都包含多个具体的实现类,如:

1. List 列表,有序、可重复

常用的 List 实现类有:ArrayList、LinkedList、Vector、Stack。

1.1 ArrayList 列表

ArrayList 数组列表,有序,可重复,内部是通过 Array 实现。

初始化对象时,如果没有传大小,则列表的大小为 DEFAULT_CAPACITY 的默认值 10。当列表容量不够时,继续往列表中追加元素,则通过数组拷贝,对原数组进行扩容,扩容的方式为 int newCapacity = oldCapacity + (oldCapacity >> 1),即新数组容量 newCapacity 为 10 + 10/2 = 15。如果一次性追加多个元素时比如 6 个,这时候列表最小容量 minCapacity 需要 10 + 6 = 16,新的容量 newCapacity 小于最小容量 minCapacity 则新数组容量取最小容量值 newCapacity = minCapacity。

对数组列表进行插入、删除操作时都需要对数组进行拷贝并重排序。所以如果能知道大概存储多少数据时,尽量初始化初始容量,提升性能。

List a = new ArrayList();
a.add(11);
a.add("aaa"); // 添加元素
a.get(1); // 获取第二个元素值
a.remove(1); // 删除第 2 个元素

1.2 LinkedList 双向链表

LinkedList 是双向链表,也即每个元素都有指向前后元素的指针。既然是链表那么顺序读取的效率非常高,而随机读取的效率较低。当随机获取一个 index位元素时,链表先比较 index 和链表长度 1/2 的大小,小于时从链表头部查找元素,大于时就从链表尾部查找元素。

LinkedList l =new LinkedList();
l.add(1);
l.add(2);
l.getFirst(); // 获取第一个元素
l.getLast(); // 获取最后一个元素
l.remove(1); // 删除第 2 个元素

对比 ArrayList 如果随机读取数据较多时使用 ArrayList 性能高,插入删除较多时使用 LinkedList 性能高。

1.3 Vector 向量,线程安全的列表

与 ArrayList 一样也是通过数组实现的,不同的是 Vector 是线程安全的,也即同一时间下只能有一个线程访问 Vector,线程安全的同时带来了性能的耗损,所以一般都使用 ArrayList。Vector 的扩容也与 ArrayList 不同,可以设置扩容值,默认每次扩容原来的一倍。

Vector v = new Vector();
v.add("aa");
v.add("bb");
v.get(1);
v.remove(1);

1.4 Stack 栈,后进先出(LIFO)

Stack 继承自 Vector 所以也是数组实现的,线程安全的栈。因为 Stack 继承自 Vector 所以就拥有 Vector 中定义的方法,但作为栈数据类型,不建议使用 Vector 中与栈无关的方法,尽量只用 Stack 中的定义的栈相关方法,这样不会破坏栈数据类型。

Stack s = new Stack();
s.push(1);
s.push(2);
s.pop(); // 抛出并删除首个元素
s.peek(); // 返回首个元素值,不删除值

1.5 ArrayQueue 数组队列,先进后出(FIFO)

ArrayQueue 是数组实现的队列,从队尾加入数据,只能队头删除数据,可随机读取队列数据。

ArrayQueue q = new ArrayQueue(12);
q.add(1);
q.add(2);
q.get(1);
q.size();、
q.remove(0);

2. Queue 队列,有序、可重复

继承自 Queue 的队列有:ArrayDeque、LinkedList、PriorityQueue。

2.1 ArrayDeque 数组实现的双端队列

ArrayDeque 是队列,但也可以作为栈使用,而且对比 Stack 更高效。作为双端队列那就可以在队列两端插入和删除元素。当追加元素超过容量限制时,则创建一个两边容量的新数组,并将原数组的内容拷贝到新数组中。

ArrayDeque d = new ArrayDeque();
d.addFirst(11);
d.addLast(22);
d.pollFirst();
d.pollLast(); // 返回并删除队尾元素
d.peekLast(); // 返回但不删除队尾元素
d.peekFirst();
d.push(44);
d.pop();

2.2 LinkedList 队列也是双向链表

上文 1.2 中已经提过,这里就不赘述了。推荐使用 ArrayDeque。

2.3 PriorityQueue 优先队列,数组实现的二叉树

PriorityQueue 是一个完全二叉树实现的小顶堆(任意一个非叶子节点的权值,都不大于其左右子节点的权值)。

PriorityQueue q = new PriorityQueue();
q.offer(1);
q.offer(2);
q.offer(3); // 插入元素
q.peek(); // 查看顶端元素
q.poll(); // 返回并删除顶端元素

详细介绍地址:PriorityQueue

3. Map 映射/字典,无序,键值对,键唯一

常用的 Map 实现有:HashMap、TreeMap、LinkedHashMap

3.1 HashMap 哈希映射/字典

HashMap 就是 key->value 的键值对数据,key 是唯一的,而且 key 和 value 都可以为 null。HashMap 和 HashTable 相似,HashTable 实现了线程同步,在Object超类解析章节中简单介绍过 HashTable 的数据存储方式。 HashMap 是个无序的字典,遍历时不保证元素顺序。HashMap 创建时默认会设置初始容量大小(默认16),和装载因子(默认 0.75,扩充容量的阀值),装载因子 = 已存入元素个数 / 总容量大小。当然这两个值也可以手动设置。 HashMap 的数据存储结构如下图:

HashMap 当插入一个数据时,先对 key 值做 hash,用得到的值与容器的大小 n 减 1 做 & 运算得到桶的位置,即:i = (n - 1) & hash,i 就是桶的位置。在桶中查找有无元素,没有直接插入,有则比较元素 key 值是否相同,相同用新值替换。

桶的位置计算为什么是 (n - 1) & hash?先看 hash 值的计算:

// hash() 函数返回一个整数
static final int hash(Object key) {
 int h;
 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

hash() 函数对 key 取值后返回一个整数。又因为 HashMap 的容量 n 大小始终为 2 的幂(默认为 16),那么 n - 1 的二进制始终是最高位为 1,其它位为 0 的数,如:10...0,这个数与整数做 & 运算就得到 hash / n 的余数,余数的取值范围在 0 ~ n-1,很巧妙的设计。相关源码,这里截取了部分:

public class HashMap<K,V> extends AbstractMap<K,V>
 implements Map<K,V>, Cloneable, Serializable {
 
 // 构造函数
 public HashMap(int initialCapacity, float loadFactor) {
 if (initialCapacity < 0)
 throw new IllegalArgumentException("Illegal initial capacity: " +
 initialCapacity);
 if (initialCapacity > MAXIMUM_CAPACITY)
 initialCapacity = MAXIMUM_CAPACITY;
 if (loadFactor <= 0 || Float.isNaN(loadFactor))
 throw new IllegalArgumentException("Illegal load factor: " +
 loadFactor);
 this.loadFactor = loadFactor;
 
 // 扩容的阀值
 this.threshold = tableSizeFor(initialCapacity);
 }
 
 /**
 * 容器大小,返回 2 的幂
 * Returns a power of two size for the given target capacity.
 */
 static final int tableSizeFor(int cap) {
 int n = cap - 1;
 n |= n >>> 1;
 n |= n >>> 2;
 n |= n >>> 4;
 n |= n >>> 8;
 n |= n >>> 16;
 return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
 }
 
 // 插入元素
 public V put(K key, V value) {
 return putVal(hash(key), key, value, false, true);
 }
 
 /**
 * // 实际插入元素的方法
 * Implements Map.put and related methods.
 *
 * @param hash hash for key
 * @param key the key
 * @param value the value to put
 * @param onlyIfAbsent if true, don't change existing value
 * @param evict if false, the table is in creation mode.
 * @return previous value, or null if none
 */
 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
 boolean evict) {
 Node<K,V>[] tab; Node<K,V> p; int n, i;
 if ((tab = table) == null || (n = tab.length) == 0)
 // resize 函数会设置扩容阀值
 n = (tab = resize()).length;
 if ((p = tab[i = (n - 1) & hash]) == null)
 tab[i] = newNode(hash, key, value, null);
 else {
 Node<K,V> e; K k;
 if (p.hash == hash &&
 ((k = p.key) == key || (key != null && key.equals(k))))
 e = p;
 else if (p instanceof TreeNode)
 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
 else {
 for (int binCount = 0; ; ++binCount) {
 if ((e = p.next) == null) {
 p.next = newNode(hash, key, value, null);
 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
 treeifyBin(tab, hash);
 break;
 }
 if (e.hash == hash &&
 ((k = e.key) == key || (key != null && key.equals(k))))
 break;
 p = e;
 }
 }
 if (e != null) { // existing mapping for key
 V oldValue = e.value;
 if (!onlyIfAbsent || oldValue == null)
 e.value = value;
 afterNodeAccess(e);
 return oldValue;
 }
 }
 ++modCount;
 if (++size > threshold)
 resize();
 afterNodeInsertion(evict);
 return null;
 }
 
 // 取 hash 
 static final int hash(Object key) {
 int h;
 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
 } 
}

3.2 TreeMap 红黑树实现的 key->value 容器,可排序

红黑树是一种自平衡二叉查找树。 更多参考:

https://www.cnblogs.com/skywang12345/p/3245399.html

https://www.ibm.com/developerworks/cn/java/j-lo-tree/index.html

3.3 LinkedHashMap 链表映射/字典

LinkedHashMap 继承自 HashMap 所以具有 HashMap 的所有特性。同时又实现了双向链表的特性,保留了元素插入顺序。

LinkedHashMap<String, Integer> l = new LinkedHashMap<>();
l.put("a", 11);
l.put("b", 22);
l.put("c", 33);
l.put("d", 44);
Iterator ita = l.entrySet().iterator();
while (ita.hasNext()) {
 Map.Entry entry = (Map.Entry) ita.next();
 System.out.println(entry.getKey() + "=" + entry.getValue());
}
// 输出:
a=11
b=22
c=33
d=44

4. Set 集合,不可重复

常用的 Set 实现有:HashSet、LinkedHashSet、TreeSet、EnumSet。

4.1 HashSet 哈希集合

HashSet 是基于 HashMap 实现的集合,对 HashMap 做了一些封装。数据结构如图:

与 HashMap 不同的是元素的保存为链表形式,插入数据时遍历链表查看是否有相同数据,有则返回 false,没有返回 true。

HashSet s = new HashSet();
s.add("aa");
System.out.println(s.add("aa")); // false
System.out.println(s.add("bb")); // true

4.2 LinkedHashSet 链表集合

继承自 HashSet 与 LinkedHashMap 相似,是对 LinkedHashMap 的封装。

4.3 TreeSet 红黑树集合

与 TreeMap 相似。是对 TreeMap 的封装。

本文只是对 Java 中的集合类做了个简单介绍,详细设计请查看源码了解详情。

相关推荐

得物可观测平台架构升级:基于GreptimeDB的全新监控体系实践

一、摘要在前端可观测分析场景中,需要实时观测并处理多地、多环境的运行情况,以保障Web应用和移动端的可用性与性能。传统方案往往依赖代理Agent→消息队列→流计算引擎→OLAP存储...

warm-flow新春版:网关直连和流程图重构

本期主要解决了网关直连和流程图重构,可以自此之后可支持各种复杂的网关混合、多网关直连使用。-新增Ruoyi-Vue-Plus优秀开源集成案例更新日志[feat]导入、导出和保存等新增json格式支持...

扣子空间体验报告

在数字化时代,智能工具的应用正不断拓展到我们工作和生活的各个角落。从任务规划到项目执行,再到任务管理,作者深入探讨了这款工具在不同场景下的表现和潜力。通过具体的应用实例,文章展示了扣子空间如何帮助用户...

spider-flow:开源的可视化方式定义爬虫方案

spider-flow简介spider-flow是一个爬虫平台,以可视化推拽方式定义爬取流程,无需代码即可实现一个爬虫服务。spider-flow特性支持css选择器、正则提取支持JSON/XML格式...

solon-flow 你好世界!

solon-flow是一个基础级的流处理引擎(可用于业务规则、决策处理、计算编排、流程审批等......)。提供有“开放式”驱动定制支持,像jdbc有mysql或pgsql等驱动,可...

新一代开源爬虫平台:SpiderFlow

SpiderFlow:新一代爬虫平台,以图形化方式定义爬虫流程,不写代码即可完成爬虫。-精选真开源,释放新价值。概览Spider-Flow是一个开源的、面向所有用户的Web端爬虫构建平台,它使用Ja...

通过 SQL 训练机器学习模型的引擎

关注薪资待遇的同学应该知道,机器学习相关的岗位工资普遍偏高啊。同时随着各种通用机器学习框架的出现,机器学习的门槛也在逐渐降低,训练一个简单的机器学习模型变得不那么难。但是不得不承认对于一些数据相关的工...

鼠须管输入法rime for Mac

鼠须管输入法forMac是一款十分新颖的跨平台输入法软件,全名是中州韵输入法引擎,鼠须管输入法mac版不仅仅是一个输入法,而是一个输入法算法框架。Rime的基础架构十分精良,一套算法支持了拼音、...

Go语言 1.20 版本正式发布:新版详细介绍

Go1.20简介最新的Go版本1.20在Go1.19发布六个月后发布。它的大部分更改都在工具链、运行时和库的实现中。一如既往,该版本保持了Go1的兼容性承诺。我们期望几乎所...

iOS 10平台SpriteKit新特性之Tile Maps(上)

简介苹果公司在WWDC2016大会上向人们展示了一大批新的好东西。其中之一就是SpriteKitTileEditor。这款工具易于上手,而且看起来速度特别快。在本教程中,你将了解关于TileE...

程序员简历例句—范例Java、Python、C++模板

个人简介通用简介:有良好的代码风格,通过添加注释提高代码可读性,注重代码质量,研读过XXX,XXX等多个开源项目源码从而学习增强代码的健壮性与扩展性。具备良好的代码编程习惯及文档编写能力,参与多个高...

Telerik UI for iOS Q3 2015正式发布

近日,TelerikUIforiOS正式发布了Q32015。新版本新增对XCode7、Swift2.0和iOS9的支持,同时还新增了对数轴、不连续的日期时间轴等;改进TKDataPoin...

ios使用ijkplayer+nginx进行视频直播

上两节,我们讲到使用nginx和ngixn的rtmp模块搭建直播的服务器,接着我们讲解了在Android使用ijkplayer来作为我们的视频直播播放器,整个过程中,需要注意的就是ijlplayer编...

IOS技术分享|iOS快速生成开发文档(一)

前言对于开发人员而言,文档的作用不言而喻。文档不仅可以提高软件开发效率,还能便于以后的软件开发、使用和维护。本文主要讲述Objective-C快速生成开发文档工具appledoc。简介apple...

macOS下配置VS Code C++开发环境

本文介绍在苹果macOS操作系统下,配置VisualStudioCode的C/C++开发环境的过程,本环境使用Clang/LLVM编译器和调试器。一、前置条件本文默认前置条件是,您的开发设备已...