Createsequence's Blog

一个努力前进的程序猿


  • 首页

  • 标签

  • 分类

  • 归档

  • 搜索

java集合源码分析(五):Map与AbstractMap

发表于 2020-12-07 | 分类于 java集合容器
字数统计: 4.4k

概述

Map 接口是 java 中两大集合接口之一,相对于 Collection,Map 接口结构规定了所有键值对形式的集合容器。同时,它与 Collection 的子接口 Set 又密切相关,Map 一部分实现依赖于 Set 集合,而 Set 集合的一些实现也依赖于 Map。

Map 接口下有四个主要实现类 TreeMap,HashMap,LinkedMap,Hashtable。基于以上四大实现类,这是他们的类关系图:

Map 接口的类关系图
Map 接口的类关系图

与其相关的还有 Dictionary 类,这是一个已过时的早期键值对集合接口,后期的新集合都基于 Map 接口实现,唯一依赖与他的 Hashtable 因为性能原因也很少被使用,因此这个类是一个过时类。

这是关于 java 集合类源码的第五篇文章。往期文章:

  1. java集合源码分析(一):Collection 与 AbstractCollection
  2. java集合源码分析(二):List与AbstractList
  3. java集合源码分析(三):ArrayList
  4. java集合源码分析(四):LinkedList

一、Map 接口

image-20201207201537859
image-20201207201537859

Map 接口就是所有键值对类型集合接口的最上层接口,他规定了一个所有 Map 类型集合应该实现的抽象方法,同时提供了一个用于视图操作的默认接口类 Entry。

1.抽象方法

阅读全文 »

ArrayList与LinkedList遍历操作问题

发表于 2020-12-04 | 分类于 java集合容器
字数统计: 3k

概述

一个 java 程序猿比较广为人知的小知识 ,是 ArrayList 和 LinkedList 最好使用迭代器删除,而不是遍历删除。

当我们尝试使用 for 循环或者 forEach 进行删除的时候,往往会出现一些意外的情况,导致集合全部删除失败。关于这点,我一直保持知其然不知其所以然的状态,刚好最近刚看完 ArrayList 和 LinkedList 的源码,今天这篇文章,就结合源码,总结一下 ArrayList 和 LinkedList 的几种错误删除。

一、List 集合的 fast-fail 机制

在开始前,我们需要了解一下集合的 fast-fail 机制。

List 接口有一个 AbstractList 抽象类,List 下的所有实现类都直接或间接的继承了它。

在它的成员变量中,有一个变量叫 modCount,当实现类进行结构性操作的时候——一般指会影响底层数据结构的操作,比如删除——就会+1。

在每一个迭代器创建的时候,会从外部获取当前的 modCount赋给迭代器的成员变量 expectedModCount,然后每次调用迭代器的 next()方法,或者其他增删方法都会比较modCount和expectedModCount是否相等,否则就会抛出 ConcurrentModificationException 异常。

这个并发修改检查可以在出现问题是时候快速抛出异常,避免可能错误的数据进入后续的操作。这也是集合操作中大部分 ConcurrentModificationException 异常的来源。

二、ArrayList 的 for 循环删除

阅读全文 »

java集合源码分析(四):LinkedList

发表于 2020-12-03 | 分类于 java集合容器
字数统计: 4.9k

概述

LinkedList 是一个不保证线程安全的、基于双向的双端链表的实现的 List 集合。LinkedList 继承了 AbstractSequentialList 抽象类,在实现 List 接口的同时还实现了 Deque 接口,也正因如此,它也具有队列的特性与方法。

这是关于 java 集合类源码的第四篇文章。往期文章:

  1. java集合源码分析(一):Collection 与 AbstractCollection
  2. java集合源码分析(二):List与AbstractList
  3. java集合源码分析(三):ArrayList

一、LinkedList 的类关系

LinkedList 的类关系
LinkedList 的类关系

LinkedList 实现了 Cloneable ,Serializable 接口,表明它可以拷贝,可以被序列化。

