使用的技术栈

1. 即时通信 —环信

开发中遇到的问题, 使用的技术栈 - 图1

  1. * 我开发过的项目在涉及到 用户之间的即时通信, 联系客服等功能时, 使用的是环信的 即时通讯云IM技术.
  2. 应用场景:
  3. 小程序社交, 单聊/群聊/聊天室, 私密社交, 直播互动等
  4. 功能如上图.
  5. 使用官方提供的ServerSDK.

2. 分布式日志技术

开发中遇到的问题, 使用的技术栈 - 图2

  1. 日志消息是基于http的协议发送到logstash中, http协议会占用微服务的性能,关于分布式日志的采集尽量采用异步的方式收集, ELK的数据采集 也支持基于redis kafka来采集日志数据.
  2. 需引入依赖, 其次修改logstash容器的配置, 修改配置文件logback.xml.

3. 数据库列式存储

开发中遇到的问题, 使用的技术栈 - 图3

  1. #
  2. 将原本每一行存储一条对象数据, 变为多行共同存储一个对象数据. POJO对象

4. 项目中哪些地方遇到了分布式事务问题
  1. # 存在前提:
  2. 1. 存在多服务之间的远程调用, 每个微服务使用独立的数据源.
  3. 2. 对每个数据源进行了增删改操作.
  4. # 存在位置:
  5. 1. shop模块中的 注册品牌功能.

5. 消息队列中间件RabbitMq

开发中遇到的问题, 使用的技术栈 - 图4

  1. # MQ消息队列中间件,提供应用与应用之间的,安全可靠的消息传递方式.
  2. - 优点: 异步调用,降低服务于服务之间的耦合,提高的运行效率.
  3. 对请求的流量进行控制,保护微服务的安全.
  4. 通过消息持久化策略,保证消息的可靠性.
  5. # RabbitMQ支持的消息模式
  6. - Basic Queue 简单队列.
  7. publisher(生产者) -> queue(队列) -> consumer(消费者)
  8. 生产者发消息给队列,消费者从队列中接收消息,队列与消费者是一对一的关系
  9. - Work Queue 工作对列.
  10. publ -> queue -> cons1,cons2...
  11. 类似简单队列,但是队列和消费者是一对多的关系
  12. - 发布/订阅模型
  13. 新增exchange(交换机)的角色,生产者发消息给交换机,交换机路由到queue,再由消费者消费
  14. - Fanout 广播模式.
  15. publ -> exchange -> queue1,queue2... -> cons1,cons2....
  16. 交换机接收到消息后,会广播给所有绑定它的队列,每个队列都会收到消息.
  17. - Direct 订阅模式
  18. 生产者发送消息的同时,需要指定routeingkey, 交换机只会把消息路由绑定指定routingkey的队列.
  19. 默认消费方式是轮询. 会浪费性能,可以开启能者多劳的消费方式.
  20. spring.rabbitmq.listener.simple.prefetch: 1
  21. - Topic 通配符模式
  22. Topic类型的Exchange可以让队列在绑定routeingkey的时候使用通配符.
  23. # 匹配一个或多个单词 * 匹配一个单词 ex.# ex.* #.ex 等等
  24. # springAMQP 基于AMQP协议提供的一套API,底层默认实现是RabbitMQ.
  25. - 自动声明队列,交换机及绑定关系
  26. - 基于注解的监听器模式,异步接收消息
  27. - 封装了RabbitTemplate工具,用于发送消息
  28. - 默认序列化方式是JDK,可通过配置第三方转换器提高性能.
  29. # RabbitMQ 保证高可用
  30. - 通过使用集群来保证高可用. 普通集群 镜像集群
  31. # RabbitMQ 保证消息的可靠性
  32. * 可以在消息传递的全阶段,采用不同的方式,来保证消息的可靠性.
  33. - 生产者发送消息的可靠性. 消息确认机制
  34. * 发送者确认. publisher-confirm
  35. - 消息成功投递到交换机,返回ack
  36. - 消息未投递到交换机,返回nack
  37. * 确认模式
  38. - simple 同步等待结果,直到超时
  39. - correlated 异步回调,定义ConfirmCallback
  40. * 发送者回调.
  41. - 消息投递到交换机,但是没有路由到队列。返回ACK,及路由失败原因。
  42. - 定义ReturnCallback
  43. - 消息队列保证投递可靠性. 消息持久化
  44. * 交换机持久化
  45. * 队列持久化
  46. * 消息持久化
  47. - 消费者消费消息的可靠性. 消息回执,失败重试
  48. * RabbitMQ默认阅后即焚.即确认消息被消费者消费后会立刻删除,通过消费者回执来确认.
  49. * 确认方式
  50. - manual 手动ack,即开发者主动调用API发送ack
  51. - auto 自动ack,默认模式
  52. - none 关闭ack,MQ假定消费者获取消息后会成功处理,因此消息投递后立即被删除
  53. * 消费重试机制,如果消息消费失败,应采取的策略
  54. - 自动补偿 在消费者出现异常时利用本地重试,而不是无限制的requeuemq队列.
  55. - 在配置文件中,设置最大重试次数,等待时长等
  56. - 但是在最大重试次数后,消息仍然消费失败,默认就会被丢弃
  57. * 失败策略
  58. - 死信队列,本地持久化.
  59. # 保证消息的幂等性
  60. - 使用全局MessageID判断消费方是否消费
  61. - 使用业务ID+逻辑保证唯一
  62. # 大量积压的消息如何快速消费
  63. - 紧急扩容. 即临时声明一个分发数据的consumer, 再创建大量的临时queue,临时的consumer,来快速消费积压的消息. 在消息消费完毕后,再将队列和消费者数量恢复正常.

