少女祈祷中...

title: 字节一面总结
cover: https://trustcdn.baidu.com/officialcard/f643171a2402c49eec852947ac71a407_originalsize.png

面试问题

1 线程和进程的区别

  1. 进程 是操作系统资源分配的最小单位。 线程(英语:thread)是[操作系统]能够进行运算[调度]的最小单位

  2. 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。

    线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。

  3. 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。

    进程间通信方式:

    • 管道
    • 消息队列:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。
    • 共享内存:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。
    • 信号量: 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
    • 套接字
    • 信号
  4. 多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

2 死锁的原理

多个线程/进程在抢占CPU执行权的时候出现了互相等待的状态。

四个条件

这四个条件是死锁产生的必要条件,只要发生死锁,一定存在这四个条件

互斥条件 :一个资源每次只能被一个进程/线程使用

请求保持条件:一个进程在申请新的资源的同时保持对原有资源的占有(只有这样才是动态申请,动态分配);

循环等待:资源申请者不能强行地从资源占有者手中夺取资源,资源只能由占有者自愿释放;

不可抢占:若干进程之间形成一种头尾相连的循环等待资源。

3 url中输入HTTPS后的全流程

  1. 解析URL

    检查这些请求是HTTPS还是HTTP,如果是HTTPS的话则使用HTTPS协议进行访问

  2. 查找IP地址

    首先检查本地缓存中是否存在该域名,如果存在则直接使用缓存中的IP地址进行访问。

    如果不存在,检查域名是否在本地的Hosts文件中,如果找到直接返回域名对应的IP。

    向DNS服务器发送一个域名查询请求,然后就执行DNS查询过程,最终返回域名对应的IP地址。

  3. 建立连接

    • 当浏览器得到了目标服务器的 IP 地址,以及 URL 中给出来端口号(http 协议默认端口号是 80, https 默认端口号是 443),它会调用系统库函数 socket ,请求一个 TCP流套接字。进行网络数据的传输。
    • 连接建立之后,则根据HTTP协议进行数据交换,资源通常是 HTML 文件,也可能是 PDF,图片,或者其他类型的内容。
  4. 浏览器获得资源文件后,HTML,css,js等文件则根据自身内核的机制,进行页面渲染,然后呈现给用户。

  5. 浏览器发送连接请求,附上自己支持的加密算法

  6. 服务器接收到客户端请求,想浏览器发送对应的CA证书,证书包含非堆成加密的公钥以及证书签名。

  7. 浏览器判断证书是否合法,并生成随机会话密钥,使用公钥加密随机会话密钥后发送给服务器

  8. 服务器采用私钥解密随机会话密钥

4 cookie和session的区别

cookie数据保存在客户端,session数据保存在服务器端

Cookie的工作原理:

(1)浏览器端第一次发送请求到服务器端
(2)服务器端创建Cookie,该Cookie中包含用户的信息,然后将该Cookie发送到浏览器端
(3)浏览器端再次访问服务器端时会携带服务器端创建的Cookie
(4)服务器端通过Cookie中携带的数据区分不同的用户

img

img

Session的工作原理:

(1)浏览器端第一次发送请求到服务器端,服务器端创建一个Session,同时会创建一个特殊的Cookie(name为JSESSIONID的固定值,value为session对象的ID),然后将该Cookie发送至浏览器端
(2)浏览器端发送第N(N>1)次请求到服务器端,浏览器端访问服务器端时就会携带该name为JSESSIONID的Cookie对象
(3)服务器端根据name为JSESSIONID的Cookie的value(sessionId),去查询Session对象,从而区分不同用户。
name为JSESSIONID的Cookie不存在(关闭或更换浏览器),返回1中重新去创建Session与特殊的Cookie
name为JSESSIONID的Cookie存在,根据value中的SessionId去寻找session对象
value为SessionId不存在(Session对象默认存活30分钟),返回1中重新去创建Session与特殊的Cookie
value为SessionId存在,返回session对象

在这里插入图片描述

在这里插入图片描述

区别:

cookie数据保存在客户端,session数据保存在服务端。

session
简单的说,当你登陆一个网站的时候,如果web服务器端使用的是session,那么所有的数据都保存在服务器上,客户端每次请求服务器的时候会发送当前会话sessionid,服务器根据当前sessionid判断相应的用户数据标志,以确定用户是否登陆或具有某种权限。由于数据是存储在服务器上面,所以你不能伪造。