但是和 ArrayList 或者 Vector 相比,因为它是链表,所以无法像数组那样通过下标快速随机访问,故而没有实现 RandomAccess 接口。

他实现了 List 接口,但是也实现了 Queue 的子接口 Deque,因此除了列表,他也具备双端队列的特性。

他的父类不再是 AbstractList,而是另一个继承了 AbstractList 的抽象类 AbstractSequentialList,这个类重写了 AbstractList 的一些方法,使之更适合 LinkedList 这样的链表。

二、AbstractSequentiaList

阅读全文 »

java集合源码分析(三):ArrayList

发表于 2020-12-02 | 分类于 java集合容器
字数统计: 7.7k

概述

ArrayList 是 List 接口下一个基于可扩展数组的实现类,它和它的兄弟类 Vector 有着一样的继承关系,也都能随机访问,但是不同的是不能保证线程安全。

这是关于 java 集合类源码的第三篇文章。往期文章:

  1. java集合源码分析(一):Collection 与 AbstractCollection
  2. java集合源码分析(二):List与AbstractList

一、ArrayList 的类关系

image-20201201161347920
image-20201201161347920

ArrayList 实现了三个接口,继承了一个抽象类,其中 Serializable ,Cloneable 与 RandomAccess 接口都是用于标记的空接口,他的主要抽象方法来自于 List,一些实现来自于 AbstractList。

1.AbstractList 与 List

ArrayList 实现了 List 接口,是 List 接口的实现类之一,他通过继承抽象类 AbstractList 获得的大部分方法的实现。

比较特别的是,理论上父类 AbstractList 已经实现类 AbstractList 接口,那么理论上 ArrayList 就已经可以通过父类获取 List 中的抽象方法了,不必再去实现 List 接口。

网上关于这个问题的答案众说纷纭,有说是为了通过共同的接口便于实现 JDK 代理,也有说是为了代码规范性与可读性的,在 Stack Overflow 上 Why does LinkedHashSet extend HashSet and implement Set 一个据说问过原作者的老哥给出了一个 it was a mistake 的回答,但是这似乎不足以解释为什么几乎所有的容器类都有类似的行为。事实到底是怎么回事,也许只有真正的原作者知道了。

阅读全文 »

java集合源码分析(二):List与AbstractList

发表于 2020-11-27 | 分类于 java集合容器
字数统计: 5.9k

概述

List 应该接口是 Collection 最常被使用的接口了。其下的实现类皆为有序列表,其中主要分为 Vector,ArrayList,LinkedList 三个实现类,其中 Vecotr 又拥有子类 Stack。

从线程安全来说,List 下拥有线程安全的集合类 Vector;从数据结构来说,List 下拥有基于数组实现的 Vector 与 ArrayList,和基于链表实现的 LinkedList。

本篇文章暂不讨论具体的实现类,而将基于 List 接口与其抽象类 AbstractList,了解 List 接口是如何承上启下,进一步从 Collection 抽象到具体的。

这是关于 java 集合类源码的第二篇文章。往期文章:

java集合源码分析(一):Collection 与 AbstractCollection

一、List 接口

List 接口的方法
List 接口的方法

List 接口继承了 Collection 接口,在 Collection 接口的基础上增加了一些方法。相对于 Collection 接口,我们可以很明显的看到,List 中增加了非常多根据下标操作集合的方法,我们可以简单粗暴的分辨一个方法的抽象方法到底来自 Collection 还是 List:参数里有下标就是来自 List,没有就是来自 Collection。

可以说,List 接口在 Collection 的基础上,进一步明确了 List 集合运允许根据下标快速存取的特性。

1.新增的方法

阅读全文 »

java集合源码分析(一):Collection 与 AbstractCollection

发表于 2020-11-25 | 分类于 java集合容器
字数统计: 3k

概述

我们知道,java 中容器分为 Map 集合和 Collection 集合,其中 Collection 中的又分为 Queue,List,Set 三大子接口。