6. 非关系型数据库之 —Redis

开发中遇到的问题, 使用的技术栈 - 图5

  1. redis是键值(Key-Value)存储数据库。
  2. 性能极高 Redis读的速度是110000次/s,写的速度是81000次/s
  3. 丰富的数据类型 Redis支持 Strings, Lists, Hashes, Sets Ordered Sets 数据类型操作。
  4. Redis运行在内存中但是可以持久化到磁盘。
  5. # 持久化机制
  6. RDB持久化机制. 默认
  7. AOF持久化机制.
  8. # Redis的高可用架构
  9. 主从架构,实现读写分离.
  10. 哨兵(Sentinel)机制来实现主从集群的自动故障恢复。
  11. 分片集群, 实现海量数据存储和高并发写入数据.

7. 非关系型数据库之 —MongoDb

开发中遇到的问题, 使用的技术栈 - 图6

  1. #
  2. * 是一个非关系型数据库. 但是它的数据结构很类似关系型数据库mysql.
  3. * MongoDB中的记录是一个文档,它是一个由字段和值对(field:value)组成的数据结构即BSON, 类似于JSON, 或者说, 就是参数均是二进制表示的 JSON格式.
  4. * 文档的ID是自动生成的UUID, 因此, 映射的pojo类中, 主键类型必须是String.
  5. * 如果绑定的文档不存在, 那么在连接数据库时,会自动创建文档.
  6. 1. 数据量大的场景. 比如社交应用中用户的发言,点赞等行为, 游戏中玩家的装备信息, 商品的物流数据等.
  7. 2. 读写操作频繁的场景. MongoDB支持2000+ QPS. (query pre second 每秒搜索)
  8. 3. 存储价值较低, 而且对事务的要求不高的数据. MongoDB的事务能力很低.

8. 分布式事务 —Seata
  1. #
  2. - 分布式架构的项目中,经常会有些功能,需要调用别的微服务的接口,修改多个数据库中的多张表.
  3. - 也有些项目,由于数据量过大,单表会极大的影响效率,会将表中的数据进行分库分表操作,以提高服务的响应速 度,优化用户体验.
  4. - 在这些情况下,一旦任务执行过程中某个地方出错,即使开启了事务,@Transactional 数据依然无法回滚到错 误发生之前的状态. 进而数据错乱,导致更严重的后果.
  5. - 这种时候,就需要采用分布式事务的技术.
  6. * 基础理论CAP定理
  7. - Consistency 强一致性
  8. - Availability 高可用性
  9. - Partition tolerance 分区容错
  10. - 这三个指标不能同时做到,只能实现两个,而P是必须保证实现的.
  11. # seata
  12. * seata是阿里巴巴和蚂蚁金服共同开源的一种分布式事务解决方案.
  13. - @GlobalTransactional 在要开启分布式事务的入口方法上添加该注解
  14. # 三个角色
  15. * TC 事务协调者
  16. 维护全局事务,负责协调全局事务的提交或回滚.
  17. * TM 事务管理者
  18. 开始全局事务、提交或回滚全局事务.
  19. * RM 分支事务
  20. 管理单个分支事务的状态.
  21. # 四种模式
  22. * XA模式
  23. - 所有分支事务执行完毕后不提交,而是将执行状态发送给TC,由TC确认事务执行成功与否,
  24. - 决定是提交事务还是回滚事务.
  25. - 强一致性,无业务侵入,会阻塞别的事务
  26. * AT模式 (seata默认模式)
  27. - 分支事务RM记录sql执行前后的数据快照undo-log,执行完毕后立即提交或回滚,并将状态报告给TC
  28. - 二阶段有TC决定是提交还是回滚.
  29. - 提交,各个RM删除undo-log , 回滚,读取undo-log,恢复数据
  30. - 最终一致,无业务侵入.
  31. - 有可能出现脏读,可通过开启全局锁解决,但会影响效率.
  32. * TCC模式
  33. - 为每个分支事务RM提供了三个方法,try confirm cancel
  34. - 一阶段完成后,直接提交事务,释放锁
  35. - 二段端,TC根据事务状态决定执行那个方法. 提交: canfirm 回滚: cancel
  36. - try 对要操作的资源进行检查和预留
  37. - confirm 业务执行和提交
  38. - cancel 释放预留的资源
  39. - 最终一致,有业务侵入.
  40. * SAGA模式
  41. -主要面向长事务,使用较少.

