后端开发八股(自看)

计网

1、TCP三次握手四次挥手

简单理解TCP三次握手四次挥手(看一遍你就懂)-CSDN博客

为什么是四次挥手不是三次:服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和 FIN 一般都会分开发送,从而比三次握手导致多了一次。

2、OSI七层模型

交换机工作在数据链路层,隔离冲突域;路由器工作在网络层,隔离广播域。

3、TCP和UDP区别(传输层协议)

TCP为什么可靠:
  • 基于数据块传输:应用数据被分割成 TCP 认为最适合发送的数据块,再传输给网络层,数据块被称为报文段或段。
  • 对失序数据包重新排序以及去重:TCP 为了保证不发生丢包,就给每个包一个序列号,有了序列号能够将接收到的数据根据序列号排序,并且去掉重复序列号的数据就可以实现数据包去重。
  • 校验和 : TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。
  • 重传机制 : 在数据包丢失或延迟的情况下,重新发送数据包,直到收到对方的确认应答(ACK)。TCP 重传机制主要有:基于计时器的重传(也就是超时重传)、快速重传(基于接收端的反馈信息来引发重传)、SACK(在快速重传的基础上,返回最近收到的报文段的序列号范围,这样客户端就知道,哪些数据包已经到达服务器了)、D-SACK(重复 SACK,在 SACK 的基础上,额外携带信息,告知发送方有哪些数据包自己重复接收了)。
  • 流量控制 : TCP 连接的每一方都有固定大小的缓冲空间,TCP 的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议(TCP 利用滑动窗口实现流量控制)。
  • 拥塞控制 : 当网络拥塞时,减少数据的发送。TCP 在发送数据的时候,需要考虑两个因素:一是接收方的接收能力,二是网络的拥塞程度。接收方的接收能力由滑动窗口表示,表示接收方还有多少缓冲区可以用来接收数据。网络的拥塞程度由拥塞窗口表示,它是发送方根据网络状况自己维护的一个值,表示发送方认为可以在网络中传输的数据量。发送方发送数据的大小是滑动窗口和拥塞窗口的最小值,这样可以保证发送方既不会超过接收方的接收能力,也不会造成网络的过度拥塞。

  4、http与https

HTTP 与 HTTPS 的区别 | 菜鸟教程 (runoob.com)

  • HTTP运行在 TCP 之上, 明文传输,数据都是未加密的,安全性较差,HTTPS是建构在 SSL/TLS 之上的 HTTP 协议, 数据传输过程是加密的,安全性较好。但HTTPS 比 HTTP 要更耗费服务器资源
  • 使用 HTTPS 协议需要到 CA(Certificate Authority,数字证书认证机构) 数字证书,一般免费证书较少,因而需要一定费用。证书颁发机构如:Symantec、Comodo、GoDaddy 和 GlobalSign 等。
  • HTTP 页面响应速度比 HTTPS 快,主要是因为 HTTP 使用 TCP 三次握手建立连接,客户端和服务器需要交换 3 个包,而 HTTPS除了 TCP 的三个包,还要加上 ssl 握手需要的 9 个包,所以一共是 12 个包。
  • http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。

5、http的流程

  • 在浏览器中输入指定网页的 URL。
  • 浏览器通过 DNS 协议,获取域名对应的 IP 地址。
  • 浏览器根据 IP 地址和端口号,向目标服务器发起一个 TCP 连接请求。
  • 浏览器在 TCP 连接上,向服务器发送一个 HTTP 请求报文,请求获取网页的内容。
  • 服务器收到 HTTP 请求报文后,处理请求,并返回 HTTP 响应报文给浏览器。
  • 浏览器收到 HTTP 响应报文后,解析响应体中的 HTML 代码,渲染网页的结构和样式,同时根据 HTML 中的其他资源的 URL(如图片、CSS、JS 等),再次发起 HTTP 请求,获取这些资源的内容,直到网页完全加载显示。
  • 浏览器在不需要和服务器通信时,可以主动关闭 TCP 连接,或者等待服务器的关闭请求。

除此之外还涉及到IP协议(建立TCP连接时需要发送数据,发送数据在网络层使用IP协议)、ARP协议(将IP地址转换为MAC地址)、OSPF开放最短路径优先协议, 是由 Internet (一种路由器间的路由选择协议)

6、HTTPS加密流程

采用对称加密+非对称加密

非对称加密的方式来传输对称加密过程中的密钥,之后采取对称加密的方式来传输数据了。具体步骤为:

  1. 某网站服务器拥有用于非对称加密的公钥A、私钥A’。
  2. 浏览器向网站服务器请求,服务器把公钥A明文给传输浏览器。
  3. 浏览器随机生成一个用于对称加密的密钥X,用公钥A加密后传给服务器。
  4. 服务器拿到后用私钥A’解密得到密钥X。
  5. 这样双方就都拥有密钥X了,且别人无法知道它。之后双方所有数据都通过密钥X加密解密即可。

中间人攻击问题:中间人掉包了服务器传来的公钥,进而得到了密钥X。根本原因是浏览器无法确认收到的公钥是不是网站自己的

如何证明浏览器收到的公钥一定是该网站的公钥使用CA机构颁发的数字证书

如何防止数字证书被篡改:使用数字签名

每次进行HTTPS请求时都必须在SSL/TLS层进行握手传输密钥吗?

错误!只需传输一次。服务器会为每个浏览器(或客户端软件)维护一个session ID,在TLS握手阶段传给浏览器,浏览器生成好密钥传给服务器后,服务器会把该密钥存到相应的session ID下,之后浏览器每次请求都会携带session ID,服务器会根据session ID找到相应的密钥并进行解密加密操作,这样就不必要每次重新制作、传输密钥了!

操作系统

1、僵尸进程与孤儿进程
  • 僵尸进程:子进程已经终止,但是其父进程仍在运行,且父进程没有调用 wait()或 waitpid()等系统调用来获取子进程的状态信息,释放子进程占用的资源,导致子进程的 PCB 依然存在于系统中,但无法被进一步使用。这种情况下,子进程被称为“僵尸进程”。避免僵尸进程的产生,父进程需要及时调用 wait()或 waitpid()系统调用来回收子进程
  • 孤儿进程:一个进程的父进程已经终止或者不存在,但是该进程仍在运行。这种情况下,该进程就是孤儿进程。孤儿进程通常是由于父进程意外终止或未及时调用 wait()或 waitpid()等系统调用来回收子进程导致的。为了避免孤儿进程占用系统资源,操作系统会将孤儿进程的父进程设置为 init 进程(进程号为 1),由 init 进程来回收孤儿进程的资源

数据库

1、聚集索引和非聚集索引

SQL索引有两种,聚集索引和非聚集索引,索引主要目的是提高了SQL Server系统的性能,加快数据的查询速度与减少系统的响应时间。

聚集索引存储记录是物理上连续存在,而非聚集索引是逻辑上的连续,物理存储并不连续。

聚集索引一个表只能有一个,而非聚集索引一个表可以存在多个。

