十、Redis面试题
1、为什么要用缓存
使用缓存的目的就是提升读写性能。而实际业务场景下,更多的是为了提升读性能,带来更好的性能,带来更高的并发量。Redis的读写性能比Mysql好的多,我们就可以把Mysql中的热点数据缓存到Redis中,提升读取性能,同时也减轻了Mysql的读取压力。
2、使用 Redis有哪些好处?
具有以下好处:
读取速度快,因为数据存在内存中,所以数据获取快;
支持多种数据结构,包括字符串、列表、集合、有序集合、哈希等;
支持事务,且操作遵守原子性,即对数据的操作要么都执行,要么都不支持;
还拥有其他丰富的功能,队列、主从复制、集群、数据持久化等功能。
3、什么是 Redis?
Redis 是一个开源(BSD 许可)、基于内存、支持多种数据结构的存储系统,可以作为数据库、缓存和消息中间件。它支持的数据结构有字符串(strings)、哈希(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)等,除此之外还支持 bitmaps、hyperloglogs 和地理空间(geospatial )索引半径查询等功能。
4、为什么Redis单线程模型效率也能那么高?
C语言实现,效率高
纯内存操作
基于非阻塞的IO复用模型机制
单线程的话就能避免多线程的频繁上下文切换问题
丰富的数据结构(全称采用hash结构,读取速度非常快,对数据存储进行了一些优化,比如亚索表,跳表等)
5、为什么 Redis 需要把所有数据放到内存中?
Redis 将数据放在内存中有一个好处,那就是可以实现最快的对数据读取,如果数据存储在硬盘中,磁盘 I/O 会严重影响 Redis 的性能。而且 Redis 还提供了数据持久化功能,不用担心服务器重启对内存中数据的影响。其次现在硬件越来越便宜的情况下,Redis 的使用也被应用得越来越多,使得它拥有很大的优势。
6、Redis 的同步机制了解是什么?
Redis 支持主从同步、从从同步。如果是第一次进行主从同步,主节点需要使用 bgsave 命令,再将后续修改操作记录到内存的缓冲区,等 RDB 文件全部同步到复制节点,复制节点接受完成后将RDB 镜像记载到内存中。等加载完成后,复制节点通知主节点将复制期间修改的操作记录同步到复制节点,即可完成同步过程。
7、说一下Redis有什么优点和缺点
优点
速度快:因为数据存在内存中,类似于HashMap ,HashMap的优势就是查找和操作的时间复杂度都是O (1) 。
支持丰富的数据结构:支持 String ,List,Set,Sorted Set,Hash 五种基础的数据结构。
持久化存储:Redis 提供 RDB 和 AOF 两种数据的持久化存储方案,解决内存数据库最担心的万一 Redis 挂掉,数据会消失掉
高可用:内置 Redis Sentinel ,提供高可用方案,实现主从故障自动转移。 内置Redis Cluster,提供集群方案,实现基于槽的分片方案,从而支持更大的 Redis 规模。
丰富的特性:Key过期、计数、分布式锁、消息队列等。
缺点
由于 Redis 是内存数据库,所以,单台机器,存储的数据量,跟机器本身的内存大小。虽然 Redis本身有 Key 过期策略,但是还是需要提前预估和节约内存。如果内存增长过快,需要定期删除数据。
如果进行完整重同步,由于需要生成 RDB文件,并进行传输,会占用主机的 CPU ,并会消耗现网的带宽。不过 Redis2.8 版本,已经有部分重同步的功能,但是还是有可能有完整重同步的。比如,新上线的备机。
修改配置文件,进行重启,将硬盘中的数据加载进内存,时间比较久。在这个过程中,Redis不能提供服务。
8、Redis的淘汰策略
Redis中共有八种内存淘汰策略:
Volatile-lru: 设置了过期时间的Key使用了LRU算法淘汰;
Allkeys-lru: 所有key使用LRU算法;
Volatile-lfu: 设置了过期时间的key使用了LFU算法淘汰;
Allkeys-lfu: 所有key使用了LFU算法淘汰;
Volatile-random: 设置了过期时间的key使用随机淘汰;
Allkeys-random: 所有key使用随机淘汰;
Volatile-ttl: 设置了过期时间的key根据过期时间淘汰,越早过期越早淘汰;
Noeviction: 默认策略,当内存达到设置的最大值时,所有申请内存的操作都会报错,(如set,ipush等),只读操作如get命令可以正常执行.
主要是4种算法,针对不同的key,形成的策略。
算法:
lru 最近很少的使用的key(根据时间,最不常用的淘汰)
lfu 最近很少的使用的key (根据计数器,用的次数最少的key淘汰)
random 随机淘汰
ttl 快要过期的先淘汰
9、Redis 为什么设计成单线程的?
多线程处理会涉及到锁,并且多线程处理会涉及到线程切···换而消耗 CPU。采用单线程,避免了不必要的上下文切换和竞争条件。其次 CPU 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存或者网络带宽。
10、Redis五种数据类型
String:key-value(做缓存)
Hash:key-fields-values(做缓存)
List:有顺序可重复Set:无顺序,不能重复
SortedSet:有顺序,不能重复
11、Redis集群一般需要几台服务器?
因为redis集群至少需要三个节点,要保证集群的高可用,每个节点都要一个备份机.理论上也需要6台虚拟机
12、什么是缓存穿透,如何避免
一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透
解决方案
对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存
对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤
13、什么是缓存雪崩,如何避免
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃
解决方案:
在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待
做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期
不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀
14、Redis 的持久化机制是什么?各自的优缺点?
持久化机制:
RDB(默认)Redis DataBase
AOF Append Only File
RDB:
RDB是Redis默认的持久化方式。
工作机制:每隔一段时间,就把内存中的数据保存到硬盘上的指定文件中。对应产生的数据文件为dump.rdb
触发RDB的方式有两种:手动触发和自动触发
优点:
只有一个文件 dump.rdb,方便持久化。
容灾性好,一个文件可以保存到安全的磁盘。
性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
相对于数据集大时,比 AOF 的启动效率更高。
缺点:
数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候。如果redis要故障时要尽可能少的丢失数据,RDB没有AOF好,例如1:00进行的快照,在1:10又要进行快照的时候宕机了,这个时候就会丢失10分钟的数据。
RDB每次fork出子进程来执行RDB快照生成文件时,如果文件特别大,可能会导致客户端提供服务暂停数毫秒或者几秒
AOF:
AOF 是 以日志的形式来记录每个写操作,将每一次对数据进行修改,都把新建、修改数据的命令保存到指 定文件中。Redis 重新启
动时读取这个文件,重新执行新建、修改数据的命令恢复数据。
当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复
优点:
数据安全,AOF持久化可以配置 appendfsync 属性,有 always,每进行一次 命令操作就记录到 AOF文件中一次。
通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题。
AOF日志文件的命令通过非常可读的方式进行记录,这个非常适合做灾难性的误删除紧急恢复,如果某人不小心用flushall命令
清空了所有数据,只要这个时候还没有执行rewrite,那么就可以将日志文件中的flushall删除,进行恢复
缺点:
对于同一份文件AOF文件比RDB数据快照要大。
AOF开启后支持写的QPS会比RDB支持的写的QPS低,因为AOF一般会配置成每秒fsync操作,每秒的fsync操作还是很高的、
数据恢复比较慢,不适合做冷备。
十一、中间件相关面试题
RabbitMQ
1、MQ的优势
应用解耦 : 服务与服务之间不再约定协议而对接接口,而是通过生产者/消费者的模式让中间件的MQ来对接两边的数据通信实现解耦合(扩展性更强)。
一步提速: 常见的服务通信:需要阻塞成功获取到响应状态在写入数据到数据库,阻塞同步往下执行。加入MQ后:提升用户体验和系统吞吐量。
削峰填谷 :
常见的服务通信:当一瞬间有5000个请求给到服务器时,服务器最大能处理1000请求,受不了了当场去世。
加入MQ后:先把请求丢到队列中等待处理,系统每秒从mq中拉取1000个请求进行处理(刚好卡在能处理的1000个,真实压榨案例),这样就变成了从1秒钟处理5000个请求的高峰期,拆分成了5秒钟,每秒钟处理1000请求的平缓处理器,哦不,是满载处理器。
2、你们项目中用到过 MQ 吗?谈谈你对 MQ 的理解?
我们项目中使用 MQ 主要是做一些异步的操作,比如说我们的接口是属于http协议接口,它是同步调用,如果说接口响应比较慢的情况下,会导致客户端同步阻塞等待,客户端同步阻塞等待的情况下,会引发客户端超时,客户端一旦发生超时,就会导致客户端触发一些重试的策略,而在触发重试的策略过程中会导致业务可能会发生重复执行,所以我们就需要注意一些幂等性问题,而接口因为它执行比较耗时,所以呢,我们会将一些执行比较耗时的业务逻辑代码把他直接改成使用 mq 异步去实现,能够提高就是我们的 http 协议接口的响应速度,比如说异步的去扣库存呢,异步的去发短信呢这些场景下我们都是使用到 MQ来进行实现落地的。
3、常用的MQ
(1)ActiveMQ:支持万级的吞吐量,较成熟完善;
(2)RabbitMQ:延时低,微妙级延时,
(3)RocketMQ:阿里维护的消息中间件,可以达到十万级的吞吐量,支持分布式事务。
(4)Kafka:阿里维护的消息中间件,可以达到十万级的吞吐量,支持分布式事务。
4、MQ消费者消费消息的顺序一致性问题?
生产者投递消息过程当中可以设定一个消息 key,相同的业务逻辑呢可以设定一个相同的消息 key 在做 hash 的时候最终都会落地到同一个分区来就行存放,最终就被同一个消费者进行消费,所以想解决消息顺序这些问题,它的核心思想就是,让我们的这个消息投递存放到同一个分区里面,最终被同一个消费者消费。
5、RabbitMQ如何保证数据⼀致性?
⽣产者确认机制:消息持久化后异步回调通知⽣产者,保证消息已经发出去;
消息持久化:设置消息持久化;
消费者确认机制:消费者成功消费消息之后,⼿动确认,保证消息已经消费。
6、如何确保消息正确地发送⾄RabbitMQ?
RabbitMQ使⽤发送⽅确认模式,确保消息正确地发送到RabbitMQ。
发送⽅确认模式:将信道设置成confirm模式(发送⽅确认模式),则所有在信道上发布的消息都会被指派⼀个唯⼀的ID。⼀旦消息被投递到⽬的队列后,或者消息被写⼊磁盘后(可持久化的消息),信道会发送⼀个确认给⽣产者(包含消息唯⼀ID)。如果RabbitMQ发⽣内部错误从⽽导致消息丢失,会发送⼀条nack(not acknowledged,未确认)消息。发送⽅确认模式是异步的,⽣产者应⽤程序在等待确认的同时,可以继续发送消息。当确认消息到达⽣产者应⽤程序,⽣产者应⽤程序的回调⽅法就会被触发来处理确认消息。
7、如何避免消息重复投递或重复消费?
在消息⽣产时,MQ内部针对每条⽣产者发送的消息⽣成⼀个inner-msg-id,作为去重和幂等的依据(消息投递失败并重传),避免重复的消息进⼊队列;在消息消费时,要求消息体中必须要有⼀个bizId(对于同⼀业务全局唯⼀,如⽀付ID、订单ID、帖⼦ID等)作为去重和幂等的依据,避免同⼀条消息被重复消费。
8、RabbitMQ的工作模式
一.simple模式(即最简单的收发模式)
二.work工作模式(资源的竞争)
三.publish/subscribe发布订阅(共享资源)
四.routing路由模式
五.topic 主题模式(路由模式的一种)
ElasticSearch
1、elasticsearch 了解多少,说说你们公司 es 的集群架构,索引数据大小,分片有多少,以及一些调优手段
面试官:想了解应聘者之前公司接触的 ES 使用场景、规模,有没有做过比较大规模的索引设计、规划、调优。
解答:如实结合自己的实践场景回答即可。
比如:ES 集群架构 13 个节点,索引根据通道不同共 20+索引,根据日期,每日递增 20+,索引:10分片,每日递增 1 亿+数据,每个通道每天索引大小控制:150GB 之内。
仅索引层面调优手段:
根据业务增量需求,采取基于日期模板创建索引,通过 roll over API 滚动索引;
使用别名进行索引管理;
每天凌晨定时对索引做 force_merge 操作,以释放空间;
采取冷热分离机制,热数据存储到 SSD,提高检索效率;冷数据定期进行 shrink操作,以缩减存储;
采取 curator 进行索引的生命周期管理;
仅针对需要分词的字段,合理的设置分词器;
Mapping 阶段充分结合各个字段的属性,是否需要检索、是否需要存储等。
2、为什么要使用elasticsearch
因为在我们课程中的数据,将来会非常多,所以采用以往的模糊查询,模糊查询前置配置,会放弃索引,导致课程查询是全表扫面,在百万级别的数据库中,效率非常低下,而我们使用ES做一个全文索引,我们将经常查询的课程的某些字段,比如说课程名,描述、价格还有id这些字段我们放入我们索引库里,可以提高查询速度。
3、项目中如何使用elasticsearch
在服务启动时将课程数据从数据库查询出来压入es中,在用户进行课程搜索时,可以通过课程关键词、讲师名称和价格区间进行数据搜索,通过关键词进行搜索时,使用了es 的高亮和分页的工具类,对查询出来的数据与查询字段相同的进行高亮处理并通过匹配度的高低进行排序。
4、谈谈分词与倒排索引的原理
首先说分词是给检索用的。
英文:一个单词一个词,很简单。I am a student,词与词之间空格分隔。
中文:我是学生,就不能一个字一个字地分,我-是-学生。这是好分的。还有歧义的,使用户放心,使用-户,使-用户。人很容易看出,机器就难多了。所以市面上有各种各样的分词器,一个强调的效率一个强调的准确率。
倒排索引:倒排针对的是正排。
正排就是我记得我电脑有个文档,讲了 ES 的常见问题总结。那么我就找到文档,从上往下翻页,找到ES的部分。通过文档找文档内容。
倒排:一个txt文件ES的常见问题 -> D:/分布式问题总结.doc。
所以倒排就是文档内容找文档。当然内容不是全部的,否则也不需要找文档了,内容就是几个分词而已。这里的txt就是搜索引擎。
Sharing-JDBC
1、Shards&Replicas分片和复制:
****分片:****将索引划分成多份,可以放置在集群任何节点上。1、允许水平分割/扩展内容数量;2、允许在分片上进行分布式、并行。
****复制:****创建分片的一份或多份拷贝。1、在分片/节点失败的情况下,提高了高可用性;2、搜索可以在所有复制上运行;3、分片和复制的数量可以在索引创建的时候指定。4、复制数量可以在之后动态改变,分片数量事后不能再改变。
2、、分库分表
*作用:缓解单机和单库带来的性能瓶颈和压力,突破网络IO、硬件资源、连接数的瓶颈。*
****方式:****垂直分库、水平分库、垂直分表、水平分表。
*垂直拆分原则:*(1)不常用的字段单独放一张表。
(2)text、blob等大字段拆分放在附表。
(3)经常组合查询的列放在同一张表中。
3、垂直分库
按照业务进行分类,分不到不同的数据库上,每个库可以放在不同的数据库上(专库专用)。
*好处:*(1)业务层面解耦
(2)对不同的业务数据进行分级管理、维护、监控、扩展。
(3)高并发场景下,提升IO、数据库连接数、降低单机硬件资源的瓶颈。
4、水平分库
把同一张表的数据按一定规则拆到不同的数据库中,每个库可以放在不同的服务器上。
****好处:****解决了单库大数据,高并发的性能瓶颈,提高了系统的稳定性和可用性。
5、垂直分表
将一个表按照字段分成多表,每个表存储一部分字段。
****好处:(****1)避免IO争抢,减少锁表的几率。
(2)充分发挥热门数据的操作效率。
6、水平分表
在同一个数据库内,把同一个表的数据按一定规则拆到多个表中。
****好处:****优化单一表数据量过大而产生的性能问题,避免IO争抢,减少锁表的几率。
7、Sharing-JDBC定义
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7CeRcw3T-1676522455060)(https://edu–learn.oss-cn-beijing.aliyuncs.com/weixin/%E5%9B%BE%E7%89%877.png)]
*当当网研发的开源分布式数据库中间件。*
Docker
1、简介
Docker是一个开源的应用容器引擎,开发者可以打包自己的应用到容器里面,然后迁移到其他机器的docker应用中,可以实现快速部署。
2、Docker基本概念
Client(客户端): 是Docker的用户端,可以接受用户命令和配置标识,并与Docker daemon通信。
Images(镜像): 是一个只读模板,含创建Docker容器的说明,它与操作系统的安装光盘有点像。
Containers(容器): 镜像的运行实例,镜像与容器的关系类比面向对象中的类和对象。
Registry(仓库): 是一个集中存储与分发镜像的服务。最常用的Registry是官方的Docker Hub 。
十二、数据结构与算法面试题
1.1 概述
数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技术有关。
JAVA中常见的数据结构有
数组
栈(Stack)
队列(Queue)
链表(LinkedList)
树(Tree)
哈希表(Hash)
1.2 数组
数组是内存中连续保存的多个相同类型的元素的数据结构。通过下标索引访问数组中的元素,索引从0开始。根据维度可以分为1维数组、2维数组……N维数组。
优点
通过下标直接定位元素位置,查找效率高。
缺点
长度固定
数据类型都必须相同
插入跟删除元素时效率比较低。
适合场景:大量查询,很少删除和插入。
1.3 栈
1.3.1 概述
栈结构只能在一端操作,该操作端叫做栈顶,另一端叫做栈底。栈结构按照“后进先出”(Last In First Out, LIFO)的方式处理结点数据。
1.4 队列
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
1.5 链表
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针连接次序实现的。每一个链表都包含多个节点,节点又包含两个部分,一个是数据域(储存节点含有的信息),一个是引用域(储存下一个节点或者上一个节点的地址)。
链表的特点:
获取数据麻烦,需要遍历查找,比数组慢
插入、删除的效率比较高
链表从结构上分为单向链表跟双向链表。
1.6 树
1.6.1 树
树(Tree)是n(n≥0)个结点的有限集T,并且当n>0时满足下列条件:
(1)有且仅有一个特定的称为根(Root)的结点;
(2)当n>1时,其余结点可以划分为m(m>0)个互不相交的有限集T1、T2 、…、Tm,每个集Ti(1≤i≤m)均为树,且称为树T的子树(SubTree)。
特别地,不含任何结点(即n=0)的树,称为空树。
1.6.2 二叉树
二叉树(Binary Tree)是有限个节点的集合,这个集合可以是空集,也可以是一个根节点和两颗不相交的子二叉树组成的集合,其中一颗树叫根的左子树,另一颗树叫右子树。所以二叉树是一个递归地概念。
1.6.3 满二叉树
一棵满二叉树就是高度为k,且拥有(2^k)-1个节点的二叉树,一棵满二叉树每个节点,要么都有两棵子树,要么都没有子树;而且每一层所有的节点之间必须要么都有两棵子树,要么都没子树。
1.6.4 完全二叉树
完全二叉树是一颗特殊的二叉树,它遵循以下规则:
假设完全二叉树高度为k,则完全二叉树需要符合以下两点:
所有叶子节点都出现在k层或k-1层,并且从1~k-1层必须达到最大节点数。
第k层可以是不满的,但是第k层的所有节点必须集中在最左边。
1.6.5 二叉查找树
二叉查找树(BinarySearch Tree,也叫二叉搜索树,或二叉排序树BinarySort Tree)或者是一棵空树,或者是具有下列性质的二叉树:
1、若任意结点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
2、若任意结点的右子树不空,则右子树上所有节点的值均大于它的根结点的值;
3、任意结点的左右子树也分别为二叉查找树;
4、没有键值相等的结点。
在实际应用中,二叉查找树的使用比较多。
1.6.6 平衡二叉树
平衡二叉树(AVL)是一种特殊的二叉查找树,其中每一个节点的左子树和右子树的高度差至多等于1。从平衡二叉树的名字中可以看出来,它是一种高度平衡的二叉排序树。那么什么叫做高度平衡呢?意思就是要么它是一颗空树,要么它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度只差的绝对值绝对不超过1。
1.6.7 红黑树
红黑树(Red Black Tree) 是一种自平衡二叉查找树。
红黑树的特性
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。
(4)如果一个节点是红色的,则它的子节点必须是黑色的。[注意:这里叶子节点,是指为空(NIL)的虚节点!]
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
红黑树跟平衡二叉树的区别
红黑树放弃了追求完全平衡,追求大致平衡,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单。
平衡二叉树追求绝对平衡,条件比较苛刻,实现起来比较麻烦,每次插入新节点之后需要旋转的次数不能预知。
1.7 哈希表
哈希表(Hash table,也叫散列表),是根据关键码值(Key)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列(哈希)函数,存放记录的数组叫做散列表。
1.7.2 哈希冲突
对应不同的关键字可能获得相同的hash地址,即 key1≠key2,但是f(key1)=f(key2)。这种现象就是冲突,而且这种冲突只能尽可能的减少,不能完全避免。
常用的冲突解决办法:
开放地址法(线性探测法)
当冲突法生时,继续往后查找数组的下一个空位,并将数据填入。比如1和101,1占据了一个位置,101进入时候就向下查找,找到下面的一个空位插入, 如果没有继续查找空位,直到找到为止并进行插入。
链地址法(拉链法)
将产生冲突的值以链表的形式连起来。
1.8 对比
每种数据结构有自己的特点跟优缺点,见如下表
数据结构 优 点 缺 点
数组 插入快 查找慢,删除慢,大小固定,只能存储单一元素
栈 提供后进先出的存取方式 存取其他项很慢
队列 提供先进先出的存取方式 存取其他项很慢
链表 插入快,删除快 查找慢
二叉树 如果树是平衡的,则查找、插入、删除都快 删除算法复杂
红黑树 查找、删除、插入都快。树总是平衡 算法复杂
哈希表 如果关键字已知则存取极快 删除慢,如果不知道关键字存取慢,对存储空间使用不充分
、简单算法
/** * 冒泡排序 * @param arr * @return */ public static int[] MaoPao(int [] arr){ int temp = 0; for (int i = 1; i < arr.length; i++) { for (int j = 1; j < arr.length-1; j++) { if (arr[j]>arr[j+1]){ temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } } return arr; } /** * 选择排序 * @param arr * @return */ public static int [] XuanZe(int [] arr){ for (int i = 0; i < arr.length-1; i++) { int temp = 0; int index = i; for (int j = i+1; j < arr.length; j++) { if (arr[index]>arr[j]){ index = j; } } temp = arr[index]; arr[index] = arr[i]; arr[i] = temp; } return arr; } /** * 二分查找 * @param arr * @param value * @return */ public static int ErFen(int[] arr,int value){ int low = 0; int high = arr.length-1; while (low<=high){ int mid = (low+high)/2; if (arr[mid]==value){ return mid; }else if(arr[mid]<value){ low = mid +1; }else { high = mid -1; } } return -1; }
3、懒汉和饿汉
/** * 饿汉 */ public class EHan { private static EHan eHan = new EHan(); private EHan(){} private static EHan getEHan(){ return eHan; } } /** * 懒汉 */ public class LanHan { private static LanHan lanHan = null; private LanHan(){ } private static LanHan getLanHan(){ if (lanHan == null){ lanHan = new LanHan(); } return lanHan; } }
冒泡排序
// 优化后的冒泡排序 public static void main(String[] args) { int[] arr = {3,5,6,8,9,1,2,4,7,7}; int k = arr.length-1;//初始化第一次排序的遍历范围 if (k > 0){ // 循环次数 for (int i = arr.length-1; i>1; i--) { // 比较次数 for (int j = 0; j < i; j++) { // 交换位置 if(arr[j] > arr[j+1]){ arr[j] = arr[j]^arr[j+1]; arr[j+1] = arr[j]^arr[j+1]; arr[j] = arr[j]^arr[j+1]; k = j; System.out.println("j = " + j); } } } } System.out.println("arr = " + Arrays.toString(arr)); }
选择排序
int[] arr = {3,5,2,6,1,8,9,1,2,4,0}; int count = 0; // 循环次数 for (int i = 0; i <arr.length-1; i++) { count++; // 第二层循环 i正好是 第二层比较的数中最小的下标int int min = i ; for (int j = i+1; j < arr.length; j++) { if (arr[j] < arr[min]){ min = j; } } if (i != min){ int temp = arr[min]; arr[min] = arr[i]; arr[i] = temp; } } System.out.println("比较次数 = " + count); System.out.println("arr = " + Arrays.toString(arr));
二分法查找
public static void main(String[] args) { int[ ] arr = { 30,20,50,10,80,9,7,12,100,40,8}; Scanner sc = new Scanner(System.in); System.out.println("请输入你要查找的数 : "); int searchWord = sc.nextInt(); Arrays.sort(arr); //二分法查找之前,一定要对数组元素排序 System.out.println(Arrays.toString(arr)); System.out.println(searchWord+"元素的索引:"+binarySearch(arr,searchWord)); System.out.println("========================"); } public static int binarySearch(int[] arr, int value) { // 开始位置 int begin = 0; // 结束 位置 int end = arr.length - 1; while(begin <= end) { int middle = (begin + end) / 2; //返回查询到的索引位置 if (value == arr[middle]) { return middle; } if (arr[middle] < value) { begin = middle + 1; } if (arr[middle] > value) { end = middle - 1; } } //上面循环完毕,说明未找到,返回-1 return -1; }