1.MySQL如何进行慢SQL优化?
参考答案
思路:
通过慢查询日志去寻找哪些 SQL 执行效率低
使用 explain 获取低效率 SQL 的执行计划
结合 SQL 与执行计划,进行分析与优化
引起 SQL 查询很慢的原因与解决办法:
1、没有索引。解决办法:
根据 where 和 order by 使用比较频繁的字段创建索引,提高查询效率
索引不宜过多,单表最好不要超过 6 个。索引过多会导致占用存储空间变大;insert、update 变慢
删除未使用的索引
2、索引未生效。解决办法:
避免在 where 子句中对字段进行 null 值判断,创建表默认值是 NULL。尽量使用 NOT NULL,或使用特殊值,如 0、-1
避免在 where 子句中使用 != 或 <> 操作符, MySQL 只有对以下操作符才使用索引:<、<=、=、>、>=、BETWEEN、IN、非 % 开头的 LIKE,避免部分 like 查询,如 '%ConstXiong%'
避免在 where 子句中使用 or 来连接条件,可以使用 UNION 进行连接
能用 union all 就不用 union,union 过滤重复数据要耗费更多的 CPU 资源
避免在索引列上使用计算、函数
in 和 not in 慎用,能用 between 不要用 in
**select 子句中避免使用 ***
3、单表数据量太大。解决办法:
分页查询(在索引上完成排序分页操作、借助主键进行关联)
单表数据过大,进行分库分表
考虑使用非关系型数据库提高查询效率
全文索引场景较多,考虑使用 ElasticSearch、solr
提升性能的一些技巧:
尽量使用数字型字段
只需要一行数据时使用 limit 1
索引尽量选择较小的列
不需要的数据在 GROUP BY 之前过滤掉
大部分时候 exists、not exists 比 in、not in 效率(除了子查询是小表的情况使用 in 效率比 exists 高)高
不确定长度的字符串字段使用 varchar/nvarchar,如使用 char/nchar 定长存储会带来空间浪费
不要使用 select *,去除不需要的字段查询
避免一次性查询过大的数据量
使用表别名,减少多表关联解析时间
多表 join 最好不超过 5 个,视图嵌套最好不超过 2 个
or 条件查询可以拆分成 UNION 多个查询
count(1) 比 count(*) 有效
判断是否存在数据使用 exists 而非 count,count 用来获取数据行数
AOP的原理
1.代理角色,内部含有对真实对象RealSubject的引用,从而可以操作真实对象。
代理对象提供与真实对象相同的接口,以便代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
批量导入
大致思路:
1.首先导入一个依赖用来读excel文件
2、判断获取的文件后缀 是xls还是xlsx
3、声明WorkBook,获取Sheet
4、通过sheet 获取row,注意观察,是不是默认从0开始计数
5、获取每一个cell,判断其类型,给每一种类型赋值
6、调用mapper层,进行批量操作
文件下载和预览
将文件以流的形式一次性读取到内存,通过响应输出流输出到前端
1.获取文件文件路径和文件名
2.用字节输入流读到一个byte的数组中
3.设置响应头信息
4.通过响应输出流输出到前端
3、简述Java中垃圾回收机制(5分)
如果一个对象没用被其他变量所引用,这时候该对象就可以被作为垃圾进行回收。垃圾回收机制的主要目标是通过销毁无法访问的对象来释放堆内存(4分)垃圾回收机制可以有效地防止内存泄露以及内存溢出。Java中通过根可达算法寻找垃圾。先找非垃圾对象,找到的都不是垃圾,常见的root有方法中的静态变量,正在运行的线程。常用的垃圾回收算法有三个,标记清除,复制拷贝,标记压缩,标记清除就是先标记出非垃圾对象,其他的直接回收掉,标记压缩是在标记清除的基础上做了内存整理;复制拷贝就是将非垃圾对象复制到另一个空间中并进行内存整理,将当前空间直接回收掉。
对于JVM中的堆内存。新生代一般使用复制拷贝算法,老年代一般使用标记清除和标记压缩。
4.堆的操作总结
插入元素 :先将元素放至数组末尾,再自底向上堆化,将末尾元素上浮
删除堆顶元素 :删除堆顶元素,将末尾元素放至堆顶,再自顶向下堆化,和较大的元素交换,将堆顶元素下沉。也可以自底向上堆化,只是会产生“气泡”,浪费存储空间。最好采用自顶向下堆化的方式。
-
堆排序:
建堆:将一个无序的数组建成堆,对n/2到1节点(非叶子节点)自顶向下堆化
排序:堆顶元素与最后一个元素交换,对堆顶元素进行自顶向下堆化。
5.树
二叉树 的第 i 层至多拥有 2^(i-1)
个节点
节点的度:一个节点的子树的个数称为节点的度
完全二叉树:如果父结点的序号是 i,那么左子节点的序号就是 2i,右子节点的序号是 2i+1。
6.面向对象
面向先抽象出对象,然后用对象执行方法的方式解决问题。具体表现为封装,继承,多态。
封装
封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。
继承
不同类型的对象,相互之间经常有一定数量的共同点。继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承,可以快速地创建新的类,可以提高代码的重用,程序的可维护性,节省大量创建新类的时间 ,提高我们的开发效率。
多态
多态,顾名思义,表示一个对象具有多种的状态,具体表现为父类的引用指向子类的实例。如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。
7.线程和进程的区别
线程是进程划分成的更小的运行单位,一个进程在其执行的过程中可以产生多个线程。多个线程共享进程的堆和方法区 (JDK1.8 之后的元空间)资源,但是每个线程有自己的程序计数器、虚拟机栈 和 本地方法栈。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。
8.全局变量和局部变量
定义在类中的变量叫做全局变量
(1)实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。
(2)静态变量不属于某个实例对象,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。
只在特定的过程或函数中可以访问的变量,被称为局部变量。
局部变量在堆栈级别内部实现。
JAVA中的全局变量和局部变量是允许重名的,但是如果在方法体内使用该重名变量时,局部变量会覆盖全局变量,出了方法,局部变量被销毁,只剩下全局变量。类.变量就会输出全局变量。
9.java编程规范
(1)驼峰命名法 类名首字母大写,方法名和变量首字母小写,常量名大写
(2)注释:文档注释(一个注释对应一个类、接口或成员),单行注释,多行注释
(3)命名要有含义,方便阅读
(4)声明变量一行一个
(5)类和接口的声明(Class and Interface Declarations) 当编写类和接口时,应该遵守以下格式规则:
在方法名与其参数列表之前的左括号"("间不要有空格 左大括号"{"位于声明语句同行的末尾 右大括号"}"另起一行,与相应的声明语句对齐,除非是一个空语句,"}"应紧跟在"{"之后。
方法与方法之间以空行分割。
10.栈溢出:
栈空间太小, -Xss 修改栈的大小
递归循环没办法结束导致栈内存满- 查看代码逻辑,寻找递归出口
OOM:堆溢出
如果是1.7以前, Java堆溢出的问题根源是简单的, 就是运行时存在的对象实例和数组太多了!
但是在1.8后, 由于还存放了字符串常量, 所以出现异常还有一种可能就是 String 过多过长导致的哦!
java.lang.OutOfMemoryError: GC Overhead limit exceeded
这个异常表示您的Java程序在运行的时候, 98%的时间都在执行GC回收, 但是每次回收只回收不到2%的空间!
因为Java程序每次都GC回收只能回收一点点内存空间,而你的程序却仍然在不停的产生新的对象实例, 这无疑导致了两种可能结果:
不停的进行GC
-
直接超出的堆内存大小
java.lang.OutOfMemoryError: Unable to create new native thread
这个异常表示,JVM无法再创建新的线程了!JVM能够创建的线程数是有限制的
-Xms : 初始堆大小 -Xmx : 最大堆大小
11.MYSQL
创建数据库 CREATE DATABASE 数据库名
删除数据库 DROP DATABASE 数据库名
选择数据库 USE 数据库名
创建数据表 CREATE TABLE IF NOT EXISTS
runoob_tbl
( )-
插入数据
INSERT INTO table_name ( field1, field2,...fieldN )
VALUES
( value1, value2,...valueN ); -
查询数据
SELECT column_name,column_name
FROM table_name
[WHERE Clause]
[LIMIT N][ OFFSET M]
limit:返回记录数
offset:指定偏移量 默认是0
MySQL 的 WHERE 子句的字符串比较是不区分大小写的。 你可以使用 BINARY 关键字来设定 WHERE 子句的字符串比较是区分大小写的。
-
更新
UPDATE table_name SET field1=new-value1, field2=new-value2
[WHERE Clause] -
删除
DELETE FROM table_name [WHERE Clause]
-
like
模糊查询:%like%
如果没有使用百分号 %, LIKE 子句与等号 = 的效果是一样的。
-
union 连接两个以上的 SELECT 语句的结果组合到一个结果集合中。多个 SELECT 语句会删除重复的数据。
SELECT expression1, expression2, ... expression_n
FROM tables
[WHERE conditions]
UNION [ALL | DISTINCT]
SELECT expression1, expression2, ... expression_n
FROM tables
[WHERE conditions];
distinct:默认,删除结果集中重复元素
all:返回所有结果集,包含重复元素
-
排序
SELECT field1, field2,...fieldN FROM table_name1, table_name2...
ORDER BY field1 [ASC [DESC][默认 ASC]], [field2...] [ASC [DESC][默认 ASC]] -
分组 GROUP BY 语句根据一个或多个列对结果集进行分组。
在分组的列上我们可以使用 COUNT, SUM, AVG,等函数。
SELECT column_name, function(column_name)
FROM table_name
WHERE column_name operator value
GROUP BY column_name;
使用 GROUP BY 语句 将数据表按名字进行分组,并统计每个人有多少条记录:
SELECT name, COUNT(*) FROM employee_tbl GROUP BY name;
WITH ROLLUP 可以实现在分组统计数据基础上再进行相同的统计(SUM,AVG,COUNT…)。
-
连接
JOIN 按照功能大致分为如下三类:
INNER JOIN(内连接,或等值连接):获取两个表中字段匹配关系的记录。INNER可省略
LEFT JOIN(左连接):获取左表所有记录,即使右表没有对应匹配的记录。
RIGHT JOIN(右连接): 与 LEFT JOIN 相反,用于获取右表所有记录,即使左表没有对应匹配的记录。
SELECT a.runoob_id, a.runoob_author, b.runoob_count FROM runoob_tbl a INNER JOIN tcount_tbl b ON a.runoob_author = b.runoob_author;
WHERE是在聚合前筛选记录的,having是聚合后筛选记录。having和group by是组合着用的。
select cid,count(id) nums from xzyd_question group by cid HAVING nums>2
-
NULL值处理
为了处理这种情况,MySQL提供了三大运算符:
IS NULL: 当列的值是 NULL,此运算符返回 true。
IS NOT NULL: 当列的值不为 NULL, 运算符返回 true。
<=>: 比较操作符(不同于 = 运算符),当比较的的两个值相等或者都为 NULL 时返回 true。
NULL与其他任何值的比较和运算都返回NULL
select * , columnName1+ifnull(columnName2,0) from tableName;
正则表达式 REGEXP
-
一般来说,事务是必须满足4个条件(ACID)::原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。
原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。undo log
一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。 redo log
-
当我们需要修改数据表名或者修改数据表字段时,就需要使用到MySQL ALTER命令。
添加字段 ADD
删除字段 DROP
修改字段类型 Modify
修改字段名 CHANGE 原字段名 新字段名 新字段类型
修改表名 RENAME TO
自增关键字 AUTO_INCREMENT
获取服务器版本 SELECT VERSION( )
12.索引类型:
主键索引 没有指定会默认创建 只有一个
唯一索引 不可重复可为空
普通索引 可重复可为空
前缀索引 前缀索引只适用于字符串类型的数据。前缀索引是对文本的前几个字符创建索引,相比普通索引建立的数据更小, 因为只取前几个字符。
全文索引
联合索引 最左前缀原则
优点 :
使用索引可以大大加快 数据的检索速度(大大减少检索的数据量), 这也是创建索引的最主要的原因。
通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
缺点 :
创建索引和维护索引需要耗费许多时间。当对表中的数据进行增删改的时候,如果数据有索引,那么索引也需要动态的修改,会降低 SQL 执行效率。
索引需要使用物理文件存储,也会耗费一定空间。
添加索引:
ALTER TABLE table_name
ADD ...
B+树比B树好的原因:
二叉查找树——平衡二叉查找树(所有节点的左右子树高度差不超过1)适合用于插入删除次数比较少,但查找多的情况。——红黑树(弱平衡二叉查找树)对于任意节点而言,其到叶子点树NULL指针的每条路径都包含相同数目的黑节点; 适应于搜索,插入,删除多的情况——B/B+树:平衡多路查找树
二叉查找树的查找时间复杂度是o(logn)
,整体的查询效率已经足够高了,那么为什么还会有b树和b+树的进化演进呢? 主要的原因是:二叉树可能会退化成一个线性树,造成磁盘io次数增高的问题,当有大量的数据存储的时候,二叉查找树查询不能将所有的数据加载到内存中,只能逐一加载磁盘页,每个磁盘对应树的节点,造成大量的磁盘io操作(最坏的情况io次数为树的高度),平衡二叉树由于树深度过大而造成磁盘io读写过于频繁,进而导致效率低下。所以,为了减少磁的io的次数,必须降低树的深度,将瘦高
的树变得矮胖
。 B树和B+树的时间复杂度与树的高度成正比。
B树的特点: 1. 节点排序 2. ⼀个节点了可以存多个元素,多个元素也排序了
B+树的特点: 1. 拥有B树的特点 2. 叶⼦节点之间有指针 3. ⾮叶⼦节点上的元素在叶⼦节点上都冗余了,也就是叶⼦节点中存储了所有的元素,并且排好顺序
13.b+tree相比b-tree更适合用来存储索引,原因如下:
-
b+树的磁盘读写代价更低,b+树内部节点不存放数据,只存放索引信息,因此其内部节点相对b-tree会更小,所以同一个页就能存储更多的关键字,一次性读入内存中需要查找的关键字也就越多,因此IO次数也就越低。因此,存储同样大小的数据,BTree显得比较高(相对B+Tree),稳定性弱一些。
Innodb按照页来进行IO操作,一页是16KB
[图片上传失败...(image-3acb9e-1664767404810)]
b+树的查询效率更加稳定,由于b+树内部节点并不是指向文件内容的节点,而只是叶子节点关键字的索引,索引任何一个数据的查询都必须是:
任何关键字查询都是从根节点到叶子节点的查询路线,因此每个数据的查询效率都是稳定一致的
。B树只需要找到相应节点就停止查找,所用时间从O ( 1 ) − O ( h ) 都有可能。b+树更有利于做范围查找,B+树叶子结点之间用链表有序连接,所以扫描全部数据只需扫描一遍叶子结点,利于扫库和范围查询;B树由于非叶子结点也存数据,所以只能通过中序遍历按序来扫。也就是说,对于范围查询和有序遍历而言,B+树的效率更高。
explain
key字段:表示走的哪个索引
type:ALL表示全表扫描 ; index 表示从左到右扫描索引对应的叶子节点 (没有where); range 表示走索引的范围查找
覆盖索引:查找的字段属于索引里面的字段(using index)
order by 导致索引失效(需要回表的情况):全表扫描,在内存中排序,不用回表,因为数据量少在内存中排序速度很快,可以忽略不计
数据类型转换导致索引失效的原理:mysql会把字符转成数字,'a' 'b' 这样的字符转换成0,‘123’转换成123 ,字符索引字段要转换成数字,不符合B+树规则,没办法走索引。
同样,只要对字段进行了操作就会导致索引失效,比如where a+1=1 这种
14.hashmap
底层架构是:数组加链表加红黑树
-
数组的特点
- 数组的查询O(1),插入和删除O(N)。
-
链表的特点
- 链表的插入删除O(1),查询的复杂度O(N)
Hash算法
-
hashcode
- 通过字符串算出他的ascii码,进行取模运算,算出哈希表中的下标
数组中存储啥(用object存储)
key
value
hash
next
15.Spring
1.Spring 是一款开源的轻量级 Java 开发框架,旨在提高开发人员的开发效率以及系统的可维护性。
我们一般说 Spring 框架指的都是 Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。
Spring core:核心模块, IoC 控制反转功能的支持
Spring Aop:提供Aop的支持
Spring Web:对 Web 功能的实现提供一些最基础的支持。
Data Access:spring-jdbc :提供了对数据库访问的抽象 JDBC。
Spring Test:支持单元测试
2.Spring MVC 是 Spring 中的一个很重要的模块,主要赋予 Spring 快速构建 MVC 架构的 Web 程序的能力。MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。
MVC 是一种设计模式,Spring MVC 是一款很优秀的 MVC 框架。Spring MVC 可以帮助我们进行更简洁的 Web 层的开发,并且它天生与 Spring 框架集成。Spring MVC 下我们一般把后端项目分为 Service 层(处理业务)、Dao 层(数据库操作)、Entity 层(实体类)、Controller 层(控制层,返回数据给前台页面)。
3.Spring Boot 旨在简化 Spring 开发(减少配置文件,开箱即用!)简化了 Spring MVC 的很多配置,真正做到开箱即用!
4.Spring IoC:控制反转,是一种设计思想,对象创建管理的权利交给外部环境(IOC容器,Spring框架)
IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。
优点:简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。
比如Service 层想要使用 Dao 层的具体实现的话,需要通过 new 关键字在UserServiceImpl
中手动 new 出 IUserDao
的具体实现类 UserDaoImpl
开发过程中突然接到一个新的需求,针对对IUserDao
接口开发出另一个具体实现类。因为 Server 层依赖了IUserDao
的具体实现,所以我们需要修改UserServiceImpl
中 new 的对象。如果只有一个类引用了IUserDao
的具体实现,可能觉得还好,修改起来也不是很费力气,但是如果有许许多多的地方都引用了IUserDao
的具体实现的话,一旦需要更换IUserDao
的实现方式,那修改起来将会非常的头疼。
使用 IoC 的思想,我们将对象的控制权(创建、管理)交有 IoC 容器去管理,我们在使用的时候直接向 IoC 容器 “要” 就可以了。
5.Spring bean:Bean 代指的就是那些被 IoC 容器所管理的对象
Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。
将一个类声明为 Bean 的注解有哪些?
@Component
:通用的注解,可标注任意类为Spring
组件。如果一个 Bean 不知道属于哪个层,可以使用@Component
注解标注。@Repository
: 对应持久层即 Dao 层,主要用于数据库相关操作。@Service
: 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。@Controller
: 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。
@Component 和 @Bean 的区别是什么?
@Component
注解作用于类,而@Bean
注解作用于方法。@Bean
告诉了 Spring 这是某个类的实例,当我需要用它的时候还给我。
@Autowired 和 @Resource 的区别是什么?
@Autowired
是 Spring 提供的注解,@Resource
是 JDK 提供的注解。Autowired
默认的注入方式为byType
(根据类型进行匹配),@Resource
默认注入方式为byName
(根据名称进行匹配)。当一个接口存在多个实现类的情况下,
@Autowired
和@Resource
都需要通过名称才能正确匹配到对应的 Bean。Autowired
可以通过@Qualifier
注解来显示指定名称,@Resource
可以通过name
属性来显示指定名称。
5.Spring Aop
Aspect oriented programming 面向切面编程
将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
OOP是纵向继承体系,AOP提出横向抽取机制,将横切逻辑代码和业务逻辑代码分离
Spring Aop 基于动态代理
排序算法
[图片上传失败...(image-c8e7d0-1664767404809)]
-
选择排序
首先,找到数组中最小的那个元素,其次,将它和数组的第一个元素交换位置(如果第一个元素就是最小元素那么它就和自己交换)。其次,在剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。如此往复,直到将整个数组排序。这种方法我们称之为选择排序。
性质:1、时间复杂度: 最佳:O(n2) ,最差:O(n2), 平均:O(n2) 2、空间复杂度:O(1) 3、非稳定排序 因为(3,3,0)第一次会把3和0交换 4、原地排序
-
插入排序
它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
性质:1、时间复杂度:最佳:O(n) ,最差:O(n2), 平均:O(n2) 2、空间复杂度:O(1) 3、稳定排序 4、原地排序
-
冒泡排序
它重复地遍历要排序的序列,依次比较两个元素,如果它们的顺序错误就把它们交换过来,每次遍历使得最大的元素沉到最下面。遍历序列的工作是重复地进行直到没有再需要交换为止,此时说明该序列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢 “浮” 到数列的顶端。
性质:1、时间复杂度 :最佳:O(n) ,最差:O(n2), 平均:O(n2)
2、空间复杂度:O(1) 3、稳定排序 4、原地排序
-
归并排序
将一个大的无序数组有序,我们可以把大的数组分成两个,然后对这两个数组分别进行排序,之后在把这两个数组合并成一个有序的数组。由于两个小的数组都是有序的,所以在合并的时候是很快的。
性质:1、时间复杂度:O(nlogn) 2、空间复杂度:O(n) 3、稳定排序 4、非原地排序
-
快排
我们从数组中选择一个元素,我们把这个元素称之为中轴元素吧,然后把数组中所有小于中轴元素的元素放在其左边,所有大于或等于中轴元素的元素放在其右边,显然,此时中轴元素所处的位置的是有序的。也就是说,我们无需再移动中轴元素的位置。
接着我们通过递归的方式,让中轴元素左边的数组和右边的数组也重复同样的操作,直到数组的大小为1,此时每个元素都处于有序的位置。
性质:1、时间复杂度:O(nlogn) 2、空间复杂度:O(logn) (递归占用栈的深度) 3、非稳定排序 不稳定发生在中轴元素和a[j] 交换的时刻 4、原地排序
-
堆排序
堆的特点就是堆顶的元素是一个最值,大顶堆的堆顶是最大值,小顶堆则是最小值。
将初始待排序列
(R1, R2, ……, Rn)
构建成大顶堆,此堆为初始的无序区;依次插入构造,如果比父节点大,就会和父节点交换顺序将堆顶元素
R[1]
与最后一个元素R[n]
交换,此时得到新的无序区(R1, R2, ……, Rn-1)
和新的有序区 (Rn), 且满足R[1, 2, ……, n-1]<=R[n]
;由于交换后新的堆顶
R[1]
可能违反堆的性质,因此需要对当前无序区(R1, R2, ……, Rn-1)
调整为新堆,堆顶元素与孩子中较大的值交换然后再次将 R [1] 与无序区最后一个元素交换,得到新的无序区
(R1, R2, ……, Rn-2)
和新的有序区(Rn-1, Rn)
。不断重复此过程直到有序区的元素个数为n-1
,则整个排序过程完成。
性质:1、时间复杂度:O(nlogn) 2、空间复杂度:O(1) 3、非稳定排序 4、原地排序
-
计数排序
计数排序是一种适合于最大值和最小值的差值不是不是很大的排序。
基本思想:申请一个新数组,数组大小是原数组最大值和最小值之差+1(k)
遍历一遍原数组,把数组元素作为数组的下标,统计该元素出现的次数,例如 temp[i] = m, 表示元素 i 一共出现了 m 次。最后再把临时数组统计的数据从小到大汇总起来(k),此时汇总起来是数据是有序的。
性质:1、时间复杂度:O(n+k) 2、空间复杂度:O(n+k) 3、稳定排序 4、非原地排序
-
桶排序
利用函数的映射关系,假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行。之后每个桶里面的数据就是有序的了,我们再进行合并汇总。
性质:1、时间复杂度:O(n+k) 2、空间复杂度:O(k) 3、稳定排序 4、非原地排序 k是桶的个数
-
基数排序
基数排序的排序思路是这样的:先以个位数的大小来对数据进行排序,接着以十位数的大小来进行排序,接着以百位数的大小......
排到最后,就是一组有序的元素了。不过,他在以某位数进行排序的时候,是用“桶”来排序的。
由于某位数(个位/十位....,不是一整个数)的大小范围为0-9,所以我们需要10个桶,然后把具有相同数值的数放进同一个桶里,之后再把桶里的数按照0号桶到9号桶的顺序取出来,这样一趟下来,按照某位数的排序就完成了
性质:1、时间复杂度:O(kn) k是数组中元素最大的位数
2、空间复杂度:O(n+k) 3、稳定排序 4、非原地排序
这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:
基数排序:根据键值的每位数字来分配桶
计数排序:每个桶只存储单一键值
桶排序:每个桶存储一定范围的数值
设计模式
1.设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的有用的经验。
2.使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性,可维护性,可扩展性,通用性,并降低软件的复杂度。
-
创建型模式 关注对象的创建,创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
-
单例模式
保证一个类仅有一个实例,并提供一个访问它的全局访问点,可以直接访问,不需要实例化该类的对象。
使用场景:需要频繁创建和销毁对象,耗时过多,创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等;想控制实例数目,节省系统资源的时候;工具类对象;频繁访问数据库或文件的对象(多线程);一个班级只有一个班主任。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
实现方式:
1.懒汉式,线程安全
优点:懒加载,第一次调用才初始化,避免内存浪费。
缺点:效率低,99% 情况下不需要同步。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}2.饿汉式
优点:没有加锁,执行效率会提高。 缺点:类加载时就初始化,浪费内存。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}3.静态内部类
懒加载,线程安全
外部类在加载的时候静态内部类不会加载,实现了懒加载;
调用getInstance方法时,加载静态内部类,只加载一次,类加载是线程安全的
public class Singleton {
private Singleton(){}
private static class SingletonLoader(){
private final static Singleton singleInstance=new Singleton();
}
public static final Singleton getInstance()
{
return SingletonLoader.singleInstance;
}}
-
工厂模式
实例化对象不用new,用工厂方法代替
实现调用者和实现类解耦
使用场景:明确地计划不同条件下创建不同实例时,比如不同的情况下创建不同品牌的手机(不同厂商下的产品),可以直接从工厂里面提货,不用关心手机是怎么做出来的。
优点: 1、一个调用者想创建一个对象,只要知道其名称就可以了。 2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。
静态工厂模式 一个类接口有几个实现类, 一个工厂静态方法,使用该方法创建对象,根据方法参数创建不同的对象
-
工厂方法模式 有一个 工厂接口,创建不同的对象使用不同的工厂实现类,如果想增加一个产品,只要扩展一个工厂类就可以,不用修改工厂静态方法的判断逻辑代码,扩展性更强 (一维)
[图片上传失败...(image-f6fd30-1664767404805)]
-
抽象工厂模式 二维 抽象工厂接口有多个方法(对应不用类型产品) 增加固定类型产品的不同具体工厂比较方便。
[图片上传失败...(image-5e9ed5-1664767404805)]
-
结构型模式 关注软件的结构,类和对象的组合,让软件的结构更具有伸缩性。继承的概念被用来组合接口和定义组合对象获得新功能的方式。
-
代理模式
为一个对象提供一个替身,通过代理对象访问目标对象,好处:可以在目标对象实现的基础上,增加额外的功能操作,扩展目标对象的功能。
使用场景:对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问(远程对象),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
-
静态代理
代理对象和真实对象实现共同的接口,在代理对象构造函数中把真实对象传入代理对象中
-
动态代理
代理对象不需要实现接口,利用反射机制动态在内存中构建代理对象(用proxy.newProxyInstance 方法)
Cglib代理
-
静态代理和动态代理的区别:
静态代理:由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
动态代理:动态代理类的源码是根据需要在程序运行期间由JVM根据反射等机制动态的为目标对象创建代理对象,无需程序员手动编写它的源代码,所以不存在代理类的字节码文件。动态代理不仅简化了编程工作,而且提高了软件系统的可扩展性,因为他不需要实现接口,如果接口中增加了新的方法,静态代理要维护代理类和真实类,动态代理不需要维护代理类,可维护性更好。即满足生产需要的同时又达到代码通用的目的。
-
行为型模式 这些设计模式特别关注对象之间的通信。
- 观察者模式
HTTP和HTTPS的区别
HTTP 明文传输,数据都是未加密的,安全性较差,HTTPS(SSL+HTTP) 数据传输过程是加密的,安全性较好。
使用 HTTPS 协议需要到 CA(Certificate Authority,数字证书认证机构) 申请证书,一般免费证书较少,因而需要一定费用。证书颁发机构如:Symantec、Comodo、GoDaddy 和 GlobalSign 等。
HTTP 页面响应速度比 HTTPS 快,主要是因为 HTTP 使用 TCP 三次握手建立连接,客户端和服务器需要交换 3 个包,而 HTTPS除了 TCP 的三个包,还要加上 ssl 握手需要的 9 个包,所以一共是 12 个包。
http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。
HTTPS 其实就是建构在 SSL/TLS 之上的 HTTP 协议,所以,要比较 HTTPS 比 HTTP 要更耗费服务器资源
HTTPS工作原理
1、客户端发起 HTTPS 请求
这个没什么好说的,就是用户在浏览器里输入一个 https 网址,然后连接到 server 的 443 端口。
2、服务端的配置
采用 HTTPS 协议的服务器必须要有一套数字证书,可以自己制作,也可以向组织申请,区别就是自己颁发的证书需要客户端验证通过,才可以继续访问,而使用受信任的公司申请的证书则不会弹出提示页面(startssl 就是个不错的选择,有 1 年的免费服务)。
这套证书其实就是一对公钥和私钥,如果对公钥和私钥不太理解,可以想象成一把钥匙和一个锁头,只是全世界只有你一个人有这把钥匙,你可以把锁头给别人,别人可以用这个锁把重要的东西锁起来,然后发给你,因为只有你一个人有这把钥匙,所以只有你才能看到被这把锁锁起来的东西。
3、传送证书
这个证书其实就是公钥,只是包含了很多信息,如证书的颁发机构,过期时间等等。
4、客户端解析证书
这部分工作是有客户端的TLS来完成的,首先会验证公钥是否有效,比如颁发机构,过期时间等等,如果发现异常,则会弹出一个警告框,提示证书存在问题。
如果证书没有问题,那么就生成一个随机值,然后用证书对该随机值进行加密,就好像上面说的,把随机值用锁头锁起来,这样除非有钥匙,不然看不到被锁住的内容。
5、传送加密信息
这部分传送的是用证书加密后的随机值,目的就是让服务端得到这个随机值,以后客户端和服务端的通信就可以通过这个随机值来进行加密解密了。
6、服务端解密信息
服务端用私钥解密后,得到了客户端传过来的随机值(私钥),然后把内容通过该值进行对称加密,所谓对称加密就是,将信息和私钥通过某种算法混合在一起,这样除非知道私钥,不然无法获取内容,而正好客户端和服务端都知道这个私钥,所以只要加密算法够彪悍,私钥够复杂,数据就够安全。
7、传输加密后的信息
这部分信息是服务段用私钥加密后的信息,可以在客户端被还原。
8、客户端解密信息
客户端用之前生成的私钥解密服务段传过来的信息,于是获取了解密后的内容,整个过程第三方即使监听到了数据,也束手无策。
高并发题解决
1.负载均衡