建立索引的原则:

  1. 定义主键的数据列一定要建立索引。(一般数据库默认都会为主键生成索引)

  2. 定义有外键的数据列一定要建立索引。

  3. 对于经常查询的数据列最好建立索引。

  4. 对于需要在指定范围内的快速或频繁查询的数据列;

  5. 经常用在WHERE子句中的数据列。

  6. 经常出现在关键字order by、group by、distinct后面的字段,建立索引。
    如果建立的是复合索引,索引的字段顺序要和这些关键字后面的字段顺序一致,否则索引不会被使用。

  7. 对于查询中很少涉及的列,重复值比较多的列不要建立索引。
    为什么重复值比较多的列不要建立索引?
    如果mysql评估使用索引比全表更慢,则不使用索引,如果一个列的重复值比较多,就算建立了索引也不会走索引.

  8. 对于定义为text、image和bit的数据类型的列不要建立索引。

  9. 对于经常存取的列避免建立索引 。

  10. 限制表上的索引数目。对一个存在大量更新操作的表,所建索引的数目一般不要超过3个,最多不要超过5个。索引虽说提高了访问速度,但太多索引会影响数据的更新操作。

  11. 对复合索引,按照字段在查询条件中出现的频度建立索引。在复合索引中,记录首先按照第一个字段排序。对于在第一个字段上取值相同的记录,系统再按照第二个字段的取值排序,以此类推。因此只有复合索引的第一个字段出现在查询条件中,该索引才可能被使用,因此将应用频度高的字段,放置在复合索引的前面,会使系统最大可能地使用此索引,发挥索引的作用。

2、三大范式

  • 第一范式(1NF): 确保每列的原子性(强调的是列的原子性,即列不能够再分成其他几列)。实际上,第一范式是所有关系型数据库的最基本要求
  • 第二范式(2NF): 是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)。第二范式要求确保表中每列与主键相关,而不能只与主键的某部分相关(主要针对联合主键),主键列与非主键列遵循完全函数依赖关系,也就是完全依赖
  • 第三范式(3NF): 是在第二范式的基础上建立起来的。第三范式指:属性不依赖于其他非主属性。第三范式确保主键列之间没有传递函数依赖关系,也就是消除传递依赖

3、mysql四种事务隔离级别

数据库事务:指的是一组数据操作,事务具有原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)四个特性,简称 ACID,缺一不可。

以下几个概念是事务隔离级别要实际解决的问题,所以需要搞清楚都是什么意思。

脏读

脏读指的是读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,也就是可能最终不会存到数据库中,也就是不存在的数据。

可重复读

可重复读指的是在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据都是一致的。通常针对数据更新(UPDATE)操作。

不可重复读

对比可重复读,不可重复读指的是在同一事务内,不同的时刻读到的同一批数据可能是不一样的,可能会受到其他事务的影响,比如其他事务改了这批数据并提交了。通常针对数据更新(UPDATE)操作。

幻读

幻读是针对数据插入(INSERT)操作来说的。假设事务A对某些行的内容作了更改,但是还未提交,此时事务B插入了与事务A更改前的记录相同的记录行,并且在事务A提交之前先提交了,而这时,在事务A中查询,会发现好像刚刚的更改对于某些数据未起作用,但其实是事务B刚插入进来的,让用户感觉很魔幻,感觉出现了幻觉,这就叫幻读。

四种事务隔离级别:

  1. 读未提交(READ UNCOMMITTED)
  2. 读提交 (READ COMMITTED)
  3. 可重复读 (REPEATABLE READ)
  4. 串行化 (SERIALIZABLE)

从上往下,隔离强度逐渐增强,性能逐渐变差。采用哪种隔离级别要根据系统需求权衡决定,其中,可重复读是 MySQL 的默认级别。

事务隔离其实就是为了解决上面提到的脏读、不可重复读、幻读这几个问题。

Java

1、String和final String,==和equals()

final String a="hello";
String b="hello";
String c="hellow";
String d=new String("hello"); 
System.out.println(a==b);
System.out.println("hell"+"ow"==c);
System.out.println((a+"w")==c);
System.out.println((b+"w")==c);
System.out.println(b==d);

//输出:
true
true
true
false
false

直接定义与new创建的区别:b指向常量池,而d指向堆。在java中,遇到new关键字,即会在堆中创建对象。String d = new String("hello")时会先去常量池中找是否有"hello",如果常量池中没有,则先在常量池中新建"hello",然后在堆中创建一份拷贝对象,然后把堆内存中对象的地址赋给d。所以,String d = new String("hello"); 有可能创建了两个对象。

==与equals的区别:“==”比较的都是地址值;equals() 定义在Object类中,意味着所有的java类中都实现了这个方法,其底层其实就是通过==来进行比较,也就是说通过比较两个对象的内存地址是否相同判断是否是同一个对象。在String类中重写了equals的方法,是先比较地址值,不相等再逐个比较字符。b变量没有被final修饰,所以b+"w"的操作,应该是String类型重载的+操作,这是在编译后执行的,由于String底层是const char*,所以应该会重新在堆上开辟空间后将地址赋值给b。

final修饰变量的时候,表示变量不可变。所以,final String a = "hello";仅仅表示a不可变,不可再被重新赋值。final修饰的String在相加的时候等同于字符串直接相加,如

final a = "a",

final b = "b";

a+b == "a"+"b" == "ab"

因为+号两边都是常量,其值在编译期就可以确定,由于编译器优化,在编译期就将+两边拼接合并了,直接合并成是一个常量"ab"。

但是如果把final去掉,

a+b == "a" + "b" 就是false了

因为不用final修饰,a和b都是对象,在编译期无法确定其值,所以要等到运行期再进行处理,处理方法:先new一个StringBuilder,然后append a和 b,最后相加的结果是一个堆中new出来的一个对象。

2、JVM、JRE 和 JDK 的关系

JVM:java虚拟机,类似于一个小型的计算机,它能够将java程序编译后的.class 文件解释给相应平台的本地系统执行,从而实现跨平台。

JRE是Java的运行环境,是面向所有Java程序的使用者,包含了jvm的标准实现和java的核心类库。JRE包含了JVM。

JDK是面向开发人员使用的SDK,它提供了Java的开发环境和运行环境,JDK中包含了JRE。

3、&和&&的区别?

&& 操作符具有短路功能, 当前置条件为false 时,将不会判断后置的条件。如 false && xxxx ,程序将不会去判断xxxx是否为true或者false,反之 true && xxxx 时才会去判断。而 & 运算符无论什么时候都会判断两边的条件,当两边的表达式为true时结果为true

4、在没有使用临时变量的情况如何交换两个整数变量的值

(1)使用加减法:

a = a + b;

b = a - b;

a = a - b;

(2)使用位运算:

a = a ^ b;

b = a ^ b;

a = a ^ b;

在执行第一个步骤时,a和b的值会进行异或运算,得到一个新的值c。此时,a的值变成了c,而b的值还是原来的y。在第二步中,我们将c和y再次进行异或运算,得到的结果就是原来的x,这个结果就被赋值给了b。最后一步同理。

6、short s1 = 1; s1 = s1 + 1;有错吗? short s1 = 1; s1 += 1;有错吗?

在Java中,short类型的数值是16位的有符号整数,范围是从 -32768 到 32767。对于 short s1 = 1; s1 = s1 + 1; 这行代码来说,s1 + 1的结果是一个int类型的值,需要将其强制转换为short类型才能赋值给s1,因此这行代码会报错。而对于 short s1 = 1; s1 += 1; 这行代码来说,它等价于 s1 = (short)(s1 + 1); 其中加法运算的结果会自动进行类型转换,然后再将结果强制转换为short类型,所有这行代码是合法的,不会报错。

7、float f=3.4;是否正确?

因为3.4是一个双精度浮点数,Java中的浮点数默认为双精度浮点数,而将一个双精度浮点数直接赋值给一个单精度浮点数变量是不安全的,因为单精度浮点数只有23位有效数字,而双精度浮点数有53位有效数字,会造成精度损失。要想正确的赋值,应该在数字后面加上一个f或F,将其显示地声明为单精度浮点数,即可以这样写:float f = 3.4f;