9. 延时任务方案 —四种常见方案
  1. # ...延时任务方案...
  2. 1. 定期轮询数据库.
  3. - 优点: 实现简单,且任务所在的服务可以设立集群,保证高可用.
  4. - 缺点: 定期扫描数据库压力大. 扫描频率难以均衡. 多线程可能出现安全问题等...
  5. 2. 定时任务 ..xxljob
  6. - 优点: 实现简单
  7. - 缺点: 每个定时业务都要开启一次任务...
  8. 3. Redis键过期通知
  9. - 优点: 高可用 实时性 支持消息的删除
  10. - 缺点: 负载大 没有确认机制,可能会出现消息丢失
  11. 4. RabbitMQ 死信队列
  12. - 优点: 实现简单 高可用 可持久化 保证消息的可靠性
  13. - 缺点: 无法删除消息 不适合时间跨度大的定时任务

10. 页面静态化之模板引擎 —Freemarker
  1. # 常用的java模板引擎有 Jsp, Freemarker, Thymeleaf, Velocity.
  2. jsp绑定了servlet,不实用. velocity久不更新,过时了.
  3. 天下英雄,唯freemarker thymeleaf尔.
  4. Freemarker简单方便,效率高.
  5. Thymeleaf功能强大,效率较低.
  6. 我们使用Freemarker.
  7. # Freemarker的使用
  8. 需要一个模板文件, 确定静态网页的样式布局.
  9. 需要开发者提供真实数据, 填充模板中差值表达式的数据位置.
  10. 模板 + 数据 = 静态文件.
  11. 基础语法 :
  12. 兼容html的语法格式.
  13. 可以在标签中加上 #, 来使用FTL语言特有的标签功能. <#...>___</#...>
  14. 差值表达式语法类似jsp. ${ }
  15. 内建函数. 标签\变量后加 ? [函数名] 来使用内置的函数.
  16. 更具体的语法, 在使用时面向百度编程.

11. 基于RPC的远程调用 —Dubbo