其下实现类与相关的实现类子类数量繁多。我们仅以最常使用的 List 接口的关系为例,简单的画图了解一下 Collection 接口 List 部分的关系图。

List集合的实现类关系图
List集合的实现类关系图

根据上图的类关系图,我们研究一下源码中,类与类之间的关系,方法是如何从抽象到具体的。

一、Iterable 接口

Iterable 是最顶层的接口,继承这个接口的类可以被迭代。

Iterable 接口的方法
Iterable 接口的方法
  • iterator():用于获取一个迭代器。

  • forEach() :JDK8 新增。一个基于函数式接口实现的新迭代方法。

    1
    2
    3
    4
    5
    6
    default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
    action.accept(t);
    }
    }
  • spliterator():JDK8 新增。用于获取一个可分割迭代器。默认实现返回一个IteratorSpliterator类。

这个跟迭代器类似,但是是用于并行迭代的,关于具体的情况可以参考一下掘金的一个讨论:Java8里面的java.util.Spliterator接口有什么用?

阅读全文 »

Redis在SpringBoot的基本使用

发表于 2020-11-24 | 分类于 Redis
字数统计: 3.2k

一、配置

1.添加依赖

在 springboot 启动器中直接添加依赖,或者创建后添加 Maven 依赖:

1
2
3
4
5
6
7
8
9
10
11
12
<!--spring-boot-starter-data-redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!--jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>

注意,默认在springboot 1X 中默认使用的是 Jedis 客户端,而在 springboot 2X 默认使用的就是 Lettuce,我这里使用的是 2X 的版本,所以要添加 Jedis 的客户端依赖。

2.配置连接池

在 springboot 配置文件中配置连接信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
spring:
# redis
redis:
# 数据库索引(默认为0)
database: 0
host: 127.0.0.1
port: 6379
password:
#- 连接超时时间(毫秒)
timeout: 10000
# jedis 线程池设置
jedis:
pool:
# 连接池中的最大空闲连接
max-idle: 8
# 连接池中的最小空闲连接
min-idle: 10
# 连接池最大连接数(使用负值表示没有限制)
max-active: 100
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1

3.配置RedisTemplate

其实这个时候已经可以使用 RedisTemplate 操作 redis了,因为 Spring 已经默认提供 RedisTemplate<Object, Object> 和 RedisTemplate<String, String> 这两个 RedisTemplate<K,V> 的两个子类供我们使用。但是前者要求作为 key 和 value 的类型必须实现 Serializable 接口,而后者需要我们在存入数据之前自己将 key 和 value 变成 string ,所以这默认的 RedisTemplate 并不是那么好用,最好自己再重新配置一个 RedisTemplate<String, Object>:

阅读全文 »

记一次使用策略模式优化代码的经历

发表于 2020-11-21 | 分类于 设计模式
字数统计: 2.2k

一、背景

之前接手了一个 springboot 项目。在我负责的模块中,有一块用户注册的功能,但是比较特别的是这个注册并不是重新注册,而是从以前的旧系统的数据库中同步旧数据到新系统的数据库中。由于这些用户角色来自于不同的系统,所以我需要在注册的时候先判断类型(这个类型由一个专门的枚举类提供),再去调用已经写好的同步方法同步数据。

伪代码大概是这样的:

1
2
3
4
5
6
7
8
9
10
11
public void register(String type, String userId, String projectId, String declareId){
// 判断用户类型
if (UserSynchronizeTyeEnum.A.type.equals(type)) {
// 同步A类型的数据
} else if (UserSynchronizeTyeEnum.A.type.equals(type)) {
// 同步B类型的数据
} else {
throw new RuntimeException("不存在的用户类型");
}
... ...
}

由于用户的类型比较多,所以当我接手的时候已经有8个 if-esle 了,由于这个项目会逐步的跟其他平台对接,要同步的用户类型会越来越多,而且也不能排除什么时候不新增,反而要取消一部分类型的同步情况。