8、Java数据结构

数组(Arrays)

数组(Arrays)是一种基本的数据结构,可以存储固定大小的相同类型的元素。

int[] array = new int[5];
  • 特点: 固定大小,存储相同类型的元素。
  • 优点: 随机访问元素效率高。
  • 缺点: 大小固定,插入和删除元素相对较慢。

列表(Lists)

Java 提供了多种列表实现,如 ArrayList 和 LinkedList。

List<String> arrayList = new ArrayList<>();
List<Integer> linkedList = new LinkedList<>();

ArrayList:

  • 特点: 动态数组,可变大小。
  • 优点: 高效的随机访问和快速尾部插入。
  • 缺点: 中间插入和删除相对较慢。

LinkedList:

  • 特点: 双向链表,元素之间通过指针连接。
  • 优点: 插入和删除元素高效,迭代器性能好。
  • 缺点: 随机访问相对较慢。

集合(Sets)

集合(Sets)用于存储不重复的元素,常见的实现有 HashSet 和 TreeSet。

Set<String> hashSet = new HashSet<>();
Set<Integer> treeSet = new TreeSet<>();

HashSet:

  • 特点: 无序集合,基于HashMap实现。
  • 优点: 高效的查找和插入操作。
  • 缺点: 不保证顺序。

TreeSet:

  • 特点:TreeSet 是有序集合,底层基于红黑树实现,不允许重复元素。
  • 优点: 提供自动排序功能,适用于需要按顺序存储元素的场景。
  • 缺点: 性能相对较差,不允许插入 null 元素。

映射(Maps)

映射(Maps)用于存储键值对,常见的实现有 HashMap 和 TreeMap。

Map<String, Integer> hashMap = new HashMap<>();
Map<String, Integer> treeMap = new TreeMap<>();

HashMap:

  • 特点: 基于哈希表实现的键值对存储结构。
  • 优点: 高效的查找、插入和删除操作。
  • 缺点: 无序,不保证顺序。

TreeMap:

  • 特点: 基于红黑树实现的有序键值对存储结构。
  • 优点: 有序,支持按照键的顺序遍历。
  • 缺点: 插入和删除相对较慢。

栈(Stack)

栈(Stack)是一种线性数据结构,它按照后进先出(Last In, First Out,LIFO)的原则管理元素。在栈中,新元素被添加到栈的顶部,而只能从栈的顶部移除元素。这就意味着最后添加的元素是第一个被移除的。

Stack<Integer> stack = new Stack<>();

Stack 类:

  • 特点: 代表一个栈,通常按照后进先出(LIFO)的顺序操作元素。

队列(Queue)

队列(Queue)遵循先进先出(FIFO)原则,常见的实现有 LinkedList 和 PriorityQueue。

Queue<String> queue = new LinkedList<>();

Queue 接口:

  • 特点: 代表一个队列,通常按照先进先出(FIFO)的顺序操作元素。
  • 实现类: LinkedList, PriorityQueue, ArrayDeque。

堆(Heap)

堆(Heap)优先队列的基础,可以实现最大堆和最小堆。

PriorityQueue<Integer> minHeap = new PriorityQueue<>();
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());

树(Trees)

Java 提供了 TreeNode 类型,可以用于构建二叉树等数据结构。

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}

图(Graphs)

图的表示通常需要自定义数据结构或使用图库,Java 没有内建的图类。

9、Java语言的特点

Java语言的特点有哪些?有什么作用?_java 具有什么特性 ,列出至少 3 个 ,分别说明其作用-CSDN博客

10、Java八种数据类型

11、hashCode() 和 equals()

在java中,hashCode() 的作用是获取哈希码,也称为散列码,hashcode()和equals()一样,也定义在Object类中,意味着java中任何类都有这个函数。

equals()相等的两个对象,hashcode()一定相等;equals()不相等的两个对象,却并不能证明他们的hashcode()不相等(我的理解是由于哈希码在生成的时候产生冲突造成的)。反之,hashcode()不等,一定能推出equals()也不等。

hashcode到底有什么用呢?它也是可以起到一定的判重作用的,但是可能会存在哈希冲突,即两个不同的对象其哈希值也有可能是相同的。其实在一般的类中hashcode并没有什么作用,一般hashcode只有在散列表中才有作用,本质是散列表的类有HashMap、HashSet、HashTable等。

Java 的容器类被分为 Collection 和 Map 两大类,Collection 又可以进一步分为 List 和 Set。 其中 Map 和 Set 都是不允许元素重复的,严格来说Map存储的是键值对,它不允许重复的键值。而Map和Set中大多数实现类底层都是散列表结构。像上面所说的HashMap、HashSet、HashTable。

当我们要在一个不能有重复元素的集合中存放新元素时,难道直接调用equals()去和已有的元素一个一个比较吗?对于元素少的集合,确实可以,但当你的元素多了后,用equals()很明显效率会低,虽然逻辑简单,但它的时间复杂度依然是O(n)。但如果先通过hashCode()进行判重------通过新元素计算出的哈希值,找到对应的哈希表位置,判断对应的内存单元是否有产生冲突,也就是判断对应的位置是否已经存有对象,如果没有,就可以直接插入;而如果产生了冲突,也就是说该位置之前已经存有元素了,那么这时就使用equals()进行比较,进一步判断两个对象是否相同。

配合散列表的操作看似复杂了一些,但其带来的效率是大大提升的。

面试官经常问为什么重写equals()时要重写hashCode()方法,这个问题经常结合着HashSet问。

例子:在Data类中并没有重写hashCode()方法,只重写equals()方法。然后实例化了三个对象,其中data1和data2内容是相同。此时我将三个对象存入到HashSet集合中,根据set中不能存放重复元素的原理,遍历集合得到的结果应该是只有两个。但实际上结果却是有重复的。

程序:

Data data1 = new Data(1,2);
        Data data2 = new Data(1,2);
        Data data3 = new Data(2,4);
        Set<Data> set = new HashSet<>();
        set.add(data1);
        set.add(data2);
        set.add(data3);
        for (Data data : set) {
            System.out.println(data);
        }

输出:

Data{val1=1, val2=2}
Data{val1=2, val2=4}
Data{val1=1, val2=2}

这是因为虽然p1 和 p2的内容相等,但是它们的hashCode()不等;所以,HashSet在添加p1和p2的时候,认为它们不相等。

所以Data类中应该同时重写equals() 和 hashCode()方法。

12、==和equals在Integer包装类中

如果是2个Integer,==比较的对象是否相等,equals比较的是值是否相等。如果是2个Integer,需要看你比较的哪种场景。

如果是1个Integer和1个int,==和equals是一样的,因为Integer会有一个拆箱的过程。只要这两个值一样,不管是==还是equals都是相等的。

  • 包装类和基本数据类型比较时,只要值相等就相等

13、String类分隔函数方法关于split(“s“)、split(“\\s“)和split(“\\s+“)的区别

split(“s”) 以字符s为分隔线,分隔后返回字符数组;
split("\\s")以碰到的每个空格、换行符、回车为分隔线,如遇到连续多个空格、换行符、回车就会连续划分,分隔后返回字符数组;
split("\\s+")以空格、换行符、回车为分隔线,相邻的多个空格、换行符、回车仍然视为只有一个,分隔后返回字符数组。

14、map和hashmap