cookie
sessionid是服务器和客户端连接时候随机分配的,如果浏览器使用的是cookie,那么所有数据都保存在浏览器端,比如你登陆以后,服务器设置了cookie用户名,那么当你再次请求服务器的时候,浏览器会将用户名一块发送给服务器,这些变量有一定的特殊标记。服务器会解释为cookie变量,所以只要不关闭浏览器,那么cookie变量一直是有效的,所以能够保证长时间不掉线。

5 MYSQL的索引

索引是存储引擎用于提高数据库表的访问速度的一种数据结构

优点:

  • 加快数据查找的速度
  • 为用来排序或者是分组的字段添加索引,可以加快分组和排序的速度
  • 加快表与表之间的连接

缺点:

  • 建立索引需要占用物理空间
  • 会降低表的增删改的效率,因为每次对表记录进行增删改,需要进行动态维护索引,导致增删改时间变长

作用:

数据是存储在磁盘上的,查询数据时,如果没有索引,会加载所有的数据到内存,依次进行检索,读取磁盘次数较多。有了索引,就不需要加载所有数据,因为B+树的高度一般在2-4层,最多只需要读取2-4次磁盘,查询速度大大提升。

建立索引的条件:

  1. 经常用于查询的字段
  2. 经常用于连接的字段建立索引,可以加快连接的速度
  3. 经常需要排序的字段建立索引,因为索引已经排好序,可以加快排序查询速度

什么情况下不建索引?

  1. where条件中用不到的字段不适合建立索引
  2. 表记录较少
  3. 需要经常增删改
  4. 参与列计算的列不适合建索引
  5. 区分度不高的字段不适合建立索引,如性别等

6 并列索引

ABC

查询AC只能部分命中,索引的部分命中

什么是最左匹配原则?

如果 SQL 语句中用到了组合索引中的最左边的索引,那么这条 SQL 语句就可以利用这个组合索引去进行匹配。当遇到范围查询(><betweenlike)就会停止匹配,后面的字段不会用到索引。

(a,b,c)建立索引,查询条件使用 a/ab/abc 会走索引,使用 bc 不会走索引。

(a,b,c,d)建立索引,查询条件为a = 1 and b = 2 and c > 3 and d = 4,那么a、b和c三个字段能用到索引,而d无法使用索引。因为遇到了范围查询。

如下图,对(a, b) 建立索引,a 在索引树中是全局有序的,而 b 是全局无序,局部有序(当a相等时,会根据b进行排序)。直接执行b = 2这种查询条件无法使用索引。

最左前缀

最左前缀

当a的值确定的时候,b是有序的。例如a = 1时,b值为1,2是有序的状态。当a = 2时候,b的值为1,4也是有序状态。 当执行a = 1 and b = 2时a和b字段能用到索引。而执行a > 1 and b = 2时,a字段能用到索引,b字段用不到索引。因为a的值此时是一个范围,不是固定的,在这个范围内b值不是有序的,因此b字段无法使用索引。

7 抽象类和接口的区别

8 抽象类和接口分别在什么场景下使用

使用抽象类的情况:

  • 需要为一些类提供公共的实现代码时,应优先考虑抽象类
  • 定义某个领域的固有属性
  • 既想约束子类具有共同的行为(但不再乎其如何实现),又想拥有缺省的方法,又能拥有实例变量

使用接口:

  • 约束多个实现类具有统一的行为,但是不在乎每个实现类如何具体实现
  • 实现类需要具备很多不同的功能,但各个功能之间可能没有任何联系。
  • 使用接口的引用调用具体实现类中实现的方法(多态)

9 JVM垃圾回收机制、算法

垃圾收集器:Serial收集器

算法:

  • 标记清除算法: 标记要回收的对象,标记完成后统一回收所有被标记的对象。效率问题、空间问题
  • 复制算法:将内存分为两部分,每次使用一部分,使用完之后将存活对象拷贝到另一块内存当中,再将所有内存全部清除。
  • 标记-整理算法:标记过程仍然与“标记-清除”算法⼀样,但后续步骤不是直接对可回收对象回收,⽽是让所有存活的对象向⼀端移动,然后直接清理掉端边界以外的内存 。
  • 分代收集算法:⽐如在新⽣代中,每次收集都会有⼤量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。⽽⽼年代的对象存活⼏率是⽐较⾼的,⽽且没有额外的空间对它进⾏分
    配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进⾏垃圾收集