就这个情况来说,一方面每一次新增或删除类型都需要修改 if-else 上逻辑分支,如果需要新增一些同步前的处理的步骤(根据经验这种情况几乎一定会出现的),大概率代码会直接被加在 if-else 方法里头;另一方面,这个业务的需求也有相对稳定的地方:同步方法会不一样,但是一定会根据类型来判断。出于以上考虑,我决定趁现在牵扯范围不大的时候重构一下。

二、思路

1.抽取策略接口和策略类

首先,由于每种用户类型的同步方法是由各模块自己提供的,其实已经算是抽出了策略,只是没有实现一个统一的策略接口。

但是我在这一步遇上了问题:

阅读全文 »

设计模式(四):模板方法模式

发表于 2020-11-21 | 分类于 设计模式
字数统计: 1.4k

概述

在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。

模板方法模式旨在为一些方法的主体部分提供骨架,将具体细节上的一些实现延迟到他的实现类。

JDBC 运用了模板模式。JDK 在实现 List 接口的过程中, AbstractCollection 和 AbstractList 也使用了模板模式。

一、简单实现

举个我在做项目的时候遇到的例子:

假设我们有一个简单的针对 Demo 类的文件导出类,他提供了 Excel 文档的基本导出功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DemoExcelExporter {
// 导出excel
public void export() {
List<Demo> list = getData();
for(Demo d : list) {
handle(d);
}
excel(list);
}

// 1.获取要到导出数据集
public List<Demo> getData() {}
// 2.处理数据集
public void handle(Demo demo) {}
// 3.导出的数据集为excel文件
public void excel(List<Demo> list) {}

}

现在我们需要为 Demo2 和 Demo3 这两个类也添加一个导出 Excel 文档的功能,最简单是方式就是复制黏贴,把 Demo 换成 Demo2 和 Demo3。但是这显然不是我们想要的,根据模板模式,我们可以设置一个 ExcelExporter 抽象类作为模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public abstract class ExcelExporter {
// 导出excel
public void export() {
List<T> list = getData();
for(T t : list) {
handle(t);
}
excel(list);
}

// 1.获取要到导出数据集
public List<T> getData() {}

// 2.处理数据集,改为抽象方法,由子类去实现
public abstract void handle(T demo);

// 3.导出的数据集为excel文件
public void excel(List<T> list) {}

}
阅读全文 »

设计模式(二):策略模式

发表于 2020-11-20 | 分类于 设计模式
字数统计: 2.3k

概述

在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。

在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。

策略模式旨在解决不同逻辑下相同的对象执行不同策略的问题。

当我们遇到同一个方法,里面会根据需要多个逻辑的分支,分支里的行为都不同,但是都服务于同一个功能,这个时候就可以使用策略模式,将行为抽象为一个策略接口中的抽象方法,由接口的实现类——也就是策略类——去实现各中具体的行为。

策略模式也是一种比较常见且好用的设计模式,线程池的拒绝策略就使用了策略模式。

一、简单实现

简单的拿一个根据情况需要导出不同文件的接口举例:

1
2
3
4
5
6
7
8
9
10
11
public void exportFile(String type){
if (type == "excel") {
// 导出excel
} else if (type == "word") {
// 导出word
} else if (type == "pdf") {
// 导出pdf
}else {
throw new RuntimeException("错误的文件类型!");
}
}

这些分支里目的都是导出文件,但是各自有各的实现代码。换而言之,这些不同逻辑分支下的代码只有行为是不同的。现在我们将导出方法抽象成为一个策略接口中的抽象方法,将每个逻辑分支的处理代码都抽成实现策略接口的各个策略类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 文件导出接口
public interface IExportFile(){
void export();
}

// 实现类
public class ExportExcel implements IExportFile{
@Override
public void export() {
// 导出excel
}
}
public class ExportWord implements IExportFile{
@Override
public void export() {
// 导出word
}
}
public class ExportPdf implements IExportFile{
@Override
public void export() {
// 导出pdf
}
}
阅读全文 »
1…345…10

99 日志
15 分类
22 标签
RSS
© 2022 Createsequence
主题 — NexT.Gemini v5.1.4
0%