不能单独使用 Map 接口来保存数据,但是我们可以创建其实现类的对象,然后使用 Map 引用来保存对象。例如,我们使用 HashMap 类来存储数据,并使用 Map 接口来保存该类的引用:

 Map<String, Integer> map = new HashMap<>();
    map.put("One", 1);

15、java八种基本数据类型及包装类、引用类型

(1)基本数据类型

 整数的默认类型为int,浮点数的默认类型为double;

(2)自动转换:低类型的向高类型的转换 

(3)对应包装类 

java八种基本数据类型不具备面向对象的特征,所以实际使用中很不便所以为每种基本数据类型提供了对应的包装类:除了char的是Character、int类型的是Integer,其他都是首字母大写。

 包装类的直接父类:前6个由于是数,直接父类为Number,而后两个的直接父类就是Object类

(4)包装类的基本方法

① 静态方法 valueOf()

参数为基本数据类型,返回包装类对象

参数为String字符串(Character类没有以String为参数的该方法),返回包装类对象

@Test
	public void a() {
	/*1.参数为基本数据类型		
	 * 作用:将基本数据类型转换为对应包装类 * */
		Integer i=Integer.valueOf(10);
		System.out.println(i);//输出10
	
	/*2.参数为String字符串时,
	 * 作用:返回指定字符串值的包装类对象
	 * 	*/
		Integer a=Integer.valueOf("100");
		System.out.println(a);//输出100
		
		Integer b=Integer.valueOf("100a")为
		System.out.println(b);//运行错误,字符串的值不少一个int类型的
		
	}

 ② 静态方法parseXXX(String str)

Character类没有该方法;

作用:将字符串装换为对应的基本数据类型(注意此处和上面的valueOf方法返回值的不同);

@Test
	public void b() {
		/*作用:将给定字符串装换为对应的基本数据类型
		 * 前提是该字符串必须正确描述该基本数据类型表示的值*/
		int a=Integer.parseInt("100");
		System.out.println(a);//输出100
		
		int b=Integer.parseInt("100a");
		System.out.println(b);//运行错误,字符串的值不为int类型
				
	}

③ 非静态方法XXXValue()

因为是非静态方法,所以不能像上面两个方法用类名调用了;

数字类的包装类(八种包装类中父类是Number的的六个类)才有该方法;

作用:将当前包装类对象转换为对应的基本数据类型;

@Test
	public void c() {
		/*作用:将包装类对象转换为对应的基本数据类型*/
		
		Integer a=Integer.valueOf(100);//将基本数据类型转换为包装类对象
		int b=a.intValue();//将包装类对象转换为对应的基本数据类型
		System.out.println(b);//输出100
		
		Double c=Double.valueOf(2.33);
		double d=c.doubleValue();
		System.out.println(d);
	}
 (5)自动拆箱与装箱

自动拆箱 包装类——>基本数据类型 (原理是调用了xxxValue方法)    
自动装箱 基本数据类型——>包装类 (原理是调用了valueOf方法)

@Test
	public void d() {
		/*自动装箱:valueOf*/
		Integer i=123;//原理是 Integer i=Integer.valueOf(123);
		
		/*自动拆箱*/
		int i1=i+1;//原理是	int i1=i.intValue()+1;
		
		Integer a=123;
		Integer b=123;
		Integer c=a+b;
		/*原理为Integer c=Integer.valueOf(a.intValue()+b.intValue());*/
	}
(6) 基本数据类型与包装类区别
  • 存储方式:基本类型直接存储值,而包装类型存储的是对应基本类型值的对象。
  • 空值处理:基本类型没有空值(null)的概念,而包装类型可以将null作为有效值来表示缺失或无效值。
  • 默认值:基本类型有默认值,例如int类型的默认值是0,boolean类型的默认值是false。而包装类型的默认值是null。
  • 对象操作:基本类型不能直接调用方法,而包装类型可以调用对应的方法,例如Integer类的intValue()方法可以获取保存在Integer对象中的值。
  • 自动装箱/拆箱:基本类型和包装类型之间可以进行自动装箱和拆箱的转换。自动装箱是指将基本类型的值自动转换为对应的包装类型对象,如int 转Integer,Integer integer = 100,底层调用了Interger.valueOf(100)方法;而自动拆箱则是将包装类型对象自动转换为基本类型的值。
  • 泛型支持:泛型只能使用引用类型,不能直接使用基本类型。因此,当需要在泛型中使用基本类型时,需要使用对应的包装类型。
  • 比较方式:基本类型使用\==进行比较时,比较的是值是否相等。而包装类型使用\==进行比较时,比较的是引用是否指向同一个对象,而不是比较值是否相等。若要比较包装类型的值是否相等,需要使用equals()方法。
(7) 注意一些容易忽视的类型转换
public static void main(String[] args) {
		int a=10;
		double b=3.4;
		System.out.println(a>b?a:b);
		System.out.println(a);
	}
/*输出:10.0   10
解析:这里是一个很容易让人不注意的类型转化,这里a与b参与了运算,
所以类型向类型大的方向转化,10就变成了10.0,但是a本身是没有变化的*/
(8) char类型能不能存储一个汉字?为什么?

能,char类型采用的是Unicode编码,Unicode编码包含汉字,所以char类型自然是可以存储一个汉字的。

(9)引用数据类型

基本数据类型:直接存储在栈(stack)中的数据

引用数据类型在栈中存储的是该对象的引用,真实的数据存放在堆内存里。栈中的地址指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

引用数据类型:类(Class)、接口(Interface)、数组(Array)

 16、next和nextline

① nextLine()方法返回的是Enter键之前的所有字符,它是可以得到带空格的字符串的;next()会自动消去有效字符前的空格,只返回输入的字符,不能得到带空格的字符串。

② next()在输入有效字符之后,将其后输入的空格键、Tab键或Enter键等视为结束符;nextLine()方法的结束符只是Enter键。

Java中.next()和.nextLine()的区别-CSDN博客

17、implements和extends

extends 是继承父类,只要那个类不是声明final或者定义为abstract就能继承;JAVA中不支持多重继承,继承只能继承一个类,但implements可以实现多个接口,用逗号分开就行了。 

18、List<Integer> list = new ArrayList<Integer>()

List< Integer>

List是一个接口

<>表示了List里面放的对象是什么类型的,这样写就表示了,你List里面放的必须是Integer类型的

ArrayList

ArrayList类是一个特殊的数组–动态数组。通过添加和删除元素,就可以动态改变数组的长度。

优点:

1、支持自动改变大小 2、可以灵活的插入元素 3、可以灵活的删除元素

局限:

比一般的数组的速度慢一些;

ArrayList是List接口的一个实现类。

List接口不能被构造,也就是我们说的不能创建实例对象,但是我们可以这样为List接口创建一个指向自己的对象引用,而ArrayList实现类的实例对象就在这充当了这个指向List接口的对象引用。

这句代码的目的是:可以调用 List接口里面的内置函数,add,get等方法。

19、重载和重写

20、ArrayList和 LinkedList 的区别。

答清楚每个分别采用什么数据结构,对比相应的优点和缺点。

另外,不要下意识地认为 LinkedList 作为链表就最适合元素增删的场景。我在上面也说了,LinkedList 仅仅在头尾插入或者删除元素的时候时间复杂度近似 O(1),其他情况增删元素的平均时间复杂度都是 O(n) 。

30、String、StringBuffer、StringBuilder

31、什么时候需要考虑线程安全

 多线程、共享变量、需保证结果正确

32、####HashMapHashTable、 ConcurrentHashMap 的区别

33、static关键字的作用

(1)static成员变量:

