面向对象、新特性、异常、JDBC、集合、泛型、ObjectAPI、String类、Web、Linux命令、基础算法、分布式微服务概念应用场景、消息队列、数据库优化、索引、个人项目相关

1、面向对象(OOP)

1、理解

面向对象是一种编程范式,将现实世界中的事物视为对象,每个对象具有属性和方法。在java中类是定义对象的模板,对象是类的实例。

面向对象具有封装、继承、多态和抽象特性

封装是将数据和方法封装在对象中,通过访问控制修饰符来控制可见性,提高代码的可维护性和重用性。

继承是允许一个类从已有的类中继承属性和方法,并且可以在此基础上进行拓展,也是多态的实现条件。

多态是指对象在不同的情况下可以表现出不同的行为,通过继承和接口实现,在父类或接口类型的引用上引用子类或实现类的对象,从而实现方法的动态绑定。提高了代码的灵活性和可扩展性

抽象是指从具体的实例中提取出共同的特征和行为,形成类和接口的过程。

2、写代码过程中怎么应用面向对象的编程思维?

为一类具有共同属性和特征的事物创建一个类,用于描述对象的状态行为。比如实际项目中除了必要的实体类,还要创建给前端返回页面参数的视图对象、数据对象和数据传输对象。问的话就说,这些都属于规范上的内容,所以记得不是很清楚

2、JDK8新特性

lambda表达式:我觉得虽然简洁,但易读性不强

函数式接口:只有一个抽象方法的接口

方法引用:就是lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法

新的StreamAPI

Optionnal类,是用来应对空指针异常的问题。

然后是接口:jdk8之后,可以有静态方法和默认方法,而且可以有方法体,通过实例调用

​ (8之前,所有变量都是公共静态常量,必须显示初始化,方法都是公共抽象,没有构造方法,不可以被实例化,实现类必 须实现接口的所有方法,可以实现多个接口使用extends关键字,用,隔开)

集合也有些变化,还有hashmap底层原理也有比较大的变动。这个要详细说一下吗?

hashmap在jdk1.8之前底层是由数组加链表组成,1.8之后为了提升查询性能改为数组加链表或红黑树,当链表长度大于等于8,并且数组长度大于等于64时,将链表转化为红黑树。当红黑树结点小于或等于6个之后又转为链表。

3、平常都遇见过什么异常?怎么解决的?

异常分为运行时异常和编译时异常,运行时异常可以不做处理也能编译通过,常见的有空指针异常、索引越界异常、算术异常、类型转换异常、字符串转为数字异常、InputMismatchException:接收非法输入异常、还有不能序列化异常,这个需要实现序列化接口,其他更多的是写代码的时候疏忽了,在前端或者后端代码中需要对输入的数据进行严格的校验。也可以一般加个if语句判断一下并做出相应处理,比如给前端返回一个错误提示,打印日志等等,也可以用try\catch处理

编译时异常常见的有类找不到异常、文件找不到异常、SQL异常和IO异常,一般使用try\catch捕获,使程序不会中断,在catch中处理出现的异常,比如打印日志等等,而finally由于其必定执行的特点,一般用于关闭流、数据库连接这类操作。

4、JDBC操作数据库的步骤

第一步加载jdbc驱动Class.forName(“com.mysql.cj.jdbc.Driver”);

第二步连接数据库Connection connection= DriverManager.getConnection(url,user,password);