开发中遇到的问题, 使用的技术栈 - 图7

  1. 1. 简介
  2. - 面向接口代理的高性能RPC调用
  3. - 智能容错和负载均衡
  4. - 服务自动注册和发现
  5. - 高度可扩展能力
  6. - 运行期流量调度
  7. - 可视化的服务治理与运维
  8. 2. dubbofeign的区别
  9. * dubbo: 基于RPC的远程调用(默认基于TCP协议), 面向接口, 智能负载均衡.
  10. - 支持多种连接协议.
  11. - 利用NettyTCP传输,单一、异步、长连接,适合数据量小、高并发的场景.
  12. - 负载均衡的算法可以精准到某个服务接口的某个方法
  13. 提供方 @DubboService 消费端 @DubboReference
  14. * feign: 基于http协议的远程调用, 也是面向接口.
  15. - 通过短连接的方式进行通信,不适合高并发的访问.
  16. - Feign默认使用Ribbon作为负载均衡的组件,Hystrix作为服务熔断的组件.
  17. - 负载均衡是Client级别的.
  18. 客户端 @FeignCilent 消费端 @Autowired 自动注入
  19. 二者都是同步调用.
  20. 3. dubbo的配置原则
  21. * dubbo推荐在Provider上尽量多配置Consumer端属性. 提供方拥有具体的业务实现的逻辑代码,可以提供基础的, 参考配置.
  22. 优先级: 方法级 > 接口级 > 全局配置, 其中 消费方consumer的配置优先于服务提供方Provider的配置.
  23. @DubboService @DubboReference注解中, 有一个methods属性, 可以针对指定的方法进行配置.
  24. 4. 常用配置
  25. - timeout 超时时间, 默认1s
  26. - retries 重试次数, 默认为2
  27. - version 灰度发布, 即在版本更新时 分时分段 的更新服务者和消费者的版本.
  28. 5. 隐式传参
  29. * org.apache.dubbo.rpc.RpcContext ;线程共享数据的处理类
  30. RpcContext.getContext().setAttachment(key, value)
  31. RpcContext.getContext().getAttachment(key, value)
  32. 多个微服务之间的上下文信息不同, 隐式传递的参数不能跨过多个微服务.
  33. 6. 负载均衡配置
  34. * Random LoadBalance 加权随机调用(默认策略)
  35. - 按权重设置随机概率. 调用次数越多, 调用越接近权重的策略
  36. * RoundRobin LoadBalance 加权轮询策略
  37. - 按公约后的权重设置轮循比率.
  38. - 在首轮分配后, 继续按照权重的次数来分配路由, 直到所有的权重分配结束, 算是一次完整的轮询, 之后开启下次轮询.
  39. * LeastActive LoadBalance 最少活跃调用数
  40. - 类似能者多劳模式, 每个服务会计算执行前后的时间差, 执行越慢的服务分配到的任务越少, 越快的服务会接到更多的请求.
  41. * ConsistentHash LoadBalance 一致性 Hash
  42. - 把请求的hash值, 对服务总数取模,把相同余数的请求路由到同一个服务.
  43. - 默认只计算第一个请求参数的hash, 默认使用160分虚拟节点.
  44. 7. 服务降级和集群容错策略
  45. * 服务降级
  46. - 自定义降级后的返回策略.
  47. * 集群容错
  48. - Failover Cluster 失败自动切换(默认容错模式)
  49. 失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries=“2 来设置重试次数.
  50. - Failfast Cluster 快速失败
  51. 快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
  52. - Failsafe Cluster 失败安全
  53. 失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
  54. - Failback Cluster
  55. 失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
  56. - Forking Cluster
  57. 并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=“2 来设置最大并行数。
  58. - Broadcast Cluster
  59. 广播调用所有提供者,逐个调用,任意一台报错则报错 [2]。通常用于通知所有提供者更新缓存或日志等本地资源信息。
  60. 8. 整合hystrix的微服务保护
  61. * 添加依赖
  62. Application类上增加@EnableHystrix来启用hystrix starter.
  63. <dependency>
  64. <groupId>org.springframework.cloud</groupId>
  65. <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
  66. <version>1.5.9.RELEASE</version>
  67. </dependency>
  68. * 配置Provider
  69. DubboProvider上增加 @HystrixCommand 配置.
  70. 注:
  71. 1. 注册中心宕机会有什么影响?
  72. - 监控中心宕掉不影响使用,只是丢失部分采样数据
  73. - 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
  74. - 注册中心对等集群,任意一台宕掉后,将自动切换到另一台
  75. - 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
  76. - 服务提供者无状态,任意一台宕掉后,不影响使用
  77. - 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
  78. 2. dubbo可以直连调用吗?
  79. 可以, 添加服务提供方的地址就可以, 但是不推荐,失去了高可用性.
  80. @Reference(url=“127.0.0.1:20880”)
  81. 3. 可以使用别的注册中心吗?
  82. 可以, zookeeper, redis等.
  83. * redis 存储的结构为 HASH, 通过订阅/发布实现注册功能, HASH KEY是当前服务的全限定名+类型(提供方/消费方), 存储的MAP对象的key 是服务的ID, value是过期时间
  84. 引入依赖
  85. <dependency>
  86. <groupId>redis.clients</groupId>
  87. <artifactId>jedis</artifactId>
  88. </dependency>
  89. 修改配置
  90. dubbo:
  91. registry:
  92. address: redis://${host}:6379