Java类提供了两种类型的变量:用static关键字修饰的静态变量和不用static关键字修饰的实例变量。静态变量属于类,在内存中只有一个复制,只要静态变量所在的类被加载,这个静态变量就会被分配空间,因此就可以被使用了。对静态变量的引用有两种方式,分别是“类.静态变量"和”对象.静态变量"

实例变量属于对象,只有对象被创建后,实例变量才会被分配内存空间,才能被使用,它在内存中存在多个复制,只有用“对象.实例变量”的方式来引用。
(2)static成员方法:

Java中提供了static方法和非static方法。static方法是类的方法,不需要创建对象就可以被调用,而非static方法是对象的方法,只有对象被创建出来后才可以被使用

static方法中不能使用this和super关键字,不能调用非static方法,只能访问所属类的静态成员变量和成员方法,因为当static方法被调用时,这个类的对象可能还没被创建,即使已经被创建了,也无法确定调用哪个对象的方法。同理,static方法也不能访问非static类型的变量。

牛客面经

1、new与malloc的区别和联系

两者的相同之处
        1、它们 都可以申请动态内存和释放内存。C++中申请动态内存和释放内存用new/delete或者malloc/free都可以,而且它们的存储方式相同。
        2、 new与malloc动态申请的内存都位于堆中,无法被操作系统自动收回,要有相应的delete或者free来释放空间;对于内置数据类型,如int、char型,其效果是一样的。

两者的区别
1、 类型不同
        malloc/free是C/C++的 标准库函数,在C中需要头文件<stdlib.h>的支持;new/delete是C++的 运算符。对类的对象而言,malloc/free无法满足动态对象的要求,对象在创建的同时要自动执行构造函数,对象消亡前要自动执行析构函数。

2、 自动计算空间
       new能 自动计算所需的内存空间,而malloc需要 手工计算字节数,然后在返回后强制转换为实际的指针类型。例如,int *p1 = new int[2]; int *p2 = malloc(2 * sizeof(int));。

3、 返回类型
       new成功分配内存 返回具体类型的指针。malloc成功分配内存 返回void类型指针,C/C++规定,void类型可以强制转换为任何其它类型的指针。 void* 表示未确定类型的指针,更明确的说是指申请内存空间时还不知道用户是用这段空间来存储什么类型的数据。

2、linux关于查找字符串

1、Linux查找字符串用grep命令,可以查找文件,也可以在命令的结果中查找。如果是在文件中查找字符串,用法是:
grep 用英文单引号括起来的字符串 文件名
如果是在命令的显示结果中查找,需要用管道符将命令与grep连接起来,像这样:
last | grep ‘root’
(在last命令的显示结果中查找字符串root)

2、在normal模式下按下/即可进入查找模式,输入要查找的字符串并按下回车。
Vim会跳转到第一个匹配。按下n查找下一个,按下N查找上一个。
Vim查找支持正则表达式,例如/vim$匹配行尾的"vim"。 需要查找特殊字符需要转义,例如/vim$匹配"vim$"。

3、linux查找文件的命令

1、find命令,可以查找任何想要的文件;2、locate命令,查不到最新变动过的文件;3、whereis命令,只搜索二进制文件、man说明文件和源代码文件;4、which命令;5、type命令。

4、进程和线程的区别和联系

一个进程中可以有多个线程,多个线程共享进程的方法区资源,但是每个线程有自己的程序计数器虚拟机栈 和 本地方法栈

5、线程同步

线程同步是指多线程通过特定的设置(如互斥量,信号量,事件对象,临界区)来控制线程之间的执行顺序(即所谓的同步)也可以说是在线程之间通过同步建立起执行顺序的关系。

6、大端小端

大端:低字节内存地址存放数据的高字节(正常)

小端:反之

7、堆和栈的区别

不同场景下,堆与栈代表不同的含义。一般情况下,有两层含义:
(1)程序内存布局场景下,堆与栈表示两种内存管理方式;
(2)数据结构场景下,堆与栈表示两种常用的数据结构。

1.程序内存分区中的堆与栈

(1)管理方式不同。栈由操作系统自动分配释放,无需我们手动控制;堆的申请和释放工作由程序员控制,容易产生内存泄漏

(2)空间大小不同。每个进程拥有的栈大小要远远小于堆大小。理论上,进程可申请的堆大小为虚拟内存大小,进程栈的大小 64bits 的 Windows 默认 1MB,64bits 的 Linux 默认 10MB;

(3)生长方向不同。堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低。

(4)分配方式不同。堆都是动态分配的,没有静态分配的堆。栈有 2 种分配方式:静态分配和动态分配。静态分配是由操作系统完成的,比如局部变量的分配。动态分配由alloca()函数分配。

(5)分配效率不同。栈由操作系统自动分配,会在硬件层级对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是由C/C++提供的库函数或运算符来完成申请与管理,实现机制较为复杂,频繁的内存申请容易产生内存碎片。显然,堆的效率比栈要低得多。

(6)存放内容不同。栈存放的内容,函数返回地址、相关参数、局部变量和寄存器内容等。当主函数调用另外一个函数的时候,要对当前函数执行断点进行保存,需要使用栈来实现,首先入栈的是主函数下一条语句的地址,即扩展指针寄存器的内容(EIP),然后是当前栈帧的底部地址,即扩展基址指针寄存器内容(EBP),再然后是被调函数的实参等,一般情况下是按照从右向左的顺序入栈,之后是被调函数的局部变量,注意静态变量是存放在数据段或者BSS段,是不入栈的。出栈的顺序正好相反,最终栈顶指向主函数下一条语句的地址,主程序又从该地址开始执行。堆,一般情况堆顶使用一个字节的空间来存放堆的大小,而堆中具体存放内容是由程序员来填充的。

2.堆与栈是两个常见的数据结构

栈是一种运算受限的线性表,其限制是指只仅允许在表的一端进行插入和删除操作,这一端被称为栈顶(Top),把另一端称为栈底(Bottom)。把新元素放到栈顶元素的上面,使之成为新的栈顶元素称作进栈、入栈或压栈(Push);把栈顶元素删除,使其相邻的元素成为新的栈顶元素称作出栈或退栈(Pop)。这种受限的运算使栈拥有“先进后出”的特性。栈分顺序栈和链式栈两种。

堆是一种常用的树形结构,是一种特殊的完全二叉树,当且仅当满足所有节点的值总是不大于或不小于其父节点的值的完全二叉树被称之为堆。堆的这一特性称之为堆序性。因此,在一个堆中,根节点是最大(或最小)节点。如果根节点最小,称之为小顶堆(或小根堆),如果根节点最大,称之为大顶堆(或大根堆)。堆的左右孩子没有大小的顺序。

8、面向对象的三大特性

(1)面向对象:把现实问题抽象为对象,通过调用每个对象的属性或功能去解决问题。

(2)封装

封装(encapsulation)即信息隐蔽。它是指在确定系统的某一部分内容时,应考虑到其它部分的信息及联系都在这一部分的内部进行,外部各部分之间的信息联系应尽可能的少。

public:对外公开,访问级别最高
protected:只对同一个包中的类或者子类公开
默认:只对同一个包中的类公开
private:不对外公开,只能在对象内部访问,访问级别最低

(3)继承

  • 继承:让某个类型的对象获得另一个类型的对象的属性和方法。继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
  • 语法:class A extends B{}
  • 一共继承了哪些东西呢?这要分两种情况

当A和B在同一包下,A继承了B中public、protected和默认访问级别的成员变量和成员方法;
当A和B不在同一包下,A继承了B中public、protected成员变量和成员方法

