java collections_扫盲java.util.Collections工具包,学习排序、二分、洗牌、旋转算法

9a8aadc1839dd3c0f7008b9089beef5b.png

作者:小傅哥
博客:https://bugstack.cn

沉淀、分享、成长,让自己和他人都能有所收获!

一、前言

算法是数据结构的灵魂!

好的算法搭配上合适的数据结构,可以让代码功能大大的提升效率。当然,算法学习不只是刷题,还需要落地与应用,否则到了写代码的时候,还是会for循环+ifelse

当开发一个稍微复杂点的业务流程时,往往要用到与之契合的数据结构和算法逻辑,在与设计模式结合,这样既能让你的写出具有高性能的代码,也能让这些代码具备良好的扩展性。

在以往的章节中,我们把Java常用的数据结构基本介绍完了,都已收录到:跳转 -> 《面经手册》,章节内容下图;

8b9da44a87d666e2abb122ae884c6655.png

那么,有了这些数据结构的基础,接下来我们再学习一下Java中提供的算法工具类,Collections

二、面试题

谢飞机,今天怎么无精打采的呢,还有黑眼圈?

:好多知识盲区呀,最近一直在努力补短板,还有面经手册里的数据结构。

:那数据结构看的差不多了吧,你有考虑 过,数据结构里涉及的排序、二分查找吗?

:二分查找会一些,巴拉巴拉。

:还不错,那你知道这个方法在Java中有提供对应的工具类吗?是哪个!

:这!?好像没注意过,没用过!

:去吧,回家在看看书,这两天也休息下。

飞机悄然的出门了,但这次面试题整体回答的还是不错的,面试官决定下次再给他一个机会。

三、Collections 工具类

java.util.Collections,是java集合框架的一个工具类,主要用于Collection提供的通用算法;排序、二分查找、洗牌等算法操作。

从数据结构到具体实现,再到算法,整体的结构如下图;

fb812494086ae1afd4f3245400a7faab.png

1. Collections.sort 排序

1.1 初始化集合

List<String> list = new ArrayList<String>();
list.add("7");
list.add("4");
list.add("8");
list.add("3");
list.add("9");

1.2 默认排列[正序]

Collections.sort(list);// 测试结果:[3, 4, 7, 8, 9]

1.3 Comparator排序

Collections.sort(list, new Comparator<String>() {@Overridepublic int compare(String o1, String o2) {return o2.compareTo(o1);}
});
  • 我们使用 o2o1 做对比,这样会出来一个倒叙排序。
  • 同时,Comparator还可以对对象类按照某个字段进行排序。
  • 测试结果如下;
[9, 8, 7, 4, 3]

1.4 reverseOrder倒排

Collections.sort(list, Collections.<String>reverseOrder());// 测试结果:[9, 8, 7, 4, 3]
  • Collections.<String>reverseOrder()的源码部分就和我们上面把两个对比的类调换过来一样。c2.compareTo(c1);

1.5 源码简述

关于排序方面的知识点并不少,而且有点复杂。本文主要介绍 Collections 集合工具类,后续再深入每一个排序算法进行讲解。

Collections.sort

集合排序,最终使用的方法:Arrays.sort((E[]) elementData, 0, size, c);

public static <T> void sort(T[] a, int fromIndex, int toIndex,Comparator<? super T> c) {if (c == null) {sort(a, fromIndex, toIndex);} else {rangeCheck(a.length, fromIndex, toIndex);if (LegacyMergeSort.userRequested)legacyMergeSort(a, fromIndex, toIndex, c);elseTimSort.sort(a, fromIndex, toIndex, c, null, 0, 0);}
}
  • 这一部分排序逻辑包括了,旧版的归并排序 legacyMergeSortTimSort 排序。
  • 但因为开关的作用,LegacyMergeSort.userRequested = false,基本都是走到 TimSort 排序 。
  • 同时在排序的过程中还会因为元素的个数是否大于32,而选择分段排序二分插入排序这一部分内容我们后续专门在排序内容讲解