12. 消息队列中间件 —Kafka
  1. # Kafka 是一款分布式流处理框架,用于实时构建流处理应用。
  2. 高吞吐量、低延迟:kafka每秒可以处理几十万条消息,它的延迟最低只有几毫秒
  3. 可扩展性:kafka集群支持热扩展
  4. 持久性、可靠性:消息被持久化到本地磁盘,并且支持数据备份防止数据丢失
  5. 容错性:允许集群中节点失败(若副本数量为n,则允许n-1个节点失败)
  6. 高并发:支持数千个客户端同时读写
  7. 消费者组: Kafka独有的概念,是Kafka提供的可扩展且具有容错性的消费者机制.
  8. Kafka 中,消费者组是一个由多个消费者实例 构成的组。多个实例共同订阅若干个主题,实现共同消费。同一个组下的每个实例都配置有 相同的组 ID,被分配不同的订阅分区。当某个实例挂掉的时候,其他实例会自动地承担起 它负责消费的分区。

13. 分布式文件存储 —fastDFS
  1. # FastDFS 是一个开源的轻量级分布式文件系统,它可以对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等
  2. - 搭建太过复杂, 不如minIO简单.
  3. - 自从阿里云oss出现后,两个都不用了.

14. alibaba旧类注册中心 —zookeeper
  1. # Zookeeper 是一个分布式协调服务的开源框架, 主要用来解决分布式集群中应用系统的一致性问题.
  2. ZooKeeper 本质上是一个分布式的小文件存储系统,提供基于类似于文件系统的目录树方式的数据存储
  3. ZooKeeper中还支持一种watch(监听)机制, 它允许对ZooKeeper注册监听, 当监听的对象发生指定的事件的时候, ZooKeeper就会返回一个通知.
  4. Watcher 分为以下三个过程:客户端向ZK服务端注册 Watcher、服务端事件发生触发 Watcher、客户端回调 Watcher 得到触发事件情况.
  5. 触发事件种类很多,如:节点创建,节点删除,节点改变,子节点改变等。
  6. Watcher是一次性的. 一旦被触发将会失效. 如果需要反复进行监听就需要反复进行注册.
  7. 简单来说zookeeper=文件系统+监听通知机制.  现在我们用nacos替代.

15. 连接数据库封装JDBC —hibrnate
  1. # 连接数据库的一个实现方案, 封装了JDBC, 是 ORM(对象关系映射) 关系的一种实现.
  2. 我们使用的是Mybatis.

16. alibaba连接数据库封装JDBC —Netty
  1. # Netty 是基于Java NIO的异步事件驱动的 网络应用框架
  2. 它提供了对TCPUDP 和文件传输的支持,作为一个异步 NIO 框架,Netty 的所有 IO 操作都是异步非阻塞的.
  3. 我们项目使用的都是springboot, 内置了tomcat, 所以没有使用netty

17. 分库分表 —Sharding-JDBC

18. Websocket
  1. # websocket是html5规范中的一个部分,它借鉴了socket这种思想,为web应用程序客户端和服务端之间(注意是客户端服务端)提供了一种全双工通信机制。  是一种通信协议
  2. 说白话, 就是 HTTP 协议有一个缺陷:通信只能由客户端发起.
  3.  websocket协议, 服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种.
  4.   <dependency>
  5. <groupId>org.springframework.boot</groupId>
  6. <artifactId>spring-boot-starter-websocket</artifactId>
  7. </dependency>
  8. 使用入门:
  9. https://zhengkai.blog.csdn.net/article/details/80275084?spm=1001.2101.3001.6650.3&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-3.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-3.no_search_link

19. Kubernetes —即k8s
  1. * Kubernetes是一个开源的,用于管理云平台中多个主机上的容器化的应用,Kubernetes的目标是让部署容器化的应用简单并且高效(powerful),Kubernetes提供了应用部署,规划,更新,维护的一种机制。\
  2. 作用是部署环境和容器, 正在逐渐的取代docker.
  3. 有时间可以学习下入门文档 https://www.kubernetes.org.cn/k8s

开发bug:

1. 热点新闻更新错误
  1. 1. rabbitmq自定义配置, 采用jackson2json序列化消息, 体积更小,效率更高
  2. 2. 在文章微服务监听消息, 收到的消息多了一对双引号, 存入redis中, 再取出序列化失败.
  3. 3. 解决方法:
  4. 1. 发消息的微服务采用默认序列化方式
  5. 2. 监听器接收到消息后, 先对内容进行处理, 去除两端的双引号,在存入redis