重写父类方法
子类haqishi重写父类Dog的say方法

class Dog{
    public String name="小白";
    public void say(){
        System.out.println("汪汪汪");
    }
}
//哈奇士
class haqishi extends Dog {
    @Override
    public void say() {
        //super.say();    继承父类原有方法
        System.out.println("哈哈哈");
    }
}

调用子类say方法,测试如下:

哈哈哈

重载父类方法
重载方法必须满足以下条件:
1、方法名相同
2、方法的参数类型、个数、顺序至少有一项不同
3、方法的返回类型可以不同
4、方法的修饰符可以不相同

class Dog{
    public String name="小白";
    public void say(){
        System.out.println("汪汪汪");
    }
}
//哈奇士
class haqishi extends Dog {
   //重载 改变参数
    public void say(String name) {
        System.out.println(name+"汪汪汪");
    }
	//重载 改变参数+返回值
    public int say(int age) {
        System.out.println("汪汪汪"+age);
        return age;
    }
}

分别调用
haqishi h = new haqishi();
h.say();
h.say(“哈奇士”);
h.say(6);
结果如下:

汪汪汪
哈奇士汪汪汪
汪汪汪6

(4)多态

多态:对于同一个行为,不同的子类对象具有不同的表现形式。多态存在的3个条件:
1)继承 2)重写 3)父类引用指向子类对象。

理解多态:
在这个运行环境中,引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定。Java虚拟机会根据引用变量指向的对象来调用该对象的指定方法,这种运行机制被称为动态绑定

public class extend {
    public static void main(String[] args) {
        Animal a = new Dog();
        Animal b = new Cat();
        a.say();
        b.say();
    }
}

abstract class Animal {
    abstract void say();
}
//猫
class Cat extends Animal{
    public String name="小黑";
    public void say(){
        System.out.println("喵喵喵");
    }
}
//狗
class Dog extends Animal{
    public String name="小白";
    public void say(){
        System.out.println("汪汪汪");
    }
}

运行结果

汪汪汪
喵喵喵

9、赋值、浅拷贝、深拷贝

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

赋值和浅拷贝的区别:

  • 当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。
  • 浅拷贝的定义:如果原型对象的成员变量是值类型,将复制一份给克隆对象,也就是说在堆中拥有独立的空间;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。

浅拷贝与深拷贝 - 知乎 (zhihu.com)

深拷贝的实现方式有2种:Serializable, Cloneable

Serializable方法就是通过java对象的序列化和反序列化的操作实现对象拷贝的一种比较常见的方式。本来java对象们都待在虚拟机堆中,通过序列化,将源对象的信息以另外一种形式存放在了堆外。这时源对象的信息就存在了2份,一份在堆内,一份在堆外。然后将堆外的这份信息通过反序列化的方式再放回到堆中,就创建了一个新的对象,也就是目标对象。

Cloneable方法的核心是Object类的native方法clone()。通过调用clone方法,可以创建出一个当前对象的克隆体,但需要注意的是,这个方法不支持深拷贝。如果对象的成员变量是基础类型,那妥妥的没问题。但是对于自定义类型的变量或者集合、数组,就有问题了。你会发现源对象和目标对象的自定义类型成员变量是同一个对象,也就是浅拷贝,浅拷贝就是对对象引用(地址)的拷贝。这样的话源对象和目标对象就不是彼此独立,而是纠缠不休了。为了弥补clone方法的这个不足。需要我们自己去处理非基本类型成员变量的深拷贝

10、###多线程

(1)同步与异步
  • 同步:发出一个调用之后,在没有得到结果之前, 该调用就不可以返回,一直等待。
  • 异步:调用在发出之后,不用等待返回结果,该调用直接返回。

(2)

11、GC如何判断对象可以回收以及GC算法

垃圾回收(Garbage Collection,简称GC)是计算机科学中的一种重要技术,用于自动管理计算机程序中的内存。通过自动回收不再使用的内存,可以避免内存泄漏和野指针等问题,提高程序的稳定性和性能。

 常见的GC算法有:

  1. 标记-清除算法(Mark-Sweep):该算法分为两个阶段,标记阶段和清除阶段。在标记阶段,算法会遍历所有对象,将正在使用的对象标记为“存活”,未使用的对象标记为“死亡”。在清除阶段,算法会清除所有标记为“死亡”的对象,从而回收内存。该算法的优点是实现简单,但缺点是在清除过程中会产生大量不连续的内存碎片。
  2. 复制算法(Copying):该算法将可用内存分为两个相等的区域,每次只使用其中一个区域。当程序需要分配内存时,算法会将对象复制到正在使用的区域中。当该区域内存使用完毕时,算法会将所有存活的对象复制到另一个区域中,然后清除当前区域的所有对象。该算法的优点是不会产生内存碎片,但缺点是需要额外的空间来复制对象。
  3. 标记-压缩算法(Mark-Compact):该算法是对标记-清除算法的一种改进,它在清除阶段将所有存活的对象压缩到一起,并将所有未使用的内存一次性清除。这样可以减少内存碎片的产生。该算法的优点是既不会产生内存碎片,又不需要额外的空间来复制对象,但缺点是实现较为复杂。
  4. 分代收集算法(Generational):该算法根据对象的存活周期的不同将其分为几代,并采用不同的收集算法来处理每一代。例如,新生代和老生代可以采用不同的收集算法来提高效率。该算法的优点是针对不同代的对象采用不同的收集策略,可以更好地利用系统资源。

12、算法的要素和特征

(1)算法要素
  • 数据对象的操作和操作:计算机可以执行的基本操作以指令的形式描述。
  • 算法的控制结构:算法的功能结构不仅取决于所选的操作,还取决于操作之间的执行顺序。
(2)算法特征 
  • 有穷性:算法的有穷性意味着算法在执行有限的步骤之后必须能够终止。
  • 确切性:算法的每一步都必须确切定义。
  • 输入项:一个算法有0个或多个输入来描述操作对象的初始条件。所谓的零输入是指由算法本身决定的初始条件。
  • 输出项:一个算法有一个或多个输出来反映处理输入数据的结果。没有输出的算法毫无意义。
  • 可行性:算法中执行的任何计算步骤都可以分解为基本的可执行操作步骤,即每个计算步骤都可以在有限的时间内完成。

13、死锁的四个条件和死锁的产生原因

四个必要条件:互斥、不可剥夺、占有并请求、循环等待

产生原因:系统资源不足、 进程运行推进的顺序不合适、资源分配不当等,导致多个进程/线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于进程/线程被无限期地阻塞,因此程序不可能正常终止。

死锁预防:破坏四个必要条件之一

死锁避免:不破坏必要条件。eg:银行家算法

死锁检测与解除:进程-资源分配图。若图中存在环路并不一定是发生了死锁。因为循环等待资源仅仅是死锁发生的必要条件,而不是充分条件。如果进程-资源分配图中有环路,且每个资源类仅有一个资源,则系统中已经发生了死锁。

 项目

 1、Spring与Spring Boot

(1)Spring是什么

(2)AOP

(3)###IOC

 依赖注入与控制反转

原本A与B的直接依赖取消了,从A主动依赖B变为A被IOC容器注入对B的依赖

(4)Spring中Bean的生命周期 

 (5)Spring Boot 

Spring Boot是一个基于Java的开源框架,用于创建微服务。

Spring Boot为Java开发人员提供了一个很好的平台,可以开发一个可以运行的独立和生产级Spring应用程序。可以开始使用最少的配置,而无需进行整个Spring配置设置。