2. Collections.binarySearch 二分查找

b1c871d598c64459b657d47017689731.png
  • 看到这张图熟悉吗,这就是集合元素中通过二分查找定位指定元素5。
  • 二分查找的前提是集合有序,否则不能满足二分算法的查找过程。
  • 查找集合元素5,在这个集合中分了三部;
    • 第一步,(0 + 7) >>> 1 = 3,定位 list.get(3) = 4,则继续向右侧二分查找。
    • 第二步,(4 + 7) >>> 1 = 5,定位 list.get(5) = 6,则继续向左侧二分查找。
    • 第三步,(4 + 4) >>> 1 = 4,定位 list.get(4) = 5,找到元素,返回结果。

2.1 功能使用

@Test
public void test_binarySearch() {List<String> list = new ArrayList<String>();list.add("1");list.add("2");list.add("3");list.add("4");list.add("5");list.add("6");list.add("7");list.add("8");int idx = Collections.binarySearch(list, "5");System.out.println("二分查找:" + idx);
}
  • 此功能就是上图中的具体实现效果,通过 Collections.binarySearch 定位元素。
  • 测试结果:二分查找:4

2.2 源码分析

Collections.binarySearchpublic static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key) {if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)return Collections.indexedBinarySearch(list, key);elsereturn Collections.iteratorBinarySearch(list, key);
}

这段二分查找的代码还是蛮有意思的,list instanceof RandomAccess 这是为什么呢?因为 ArrayList 有实现 RandomAccess,但是 LinkedList 并没有实现这个接口。同时还有元素数量阈值的校验 BINARYSEARCH_THRESHOLD = 5000,小于这个范围的都采用 indexedBinarySearch 进行查找。那么这里其实使用 LinkedList 存储数据,在元素定位的时候,需要get循环获取元素,就会比 ArrayList 更耗时。

