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

如何检查 Java 数组中是否包含某个值?

bigegpt 2025-03-19 10:43 10 浏览

作者 | 沉默王二

本文经授权转载自沉默王二(ID:cmower)

在逛 programcreek 的时候,我发现了一些专注细节但价值连城的主题。比如说:如何检查Java数组中是否包含某个值 ?像这类灵魂拷问的主题,非常值得深入地研究一下。

另外,我想要告诉大家的是,作为程序员,我们千万不要轻视这些基础的知识点。因为基础的知识点是各种上层技术共同的基础,只有彻底地掌握了这些基础知识点,才能更好地理解程序的运行原理,做出更优化的产品。

我曾在某个技术论坛上分享过一篇非常基础的文章,结果遭到了无数的嘲讽:“这么水的文章不值得分享。”我点开他的头像进入他的主页,发现他从来没有分享过一篇文章,不过倒是在别人的博客下面留下过不少的足迹,大多数都是冷嘲热讽。我就纳闷了,技术人不都应该像我这样低调谦逊吗?怎么戾气这么重!

好了,让我们来步入正题。如何检查数组(未排序)中是否包含某个值 ?这是一个非常有用并且经常使用的操作。我想大家的脑海中应该已经浮现出来了几种解决方案,这些方案的时间复杂度可能大不相同。

我先来提供四种不同的方法,大家看看是否高效。

1)使用 List

public static boolean useList(String[] arr, String targetValue) {
return Arrays.asList(arr).contains(targetValue);
}

Arrays 类中有一个内部类 ArrayList(可以通过 Arrays.asList(arr) 创建该实例),其 contains 方法的源码如下所示。

public boolean contains(Object o) {
return indexOf(o) != -1;
}
public int indexOf(Object o) {
E a = this.a;
if (o == ) {
for (int i = 0; i < a.length; i++)
if (a[i] == )
return i;
} else {
for (int i = 0; i < a.length; i++)
if (o.equals(a[i]))
return i;
}
return -1;
}

上面的源码可以看得出,contains 方法调用了 indexOf 方法,如果返回 -1 则表示 ArrayList 中不包含指定的元素,否则就包含。其中 indexOf 方法用来获取元素在 ArrayList 中的下标,如果元素为 ,则使用“==”操作符进行判断,否则使用 equals 方法进行判断。

PS:关于“==”操作符和 equals 方法,可以参照我另外一篇文章《如何比较 Java 的字符串?》

2)使用 Set

public static boolean useSet(String[] arr, String targetValue) {
Set set = new HashSet(Arrays.asList(arr));
return set.contains(targetValue);
}

HashSet 其实是通过 HashMap 实现的,当使用 new HashSet(Arrays.asList(arr)) 创建并初始化了 HashSet 对象后,其实是在 HashMap 的键中放入了数组的值,只不过 HashMap 的值为默认的一个摆设对象。大家感兴趣的话,可以查看一下 HashSet 的源码。

我们来着重看一下 HashSet 的 contains 方法的源码。

public boolean contains(Object o) {
return map.containsKey(o);
}

public boolean containsKey(Object key) {
return getNode(hash(key), key) != ;
}

上面的源码可以看得出,contains 方法调用了 HashMap 的 containsKey 方法,如果指定的元素在 HashMap 的键中,则返回 true;否则返回 false。

3)使用一个简单的循环

public static boolean useLoop(String[] arr, String targetValue) {
for (String s : arr) {
if (s.equals(targetValue))
return true;
}
return false;
}

for-each 循环中使用了 equals 方法进行判断——这段代码让我想起了几个词,分别是简约、高效、清晰。

4)使用 Arrays.binarySearch

public static boolean useArraysBinarySearch(String[] arr, String targetValue) {
int a = Arrays.binarySearch(arr, targetValue);
if (a > 0)
return true;
else
return false;
}

不过,binarySearch 只适合查找已经排序过的数组。

由于我们不确定数组是否已经排序过,所以我们先来比较一下前三种方法的时间复杂度。由于调用 1 次的时间太短,没有统计意义,我们就模拟调用 100000 次,具体的测试代码如下所示。

String arr = new String{"沉", "默", "王", "二", "真牛逼"};
// 使用 List
long startTime = System.nanoTime;
for (int i = 0; i < 100000; i++) {
useList(arr, "真牛逼");
}
long endTime = System.nanoTime;
long duration = endTime - startTime;
System.out.println("useList: " + duration / 1000000);

