作者 | 沉默王二

责编 | Elle

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

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

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

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

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

1)使用 List

publicstaticbooleanuseList(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;}publicintindexOf(Object o) { E[] a = this.a;if (o == null) {for (int i = 0; i < a.length; i++)if (a[i] == null)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 中的下标,如果元素为 null,则使用“==”操作符进行判断,否则使用 equals() 方法进行判断。

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

2)使用 Set

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

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

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

publicbooleancontains(Object o){return map.containsKey(o);}publicbooleancontainsKey(Object key){return getNode(hash(key), key) != null;}

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

3)使用一个简单的循环

publicstatic boolean useLoop(String[] arr, String targetValue) {for (String s : arr) {if (s.equals(targetValue))returntrue; }returnfalse;}

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

4)使用 Arrays.binarySearch()

publicstaticbooleanuseArraysBinarySearch(String[] arr, String targetValue){int a = Arrays.binarySearch(arr, targetValue);if (a > 0)returntrue;elsereturnfalse;}

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

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

String[] arr = new String[]{"沉", "默", "王", "二", "真牛逼"};// 使用 Listlong 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);// 使用 SetstartTime = 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: 6useSet: 40useLoop: 2

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

String[] arr = newString[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: 91useSet: 1460useLoop: 70useArraysBinarySearch: 4

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

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

统计结果如下所示:

useList: 1137useSet: 15711useLoop: 1115useArraysBinarySearch: 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 实现的)。哈希表是通过哈希函数来映射的,所以拿到一个关键字,通过哈希函数转换一下,就可以直接从表中取出对应的值——一次直达。

好了各位读者朋友们,以上就是本文的全部内容了。能看到这里的都是最优秀的程序员,我必须要为大家点个赞。

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

【End】

java 数组包含_灵魂拷问:如何检查 Java 数组中是否包含某个值?相关推荐

  1. java 字符串赋值_灵魂拷问:为什么 Java 字符串是不可变的?

    在逛 programcreek 的时候,发现了一些精妙绝伦的主题.比如说:为什么 Java 字符串是不可变的?像这类灵魂拷问的主题,非常值得深思.对于绝大多数的初级程序员来说,往往停留在"知 ...

  2. c++获取数组长度_灵魂拷问:Java如何获取数组和字符串的长度?length还是length()?...

    限时 1 秒钟给出答案,来来来,听我口令:"Java 如何获取数组和字符串的长度?length 还是 length()?" 在逛 programcreek 的时候,我发现了上面这个 ...

  3. stream判断数组长度大于0_java8 stream:检查list集合中是否存在某个值

    目录 import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; public cla ...

  4. java中怎么表示数组中的某个值_简易Java(12):如何高效检查一个数组中是否包含某个值?...

    如何检查一个数组(未排序)中是否包含某个特定的值?在Java中,这是一个非常有用并又很常用的操作.同时,在StackOverflow中,有时一个得票非常高的问题.在得票比较高的几个回答中,时间复杂度差 ...

  5. 数组字段查询不包含_不可不知的可变Java长数组

    前言 有时我们希望将把数据保存在单个连续的数组中,以便快速.便捷地访问数据,但这需要调整数组大小或者对其扩展.Java 数组不能调整大小,只用数组不足以达成目标.可变长原始类型数组需要自己实现.本文将 ...

  6. 独占设备的分配与回收_灵魂拷问:Java对象的内存分配过程是如何保证线程安全的?...

    点击上方"linkoffer", 选择关注公众号高薪职位第一时间送达 作者 l Hollis JVM内存结构,是很重要的知识,相信每一个静心准备过面试的程序员都可以清楚的把堆.栈. ...

  7. java 数组 反射_【译】10. Java反射——数组

    ===================================================================================== 用Java反射来处理数组有时 ...

  8. 灵魂拷问:为什么 Java 字符串是不可变的?

    在逛 programcreek 的时候,发现了一些精妙绝伦的主题.比如说:为什么 Java 字符串是不可变的?像这类灵魂拷问的主题,非常值得深思. 对于绝大多数的初级程序员来说,往往停留在" ...

  9. java数组 序列化_程序猿的日常——Java基础之clone、序列化、字符串、数组

    其实Java还有很多其他的基础知识,在日常工作技术撕逼中也是经常被讨论的问题. 深克隆与浅克隆 在Java中创建对象有两种方式: 一种是new操作符,它创建了一个新的对象,并把对应的各个字段初始化成默 ...

最新文章

  1. 卡尔曼滤波 -- 从推导到应用(一) 转有关键思想 K增益是最小 贺一佳博士
  2. 快速更换一个国内的yum源
  3. nodejs实现的高性能服务器编程,使用NodeJS搭建自己的接口服务器,提交APP的开发效率...
  4. Linux基础第一章 概述
  5. Codeforces Round #686 (Div. 3) E. Number of Simple Paths 基环树 + 容斥
  6. C++(11)--编程实践1-经典养成类游戏简单实践
  7. OpenWRT开发之——对C++的支持(解决库依赖问题)
  8. Dubbo的微内核机制
  9. ipc原理linux,传统的Linux中IPC通信原理
  10. coreos安装php,window_win10系统运行wps无响应的解决方案,wps是我们在日常办公中经常会 - phpStudy...
  11. 没有方案你抱怨;有了方案你会认真看吗?
  12. python笔记记录神器 jupyter notebook
  13. SQL数据库日志文件丢失,日志文件恢复的办法
  14. 初中计算机授课教案模板,初中课程教案模板
  15. Unity—火焰特效制作
  16. 海信电视power android,海信电视工厂模式中“To Fac”设置“U”、“M”参数不完全对比...
  17. stimulsoft 变量
  18. Mac 安装目前最新版本的 macOS Big Sur
  19. Mac ubuntu安装无线驱动
  20. 主动变被动9个例句_初中英语,被动语态的特殊用法考点总结

热门文章

  1. 【程序源代码】小程序-前后开源
  2. Linux内存管理宏观篇(三)物理内存:物理页面
  3. 记一篇迟到的2019年年终总结
  4. 【django】搭建博客教程(2)——Template的使用
  5. 穷爸爸与富爸爸读后感(3)
  6. 编程修养-C语言篇(下)(转)
  7. Handler更新子线程数据
  8. ajax的常用api测试
  9. oracle技术圈熊掌号,熊掌号运营指南:值得关注的4个小技巧!
  10. int是逻辑炸弹吗?