Collections.indexedBinarySearch(list, key)

 private static <T> int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) {int low = 0;int high = list.size()-1;while (low <= high) {int mid = (low + high) >>> 1;Comparable<? super T> midVal = list.get(mid);int cmp = midVal.compareTo(key);if (cmp < 0)low = mid + 1;else if (cmp > 0)high = mid - 1;elsereturn mid; // key found}return -(low + 1);  // key not found}

以上这段代码就是通过每次折半二分定位元素,而上面所说的耗时点就是list.get(mid),这在我们分析数据结构时已经讲过。《LinkedList插入速度比ArrayList快?你确定吗?》

Collections.iteratorBinarySearch(list, key)

private static <T> int iteratorBinarySearch(List<? extends Comparable<? super T>> list, T key)
{int low = 0;int high = list.size()-1;ListIterator<? extends Comparable<? super T>> i = list.listIterator();while (low <= high) {int mid = (low + high) >>> 1;Comparable<? super T> midVal = get(i, mid);int cmp = midVal.compareTo(key);if (cmp < 0)low = mid + 1;else if (cmp > 0)high = mid - 1;elsereturn mid; // key found}return -(low + 1);  // key not found
}

上面这段代码是元素数量大于5000个,同时是 LinkedList 时会使用迭代器 list.listIterator 的方式进行二分查找操作。也算是一个优化,但是对于链表的数据结构,仍然没有数组数据结构,二分处理的速度快,主要在获取元素的时间复杂度上O(1) 和 O(n)。

3. Collections.shuffle 洗牌算法

洗牌算法,其实就是将 List 集合中的元素进行打乱,一般可以用在抽奖、摇号、洗牌等各个场景中。

3.1 功能使用

Collections.shuffle(list);Collections.shuffle(list, new Random(100));

它的使用方式非常简单,主要有这么两种方式,一种直接传入集合、另外一种还可以传入固定的随机种子这种方式可以控制洗牌范围范围

3.2 源码分析

按照洗牌的逻辑,我们来实现下具体的核心逻辑代码,如下;

@Test
public void test_shuffle() {List<String> list = new ArrayList<String>();list.add("1");list.add("2");list.add("3");list.add("4");list.add("5");list.add("6");list.add("7");list.add("8");Random random = new Random();for (int i = list.size(); i > 1; i--) {int ri = random.nextInt(i);  // 随机位置int ji = i - 1;              // 顺延System.out.println("ri:" + ri + " ji:" + ji);list.set(ji, list.set(ri, list.get(ji)));        // 元素置换}System.out.println(list);
}

运行结果:

ri:6 ji:7
ri:4 ji:6
ri:1 ji:5
ri:2 ji:4
ri:0 ji:3
ri:0 ji:2
ri:1 ji:1
[8, 6, 4, 1, 3, 2, 5, 7]

这部分代码逻辑主要是通过随机数从逐步缩小范围的集合中找到对应的元素,与递减的下标位置进行元素替换,整体的执行过程如下;

86e8d0cde214a2431ac76cd8b0f01e09.png

4. Collections.rotate 旋转算法

旋转算法,可以把ArrayList或者Linkedlist,从指定的某个位置开始,进行正旋或者逆旋操作。有点像把集合理解成圆盘,把要的元素转到自己这,其他的元素顺序跟随。

4.1 功能应用

List<String> list = new ArrayList<String>();
list.add("7");
list.add("4");
list.add("8");
list.add("3");
list.add("9");
Collections.rotate(list, 2);
System.out.println(list);

这里我们将集合顺序;7、4、8、3、9,顺时针旋转2位,测试结果如下;逆时针旋转为负数

测试结果

[3, 9, 7, 4, 8]

通过测试结果我们可以看到,从元素7开始,正向旋转了两位,执行效果如下图;

483d85e5b22a829b248608a1685dcdb6.png

4.2 源码分析

public static void rotate(List<?> list, int distance) {if (list instanceof RandomAccess || list.size() < ROTATE_THRESHOLD)rotate1(list, distance);elserotate2(list, distance);
}

关于旋转算法的实现类分为两部分;

  1. Arraylist 或者元素数量不多时,ROTATE_THRESHOLD = 100,则通过算法rotate1实现。
  2. 如果是 LinkedList 元素数量又超过了 ROTATE_THRESHOLD,则需要使用算法rotate2实现。

那么,这两个算法有什么不同呢?

4.2.1 旋转算法,rotate1

private static <T> void rotate1(List<T> list, int distance) {int size = list.size();if (size == 0)return;distance = distance % size;if (distance < 0)distance += size;if (distance == 0)return;for (int cycleStart = 0, nMoved = 0; nMoved != size; cycleStart++) {T displaced = list.get(cycleStart);int i = cycleStart;do {i += distance;if (i >= size)i -= size;displaced = list.set(i, displaced);nMoved ++;} while (i != cycleStart);}
}

这部分代码看着稍微多一点,但是数组结构的操作起来并不复杂,基本如上面圆圈图操作,主要包括以下步骤;

  1. distance = distance % size;,获取旋转的位置。
  2. for循环和dowhile,配合每次的旋转操作,比如这里第一次会把0位置元素替换到2位置,接着在dowhile中会判断i != cycleStart,满足条件继续把替换了2位置的元素继续往下替换。直到一轮循环把所有元素都放置到正确位置。
  3. 最终list元素被循环替换完成,也就相当从某个位置开始,正序旋转2个位置的操作。

4.2.2 旋转算法,rotate2

private static void rotate2(List<?> list, int distance) {int size = list.size();if (size == 0)return;int mid =  -distance % size;if (mid < 0)mid += size;if (mid == 0)return;reverse(list.subList(0, mid));reverse(list.subList(mid, size));reverse(list);
}

接下来这部分源码主要是针对大于100个元素的LinkedList进行操作,所以整个算法也更加有意思,它的主要操作包括;

  1. 定位拆链位置,-distance % size + size,也就是我们要旋转后找到的元素位置
  2. 第一次翻转,把从位置0到拆链位置
  3. 第二次翻转,把拆链位置到结尾
  4. 第三次翻转,翻转整个链表

整体执行过程,可以参考下图,更方便理解;

da14e77124aed22e9608fa33a3b54889.png

5. 其他API介绍

这部分API内容,使用和设计上相对比较简单,平时可能用的时候不多,但有的小伙伴还没用过,就当为你扫盲了。

5.1 最大最小值

String min = Collections.min(Arrays.asList("1", "2", "3"));
String max = Collections.max(Arrays.asList("1", "2", "3"));
  • Collections 工具包可以进行最值的获取。

5.2 元素替换

 List<String> list = new ArrayList<String>();list.add("7");list.add("4");list.add("8");list.add("8");Collections.replaceAll(list, "8", "9");// 测试结果: [7, 4, 9, 9]
  • 可以把集合中某个元素全部替换成新的元素。

5.3 连续集合位置判断

@Test
public void test_indexOfSubList() {List<String> list = new ArrayList<String>();list.add("7");list.add("4");list.add("8");list.add("3");list.add("9");int idx = Collections.indexOfSubList(list, Arrays.asList("8", "3"));System.out.println(idx);// 测试结果:2
}

在使用String操作中,我们知道"74839".indexOf("8");,可以获取某个元素在什么位置。而在ArrayList集合操作中,可以获取连续的元素,在集合中的位置。

5.4 synchronized

List<String> list = Collections.synchronizedList(new ArrayList<String>());
Map<String, String> map = Collections.synchronizedMap(new HashMap<String, String>());
  • 这个很熟悉吧,甚至经常使用,但可能会忽略这些线程安全的方法来自于 Collections 集合工具包。

四、总结

  • 本章节基本将java.util.Collections工具包中的常用方法介绍完了,以及一些算法的讲解。这样在后续需要使用到这些算法逻辑时,就可以直接使用并不需要重复造轮子。
  • 学习数据结构、算法、设计模式,这三方面的知识,重点还是能落地到日常的业务开发中,否则空、假、虚,只能适合吹吹牛,并不会给项目研发带来实际的价值。
  • 懂了就是真的懂,别让自己太难受。死记硬背谁也受不了,耗费了大量的时间,知识也没有吸收,学习一个知识点最好就从根本学习,不要心浮气躁。

五、系列推荐

  • DDD领域驱动设计落地方案
  • 重学Java设计模式(22个真实开发场景)
  • 面经手册(上最快的车,拿最贵的offer)
  • 字节码编程(非入侵式全链路监控实践)

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

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

相关文章

如何进行MaxCompute 用户认证?

您可以通过以下两种方式进行用户认证。 检查请求Request发送者的真实身份 即请求发送后&#xff0c;参数包括用户名和密码&#xff0c;服务端会验证用户名和密码是否正确&#xff0c;以此判断Request发送者的身份是否可信。 使用此方法有可能在请求时间较长的情况下&#xf…

Linux Shell脚本专栏_MySQL数据库备份_09

文章目录一、MySQL数据库备份单循环1. 安装mysql2. 配置mysql环境变量3. 刷新环境变量4. 创建数据库和表lue5. 脚本制作6. 运行脚本7. 查看备份的sql文件7. 脚本升级动态传参8. 运行脚本9. 查看备份的sql文件二、MySQL数据库表备份多循环2.1. 脚本制作2.2. 运行脚本2.3. 指定目…

【当头棒喝】你是真的了解云计算吗?

戳蓝字“CSDN云计算”关注我们哦&#xff01;作者 | 全球物联网观察责编 | 阿秃对于“云计算”这个名词&#xff0c;大家早已耳熟能详&#xff0c;而且出场率越来越高&#xff01;但对于很多新入行的“小白”来说&#xff0c;其中涉及的各种概念可能令人毫无头绪&#xff0c;这…

专注数据,打造阿里云Elasticsearch“一站式”数据服务体系

众所周知&#xff0c;Elasticsearch的问世使得各种结构、非结构数据得以实现实时搜索、分析的可能&#xff0c;越来越多的用户使用ES集群&#xff08;即Elasticseach集群&#xff0c;下文均简称ES集群&#xff09;实现数据的升值与挖掘。而用户在开发的过程中总是面临数据导入、…

java8 lambda maplist排序_「java8系列」流式编程Stream

前言「Java8系列」神秘的Lambda「Java8系列」神奇的函数式接口继上两篇之后&#xff0c;本文已经java8系列的第三篇了。本篇文章比较长&#xff0c;但我希望大家都能认真读完。读不完可以先收藏&#xff0c;在找时间读。没看过前两篇的可以点上边的链接看看&#xff0c;前两篇文…

Centos7 下载、安装、配置、启动部署

文章目录1. 下载tomcat2. 解压tomcat3. 重命名tomcat4. 配置tomcat环境变量5. 刷新配置文件6. 启动tomcat1. 下载tomcat wget http://mirror.bit.edu.cn/apache/tomcat/tomcat-9/v9.0.31/bin/apache-tomcat-9.0.31.tar.gz2. 解压tomcat [rootly-01 ~]# tar -zxf apache-tomca…

世界杯千万级直播高稳定的挑战和实践

今年夏天&#xff0c;俄罗斯世界杯召开。在刚刚落幕的重庆云栖飞天技术汇专场中&#xff0c;阿里视频云技术专家裘良科&#xff0c;就世界杯这个话题&#xff0c;跟参会嘉宾一起探讨了千万级直播高稳定的挑战和相关实践&#xff0c;本文为演讲全文。 2018年俄罗斯世界杯从6月14…

会议邀请 | 10月25日北京,首期D2iQ云原生主题论坛正式启动

在开始今天的内容之前&#xff0c;先问大家一个问题&#xff1a;云原生究竟是什么&#xff1f; 一般来说&#xff0c;“云原生”是一种构建和运行应用程序的方法&#xff0c;它利用了云计算交付模型的优势。CNCF&#xff08;云原生计算基金会&#xff09;将“云原生”定义为使…

Flutter快速上车之Widget

Flutter作为一种全新的响应式&#xff0c;跨平台&#xff0c;高性能的移动开发框架。从开源以来&#xff0c;已经得到越来越多开发者的喜爱。闲鱼是最早一批与谷歌展开合作&#xff0c;并在重要的商品详情页中使用上线的公司。一路走来&#xff0c;积累了大量的开发经验。虽然越…

Linux Shell脚本专栏_自动发布Java项目(tomcat)_10

文章目录一、需求背景及实现流程1. 需求背景2. 实现流程二、软件准备2.1. 公共工具包yum下载2.2. tomcat安装及配置2.3. maven安装及配置2.4. 安装mysql2.5. order项目配置2.6. 脚本制作2.7. 运行脚本一、需求背景及实现流程 1. 需求背景 order项目代码已经到版本仓库中&…

[Phoenix] 十、全局索引设计实践

概述 全局索引是Phoenix的重要特性&#xff0c;合理的使用二级索引能降低查询延时&#xff0c;让集群资源得以充分利用。 本文将讲述如何高效的设计和使用索引。 全局索引说明 全局索引的根本是通过单独的HBase表来存储数据表的索引数据。我们通过如下示例看索引数据和主表数…

云原生数据库崛起,阿里云POLARDB当选世界互联网领先科技成果

戳蓝字“CSDN云计算”关注我们哦&#xff01;作者 | 晶少责编 | 阿秃10月20日&#xff0c;在第六届世界互联网大会上&#xff0c;阿里云自研数据库POLARDB当选世界互联网领先科技成果&#xff0c;POLARDB解决了企业在云时代的数据库难题&#xff0c;帮助企业在数小时内完成上云…

gitee提交代码_git 版本控制,github和gitee

3.4 git 版本控制# 查看本地状态git status # 修改README.txt &#xff0c;添加一行,保存&#xff0c;添加到暂存区git add README.txt # 查看当前仓库某一个文件的版本git diff README.txt# 提交到本地仓库git commit README.txt -m second Commit# 查看当前仓库某一个文件版本…

IT 拉呱室 | 论我遇到的最刺激的bug【长期福利站】

戳蓝字“CSDN云计算”关注我们哦&#xff01;套路的最高境界是没有套路&#xff1a;这是一个只送福利的长期互动栏目【如果你们让我尴尬&#xff0c;我就哭给你们看】。书籍、键盘、鼠标、鼠标垫、CSDN 20周年纪念T恤、技术沙龙门票、线上公开课名额……你敢一直在&#xff0c;…

“百变”Redis带你见识不同场景下的产品技术架构

2018飞天技术汇24期-云数据库Redis产品发布会&#xff0c;由阿里云数据库技术组技术专家王欢、怀听、梁盼分别带来以“Redis全球多活产品”、“Redis混合存储产品”、“Redis多线程性能增强版”为题的演讲。本文对Redis进行了简单的介绍&#xff0c;进而针对不同的应用场景研制…

阿里云PyODPS 0.7.18发布,针对聚合函数进行优化同时新增对Python 3.7支持

近日&#xff0c;阿里云发布PyODPS 0.7.18&#xff0c;主要是针对聚合函数进行优化同时新增对Python 3.7支持。 PyODPS是MaxCompute的Python版本的SDK&#xff0c;SDK的意思非常广泛&#xff0c;辅助开发某一类软件的相关文档、范例和工具的集合都可以叫做“SDK”。 PyODPS在这…

centos安装rabbitmq_【SpringBoot MQ系列教程】RabbitMq 初体验

SpringBoot 系列教程之 RabbitMq 初体验​mp.weixin.qq.commq 在异步解耦削峰的优势非常突出&#xff0c;现在很多的项目都会用到&#xff0c;掌握 mq 的知识点&#xff0c;了解如何顺畅的使用 mq&#xff0c;可以说是一个必备的职业技能点了接下来我们进入 rabbitmq 的学习过程…

java rpc与webservice_RPC体系,RPC和WebService的区别详解

RPC和WebService的关系RPC(Remote Procedure Call)— 远程过程调用&#xff0c;是一个很大的概念, 它是一种通过网络从远程计算机程序上跨语言跨平台的请求服务&#xff0c;rpc能省略部分接口代码的开发&#xff0c;可以跨机器之间访问对象(java rmi)&#xff0c;可以有更方便的…

免费公测中-GPU数据库SQream DB正式上线云市场

业内领先的GPU 数据库服务SQream DB在阿里云云市场正式开启免费公测&#xff01;SQream DB是一款由阿里战略投资的以色列SQream公司提供&#xff0c;能够支撑海量数据高速分析的业内领先的GPU数据库。通过将计算密集型操作卸载到GPU上&#xff0c;与业界的解决方案相比&#xf…

唏嘘!2019榜单出炉:铁打的Python连续3年第一,它居然跌出前十?

IEEE Spectrum2019年度编程语言排行榜最近刚刚出炉&#xff0c;Python不出意外的又拿了个第一&#xff0c;但是意料之外的是&#xff0c;曾经大火的PHP&#xff0c;居然跌出了前十&#xff01;PHP曾被大家称为“世界上最好的编程语言”&#xff0c;去年排名第六&#xff0c;前年…