// 使用 Set
startTime = System.nanoTime;
for (int i = 0; i < 100000; i++) {
useSet(arr, "真牛逼");
}
endTime = System.nanoTime;
duration = endTime - startTime;
System.out.println("useSet: " + duration / 1000000);

// 使用一个简单的循环
startTime = System.nanoTime;
for (int i = 0; i < 100000; i++) {
useLoop(arr, "真牛逼");
}
endTime = System.nanoTime;
duration = endTime - startTime;
System.out.println("useLoop: " + duration / 1000000);

PS:nanoTime 获取的是纳秒级,这样计算的时间就更精确,最后除以 1000000 就是毫秒。换算单位是这样的:1秒=1000毫秒,1毫秒=1000微秒,1微秒=1000纳秒。

统计结果如下所示:

useList: 6
useSet: 40
useLoop: 2

假如把数组的长度增加到 1000,我们再来看一下统计结果。

String arr = new String[1000];

Random s = new Random;
for(int i=0; i< 1000; i++){
arr[i] = String.valueOf(s.nextInt);
}

这时数组中是没有我们要找的元素的。为了做比较,我们顺便把二分查找也添加到统计项目中。

// 使用二分查找
startTime = System.nanoTime;
for (int i = 0; i < 100000; i++) {
useArraysBinarySearch(arr, "真牛逼");
}
endTime = System.nanoTime;
duration = endTime - startTime;
System.out.println("useArraysBinarySearch: " + duration / 1000000);

统计结果如下所示:

useList: 91
useSet: 1460
useLoop: 70
useArraysBinarySearch: 4

我们再把数组的长度调整到 10000。

String arr = new String[10000];

Random s = new Random;
for(int i=0; i< 10000; i++){
arr[i] = String.valueOf(s.nextInt);
}

统计结果如下所示:

useList: 1137
useSet: 15711
useLoop: 1115
useArraysBinarySearch: 5

从上述的统计结果中可以很明显地得出这样一个结论:使用简单的 for 循环,效率要比使用 List 和 Set 高。这是因为把元素从数组中读出来再添加到集合中,就要花费一定的时间,而简单的 for 循环则省去了这部分时间。

在得出这个结论之前,说实话,我最喜欢的方式其实是第一种“使用 List”,因为只需要一行代码 Arrays.asList(arr).contains(targetValue) 就可以搞定。

虽然二分查找(Arrays.binarySearch())花费的时间明显要少得多,但这个结论是不可信的。因为二分查找明确要求数组是排序过的,否则查找出的结果是没有意义的。可以看一下官方的 Javadoc。

Searches the specified array for the specified object using the binary search algorithm. The array must be sorted into ascending order according to the natural ordering of its elements (as by the sort(Object []) method) prior to making this call. If it is not sorted, the results are undefined.

实际上,如果要在一个数组或者集合中有效地确定某个值是否存在,一个排序过的 List 的算法复杂度为 O(logn),而 HashSet 则为 O(1)。

我们再来发散一下思维:怎么理解 O(logn) 和 O(1) 呢?

O(logn) 的算法复杂度,比较典型的例子是二分查找。举个例子,假设现在一堆试卷,已经按照分数从高到底排列好了。现在要查找有没有 79 分的试卷,怎么办呢?可以先从中间找起,因为按照 100 分的卷子来看,79 分大差不差应该就在中间的位置(平均分如果低于 79 说明好学生就比较少了),如果中间这份卷子的分数是 83,那说明 79 分的卷子就在下面的一半,这时候可以把上面那半放在一边了。然后按照相同的方式,每次就从中间开始找,直到找到 79 分的卷子(当然也可能没有 79 分)。

假如有 56 份卷子,找一次,还剩 28 份,再找一次,还剩 14 份,再找一次,还剩 7 份,再找一次,还剩 2 或者 3 份。如果是 2 份,再找一次,就只剩下 1 份了;如果是 3 份,就还需要再找 2 次。

我们知道,log2(32) = 5,log2(64) = 6,而 56 就介于 32 和 64 之间。也就是说,二分查找大约需要 log2(n) 次才能“找到”或者“没找到”。而在算法复杂度里,经常忽略常数,所以不管是以 2 为底数,还是 3 为底数,统一写成 log(n) 的形式。

再来说说 O(1),比较典型的例子就是哈希表(HashSet 是由 HashMap 实现的)。哈希表是通过哈希函数来映射的,所以拿到一个关键字,通过哈希函数转换一下,就可以直接从表中取出对应的值——一次直达。

好了各位读者朋友们,以上就是本文的全部内容了。。

声明:本文为作者投稿,版权归作者个人所有。