2. Long类型数据精度丢失
  1. * 如果是对象, 主键ID修改序列化方式.
  2. * 如果是Long对象, 可选一: 转为String返回 可选二: 前段js修改, 异步请求接收结果时不做转换,直接返回

3. redis分布式锁失效

开发中遇到的问题, 使用的技术栈 - 图8

  1. 起因: 测试开桌的时候, 模拟前段多个请求, 而服务器内部网络堵塞(debug模拟), 恢复后会出现重复开桌的安全问题.
  2. 原理: 在释放分布式锁后, 该任务仍有很多后续代码要执行, 而由于方法上加了事务, 在执行完毕之前, order表并不会写入数据. 此时下一个线程获取了分布式锁, 并通过了order状态判断, 导致重复开桌.
  3. 解决方案: 见图.
  4. 选择三: 该段代码结束后直接return 当前结果参数, web层的方法里调用后续步骤并传参. (推荐)

4. mybatis的分页插件bug
  1. # 问题描述
  2. 一个本来没有做分页的查询,通过输出日志可以看到,在执行查询的时候却用了分页。导致查询不到数据,或是查询到错误的数据。
  3. # 问题原因
  4. mybaits分页插件底层使用了ThreadLocal封装了Page对象, 这个变量内封装了分页查询要用的参数,以及查询的结果。
  5. 它会在执行sql时自动插入分页语句. 底层源码在finally里执行了clear(), 似乎是不会出现脏数据的问题. 但是, 如果还没有进入try就抛异常了,就会导致没能让finally中的内容执行到。
  6. 因为它只有在执行过mapper查询语句后, 才会执行finally清空缓存的逻辑!!!
  7. 在我们的业务代码里获取到Page对象后,没有立即调用mapper做查询。而是执行了一些其他的内容。正是这些其他的内容抛出了异常,导致后面没有调用mapper做查询,也就无法进入到上面贴的插件中的代码,更没有可能清空ThreadLocal变量了。
  8. 在这个线程下次被使用时,如果正好是一个不需要分页查询的功能,也就没法把ThreadLocal中已有的变量覆盖掉,导致把一个不需要分页查询的功能做了分页查询。
  9. # 解决方案
  10. 要使用MyBatis分页插件PageHelper做分页查询时 ,一定要记得,在获得了Page对象后要立即调用mapper做查询,不要有其他的逻辑插入其中。

面试题:

1. 选择排序优化
  1. //选择排序的优化
  2. for(int i = 0; i < arr.length - 1; i++) {// 做第i趟排序
  3. int k = i;
  4. for(int j = k + 1; j < arr.length; j++){// 选最小的记录
  5. if(arr[j] < arr[k]){
  6. k = j; //记下目前找到的最小值所在的位置
  7. }
  8. }
  9. //在内层循环结束,也就是找到本轮循环的最小的数以后,再进行交换
  10. if(i != k){ //交换a[i]和a[k]
  11. int temp = arr[i];
  12. arr[i] = arr[k];
  13. arr[k] = temp;
  14. }
  15. }

2. 冒泡排序优化
  1. public static void bubbleSort3(int[] values) {
  2. System.out.println("排序前:" + Arrays.toString(values));
  3. int tmp;// 循环外创建变量,减少垃圾产生,提高性能
  4. int count = 0;// 比较了多少趟
  5. int num = 0; // 比较次数
  6. // 外层循环,n 个元素,最多需要 n -1 趟循环
  7. for(int i = 0; i < values.length -1 ; i++) {
  8. // 定义一个boolean 值,标记数组是否达到有序状态
  9. boolean flag = true;
  10. count ++;
  11. // 内层循环, 每一次都从开始两个元素开始两两比较到最后
  12. /* 每一趟循环都可以找你最大的数,内循环可以比上一趟循环减少一次比较 */
  13. for (int j = 0; j < values.length - 1 - i; j++) {
  14. if(values[j] > values[j + 1]) {
  15. tmp = values[j];
  16. values[j] = values[j + 1];
  17. values[j + 1] = tmp;
  18. // 如果本趟发生了交换,表明数组还是无序,需要进入下一轮循环
  19. flag = false;
  20. }
  21. num ++;
  22. }
  23. // 根据标记的布尔值判断数组是否达到有序状态,如有序,则退出循环
  24. if( flag) {
  25. break;
  26. }
  27. }
  28. System.out.println("排序后:" + Arrays.toString(values));
  29. System.out.println("比较趟数:"+count + ",比较次数:" + num);
  30. }