第三步创建sql载体,预编译的sql执行器(PreparedStatement。

第四步设定参数值

第五步调用相应的方法返回结果集,取出想要的数据

第六步关闭数据库连接

5、谈谈集合

集合分为collection和map两大类

collection为单列数据,分有List和set接口,List可以重复,有索引,按照元素插入的顺序进行存储。而Set接口不可以重复,值存储在键的位置,没有索引,是无序的(有一个是有序的),需要用迭代器或增强for遍历。

map存储键值对,键是唯一的,值可以重复,有hashmap、treemap、hashtable和concurrentHashMap

hashmap底层是基于哈希表的数据结构,键值都可以存储空值,初始容量为16,每次扩容后都是2的幂(在原来的基础上乘2),加载因子为0.75.而且它是线程不安全的,效率比较高。

treemap底层是红黑树(有序性和平衡性),依赖自然排序或者比较器排序,对键进行排序

hashtable和concurrentHashMap都是线程安全的,hashtable 并发度低,整个hashtable对应一把锁,使用synchronized粗暴地在每个方法上设置一个全局锁。‘导致性能非常低。而conCurrentHashMap不但线程安全,性能也比较高,它采用了分段锁来实现线程安全,将整个哈希表分为多个小的段,每个段都是一个独立的哈希表,维护着各自的键值对,不同的线程可以同时访问不同的段,加锁的时候只需要对涉及到的段进行加锁(重入锁),就不需要锁住整个哈希表,查询的时候通过哈希码来确定,用哈希码来与16进行一个按位与的操作,16就是总段数,得到一个相当于索引的值ReentrantLock

各集合的使用场景

集合主要分为Collection和Map两大类

Collection为存储单例数据,有Lis和Set接口,List集合有序,是根据元素插入的顺序进行存储的,并且可以根据索引进行访问和操作,也可以存储重复元素,适用于需要保持元素顺序或者可能有重复元素的场景,也支持特点位置的插入和删除。而Set集合中不允许存在重复的元素,因此适用于需要本重保证元座唯一的场景,比如秒杀系统中可以使用Set集合存储已经秒杀过的用户,防止多次参与秒杀,另外他也不保持是教而学,造用手不美尧素顺序的时候。Map存储健值对,键是性一的,值可以重复,其中,HashMap使用最广泛,健值都可以存储空值,线程不安全,但效率比较高,fhashtable是线程安全的,但他照个hashtable对应把锁,使用synchroized粗暴的在每个方法上设置一个全局锁,导致性能非常低,所以一般不用,想要线程安全可以使用concurrenthashmap,它使用了分段锁,能保持一定的性能。treemap底层是红黑树,对键进行了排序

1、详细说一下哈希表

Java基础之哈希表与红黑树_java 哈希表红黑树_极小白的博客-CSDN博客

在jdk8之前,哈希表的底层采用数组加链表的形式,每个数组元素都是一个链表,但链表长度过长会导致查询性能低,所以8之后为了提升查询效率,改为数组加链表或红黑树,当链表长度大于等于8,并且数组长度大于等于64,就会进行树话,变成红黑树,反正如果树的结点小于等于6的时候,就又变回链表。

2、解决哈希冲突的方法有哪些?

哈希冲突是什么?

往map集合里面存储数据的时候,是根据键的哈希码来指定存储在哪的,但是不同的键也有可能计算出相同的哈希码,就会产生地址冲突,这个就是哈希冲突。

hashmap:

链式寻址法:就是将存在哈希冲突的key以单向链表的方式存储,数组的每个元素都是一个链表,这是hashmap默认的办法。在1.8版本之后还引入了红黑树,是为了优化链表过长导致查询性能低的问题,当链表长度大于等于8,并且数组长度大于等于64时,链表就会转化为红黑树,反之当树的结点小于等于6时,就会变回链表。

但也可以使用开放定址法(线性探测法 ):就是从发生冲突的那个位置开始,线性向前推进寻找空闲位置存储。扩展:怎么删除?

用开放定址法存的数据应该要存放在一个查找序列中,不能有空桶缝隙,如果直接删除的话会造成查找链断裂,就导致后面明明有数据,但就是访问不到,所以一般把要删除的数据做一个标记,就是一个删除标记,以后查的时候遇到这个标记就直接跳过,插入的时候直接覆盖掉就行。

其他的还有哈希法:使用另一个哈希函数对这个key进行哈希运算,一直到不产生冲突为止,这种方法会增加计算时间,性能影响较大。

3、初始容量16,扩容后都是2的幂?加载因子0.75?

这些都跟内部性能有关。

初始容量是在性能和内存开销之间的权衡,选择较小的初始容量会减少内存占用,但可能导致更频繁的扩容,因为哈希冲突发生的频率更高。另外也是为了与扩容后是2的幂相配合,使得哈希函数的计算更加高效,因为他可以通过位运算来计算。

因为扩容是在原来的基础上翻一倍,另外也可以使得哈希函数的计算更加高效,因为可以通过位运算来计算。

加载因子是控制哈希表何时扩容的参数,当表中键值对数量达到容量乘以加载因子的值时,就会进行扩容,选择0.75是为了平衡内存使用和性能之间的折中,较低的加载因子,可能导致更加频繁的扩容,较高的加载因子可以减少扩容的频率,但链表容易变长,数据密度较大,性能也会下降

4、链表跟红黑树区别?为什么红黑树快

链表由节点组成(每个节点是包含一个数据项和一个指向下一个节点的引用),他的地址不是连续的,在查询的时候要从头开始一个一个的查,链表太长的话,查询的时间会比较长(红黑树的性能更高),时间复杂度是O(n),n是元素的个数。

而红黑树是一种自平衡的二叉树,平衡树的特点就是左右子树的高度差不会超过一,他的高度比较小,有比较快的查询和增删速度。时间复杂度是O(logn),n是元素个数。

红黑树:

  • 红黑树是一种自平衡的二叉搜索树,具有以下特性:(1)每个节点要么是红色,要么是黑色;(2)根节点是黑色;(3)红色节点的子节点都是黑色;(4)对于每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点;(5)空节点(叶节点)都是黑色。
5、为什么不一开始就使用红黑树?

因为元素较少的时候,链表的那个效率也挺高的,而且红黑树跟链表比,需要更多的内存空间和计算开销。最重要的是,早期的版本都没有红黑树这种东西,这种高级的数据结构(1.6之后有)

6、HashMap的put过程

首先检查数组是否为空,为空就进行一次resize操作。否则就计算key的哈希码,确定要插入数组的哪个位置,如果这个位置为空,直接新建一个节点添加就行,不为空就遍历链表或者红黑树,有相同的key就把对应的value值覆盖掉,如果没有,分两种情况,如果链表长度没达到域值8,就插入到链表尾部,否则就尝试进行数组扩容,再重新计算,如果数组容量已经达到域值64,那就将链表转换为红黑树再插入。最后一步就是判断是否满足扩容条件,如果满足,就执行resize方法进行扩容。

7、HashSet和TreeSet的区别

HashSet的底层就是基于HashMap实现的,HashSet 的源码非常非常少,因为除了 clone()、writeObject()、readObject()是 HashSet 自己不得不实现之外,其他方法都是直接调用 HashMap 中的方法。所以他底层还是一个哈希表,存储的元素是无序的。

TreeSet则是红黑树,依赖自然排序或者比较器排序,对键进行排序

性能:HashSet的插入、删除和查找操作的时间复杂度都是O(1),而TreeSet的这些操作的时间复杂度都是O(log n)。

因此,如果需要快速的插入、删除和查找操作,并且不需要元素有序,则可以选择HashSet。如果需要元素有序,或者需要按照其他方式进行排序,则可以选择TreeSet。

8、new一个ArrayList,不传任何参数,那他容量是多少

一开始他其实是0,只有第一次往集合里添加元素的时候,那个default_capacity才会被用上,也就是10,然后以后扩容的时候是上一次容量的1.5倍。

6、什么是泛型?

泛型就是参数化类型,相当于一个类型模板,可以传入各种不同的数据类型,提高代码的重用性。另外泛型信息只会存在于代码编译阶段,编译完成后会被类型擦除,替换成传入的具体类型。并且不会对程序的性能有太大的影响

7、api是什么,常见api有哪些?

api就是应用程序编程接口,常见的有object类下的几个,集合相关的。String类下的,日期工具类这些。

Object类:equals()方法、toString()、hashCode()、clone()、getClass()

​ equals:只有引用数据类型有equals方法,默认继承自object类,作用是判断两个对象是否相等,底层还是使 双等号来判断,比较的是对象引用地址,一般都会进行重写,来比较对象的值是否相等。

​ toString():返回对象的字符串表示

​ hashCode():返回对象的哈希码,哈希码是一个32位的整数

​ clone():用于创建对象的浅拷贝,即复制了对象的地址引用

​ 对于基本数据类型,浅拷贝和深拷贝都是进行值传递,对于引用数据类型,浅拷贝是进行引用传递,而深拷贝创建一个新的对象,并复制其内容。

​ getClass():返回对象的运行时的类,该对象包括这个类的信息如名称、方法和字段等。(对象.getClass())通常与反射机制一起使用

8、String类

1、api

创建String对象:直接复制或使用构造函数

length()方法:返回字符串长度

charAt(int index):返回指定索引处的字符型的值

indexOf():返回指定字符第一次出现的字符串内的索引

lastIndexOf():返回指定字符串最后一次出现的字符串中的索引

startWith(String pre):检查该字符串是否以指定字符串为开头,返回值是布尔型。

endsWith():检查该字符串是否已制定字符串为结尾

contains():检查该字符串中是否包含指定的字符串

split(String regex):将字符串分割,返回值是一个String数组 String[] arr =s.split(" ");

toCharArray():将字符串转化为字符数组 char[] arr =s.toCharArray();

toUpperCase():将字符串全部转为大写s.toUpperCase()

toLowerCase():将字符串全部转为小写

trim();用于忽略字符串前后空格

subString(a,b):用于截取字符串,两个参数为前闭后开

replace(a,b):将a替换为b:用于字符串替换

concat(String str):用于字符串拼接

还有判空isEmpty()

Java常用API整理——String方法_java string 接口_憨憨爱学习zz的博客-CSDN博客

2、String为什么不可变?

因为String底层是一个private final修饰的字符数组(1.8及以前是字符数组,1.9及之后是字节数组)

当对String对象进行拼接替换或者其他修改操作时,实际上会创建一个新的String对象,而原始的String对象保持不变

9、Key-Value存储原理

依赖于键值对的概念,每个数据项都与一个唯一的键相关联,这个键用于查找和检索该数据项。

hashMap是一种常见的keyvalue存储实现,它基于哈希表数据结构,将键映射到值,通过哈希函数将键转换为数组索引。

Redis存储也是使用keyvalue的结构,其中key是字符串类型,value有字符串(String)、哈希(hash)、列表(list)、集合(set)和有序集合(sorted set)五种常用数据类型

10、Java Web相关

1、Servlet生命周期

Servlet生命周期包括加载和实例化,然后容器运行其init方法初始化,请求达到时运行其service方法处理请求,service方法自动派遣运行与请求对应的doget或者gopost这些方法,当服务器关闭时调用destroy方法销毁。

2、请求转发和重定向

请求转发是服务端的转向,在同一个Servlet容器内部处理,浏览器不知道发生了转发,所以地址url不会变化,使用的同一个请求和响应对象,因此可以共享请求属性和会话信息。通常用于将请求转发到同一个应用程序的不同Servlet或jsp,以共享数据或执行服务器内部的操作。

重定向是客户端的跳转,它通知浏览器重定向到另一个url,浏览器会发起一个新的请求,这个请求一般是get方式,地址url会改变。通常用于将请求重定向到另一个应用程序或完全不同的url,可以跳转到外部网站或跳转页面。

3、session、Cookie

session是一种会话跟踪技术,用于在客户端和服务器之间存储用户会话数据,以便在用户访问不同页面或进行不同的请求时,能够获取并保持用户的状态。由于session域存储在服务器内存中,在大量用户同时访问时可能会占用较多的资源,对服务器性能造成负面影响。一般用于存储用户身份信息这些敏感数据

实现session的方式有很多,基于cookie的session,将会话信息存储在cookie中,通常会在用户登录成功后将一个唯一的sessionid存储在cookie中,并在每次请求中都会将该sessionid发送给服务器进行验证和恢复会话信息。

还有基于url的会话,基于服务端的会话,是将会话信息存储在服务器的内存中,服务器在每个请求中都会通过会话id来查找和恢复会话信息。还有一个基于Token的会话,token可以在多个服务器之间进行传递,从而实现会话的跨服务器共享。

一个session存储在一个服务器上,如果有多个服务器,session还能使用吗?多个服务器具体如何实现通信?

有多个服务器,可以使用数据库存储会话信息,通过读写数据库来访问和同步会话数据

还可以使用分布式缓存系统,将会话信息存储在共享的缓存中,另外也可以使用文件存储或者存储在Token中,Token可以在多个服务器之间进行传递,从而实现会话的跨服务器共享。

cookie和session的区别:

cookie和session都是会话跟踪技术,cookie是通过客户端记录信息确定用户身份的,而session是基于服务器,但是session的实现依赖于cookie,session的唯一标识sessionID需要存放在客户端。

cookie不是很安全,在浏览器上按下f12就能看到了,而session会占用服务器的性能,因为session会在服务器保存一段时间,所以一般把重要信息如登录信息存放到session中,其他信息有需要就存到cookie中,不过cookie能保存的数据容量有限,一般为4k,session可以存储较大量的数据。

4、九大内置对象以及四大作用域

应用对象是servlet正在执行的内容

会话对象是与请求有关的会话期

请求对象是用户端请求

页面对象是JSP网页本身

上下文对象,网页的属性是在这里管理

输出对象用来传送回应的输出

配置对象是servlet的架构部件

异常对象针对错误网页

响应对象是网页传回用户端的回应

应用域代表整个web应用的全局变量

会话域用于在客户端和服务器之间存储用户会话信息,是客户端与服务器首次建立连接时创建的,通常是在客户端发送第一次请求时创建,服务器会为每个客户端创建一个唯一的session对象,用于跟踪当前客户端的状态信息。

请求域表示客户端的请求

页面域就是当前页面,只在当前页面有效。

5、EL表达式、JSTL等技术

el表达式就是用来获取域里面的对象,通过$和大括号获取,Thymeleaf模板在前面还要加个th:

JSTL提供了许多方便快捷的标签,用来逻辑控制、循环控制等等如if、choose和迭代标签forEach

11、Linux基本命令

ps:显示进程状态

ping:测试与目标主机的连通性、

kill:终止进程

reboot:重启系统

shutdown:关机

ls:列出目录中的文件和子目录

pwd:显示当前工作目录的路径

cd:切换目录

mkdir:创建新目录

rm:删除文件或目录

cp:复制文件或目录

mv:移动文件或目录

touch:创建空文件

cat:查看文件内容

more:逐页查看文件内容

在Linux系统中,常见的Shell命令是基于bash(Bourne Again SHell)的。以下是一些常见的Linux系统中的Shell命令:

  1. 文件和目录操作:
  • ls:列出目录内容
  • cd:切换目录
  • pwd:显示当前工作目录
  • mkdir:创建目录
  • rm:删除文件或目录
  • cp:复制文件或目录
  • mv:移动或重命名文件或目录
  • find:查找文件
  1. 文件操作:
  • cat:显示文件内容
  • touch:创建空文件或更新文件的时间戳
  • head:显示文件头部内容
  • tail:显示文件尾部内容
  • grep:在文件中搜索指定模式
  • sed:对文件进行文本替换和处理
  • awk:处理文本文件的行
  1. 系统操作:
  • ps:显示进程状态
  • top:实时显示系统资源使用情况和进程信息
  • kill:终止进程
  • reboot:重启系统
  • shutdown:关机
  • uname:显示系统信息
  • ifconfig:查看和配置网络接口
  1. 网络操作:
  • ping:测试与目标主机的连通性
  • curl:命令行工具和库,用于发送和接收HTTP请求和数据
  • ssh:远程登录到其他计算机
  • scp:在本地和远程计算机之间拷贝文件
  1. 压缩和解压缩:
  • tar:创建和提取tar归档文件
  • gzip:压缩文件
  • gunzip:解压缩文件
  • zip:压缩文件
  • unzip:解压缩文件

这只是一些常见的Shell命令,实际上Linux系统中有很多其他的命令,可以根据需要进行查找和学习。

12、排序

快速排序

选择一个基准值,小于基准值的放在左边,大于基准值的放在右边,基准值放在两个子数组的中间,递归的对子数组重复刚才的操作,直到数组长度为1或0;

时间复杂度:最好情况下O(n log n)。最坏情况下,O(n^2)。平均情况下,快速排序的时间复杂度也为O(n log n)。

空间复杂度:为O(log n)

public static void quickSort(int[] arr,int left,int right){
    if(left>=right){
        return;
    }

    int base = arr[left];
    int L=left;
    int R=right;

    while (L<R){
        while(arr[R]>=base && L<R){
            R--;
        }
        if(L<R){
            arr[L]=arr[R];
        }
        while(arr[L]<=base && L<R){
            L++;
        }
        if(L<R){
            arr[R]=arr[L];
        }
        if(L>=R){
            arr[L]=base;
        }
    }

    quickSort(arr,left,R-1);
    quickSort(arr,R+1,right);
}
冒泡排序

从数组的第一个元素开始,依次比较相邻的两个元素,每一轮排序都会得出一个最大或最小值放在后面,然后对剩下的元素继续重复刚才的步骤直到只有一个元素为止。时间复杂度O(n^2) 空间复杂度O(1)

package com.lll.datastructure.sort;

import java.util.Arrays;

/**
    算法思想:
    1.比较相邻的两个元素,如果第一个元素大于第二个元素,就交换位置
    2.对每一对相邻的元素再做同样的比较,从最开始的一对到结尾的一对完成全部的比较。最后一个元素肯定最大的
    3.对剩下的元素重复第二步操作,直到只有一个元素为止
 */
public class BubbleSortTest {
    public static void main(String[] args) {
        int[] arr = {12,3,33,11,32,9,8,5};
        System.out.println("排序前:" + Arrays.toString(arr));

        // 控制循环趟数
        for (int i=0; i < arr.length-1; i++) {
            // 是否发生交换
            boolean isSwap = false;
            for (int j=0; j<arr.length-1-i; j++) {
                if (arr[j] > arr[j+1]) {
                    // 进行交换
                    int tmp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = tmp;
                    isSwap = true;
                }
            }
            System.out.println("第"+i+"次:"+Arrays.toString(arr));
            if (!isSwap) {
                // 如果没有发生交换,则说明已经是有序的了,不用再进行额外的排序,做无用功
                break;
            }
        }
    }
}
插入排序

将数组分为已排序区和待排区,根据自己要正序还是倒序排,从待排区选择一个元素与已排区的元素比较,根据自己写的逻辑向前移或者向后移,插入合适的位置,重复步骤,直到待排区为空。时间复杂度O(n^2) 空间复杂度:O(1)

package demo0804;

import java.util.Arrays;

/**
 * 插入排序
 */
public class Test01 {
    public static void main(String[] args) {
//自己写的
        int[] a = new int[100];
        for(int i=0;i<a.length;i++){
            a[i]=(int)(Math.random()*80+20);
            System.out.print(a[i]+"    ");
        }
        System.out.println();

        for (int i = 1; i < a.length; i++) {

            int t = a[i];

            int j = i;
            while (j > 0 && t < a[j-1]) {
                a[j]=a[j-1];
                j--;
            }

            if(j!=i){
                a[j]=t;
            }
        }
        System.out.println(Arrays.toString((a)));
    }
}
希尔排序

就是对插入排序最坏的情况的改进,主要是减少数据移动次数,增加算法的效率。

先将数组分组,然后在组内分别进行插入排序,最后再对整体进行插入排序。

要保证最后一次排序的步长为1,这样就会保证整个数组将会被排序,并且步长必须小于数组长度。

①最好情况:时间复杂度为O(n)②最坏情况下:时间复杂度为O(n^2)③空间复杂度为:O(1)

选择排序

将数组分为已排区和待排区两部分,从待排区找出最大或最小值与待排区的第一个元素交换位置,并加入已排区,重复步骤,直到待排区为空(1)时间复杂度:O(n^2;(2)空间复杂度:O(1;

package priv.sort.selection;

/*
    选择排序
*/

import java.util.Arrays;
import java.util.Random;

public class selectionSortDemo {
    public static void main(String[] args) {
        //创建要排序的数组
        int[] arr = new int[10];

        Random random = new Random();

        //遍历数组对每个元素赋值
        for (int i = 0; i < arr.length; i++) {
            //获取[0,1000)的随机整数,并给数组的每个元素赋值
            arr[i] = random.nextInt(100);
        }

        //在控制台输出显示未排序的数组
        System.out.println(Arrays.toString(arr));

        //选择排序原理排序数组元素大小(从小到大)
        for (int i = 0; i < arr.length-1; i++) {//外循环控制循环的轮数
            int minIndex = i;//定义变量,表示每一轮假设最小元素的索引
            for (int j = 1 + i; j < arr.length; j++) {//内循环控制每一轮被比较的元素
                if (arr[minIndex] > arr[j]) {
                    //交换元素的位置
                    int empty = arr[minIndex];
                    arr[minIndex] = arr[j];
                    arr[j] = empty;
                }
            }
        }

        //在控制台输出显示排好序的数组
        System.out.println(Arrays.toString(arr));
    }
}
归并排序

就是先将数组分组,刚开始每个元素作为一组,然后两两合并,如果正序排序,就将小的放左边,完成第一轮合并后,第二轮就是比较每一组的头部元素,哪个更小就放在最左边,比如有一组元素a,b,a拿出去了,b就成了头部元素了,就这样继续两两合并,直到只剩一个数组就行了。(1)时间复杂度:O(n*logn);(2)空间复杂度:O(n);

13、二分查找

二分查找:二分查找的前提是数组有序,初始化左边界为数组第一个元素的索引,右边界为数组的最后一个索引,再计算中间的位置,比较中间位置的值与目标值,相等则返回该位置,大于目标值则将右边界赋值为中间位置-1,小于目标值则将左边界赋值为中间位置+1,重复操作,如果左边界大于右边界了,说明数组中没有目标值。O(logn),n是元素的个数

public class test {
    public static void main(String[] args) {
        int[] a = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        System.out.println(binary(a, 0));
    }

    private static int binary(int[] a, int i) {
        int left = 0;
        int right = a.length - 1;

        while (left <= right) {
            int mid = (left + right) / 2;
            if (a[mid] == i) {
                return mid;
            }
            if(a[mid]>i){
                right=mid-1;
            }else {
                left=mid+1;
            }
        }
        return -1;
    }
}

14、动态规划、贪心

动态规划的基本思想是将原始问题分解为多个子问题来求解,通常适用于具有最优子结构的问题。写代码过程中最重要的是推出递推公式,递推求解,将子问题的解保存下来备用,但只保存一次,后面的子问题的解会覆盖掉。

贪心算法的每一步都选择当前的最优解,不考虑未来的情况,因此可能得到局部最优解,但他的效率高。

15、分布式、微服务、分布式锁

1、分布式?场景

分布式就是将一个大的系统划分为多个业务模块,业务模块分别部署到不同的机器上,他们通过接口和网络通信进行数据交互。

好处:分布式具有高可用性,高性能、扩展性强,还有容灾备份,灵活性强

应用场景:多应用于大规模数据处理,大型的应用服务,还有分布式缓存、分布式消息队列、分布式存储系统等

2、微服务?场景

微服务是一种架构模式,旨在将单个应用程序拆分乘小型服务,这些服务可以独立的开发、部署和维护。

好处:高可用性、弹性扩展、负载均衡、容错性也高,能够独立部署

一些常见的微服务应用场景包括:

  1. 电子商务平台:将商品管理、订单管理、用户管理等功能拆分为多个微服务,实现松耦合和独立部署。
  2. 社交媒体平台:将用户管理、内容管理、评论管理等功能拆分为多个微服务,以实现可扩展性和高性能。
  3. 云存储服务:将文件管理、权限管理、数据备份等功能拆分为多个微服务,以实现弹性扩展和高可用性。
  4. 物流管理系统:将订单管理、库存管理、运输管理等功能拆分为多个微服务,以实现快速响应和灵活扩展。
3、分布式锁

分布式锁用来解决分布式系统中多个应用同时访问同一个资源的问题。

16、消息队列

消息队列是一种用于在应用程序之间传递消息的中间件,它提供了一种异步通信机制,可以将客户发出的请求存到队列中,并由服务器从队列中接收消息。解耦了双方之间的直接通信。而且消息队列具有单线程的特性,在并发环境中,将短时间内高并发产生的请求存储在队列中,服务器根据自己的能力慢慢的一个一个去处理,避免了并发产生的数据不一致的问题,还能避免服务器崩溃。

17、数据库优化方式

第一种方式是创建索引,第二种。。。。。。。

创建索引,mysql索引使用了B+树,可以快速定位到包含特定值的行而不必扫描整个表

还有进行分表,采用水平分表或垂直分表来优化

当一台服务器不能满足需求时,采用读写分离的方式进行集群,主库负责写,从库负责读

另外也可以使用redis来缓存。

存储过程不知道是不是也是一种优化方式,不过他确实也能够提高数据库执行速度

还能使用存储过程:复杂的业务逻辑需要多条SQL语句,可以把这些操作放在一个存储过程中,使客户机和服务器之间的网络传输就会大大减少,降低了网络负载。存储过程只在创建时进行编译,以后每次执行存储过程都不需要再重新编译,而一般SQL语句每执行一次就编译一次,因此存储过程可以大大的提高数据库执行速度

18、sql优化、索引失效:

尽量减少在where子句中使用!=或者<>操作符和对null值进行判断,还有模糊查询的时候以通配符开头,这些都会会导致索引失效导致全表扫描,还有为那些经常出现在order by,group by,distinct后面的字段,建立索引,对那些经常作为查询条件的字段创建索引。不过索引也不是越多越好。另外尽量使用union all 代替union,因为union在查询的时候会进行一个去重操作,效率会比较低,去重的操作可以在java代码中实现,因为数据库的资源比服务器的资源要珍贵得多。

在使用limit进行分页查询的时候,比如有10000条数据,要查最后5条数据,使用limit的话,就要对前面所有数据进行一个检索,再提取最后五条数据,然后就把前面的数据给抛弃了,就会做许多无用功,这时候甚至直接用大于和小于号限制结果集反而更好(其他方式索引覆盖+子查询)。

其他的话,查询时避免使用*来查询所有列,减少数据传输。另外OR和IN操作符也不建议使用(用exists代替in是一个很好的选择),因为会增加查询的复杂性和耗时。存储过程不知道是不是也是一种优化方式,不过他确实也能够提高数据库执行速度

19、索引(类型、使用原则)

索引是一种能提高数据库查询效率的数据结构,mysql索引使用的是B+树,可以快速定位到包含特定值的行而不必扫描整个表。

索引有B+树索引、哈希索引、全文索引、聚簇索引(以主键创建的索引)、非聚簇索引、主键索引、唯一索引和联合索引等等

使用场景:频繁作为查询条件的字段应该创建索引、查询中与其他表关联的字段,外键关系创建索引还有排序的字段等等。

根据查询需求选择合适的索引类型,如B-Tree索引、Hash索引等

不需要:表记录太少,经常增删改的表、频繁更新的字段、数据重复的字段。

还有最后一个,索引不是越多越好,索引存储在内存中,太多索引不但耗内存,还要花更多时间去维护。

20、编码规范

类名使用首字母大写的驼峰命名法,方法名和变量名使用首字母小写的驼峰式命名法;常量全部使用大写字母。标识符由字母数字下划线组成,不能以数字开头,不能是关键字。允许使用$符、Unicode符。另外对于异常不要简单的抛给调用者,尽量自己处理;还有合理的进行模块化设计,提高代码的重用性和可维护性。

项目相关

1、项目介绍

该项目是直接面向消费者的网上商城系统,整体分为前台和后台两部分,各层分模块开发。项目基于ssm框架和Thymeleaf模板引擎。

前台包括首页门户、新品、热销商品等,左侧栏有商品分类信息,使用了css那个滑过显示的那个什么东西来显示下级分类信息,点击可查看类别下的商品展示,并且可以根据商品上架时间、销量和价格等指标排序。商品详情使用富文本编辑器。具有地址管理、购物车、生成订单、订单结算和查看订单流程和退款功能。后台就是对用户、商品、商品分类还有订单进行各种crud操作,另外还能对用户进行冻结,重置密码,商品上下架,修改库存、订单配货发货退款等功能。

然后我对那些对不易改动的数据添加Redis缓存mybatis二级缓存(单个方法调用次数较频繁的时候用Redis缓存(商品分类信息,前台的分类数据一次性全查出来,鼠标悬浮的时候显示其子类),多个方法调用同一个sql操作的时候,在该sql操作上开启二级缓存(后台对分类进行管理的时候,按分类等级管理,每次只需要查询部分分类数据,但调用的都是同一个sql操作))。并且对复杂的业务添加事务,保证数据的一致性,比如生成订单功能,涉及到购物车商品的逻辑删除,商品库存修改,生成订单,添加订单快照,有多个非读的sql操作,所以要加上事务,保证事务的一致性。

自动登录:

在js代码中通过localstorage,如果登录的时候选择自动登录,就会把js中自设定的标识改为true,开启自动登录,并将此次登录信息存到本地缓存中。实现自动登录那部分代码就是将手动输入登录信息,变成在加载js代码的时候就直接将缓存中的登录信息发送给后端,并且跳过图形验证码进行验证登录。

登录:

用户输入账号密码后,首先在前端进行判空和一个正则表达式的格式验证,利用表单提交的方式传给后端,后端根据用户名去查数据库,查到后将密码与用户输入的转为md5加密形式后的密码进行比对,相等就给通过,并将登录信息存放到会话域中

做登录功能的时候有考虑哪些问题吗?

考虑到安全性,就是把用户输入的密码进行一个加密。

还有防止攻击,通过图形验证码来确保,呃,只能说尽量确保是真人操作。同时为了防止SQL注入,还要对输入的信息进行严格的格式校验,比如用一个正则表达式来规定。编写sql的时候也是不要用$,而是用#代替。

其他一些小细节就是做一些友好的错误提示,不过像第三方登录这些功能倒是心有余而力不足。

分模块

就是在父工程下创建一个个子工程,分别用来写控制层、业务层等这些代码,这个专业术语应该是叫分层解耦,便于多人合作开发。

Thymeleaf作用

将THML页面和服务端数据绑定,生成最终的html内容将其呈现给客户

Ajax:

ajax可以使网页实现异步更新,在不重新加载整个页面的情况下,对网页的某部分进行更新

怎么用ajax发送请求到后端?

使用$.ajax,设置好请求类型、请求路径、请求参数和数据类型,再写个回调函数用来接收后端处理结果就行了。

ajax中method都有哪些?

常用的method有GET、POST、DELLETE、和PUT

ajax只用过data传参吗?

data通常用于post请求,在get请求中,参数一般以键值对的形式附加在url末尾中,另外还可以使用formdata对象和headers请求头

ajax内部有哪些数据?

请求url、请求方法(类型)、请求头、请求主体,响应状态码 、响应头、响应主体、回调函数、XMLHttpRequest对象(用于创建和发送ajax请求,以及接收响应。),还有个异步标志async

get和post的区别:

get请求将参数以键值对的方式附加在url中,参数传递的大小有限制,另外get请求可被浏览器缓存,当下次发起相同的请求时浏览器可直接从缓存中获取数据,整体来说不太安全;而post请求将参数作为请求的一部分,不会在url中显示,对参数传递的大小也没有限制,不具备缓存功能,整体来说比较安全。关键词 :request语法:request.getRequestDispatcher(URL地址).forward(request, response)关键词: response语法:response.sendRedirect(URL地址)

什么是同步请求?异步请求?ajax怎么实现异步请求的?

同步请求是前端发送一个请求到服务端之后,js会等到服务器响应就绪才继续执行,在ajax中将async参数设置为false即可,默认是true。而异步请求时ajax发送请求后,js无需等待服务器的响应,继续执行接下来的代码,等到响应成功以后,执行响应的结果。同步请求只能等上个请求结束后才执行下一个请求,当请求失败的时候,很可能出现卡死状态,异步请求可以同时发出多个请求。

ajax是因为使用了js的事件驱动机制,当发起一个ajax请求时,js会创建一个XMLHttpRequest对象,并向服务器发送请求,并且不用等待服务器响应就绪,而是继续执行往后的代码,当服务器响应结束后,XMLHttpRequest对象接收响应,触发js代码中定义的回调函数,来执行响应结果。

图形验证码

这个我就调用了一下第三方接口CaptchaUtil,它默认将验证码存放在session域中。取出来与用户输入的忽略大小写equals一下就行了。equalsIgnoreCase

怎么上传图片的?

图片从前端传到后端的是二进制流,使用MultipartFile工具类接收,通过工具类的一些方法获取图片的名字后使用subStrign得到后缀名,然后再整个uuid或者根据时间命名,或者直接用雪花算法命名,反正使图片名称不重复就行,然后将源文件存到本地磁盘和另一个服务器中,再将服务器中的文件的路径传回前端的img标签的src属性进行回显,然后与其他信息一起传给后端存储到数据库中。

文件加载到页面的时候就从服务器里面取。

商品详情可以显示文字和图片,具体内容根据商品而定。后台编辑商品详情的时候,使用的是富文本编辑器,将文字图片什么的都转成html代码,直接存到数据库中,图片就将他的路径赋值给img标签的src属性。显示的时候用th:utext解析html内容。

自己在项目中的职责

作为组长,统筹组员协作分工。在项目启动阶段,与组员共同制定详细的项目计划,包括目标、阶段、任务分配、和时间表;确定项目的关键点,优先解决什么问题;并且为各个团队成员分配详细的任务和截止日期,定期检查和评估进展情况,确保在按预期进行;后续根据实际情况与组员沟通交流,及时调整计划。对系统业务和需求进行分析与设计,承担后端代码的开发,并参与前端代码中与后端进行交互那部分的代码的设计。

2、项目中遇到什么难题?

设计表的时候,就是用户表用户名是唯一的,本来加个唯一索引就行了吧,然后删除的时候我要给他留痕,就加了个逻辑删除的字段,就用个deleted字段0否1是,但是如果我把用户a删除了,下次新增一个用户还叫a,这不就违反了这个唯一索引了,等于没法新增这个用户,然后就想到了把这两个字段做一个联合索引,但是好像还是不行,比如本来有一个a用户已经删除,现在又有一个a用户被删除,又变成一样的了,也会违反唯一索引。后来实在没辙,直接给删除后的用户名后面加个时间标识来保证唯一性。

在生成订单的时候,涉及到的业务较多,先获取购物车所选商品所有id,判断库存是否充足,是否上架,条件都满足的话先将购物车中所选商品进行逻辑删除,利用id生成器生成一个唯一id作为订单号,并将当前用户id、所选地址id, 订单初始状态什么的一并存入订单表中,更新商品库存,再把所选商品id和当前商品信息(商品信息以后可能改变)与订单号存入订单项表中。涉及多个非读的sql,在方法上添加@Transactional注解,用于指示该方法为一个事务。考虑多个用户同时下单的并发问题,可以使用消息队列,将用户请求存储在队列中,利用其单线程的特性,交给消费者逐一处理,保证数据的一致性,也适用于秒杀、促销等操作,将同时间内高并发产生的大量请求暂存在队列中,根据服务器的的能力慢慢处理,避免了服务器的崩溃。另外还能使用Redis分布式锁,在方法体前加锁,客户端拿到锁后往下执行,执行完后释放锁,交给下一个请求。

第一个项目中有个关于分类数据的问题,我的目的是想把所有分类信息封装到一个集合里面,大体上是利用集合嵌套分级封装,虽然想法简单,但真正实现花了挺长时间,首先我写了三个模型类,第一个用来封装所有分类数据,其中一个属性是二级分类数据的集合,也就是第二个模型类,这个模型下又有封装了三级分类数据的属性,也就是第三个模型类。具体实现首先获取所有各个等级的分类数据,然后又建立一个map集合,key值存类别ID,value值是该类别下的子类集合,接下来对二级分类数据遍历,如果类别id在map集合的key中存在,就将对应的value值设置到二级分类模型类的三级分类属性中,遍历完后得到一个二级分类及对应下级分类数据的集合。然后对一级分类重复上述操作,得到一个所有分类数据的集合。

3、做这个项目有什么用?

首先是练习和巩固java中的各种开发技术,加深对这些技术的理解和熟练度。另外商城项目一般涉及到较多的业务,虽然底层还是增删改查,但复杂度提高了不少,通过实际操作,更好的理解和学习如何分析和设计业务流程。

4、雪花算法?

雪花算法基于时间戳,和机器标识还有序列号三部分组成,在同一时间戳内序列号自增,基本能够在一秒内生成百万个不重复的id。

uuid它生成的是一个无序的字符串,而且没有排序,阅读性不太好。不过它优点就是可以在全球范围内唯一标识一个对象,不会与其他对象的ID重复。还有UUID生成的ID位数较多,不便于存储和传输。而且好像这种算法有漏洞,我听说之前有个叫梅丽莎病毒的制作者就因为用了UUID被抓到了。(信息不安全:基于MAC地址生成UUID的算法可能会造成MAC地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。)

Snowflake sf = IdUtil.getSnowflake(1, 1);

雪花算法(64位的整数)是一种用于生成唯一ID的分布式算法,他就是将ID分成时间戳(毫秒级)、机器标识和序列号三部分,通过组合这些信息最终组成唯一id。每台机器都有不同的机器标识,但要注意时间同步,在同一主机的同一毫秒内生成的数据也是不相同的,因为序列号就是用来解决这个问题的,通过递增序列号的方式保证唯一性。如果序列号已经达到最大值,那么就会等到下一毫秒再生成。

我是调用了他那个工具类的一个接口就生成了

多台机器如果时间不同步,或者系统时钟发生回拨,可能会生成相同的id(算法中可通过记录最后一个生成 id 时的时间戳来解决,每次生成 id 之前比较当前服务器时钟是否被回拨,避免生成重复 id。),而且雪花算法是用于单机或主从式的分布式环境,而不是大规模的分布式系统。另外雪花算法生成的id是一个64位的长整型数,所以他的id数量是有限的。虽然这个数量很难达到上限吧,但至少能预知出来,也算是一种风险把

5、MD5加密

我在项目中是直接调用DigestUtils下的一个接口完成加密的

md5本质上就是一个哈希函数,将 任意长度的数据转换为固定长度的哈希值,如果数据长度不够就在数据后添加一个1,后面跟一堆0,直到长度满足(128比特)。超过的话使用压缩函数

MD5它就是一种哈希算法,将任意长度的消息转换为固定长度的哈希值。但是不同的数据可能会生成相同的哈希值,也就是哈希冲突,攻击者可以通过暴力破解的方式,那个专业名词叫做碰撞攻击,寻找两个可能不同的输入,但哈希值相同的值,还有一个是预计算攻击,就是预先存储大量的数据和对应的哈希值

其他:SHA系列、bcrypt、Argon

6、拦截器、过滤器

拦截器怎么使用的?

编写一个拦截器类实现HandlerInterceptor接口,一般重写preHandle方法,表示在控制器处理请求之前拦截,根据实际需求判断是否放行,符合条件的话就放行,返回true,不符合就跳转到登录页面或者主页,返回false。再编写一个配置类实现WebMvcConfigurer接口,重写addInterceptors方法,添加刚才写的拦截器,声明这个拦截器要拦截哪些路径,哪些路径不用拦截

拦截器与过滤器的区别?使用场景?

拦截器是基于java的反射机制(动态代理)实现的,是spring的一个组件,不仅能在web程序中应用,还可以使用在其他地方。他是在请求进入DispatcherServlet之后,执行控制器之前进行拦截,只对action请求起作用,另外拦截器可以获取IOC容器的bean,在拦截器注入一个service,可以调用业务逻辑代码。

过滤器是基于回调函数的,(实现的接口在Servlet规范中定义的)实现依赖于Tomcat等容器,只能在web程序中应用。触发时机是请求进入容器之后,进入Servlet之前,几乎对所有的请求起作用

拦截器本质上是面向切面编程AOP,符合横切关注点的功能都可以用拦截器。拦截器多用于登录判断、权限判断等等,而过滤器多用于实现通用功能过滤的,如敏感词过滤、字符集编码设计等等。

拦截器的作用?

用来拦截客户请求,检查用户是否登录,是否有权限,也可以做日志记录等等。

9、JWT

登录的时候调用jwt工具类的接口,将登录信息存储到jwt令牌中,再在拦截器里解析令牌信息,解析成功就放行。

这个时间有点久,,当初是参照了网上的教程写的,所以记得不是很清楚

11、IOC、DI

概念+实现+好处

将创建对象的过程交给IOC容器来完成,这就叫控制反转。可以降低创建者和被创建者之间的耦合度。通过反射和动态代理技术来实现。还能提高资源利用率,因为ioc默认是创建单例实例

依赖注入(DI)将对象之间的相互依赖关系交给 IOC 容器来管理,并由 IOC 容器完成对象的注入。

实现方式:

1、构造器依赖注入:构造器依赖注入在容器触发构造器的时候完成,该构造器有一系列的参数,每个参数代表注入的对象。

2、Setter方法依赖注入:首先容器会触发一个无参构造函数或无参静态工厂方法实例化对象,之后容器调用bean中的setter方法完成Setter方法依赖注入。

3.直接在对象名上一行添加@Autowire注解

12、AOP

aop是一种面向切面编程的思想,通过程序在运行时将代码插入到现有的代码流程中,以实现一些额外的的功能如日志记录、事务管理等等。

springAOP基于动态代理实现,通过定义切面(Aspect)和切点(Pointcut),以及使用通知(Advice)来实现。

通过aop,可以在不修改原有代码的情况下,通过切面的配置来添加一些功能

AOP通知:

1、前置通知@Before:在方法调用之前执行

2、后置通知@AfterReturning:在方法正常调用之后执行

3、环绕通知@Around:在方法调用之前和之后,都分别可以执行的通知

4、异常通知@AfterThrowing:如果在方法调用过程中发生异常,则通知

5、最终通知 @After:在方法调用之后执行,类似于finally

14、动态代理

就是在不修改原有代码的情况下,在代理对象的方法执行前后插入一些逻辑操作,实现额外的功能

动态代理是一种实现代理模式的技术,它允许在运行时动态的创建代理类和代理对象。在不修改原始类的情况下,通过代理类来扩展或增强原始类的功能。动态代理在很多场景中被广泛应用,如aop中的切面代理,mybatis底层也是,通过拦截器方法回调,对目标方法进行增强。

实现方式:有基于接口的,还有基于类的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值