SpringBoot只是Spring本身的扩展,使开发,测试和部署更加方便。

① 功能和优点:

  • 它提供了一种灵活的方法来配置Java Bean,XML配置和数据库事务。( Java Bean 是一种类,而且是特殊的、可重用的类。Java language 是一种面向对象的编程语言,类是面向对象的编程语言的基础;可重用又是面向对象编程思想存在的意义之一)
  • 它提供强大的批处理和管理REST端点。
  • 在Spring Boot中,一切都是自动配置的; 无需手动配置。
  • 它提供基于注释的spring应用程序。
  • 简化依赖管理。
  • 它包括嵌入式Servlet容器。

② Spring Boot是如何工作的

一文介绍 Spring Boot 中有哪些常用注解 (apifox.com)

SpringBoot Bean 概念理解和使用 - Luo3House

(1)自动配置:Spring Boot会根据使用@EnableAutoConfiguration批注,根据在项目中添加的JAR依赖项自动配置Spring应用程序 。例如,如果MySQL数据库在类路径上,但尚未配置任何数据库连接,则Spring Boot会自动配置内存数据库。

(2)应用程序:Spring Boot应用程序的入口点是包含@SpringBootApplication注释和main方法的类。

如果将@SpringBootApplication批注添加到类中,则无需添加@EnableAutoConfiguration@ComponentScan@SpringBootConfiguration批注。@SpringBootApplication注释包括所有其他注释。 //更多请阅读:https://www.yiibai.com/spring-boot/spring_boot_introduction.html
 

(3)组件扫描:Spring Boot应用程序在应用程序初始化时扫描所有bean和包声明。需要为类文件添加@ComponentScan批注,以扫描项目中添加的组件。

2、MVC与三层架构理解

(1)一些概念

Servlet :

Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层

JSP是简化的Servlet设计,在HTML标签中嵌套Java代码,用以高效开发Web应用的动态网页。在仅使用Servlet的web应用开发中,部分Servlet代码通过使用打印语句打印HTML标签来在浏览器中显示页面,而JSP可以代替显示页面的Servlet

Servlet 执行以下主要任务:

  • 读取客户端(浏览器)发送的显式的数据。这包括网页上的 HTML 表单,或者也可以是来自 applet 或自定义的 HTTP 客户端程序的表单。
  • 读取客户端(浏览器)发送的隐式的 HTTP 请求数据。这包括 cookies、媒体类型和浏览器能理解的压缩格式等等。
  • 处理数据并生成结果。这个过程可能需要访问数据库,执行 RMI 或 CORBA 调用,调用 Web 服务,或者直接计算得出对应的响应。
  • 发送显式的数据(即文档)到客户端(浏览器)。该文档的格式可以是多种多样的,包括文本文件(HTML 或 XML)、二进制文件(GIF 图像)、Excel 等。
  • 发送隐式的 HTTP 响应到客户端(浏览器)。这包括告诉浏览器或其他客户端被返回的文档类型(例如 HTML),设置 cookies 和缓存参数,以及其他类似的任务。

JSP:

JSP(全称JavaServer Pages)是由Sun公司主导创建的一种动态网页技术标准。JSP部署于网络服务器上,可以响应客户端发送的请求,并根据请求内容动态地生成HTML、XML或其他格式文档的Web网页,然后返回给请求者。

 系统结构之MVC

View:视图,为用户提供使用界面,与用户直接进行交互。JSP、HTML、CSS、JS等来进行数据展示

Model:模型,JavaBean。承载数据,并对用户提交请求进行计算的模块。其分为两类: 一类称为数据承载 Bean------实体类,专门用户承载业务数据的,如 Student、User 等 一类称为业务处理 Bean:指 Service 或 Dao 对象,专门用于处理用户提交请求的。

Controller:控制器, Servlet。用于将用户请求转发给相应的 Model 进行处理,并根据 Model 的计算结果向用户提供相应响应。

MVC 架构程序的工作流程: (1)用户通过 View 页面向服务端提出请求,可以是表单请求、超链接请求、AJAX 请求等 (2)服务端 Controller 控制器接收到请求后对请求进行解析,找到相应的 Model 对用户请求进行处理 (3)Model 处理后,将处理结果再交给 Controller (4)Controller 在接到处理结果后,根据处理结果找到要作为向客户端发回的响应 View 页面。页面经渲染(数据填充)后,再发送给客户端。

 系统结构之三层架构

三层架构是指:视图层 View、服务层 Service,与持久层 Dao。它们分别完成不同的功能。

  • View 层:用于接收用户提交请求的代码在这里编写。

  • Service 层:系统的业务逻辑主要在这里完成。

  • Dao 层:直接操作数据库的代码在这里编写。

为什么要分层
方便团队分工,一个程序员单独完成一个软件产品不是不可以,但遇到大型软件需要团队配合的时候问题就来了,由于每个程序员风格不一样,而开发软件大量的代码风格不统一就会造成后期调试和维护出现问题,然而软件分层后,每个层合理分工这样的问题便迎刃而解。规范代码,在开发软件时对每个层的代码进行规范,固定开发语言的风格。忽略数据库差异,当软件系统要换数据库时,只要将数据访问层的代码修改就好了。实现"高内聚、低耦合"。把问题划分开来各个解决,易于控制,易于延展,易于分配资源

三层优缺点
【优点】

开发人员可以只关注整个结构中的其中某一层;

可维护性高,可扩展性高;

可以降低层与层之间的依赖;

有利于标准化;

利于各层逻辑的复用。

【缺点】

降低了系统的性能。如果不采用分层式结构,很多业务可以直接造访数据库,以此获取相应的数据,如今却必须通过中间层来完成;

有时会导致级联的修改,这种修改尤其体现在自上而下的方向。如果在表示层中需要增加一个功能,为保证其设计符合分层式结构,可能需要在相应的业务逻辑层和数据访问层中都增加相应的代码;

增加了开发成本。

3、前端(web开发)

HTML 定义了网页的内容

CSS 描述了网页的布局

JavaScript 控制了网页的行为

4、Redis

(1) 为什么快

①Redis的操作都是基于内存的,CPU不是 Redis性能瓶颈,,Redis的瓶颈是机器内存和*网络带宽*。在计算机的世界中,CPU的速度是远大于内存的速度的,同时内存的速度也是远大于硬盘的速度。

②Redis使用单线程,可以省去多线程时CPU上下文会切换的时间,也不用去考虑各种锁的问题,不存在加锁释放锁操作,没有死锁问题导致的性能消耗。对于内存系统来说,多次读写都是在一个CPU上,没有上下文切换效率就是最高的

③Redis高效的数据结构,对数据的操作也比较简单。

④核心是基于非阻塞的IO多路复用机制(同时监听多个socket,根据socket上的事件类型来选择相应的事件处理器)

(2) 常用的 5 种数据类型和应用场景:

String: 缓存、计数器、分布式锁等。 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象;string 类型是 Redis 最基本的数据类型

List: 链表、队列、微博关注人时间轴列表等。

Hash: 用户信息、Hash 表等。Redis hash 是一个键值(key=>value)对集合。

Set: 去重、赞、踩、共同好友等。无序集合

Zset: 访问量排行榜、点击量排行榜等。有序集合

(3) Redis用作缓存

将Redis部署到数据库前端,步骤如下:

  • 当业务应用需读取数据时,首先请求Redis数据,比如用get、hget方法获取Redis缓存。
  • 判断第一步的缓存是否存在,存在就直接返回数据了; 若缓存不存在,这时要从数据库来查询数据。
  • 数据查询到之后,还得更新到Redis中,比如set、hset方法的调用。