北邮教授为你揭秘5G的发展历程、内在规律,并重点阐述新技术在数字经济时代的作用以及对我们每个人的影响,5G时代你绝不能错过的干货课程,立即免费报名:
https://edu.csdn.net/huiyiCourse/detail/1138

相关推荐

最全的MySQL总结,助你向阿里“开炮”(面试题+笔记+思维图)

前言作为一名编程人员,对MySQL一定不会陌生,尤其是互联网行业,对MySQL的使用是比较多的。对于求职者来说,MySQL又是面试中一定会问到的重点,很多人拥有大厂梦,却因为MySQL败下阵来。实际上...

Redis数据库从入门到精通(redis数据库设计)

目录一、常见的非关系型数据库NOSQL分类二、了解Redis三、Redis的单节点安装教程四、Redis的常用命令1、Help帮助命令2、SET命令3、过期命令4、查找键命令5、操作键命令6、GET命...

netcore 急速接入第三方登录,不看后悔

新年新气象,趁着新年的喜庆,肝了十来天,终于发了第一版,希望大家喜欢。如果有不喜欢看文字的童鞋,可以直接看下面的地址体验一下:https://oauthlogin.net/前言此次带来得这个小项目是...

精选 30 个 C++ 面试题(含解析)(c++面试题和答案汇总)

大家好,我是柠檬哥,专注编程知识分享。欢迎关注@程序员柠檬橙,编程路上不迷路,私信发送以下关键字获取编程资源:发送1024打包下载10个G编程资源学习资料发送001获取阿里大神LeetCode...

Oracle 12c系列(一)|多租户容器数据库

作者杨禹航出品沃趣技术Oracle12.1发布至今已有多年,但国内Oracle12C的用户并不多,随着12.2在去年的发布,选择安装Oracle12c的客户量明显增加,在接下来的几年中,Or...

flutter系列之:UI layout简介(flutter-ui-nice)

简介对于一个前端框架来说,除了各个组件之外,最重要的就是将这些组件进行连接的布局了。布局的英文名叫做layout,就是用来描述如何将组件进行摆放的一个约束。在flutter中,基本上所有的对象都是wi...

Flutter 分页功能表格控件(flutter 列表)

老孟导读:前2天有读者问到是否有带分页功能的表格控件,今天分页功能的表格控件详细解析来来。PaginatedDataTablePaginatedDataTable是一个带分页功能的DataTable,...

Flutter | 使用BottomNavigationBar快速构建底部导航

平时我们在使用app时经常会看到底部导航栏,而在flutter中它的实现也较为简单.需要用到的组件:BottomNavigationBar导航栏的主体BottomNavigationBarI...

Android中的数据库和本地存储在Flutter中是怎样实现的

如何使用SharedPreferences?在Android中,你可以使用SharedPreferencesAPI来存储少量的键值对。在Flutter中,使用Shared_Pref...

Flet,一个Flutter应用的实用Python库!

▼Flet:用Python轻松构建跨平台应用!在纷繁复杂的Python框架中,Flet宛如一缕清风,为开发者带来极致的跨平台应用开发体验。它用最简单的Python代码,帮你实现移动端、桌面端...

flutter系列之:做一个图像滤镜(flutter photo)

简介很多时候,我们需要一些特效功能,比如给图片做个滤镜什么的,如果是h5页面,那么我们可以很容易的通过css滤镜来实现这个功能。那么如果在flutter中,如果要实现这样的滤镜功能应该怎么处理呢?一起...

flutter软件开发笔记20-flutter web开发

flutterweb开发优势比较多,采用统一的语言,就能开发不同类型的软件,在web开发中,特别是后台式软件中,相比传统的html5开发,更高效,有点像c++编程的方式,把web设计出来了。一...

Flutter实战-请求封装(五)之设置抓包Proxy

用了两年的flutter,有了一些心得,不虚头巴脑,只求实战有用,以供学习或使用flutter的小伙伴参考,学习尚浅,如有不正确的地方还望各路大神指正,以免误人子弟,在此拜谢~(原创不易,转发请标注来...

为什么不在 Flutter 中使用全局变量来管理状态

我相信没有人用全局变量来管理Flutter应用程序的状态。毫无疑问,我们的Flutter应用程序需要状态管理包或Flutter的基本小部件(例如InheritedWidget或St...

Flutter 攻略(Dart基本数据类型,变量 整理 2)

代码运行从main方法开始voidmain(){print("hellodart");}变量与常量var声明变量未初始化变量为nullvarc;//未初始化print(c)...