10 JAVA中线程、进程同步方式

11 JAVA类的加载过程

  1. 加载
  2. 验证
  3. 准备
  4. 解析
  5. 初始化
  6. 使用
  7. 卸载

12.Linux中的内存模型

内存管理分为连续分配管理以及非连续分配管理两种

连续分配管理 :块式管理。

非连续分配管理:页式管理、段式管理以及段页式管理.

在分页式内存管理当中,由于存在虚拟内存,存在

  1. 虚拟地址到物理地址的转换要快。
  2. 解决虚拟地址空间⼤,⻚表也会很⼤的问题

为了解决虚拟地址到物理地址的转换速度,操作系统在 ⻚表⽅案 基础之上引⼊了 快表 来加速虚拟地
址到物理地址的转换。其中的内容是⻚表的⼀部分或者全部内容。作为⻚表的 Cache,它的作⽤与⻚表相似,但是提⾼了访问速率。由于采⽤⻚表做地址转换,读写内存数据时 CPU 要访问两次主存。有了快表,有时只要访问⼀次⾼速缓冲存储器,⼀次主存,这样可加速查找并提⾼指令执⾏速度。

  1. 根据虚拟地址中的⻚号查快表;
  2. 如果该⻚在快表中,直接从快表中读取相应的物理地址;
  3. 如果该⻚不在快表中,就访问内存中的⻚表,再从⻚表中得到物理地址,同时将⻚表中的该映射
    表项添加到快表中;
  4. 当快表填满后,⼜要登记新⻚时,就按照⼀定的淘汰策略淘汰掉快表中的⼀个⻚。

算法

题目

给定一个数n如23121;给定一组数字a如[2 4 9]求由a中元素组成的小于n的最大数

代码解析思路

该题使用到的算法主要是贪心、回溯以及二分,首先将nums数组进行排序方便进行二分查找,之后将给定数字n分解成一个列表,注意最高位在列表的最后一位,需要从前往后尽心寻找。

首先判断能取的最大数跟N的位数是否相同,不同直接取数组中的最大值即可。
注意如果出现前面取跟n一样的数,而后面出现没有数可以取的情况需要回溯减小前面的数。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package com.byte_mianshi;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
public class Nmin {
private static int ret = 0;
private static boolean over = false;
public static void resolve(int n,int[] data){
Arrays.sort(data);
List<Integer> list = new ArrayList<Integer>();
int n_copy = n;
int tmp = 0;
while (n_copy >0){
tmp = n_copy % 10;
n_copy = n_copy / 10;
list.add(tmp);
}
System.out.println("数据打印开始");

for (int d: list) {
System.out.println(d);
}
System.out.println("数据打印开始");
System.out.println("数组打印开始");
for(int d : data){
System.out.println(d);
}
System.out.println("数组打印完毕");
System.out.println("数据大小:" + list.size());


int minData = 0;
for (int d: list) {
minData = minData * 10 + data[0];
}
System.out.println("最小值"+minData);
if(minData >= n){
//只能取比n少一位
dfs(data,list,n,list.size()-2,0,true);
}else{
dfs(data,list,n,list.size()-1,0,false);
}

System.out.println(ret);
}

/**
*
* @param data 可选数组数据
* @param list 分解后的n值
* @param n
* @param index 当前正在选择的位数
* @param ans 已经选择的数组成的值
* @param flag 是否可以无脑选择最大数的标志位
*/
public static void dfs(int[] data,List<Integer> list,int n,int index,int ans,boolean flag){
int len = list.size();
if(over == true){
return ;
}
if(index < 0){
//选择完毕
if(ans < n){
ret = Math.max(ret,ans);
over = true;
return ;
}
return ;
}
if(flag == false){
//找到最大的不大于list.get(index)的坐标
int f = find(list.get(index) , data);
//当前所有的数都比需要小于的数大,说明前面的数取大了,直接返回
if( f < 0){
return;
}else{
for(int i = f;i>=0;i--){
ans = ans * 10 + data[i];
if(data[i] < list.get(index)){
flag = true;
dfs(data,list,n,index-1,ans,flag);
}else{
//相等的情况
dfs(data,list,n,index-1,ans,flag);
}
ans = (ans - data[i])/10;
}
}
}else{
//说明前面有数字比n对应的数字小,可以一直取最大值
ans = ans * 10 + data[data.length-1];
dfs(data,list,n,index-1,ans,flag);
}

}

/**
* 找到不大于min的第一个序列号
* @param min
* @param data 查找的数组
* @return 数组中第一个小于等于min的序号
*/
public static int find(int min,int[] data){
int left = 0;
int right = data.length-1;
while (left <= right){
int mid = left + ((right - left)>>1);
if( data[mid] <= min){
left = mid + 1;
}else{
right = mid - 1;
}
}
if( right <0 ){
return -1;
}
return right;

}
}