如何保证缓存与数据库一致性:

想要保证缓存与数据库的一致,一共有4种方式,即4种同步策略:

  • 先更新缓存,再更新数据库;
  • 先更新数据库,再更新缓存;
  • 先删除缓存,再更新数据库;
  • 先更新数据库,再删除缓存。

从这4种同步策略中,我们需要作出比较的是:

更新缓存与删除缓存哪种方式更合适?应该先操作数据库还是先操作缓存?

如何保证Redis缓存与数据库的一致性?_redis如何保证缓存和数据库一致性-CSDN博客

面试官:如何保障数据库和redis缓存的一致性 - 知乎 (zhihu.com)

① 更新缓存还是删除缓存
下面,我们来分析一下,应该采用更新缓存还是删除缓存的方式。

1.1 更新缓存
优点:每次数据变化都及时更新缓存,所以查询时不容易出现未命中的情况。

缺点:更新缓存的消耗比较大。如果数据需要经过复杂的计算再写入缓存,那么频繁的更新缓存,就会影响服务器的性能。如果是写入数据频繁的业务场景,那么可能频繁的更新缓存时,却没有业务读取该数据。

1.2 删除缓存
优点:操作简单,无论更新操作是否复杂,都是将缓存中的数据直接删除。

缺点:删除缓存后,下一次查询缓存会出现未命中,这时需要重新读取一次数据库。从上面的比较来看,一般情况下,删除缓存是更优的方案

② 先操作数据库还是缓存
下面,我们再来分析一下,应该先操作数据库还是先操作缓存。

无论上面我们采用哪种方式去同步缓存与数据库,都建议采用重试机制保证数据库更新操作成功

  • 方案1,先淘汰缓存,后更新数据库的策略,有可能导致长时间的数据不一致问题,可以通过延时双删 or 异步更新缓存策略进行解决。
  • 方案2,先更新数据库,后更新缓存,有可能导致极短时间内的数据不一致,但是数据最终是一致的

 

(4)缓存雪崩(缓存重启或首次启动时)、缓存穿透(缓存受到攻击或商城等高并发场景)、缓存击穿(缓存到期)

(5)其他用途(待续) 

 Redis 可以用来做限流、分布式锁,Redis 内存不够用的情况下,还能使用 Redis Cluster 来解决

5、####消息队列RabbitMQ

RabbitMQ(总结最全面的面试题!!!)_rabitmq面试题-CSDN博客

(1)为什么使用消息队列?

异步处理 - 相比于传统的串行、并行方式,提高了系统吞吐量。
应用解耦 - 系统间通过消息通信,不用关心其他系统的处理。
流量削锋 - 可以通过消息队列长度控制请求量;可以缓解短时间内的高并发请求。
日志处理 - 解决大量日志传输。
消息通讯 - 消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等。

(2)如何保证消息不丢失?

丢失分为:生产者丢失消息、消息列表丢失消息、消费者丢失消息

  • 生产者丢失消息:从生产者弄丢数据这个角度来看,RabbitMQ提供transaction和confirm模式来确保生产者不丢消息;

transaction机制就是说:发送消息前,开启事务(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事务就会回滚(channel.txRollback()),如果发送成功则提交事务(channel.txCommit())。然而,这种方式有个缺点:吞吐量下降;

confirm模式用的居多:一旦channel进入confirm模式,所有在该信道上发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后;rabbitMQ就会发送一个ACK给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了;如果rabbitMQ没能处理该消息,则会发送一个Nack消息给你,你可以进行重试操作。

  • 消息队列丢数据:消息持久化

处理消息队列丢数据的情况,一般是开启持久化磁盘的配置。

这个持久化配置可以和confirm机制配合使用,你可以在消息持久化磁盘后,再给生产者发送一个Ack信号。这样,如果消息持久化磁盘之前,rabbitMQ阵亡了,那么生产者收不到Ack信号,生产者会自动重发。

那么如何持久化呢?,就下面两步

​ 1. 将queue的持久化标识durable设置为true,则代表是一个持久的队列

​ 2. 发送消息的时候将deliveryMode=2

这样设置以后,即使rabbitMQ挂了,重启后也能恢复数据

  • 消费者丢失消息:消费者丢数据一般是因为采用了自动确认消息模式,改为手动确认消息即可

消费者在收到消息之后,处理消息之前,会自动回复RabbitMQ已收到消息;如果这时处理消息失败,就会丢失该消息;解决方案:处理消息成功后,手动回复确认消息

(3)如何保证消息的顺序性?

(4)如何保证消息不被重复消费

6、JavaScript Cookie

Cookie 是一些数据, 存储于你电脑上的文本文件中。

当 web 服务器向浏览器发送 web 页面时,在连接关闭后,服务端不会记录用户的信息。

Cookie 的作用就是用于解决 "如何记录客户端的用户信息":

  • 当用户访问 web 页面时,他的名字可以记录在 cookie 中。
  • 在用户下一次访问该页面时,可以在 cookie 中读取用户访问记录。

Cookie 以名/值对形式存储,如下所示:

username=John Doe

当浏览器从服务器上请求 web 页面时, 属于该页面的 cookie 会被添加到该请求中。服务端通过这种方式来获取用户的信息。

7、docker

什么是docker

  • Docker 是世界领先的软件容器平台。
  • Docker 使用 Google 公司推出的 Go 语言 进行开发实现,基于 Linux 内核 提供的 CGroup 功能和 namespace 来实现的,以及 AUFS 类的 UnionFS 等技术,对进程进行封装隔离,属于操作系统层面的虚拟化技术。 由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。
  • Docker 能够自动执行重复性任务,例如搭建和配置开发环境,从而解放了开发人员以便他们专注在真正重要的事情上:构建杰出的软件。
  • 用户可以方便地创建和使用容器,把自己的应用放入容器。容器还可以进行版本管理、复制、分享、修改,就像管理普通的代码一样。

为什么要使用docker

  • Docker 的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性,从而不会再出现 “这段代码在我机器上没问题啊” 这类问题;——一致的运行环境
  • 可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。——更快速的启动时间
  • 避免公用的服务器,资源会容易受到其他用户的影响。——隔离性
  • 善于处理集中爆发的服务器使用压力;——弹性伸缩,快速扩展
  • 可以很轻易的将在一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境的变化导致应用无法正常运行的情况。——迁移方便
  • 使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。——持续交付和部署

容器和虚拟机具有相似的资源隔离和分配优势(虚拟机:划分物理计算机为不同的虚拟部分供给不同应用或业务来使用,1提高资源利用率,2实现相对隔离),但功能有所不同,因为容器虚拟化的是操作系统,而不是硬件,因此容器更容易移植,效率也更高。

8、什么是Maven

Apache Maven 的本质是一个软件项目管理和理解工具。基于项目对象模型 (Project Object Model,POM) 的概念,Maven 可以从一条中心信息管理项目的构建、报告和文档。

什么是 POM? 每一个 Maven 工程都有一个 pom.xml 文件,位于根目录中,包含项目构建生命周期的详细信息。通过 pom.xml 文件,我们可以定义项目的坐标、项目依赖、项目信息、插件信息等等配置。

对于开发者来说,Maven 的主要作用主要有 3 个:

  1. 项目构建:提供标准的、跨平台的自动化项目构建方式。
  2. 依赖管理:方便快捷的管理项目依赖的资源(jar 包),避免资源间的版本冲突问题。
  3. 统一开发结构:提供标准的、统一的项目结构。

9、

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值