JAVA

测试案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class NminTest {

@Test
public void Test(){
Nmin nmin = new Nmin();
int[] data = new int[] {5,6,7,8};
nmin.resolve(2333,new int[]{2,3});
nmin.resolve(233332,new int[]{2,3});
nmin.resolve(500,new int[]{4,9});
nmin.resolve(5612,new int[] {5,6,7,8});
nmin.resolve(5416,new int[] {5,4,8,2});
nmin.resolve(123434,new int[] {1,4,6,3,8});
nmin.resolve(32,new int[] {2,3});
}

}

JAVA

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
2332
233323
499
5588
5288
118888
23

Process finished with exit code 0
=======
---
title: 字节一面总结
cover: https://trustcdn.baidu.com/officialcard/f643171a2402c49eec852947ac71a407_originalsize.png
---

# 面试问题

# 1 线程和进程的区别

1. **进程 是操作系统资源分配的最小单位。** **线程**(英语:thread)**是[操作系统]能够进行运算[调度]的最小单位**

2. 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。

线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。

3. 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。

进程间通信方式:

- 管道
- 消息队列:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。
- 共享内存:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。
- 信号量: 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
- 套接字
- 信号

4. 多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

# 2 死锁的原理

多个线程/进程在抢占CPU执行权的时候出现了互相等待的状态。

## 四个条件

这四个条件是死锁产生的必要条件,只要发生死锁,一定存在这四个条件

互斥条件 :一个资源每次只能被一个进程/线程使用

请求保持条件:一个进程在申请新的资源的同时保持对原有资源的占有(只有这样才是动态申请,动态分配);

循环等待:资源申请者不能强行地从资源占有者手中夺取资源,资源只能由占有者自愿释放;

不可抢占:若干进程之间形成一种头尾相连的循环等待资源。

# 3 url中输入HTTPS后的全流程

1. 解析URL

检查这些请求是HTTPS还是HTTP,如果是HTTPS的话则使用HTTPS协议进行访问

2. 查找IP地址

首先检查本地缓存中是否存在该域名,如果存在则直接使用缓存中的IP地址进行访问。

如果不存在,检查域名是否在本地的Hosts文件中,如果找到直接返回域名对应的IP。

向DNS服务器发送一个域名查询请求,然后就执行DNS查询过程,最终返回域名对应的IP地址。

3. 建立连接

- 当浏览器得到了目标服务器的 IP 地址,以及 URL 中给出来端口号(http 协议默认端口号是 80, https 默认端口号是 443),它会调用系统库函数 socket ,请求一个 TCP流套接字。进行网络数据的传输。
- 连接建立之后,则根据HTTP协议进行数据交换,资源通常是 HTML 文件,也可能是 PDF,图片,或者其他类型的内容。

4. 浏览器获得资源文件后,HTML,css,js等文件则根据自身内核的机制,进行页面渲染,然后呈现给用户。

5. 浏览器发送连接请求,附上自己支持的加密算法

6. 服务器接收到客户端请求,想浏览器发送对应的CA证书,证书包含非堆成加密的公钥以及证书签名。

7. 浏览器判断证书是否合法,并生成随机会话密钥,使用公钥加密随机会话密钥后发送给服务器

8. 服务器采用私钥解密随机会话密钥

# 4 cookie和session的区别

cookie数据保存在**客户端**,session数据保存在**服务器端**。

Cookie的工作原理:

1)浏览器端第一次发送请求到服务器端
2)服务器端创建Cookie,该Cookie中包含用户的信息,然后将该Cookie发送到浏览器端
3)浏览器端再次访问服务器端时会携带服务器端创建的Cookie
4)服务器端通过Cookie中携带的数据区分不同的用户

[![img](https://img-blog.csdnimg.cn/6a2001cb8aaa4cb99a588b13904a7d2d.png#pic_center)](https://img-blog.csdnimg.cn/6a2001cb8aaa4cb99a588b13904a7d2d.png#pic_center)

img



Session的工作原理:

1)浏览器端第一次发送请求到服务器端,服务器端创建一个Session,同时会创建一个特殊的Cookie(name为JSESSIONID的固定值,value为session对象的ID),然后将该Cookie发送至浏览器端
2)浏览器端发送第N(N>1)次请求到服务器端,浏览器端访问服务器端时就会携带该name为JSESSIONID的Cookie对象
3)服务器端根据name为JSESSIONID的Cookie的value(sessionId),去查询Session对象,从而区分不同用户。
name为JSESSIONID的Cookie不存在(关闭或更换浏览器),返回1中重新去创建Session与特殊的Cookie
name为JSESSIONID的Cookie存在,根据value中的SessionId去寻找session对象
value为SessionId不存在**(Session对象默认存活30分钟)**,返回1中重新去创建Session与特殊的Cookie
value为SessionId存在,返回session对象

[![在这里插入图片描述](https://img-blog.csdnimg.cn/8d1f669c00e346d7a5c5e0332b3dbd94.png#pic_center)](https://img-blog.csdnimg.cn/8d1f669c00e346d7a5c5e0332b3dbd94.png#pic_center)

在这里插入图片描述



区别:

cookie数据保存在客户端,session数据保存在服务端。

session
简单的说,当你登陆一个网站的时候,如果web服务器端使用的是session,那么所有的数据都保存在服务器上,客户端每次请求服务器的时候会发送当前会话sessionid,服务器根据当前sessionid判断相应的用户数据标志,以确定用户是否登陆或具有某种权限。由于数据是存储在服务器上面,所以你不能伪造。

cookie
sessionid是服务器和客户端连接时候随机分配的,如果浏览器使用的是cookie,那么所有数据都保存在浏览器端,比如你登陆以后,服务器设置了cookie用户名,那么当你再次请求服务器的时候,浏览器会将用户名一块发送给服务器,这些变量有一定的特殊标记。服务器会解释为cookie变量,所以只要不关闭浏览器,那么cookie变量一直是有效的,所以能够保证长时间不掉线。

# 5 MYSQL的索引

索引是存储引擎用于提高数据库表的访问速度的一种**数据结构**。

### 优点:

- **加快数据查找的速度**
- 为用来[排序](https://fufujin.github.io/2022/07/25/字节1面/)或者是分组的字段添加索引,可以加快分组和[排序](https://fufujin.github.io/2022/07/25/字节1面/)的速度
- 加快表与表之间的连接

### 缺点:

- 建立索引需要**占用物理空间**
- 会降低表的增删改的效率,因为每次对表记录进行增删改,需要进行**动态维护索引**,导致增删改时间变长

### 作用:

数据是存储在磁盘上的,查询数据时,如果没有索引,会加载所有的数据到内存,依次进行检索,读取磁盘次数较多。有了索引,就不需要加载所有数据,因为B+树的高度一般在2-4层,最多只需要读取2-4次磁盘,查询速度大大提升。

### 建立索引的条件:

1. 经常用于查询的字段
2. 经常用于连接的字段建立索引,可以加快连接的速度
3. 经常需要[排序](https://fufujin.github.io/2022/07/25/字节1面/)的字段建立索引,因为索引已经排好序,可以加快[排序](https://fufujin.github.io/2022/07/25/字节1面/)查询速度

### 什么情况下不建索引?

1. `where`条件中用不到的字段不适合建立索引
2. 表记录较少
3. 需要经常增删改
4. **参与列计算**的列不适合建索引
5. **区分度不高**的字段不适合建立索引,如性别等

# 6 并列索引

ABC

查询AC只能部分命中,索引的部分命中

### 什么是最左匹配原则?

如果 SQL 语句中用到了组合索引中的最左边的索引,那么这条 SQL 语句就可以利用这个组合索引去进行匹配。当遇到范围查询(`>`、`<`、`between`、`like`)就会停止匹配,后面的字段不会用到索引。

对`(a,b,c)`建立索引,查询条件使用 a/ab/abc 会走索引,使用 bc 不会走索引。

对`(a,b,c,d)`建立索引,查询条件为`a = 1 and b = 2 and c > 3 and d = 4`,那么a、b和c三个字段能用到索引,而d无法使用索引。因为遇到了范围查询。

如下图,对(a, b) 建立索引,a 在索引树中是全局有序的,而 b 是全局无序,局部有序(当a相等时,会根据b进行[排序](https://fufujin.github.io/2022/07/25/字节1面/))。直接执行`b = 2`这种查询条件无法使用索引。

[![最左前缀](https://uploadfiles.nowcoder.com/files/20211030/8683776_1635580314321/%E6%9C%80%E5%B7%A6%E5%89%8D%E7%BC%80.png)](https://uploadfiles.nowcoder.com/files/20211030/8683776_1635580314321/最左前缀.png)

最左前缀



当a的值确定的时候,b是有序的。例如`a = 1`时,b值为12是有序的状态。当`a = 2`时候,b的值为14也是有序状态。 当执行`a = 1 and b = 2`时a和b字段能用到索引。而执行`a > 1 and b = 2`时,a字段能用到索引,b字段用不到索引。因为a的值此时是一个范围,不是固定的,在这个范围内b值不是有序的,因此b字段无法使用索引。

# 7 抽象类和接口的区别

# 8 抽象类和接口分别在什么场景下使用

使用抽象类的情况:

- 需要为一些类提供公共的实现代码时,应优先考虑抽象类
- 定义某个领域的固有属性
- 既想约束子类具有共同的行为(但不再乎其如何实现),又想拥有缺省的方法,又能拥有实例变量

使用接口:

- 约束多个实现类具有统一的行为,但是不在乎每个实现类如何具体实现
- 实现类需要具备很多不同的功能,但各个功能之间可能没有任何联系。
- 使用接口的引用调用具体实现类中实现的方法(多态)

# 9 JVM垃圾回收机制、算法

垃圾收集器:Serial收集器

算法:

- 标记清除算法: 标记要回收的对象,标记完成后统一回收所有被标记的对象。效率问题、空间问题
- 复制算法:将内存分为两部分,每次使用一部分,使用完之后将存活对象拷贝到另一块内存当中,再将所有内存全部清除。
- 标记-整理算法:标记过程仍然与“标记-清除”算法⼀样,但后续步骤不是直接对可回收对象回收,⽽是让所有存活的对象向⼀端移动,然后直接清理掉端边界以外的内存 。
- 分代收集算法:⽐如在新⽣代中,每次收集都会有⼤量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。⽽⽼年代的对象存活⼏率是⽐较⾼的,⽽且没有额外的空间对它进⾏分
配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进⾏垃圾收集

# 10 JAVA中线程、进程同步方式

# 11 JAVA类的加载过程

1. 加载
2. 验证
3. 准备
4. 解析
5. 初始化
6. 使用
7. 卸载

# 12.Linux中的内存模型

内存管理分为连续分配管理以及非连续分配管理两种

连续分配管理 :块式管理。

非连续分配管理:页式管理、段式管理以及段页式管理.

在分页式内存管理当中,由于存在虚拟内存,存在

1. 虚拟地址到物理地址的转换要快。
2. 解决虚拟地址空间⼤,⻚表也会很⼤的问题

为了解决虚拟地址到物理地址的转换速度,操作系统在 ⻚表⽅案 基础之上引⼊了 快表 来加速虚拟地
址到物理地址的转换。其中的内容是⻚表的⼀部分或者全部内容。作为⻚表的 Cache,它的作⽤与⻚表相似,但是提⾼了访问速率。由于采⽤⻚表做地址转换,读写内存数据时 CPU 要访问两次主存。有了快表,有时只要访问⼀次⾼速缓冲存储器,⼀次主存,这样可加速查找并提⾼指令执⾏速度。

1. 根据虚拟地址中的⻚号查快表;
2. 如果该⻚在快表中,直接从快表中读取相应的物理地址;
3. 如果该⻚不在快表中,就访问内存中的⻚表,再从⻚表中得到物理地址,同时将⻚表中的该映射
表项添加到快表中;
4. 当快表填满后,⼜要登记新⻚时,就按照⼀定的淘汰策略淘汰掉快表中的⼀个⻚。

# 算法

## 题目

给定一个数n如23121;给定一组数字a如[2 4 9]求由a中元素组成的小于n的最大数

## 代码解析思路

该题使用到的算法主要是贪心、回溯以及二分,首先将nums数组进行排序方便进行二分查找,之后将给定数字n分解成一个列表,注意最高位在列表的最后一位,需要从前往后尽心寻找。

首先判断能取的最大数跟N的位数是否相同,不同直接取数组中的最大值即可。
注意如果出现前面取跟n一样的数,而后面出现没有数可以取的情况需要回溯减小前面的数。

## 代码

package com.byte_mianshi;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
public class Nmin {
private static int ret = 0;
private static boolean over = false;
public static void resolve(int n,int[] data){
Arrays.sort(data);
List list = new ArrayList();
int n_copy = n;
int tmp = 0;
while (n_copy >0){
tmp = n_copy % 10;
n_copy = n_copy / 10;
list.add(tmp);
}
System.out.println(“数据打印开始”);

    for (int d: list) {
        System.out.println(d);
    }
    System.out.println("数据打印开始");
    System.out.println("数组打印开始");
    for(int d : data){
        System.out.println(d);
    }
    System.out.println("数组打印完毕");
    System.out.println("数据大小:" + list.size());


    int minData = 0;
    for (int d: list) {
        minData = minData * 10 + data[0];
    }
    System.out.println("最小值"+minData);
    if(minData >= n){
        //只能取比n少一位
        dfs(data,list,n,list.size()-2,0,true);
    }else{
        dfs(data,list,n,list.size()-1,0,false);
    }

    System.out.println(ret);
}

/**
 *
 * @param data  可选数组数据
 * @param list  分解后的n值
 * @param n
 * @param index  当前正在选择的位数
 * @param ans    已经选择的数组成的值
 * @param flag   是否可以无脑选择最大数的标志位
 */
public static  void dfs(int[] data,List<Integer> list,int n,int index,int ans,boolean flag){
    int len = list.size();
    if(over == true){
        return ;
    }
    if(index < 0){
        //选择完毕
        if(ans < n){
            ret = Math.max(ret,ans);
            over = true;
            return ;
        }
        return ;
    }
    if(flag == false){
        //找到最大的不大于list.get(index)的坐标
        int  f = find(list.get(index) , data);
        //当前所有的数都比需要小于的数大,说明前面的数取大了,直接返回
        if( f < 0){
            return;
        }else{
            for(int i = f;i>=0;i--){
                ans = ans * 10 + data[i];
                if(data[i] < list.get(index)){
                    flag = true;
                    dfs(data,list,n,index-1,ans,flag);
                }else{
                    //相等的情况
                    dfs(data,list,n,index-1,ans,flag);
                }
                ans = (ans - data[i])/10;
            }
        }
    }else{
        //说明前面有数字比n对应的数字小,可以一直取最大值
        ans = ans * 10 + data[data.length-1];
        dfs(data,list,n,index-1,ans,flag);
    }

}

/**
 * 找到不大于min的第一个序列号
 * @param min
 * @param data  查找的数组
 * @return   数组中第一个小于等于min的序号
 */
public static int find(int min,int[] data){
    int left  = 0;
    int right = data.length-1;
    while (left <= right){
        int mid = left + ((right - left)>>1);
        if( data[mid] <= min){
            left = mid + 1;
        }else{
            right = mid - 1;
        }
    }
    if( right <0 ){
        return -1;
    }
    return right;

}

}

JAVA

1
2
3

## 测试案例

public class NminTest {

@Test
public void Test(){
    Nmin nmin = new Nmin();
    int[] data = new int[] {5,6,7,8};
    nmin.resolve(2333,new int[]{2,3});
    nmin.resolve(233332,new int[]{2,3});
    nmin.resolve(500,new int[]{4,9});
    nmin.resolve(5612,new int[] {5,6,7,8});
    nmin.resolve(5416,new int[] {5,4,8,2});
    nmin.resolve(123434,new int[] {1,4,6,3,8});
    nmin.resolve(32,new int[] {2,3});
}

}

JAVA

1
2
3
4
5
6
7
8
9
10
11
12
13
14

## 输出结果

```java
2332
233323
499
5588
5288
118888
23

Process finished with exit code 0
>>>>>>> 660263c04a65ea2551ce86cca85df1d3e5eb5ec1