自己这段时间看的面试题合集(一)

文章目录

1.面试题

1.3.volatile是什么?

是java虚拟机提供的一种轻量级的同步机制。

有三个特性:

  • 保证可见性
  • 不保证原子性
  • 禁止指令重排

2.请你说说Java和PHP的区别?

JIT指的是动态编译,即运行的时候编译。与之相对的便是静态编译,即事前编译。

JIT编译指的是在某个程序即将第一次运行的时候进行编译,因而叫作即时编译,JIT编译是动态编译的一种,但久而久之,人们也用广义的JIT编译代替动态编译。

java最开始使用解释器进行解释执行的,当JVM发现某个代码使用的特别频繁,它就开始把它设置为热点代码,把它以机械码的形式保存在内存中,进行进一步的优化。

1.编译方面

php还不能像java语言那样利用JIT动态的即时编译,但php有着opcache机制,能把脚本对应的opcache缓存放在内存里,php还可以将opcache.file_cache导出opcaode到文件里,php的JIT分支也在测试中,据称该分支的编译速度是php5.4的10倍。

2.库函数的编译问题上

PHP的库函数是基于c进行实现的,而java的库函数是基于Java语言实现的。所以java应用运行时,框架和库函数都是基于jvm运行的,为了防止JIT编译比解释执行消耗的时间多,java的HotShot机制,直到有方法被执行10000次才会被JIT设置为热点编译。

3.模板引擎方面

php自己就是模板语言,而java还需要用jsp容器和第三方模板引擎

4.php是面向过程编程,专注于互联网在web领域 php优势很大,但是java是面向对象编程的,做的应用的面比php广

5.数据库访问上

java由jdbc提供了统一的接口,只需要少量的修改,而php则需要大量的修改。

6.安全性

php的源代码是公开的,而java的代码都会被编译成.class文件,用户很难看到文件内容,提高了安全性。

7.php适合开发小的应用,开源免费,开发快,投入少。java更适合开发企业级应用,并发性比php强

h1 11:40 h1 11:41 h1 11:42 h1 11:43 h1 11:44 h1 11:45 h1 11:46 h1 11:47 h1 11:48 h1 11:49 M0 J1 T2 M0 J0 T3 M1 J2 T4 M1 J0 T2 M1 J1 T4 M2 J1 T1 M2 J2 T3 M2 J0 T2 M0 M1 M2 车间作业调度

2021.11.25

1.java8加了哪些新特性?

答:jdk8引入了lambda表达式,lambda表达式实质上是一种匿名内部类,只是写法上简化了,他将原来繁琐的匿名内部类的形式缩减成较为简短的形式,由jvm进行还原,相对于匿名内部类,lambda表达式的书写形式更加简单便捷,更加便于我们使用。jdk8还引入了函数接口,四个常见的函数的接口为 有参无返回值的消费型接口,无参由返回值的供给型接口,有参有返回值的函数型接口,还有返回boolean类型的断言型接口。

同时jdk8又引入了方法引用和构造器引入,用类双冒号静态/非静态函数 对象双冒号函数的形式来代替了原来调用函数的引用形式。

jdk8也引入了stream流,stream流极大的简化了我们对数组元素的操作,他为我们提供了很多方法,并且在内部已经完成了本来应该由我们做的那些数组的迭代,极大的改善了我们的代码编写效率和代码整洁性。

同时jdk8也为我们提供了新的时间Api Localtime LocalDate LocalDateTime,这些新的api的加入解决了几个原本繁琐的问题:

  • 之前的Date类默认月份是从0开始计算的,而现在jdk8把他们加入到了枚举类型

  • 保证了线程安全:以前的java.util.Date和SimpleDateFormat都不是线程安全的而现在新的类型和String类型都是一样的,用final修饰,是不变的,保证了线程安全

    Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。

    Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。

2. 请你谈谈Java中是如何支持正则表达式操作的?

答:java的String类提供了很多关于正则表达式的操作方法,如matches(),replaceAll(),replaceFirst(),spilt()等方法,同时也可用Pattern类来储存Pattern.compile()方法制定的规则,以及用Matcher对象来记录符合条件的数据。

详细。如下

import java.util.regex.Matcher;
import java.util.regex.Pattern;
class RegExpTest {
    public static void main(String[] args) {
        String str = "成都市(成华区)(武侯区)(高新区)";
        Pattern p = Pattern.compile(".*?(?=\\()");
        Matcher m = p.matcher(str);
        if(m.find()) {
            System.out.println(m.group());
        }
    }
}

3. 请你简单描述一下正则表达式及其用途?

答: 在处理字符串的程序里,我们经常会用到正则表达式,正则表达式用于制定相应的规则,便于我们进行匹配。在实际应用中,比如网页注册里的账号格式匹配,最常用的电话格式,身份证格式匹配,很多地方等能用到正则表达式的匹配功能,正则表达式真的十分强大。

4.请你描述一下hashmap的扩容原理

hashmap的长度默认是16,也就是说,允许数据的索引在0-15之间。当数据超过这个范围时,就需要扩容hashmap。hashmap的扩容是通过创建一个长度是他两倍的数组的方式实现的。

在1.8之前,扩容方式为用链表的形式将多出的数据储存hashmap里的元素,但这种方法很容易发生碰撞。

于是为了解决这个问题,jdk1.8引入了红黑树机制。

当链表储存的数据达到8个以上时,hashmap会启动红黑树来储存数据

红黑树一般要满足以下特征:

  • 根节点时黑色节点,其他节点要么时黑色,要么是红色的。
  • 任何一个相邻的节点都不能是相同颜色的
  • 任何节点到达它可以到达的节点包含的节点数量都是相同的

红黑树的查找的时间复杂度较低,是O(logn),相对于AVL树,虽然AVL树也有着一样时间复杂度,但是它插入数据的操作是在过于繁琐,与之对比,红黑树的性能就好了很多。

5. 请你讲讲&和&&的区别?

&是按位与,&&是逻辑与。

二者的区别是,&用于二进制运算,进行二进制的与运算 相同为1 不同为0。

而&&则是逻辑与运算,是一种短路运算,必须前后都成立才能判定为真,若前面不成立,&&会直接短路运算,不去关后面的判断。否则会空指针异常。

6. int和Integer有什么区别?

int 和 Integer是包装和没有被包装的区别,Integer是int的包装类。java对八种基本的数据类型都进行了封装(Wrapper.class),

boolean > Boolean

int > Interger

long >Long

short >Short

byte>Byte

char> Character

float >Float

double> Double

jdk可以对这个封装进行自动包装/自动拆包,便于我们的日常使用。

 public static void main(String[] args) {
        Integer a = new Integer(3);
        Integer b = 3;                  // 将3自动装箱成Integer类型
        int c = 3;
        System.out.println(a == b);     // false 两个引用没有引用同一对象
        System.out.println(a == c);     // true a自动拆箱成int类型再和c比较
    }

7.String和StringBuffer的区别

String是个不变的字符序列,StringBuffer是个可变的字符序列。

String的对象是不变的,每次添加字符串就会生成一个String对象,每次更改都是改变地址而不是值,这种方式十分浪费内存,不被提倡。

而StringBuffer是可变的,他有自己的缓存区,每次添加字符串就是添加到缓存区里(16),缓存区满了会自动扩充

8. 请你讲讲数组(Array)和列表(ArrayList)的区别?什么时候应该使用Array而不是ArrayList?

答:Array用来储存基本数据类型和对象数据类型的数据,而ArrayList只能存储对象数据类型的数据。Array的大小是固定不变的,而ArrayList的大小不是固定的,可以通过许多方法来动态添加全部,删除全部,迭代器等方法。

如果想保存一个整个程序运行中都不变的数组,可以使用一个全局的数组储存。

如果只是想以数组的方式储存数据,较少进行添加,删除操作,可以使用ArrayList。

如果频繁的添加,删除操作数据,ArrayList就不太合适了。效率太低,建议使用LinkedList。‘

9. 请你解释什么是值传递和引用传递?

值传递是对基本类型而言的,值传递传递的是值,同样改变的只是传递得到的值,原来的值不变。

而引用传递传递的是该值的一个副本,传递的是地址,当传递的值改变的时候,原来被传递的对象的值也会改变。

10. 请你讲讲一个十进制的数在内存中是怎么存的?

以补码的形式储存

2021.11.26

1.什么是HTTP报文?

Http报文就是客户端和服务端之间传送的数据块

2.HTTP报文由哪三部分组成?

HTTP报文由起始行,头部,主体组成,其中起始行是对该报文进行的描述,头部是对报文的属性进行的描述,主体则是数据的内容。

3.HTTP报文分为哪两类?

HTTP报文分为请求报文和响应报文,当客户端给服务器发送请求时,发送的报文就是请求报文,当服务器端給客户端返回请求时,返回的请求就是响应报文。

4.HTTP常见的请求方法有哪些?

常见的请求方式有 get请求:向服务器端发送报文,post请求:向服务器端发送待处理的数据请求,HEAD请求:获取服务器端制定的报文头部信息,PUT请求:向服务器端发送请求并且更改制定的信息,DELETE请求:删除指定的报文数据

5.HTTP的状态码分为哪几类?

100-101(2个)代表有提示信息,200-206:代表请求成功 ,300-305:重定向或者转发 400-415:客户端方出现了错误 500-505 :服务端出现了错误

6.HTTP常见的状态码有哪些?

200请求成功

301和302都代表重定向,但是301代表的是永久性的重定向,而302代表的暂时的重定向,二人虽说都是将地址变成重定向的地址,但是301重定向之后会消除原来的地址,原来的资源会不能访问,浏览器会自动转向新的地址。而302表示的是暂时的重定向,原来的地址资源还在,只是浏览器临时跳转到了新的地址。

401:未授权,一般是请求需要得到服务器授权

403:服务器拒绝了你的请求

404:请求的资源不存在

405:方法禁用,比如该请求只让用post访问,无法用get访问

406:请求和响应的格式不匹配,比如请求报文是application/json形式 而响应报文确是 text/html格式,就回导致不匹配

407:代理服务器没有授权,服务器使用了代理服务器,但是代理服务器没有授权

500:服务器异常

503:服务器可能停机维护或者超载,暂时无法接受请求

7.说一下转发和重定向的区别

首先重定向和转发,是两个端的操作,重定向指的是你访问一个地址,浏览器通过服务器端给的新的地址,加载那个地址的页面,是客户端/浏览器进行的操作。

而转发是服务器端的操作,它是指,当你的浏览器请求到某个页面,服务器端会在内部把这个页面换成另外一个请求的页面,体现的是服务器端的操作。

8.简述下TCP三次握手的过程,并解释采用3次握手建立连接的原因?

QQ截图20211126154413

客户端发送连接请求: syn=1 seq=x

服务端发送响应请求 syn=1 ack=x+1 seq =y 表示服务端准备好了

客户端发送确认的请求 ack=y+1 seq=x+1 客户端确认是刚才自己发的消息,且消息有效

那为什么需要三次握手呢?其实就像客户端的第二次握手我描述的那样是为了判断消息有效。

试想这样一个场景:客户端在很久很久之前发送了一个请求,但是由于网络因素或者是其他因素,他没有及时被服务端收到,也没有消失,而是暂时发送不到服务端了,而此时客户端没有收到服务端的回信,又发了一次,这次网速快,三次握手成功了。

更巧的是,成功之后,滞留的之前的客户端发的消息发出去了,然后被服务端收到了,服务端又发了一个确认消息,但客户端这次没有确认。

服务端就会立马知道----啊啊! 这是滞留的消息。

这就是三次握手的作用。

QQ截图20211126160056

答:四次挥手的过程是:

客户端发送一个FIN报文告诉服务器,我要停止对你发送数据了

服务器端发送收到

服务器端发送一个FIN报文,告诉客户端,我也要停止对你发送数据了。

客户端回答收到

为什么要四次挥手呢?因为客户端和服务端的连接是双向的,客户端向服务器端发送FIN只是段龛客户端到服务器的连接,服务器还可以向客户端发送数据,此时只是单向断开,只有服务器再次发送FIN报文的时候才是双向断开。

9.什么是https?

https是基于http/ssl/tls通过密文传输信息,保证了用户信息的安全。

10.什么是2MSL?

先说一下什么是MSL,MSL指的是最大的生存时间。指定是包本身有一个最大的生存时间,超过这个时间,包就会消失。

2MSL指的就是2倍的最大生存时间,当服务器发送完第三次挥手请求后需要的得到客户端的回应的时间是2MSL超过这个时间就需要客户端重新发送FIN请求

2021.11.28

1.请简述https是如何保住信息安全的。

https采用的是非对称加密以及对称加密混合的加密方式加密来保证用户安全的。他比http协议多了一层加密层(SSL/TSL)

要详细说明这个,我们要从不加密开始说起。

QQ截图20211127191607

如果不加密的话,浏览器和服务器的交流,很容易就会被第三方拦截,然后信息被偷看/篡改你的信息。而且也很容易被钓鱼网站拦截,你也没办法确认你连接的是否是你想要的服务器。

于是,人们便想到对数据加密。

最开始使用的是对称加密,也就是服务器和浏览都有一个钥匙,能通过这个钥匙加密和解密双方通信中的文件。来实现加密信息。这种方法信息传递的速度快。

QQ截图20211127191607

但这种方法存在问题:如果服务器和很多浏览器同时建立连接,那么服务器就需要储存多把钥匙,此时便十分的不便捷。

于是,就有了第二种加密方式,非对称加密

QQ截图20211127191930

非对称加密指的是,服务器方有两个属于自己的公钥和私钥,公钥上锁的箱子只能用私钥打开,服务器将公钥给了浏览器端。

浏览器用公钥将明文放进去再上锁,发给服务器,服务器用私钥再打开。

这就是非对称加密,解决了上面的问题,但是速率减慢。(私钥解公钥加密的文件速度很慢,随着明文的增大,传输速率慢)

于是,我们将对称性加密和非对称性加密结合起来。

QQ截图20211127192133

还是,服务器方给浏览器发送一个公钥。此时,浏览将生成一个随机公钥(Randkey(很小)),浏览器方将Randkey用服务器的公钥加密,服务器再用自己的私钥解开,(因为Randkey很小所以速度很快)。然后浏览器再用Randkey加密明文,发送给服务器方。服务器方再用Randkey解密,得到明文。

这样,速度就快了许多。

但仅仅是这样,仍存在问题。

第三方可以在中间拦截,将自己的公钥给浏览器,自己的随机公钥给服务器。

QQ截图20211127192234

此时就需要有人证明服务器的身份。

​ CA机构因此而生。

QQ截图20211127192556

服务器不再需要将公钥给发给浏览器,而是直接将信息发给CA机构。然后请求CA机构颁发一个https证书给自己。

因为普通的浏览器都认识几个CA机构,拥有他们保证的https协议网站的信息,此时只需要把公钥的信息和CA机构的核对,就知道是不是真的我们要访问的服务器了。

这就是https保证我们访问安全的方法。

公开秘钥加密算法:RSA

共享秘钥加密算法:DES, AES

2.常见的WEB攻击?

sql注入,通过一些特殊字符改变sql的语义,比如:select * from A where b = 字符串,当前语义找到A表中b字段为某个字符串的记录,如果字符串是1 or 1=1,那么语义就变成了全表查询。

当前可以通过预编译的方式防止sql注入攻击,如上面的例子,预编译之后sql语义会固定,1 or 1=1 会被当做一串字符串去和b字段进行匹配。

xss, 脚本注入,通过修改前端代码来进攻,但后端做好强校验就好了

c. csrf, 跨站式请求伪造。先正常登入A网站,浏览器存放了登入态。用户又打开了个B网站,让后B网站利用用户的登入态去访问A网站,冒充用户进行操作。

预防方式:校验请求头中的refer属性,登入态的校验使用token来进行,通过哦请求头进行传递。

3.什么是http?

http是一种超文本数据的传输协议,与https相比,它少了ssl/tsl的那层加密,是一种不加密传输文本的协议,较为不安全。

5.StringBuilder和StringBuffer&String的区别,以及它的基本用法

StringBuilder在java5中引入,算的上是一个StringBuffer的一个用于单线程的版本,StringBuilder用于拼接字符串,用法跟StringBuffer差不多,都是创建一个字符缓存区,不用像String一样每增加一个字符就创建一个对象。

而相对于StringBuffer来说,StringBuilder的优势在于单线程的性能弊StringBuilder要高了不少。而StringBuffer虽然效率比StringBuilder略低,但是,更适用于多线程。两者各有应用的领域吧。底层实现上的话,StringBuffer其实就是比StringBuilder多了Synchronized修饰符。

至于用法,跟StringBuffer大同小异,这里就拍下java核心技术书里所写:

718172708EC73D54658853F2B59C1D68

B66F7DE6B3605B298767126B2D00DF13

2021.11.29----java基础

1.请你解释为什么会出现4.0-3.6=0.40000001这种现象?

答:这是因为二进制数没办法准确的表示十进制数的数,十进制的小数在转化为二进制是会有误差,导致该运算结果的诞生。

2.请你说说Lamda表达式的优缺点。

答:优点:简洁,便于书写,应用的越来越多,将会影响未来的编码形式。

​ 缺点:效率没有传统的直接写函数或是for的效率高,而且代码的可读性降低。难以调试

3. 请你说明符号“==”比较的是什么?

答:分为两种情况,在对象对比时,他比较的是引用的地址,而比较的如果是基本数据类型,那他比较的就是基本数据类型的值。

4.请你解释Object若不重写hashCode()的话,hashCode()如何计算出来的?

答:hashcode的方法是本地方法,是由c++实现的该方法返回的是对象的内存地址。

public final native hashcode()

5.为什么重写equals()就一定要重写hashCode()方法?

答: 重写equals()就一定要重写hashCode()方法是为方便java.util.Map包下得HashMap和HashSet的添加操作。

HashMap,HashSet集合添加元素时要进行两次检查,检查hashcode是否相等再检查equals是否相等,两个都不相等才能添加这个元素,保证元素不重复。

我们再回到Object类说起,Objiecr类的equals()是对两个对象的内存地址进行判断,hashcode()也只是返回对象的内存地址。

我们创建两个不同的对象,让他们的值一样,因为它们两个的内存地址不同,所以HashMap()还是默认能添加这两个的。

但就数据意义来看,它们就是相同的。所以添加一个就行了。

	Student s1 = new Student("lisi",12);
	Student s2 = new Student("lisi",12);

这有时候原有的不能满足我们的要求,我们需要重写,使得equals()和hashcode()都判定这两个对象相同。

这就我们去重写。

@Override
public int hashCode() {
	final int prime = 31;
	int result = 1;
	result = prime * result + ((name == null) ? 0 : name.hashCode());
	return result;
}
@Override
public boolean equals(Object obj) {
	if (this == obj)
		return true;
	if (obj == null)
		return false;
	if (getClass() != obj.getClass())
		return false;
	Student other = (Student) obj;
	if (name == null) {
		if (other.name != null)
			return false;
	} else if (!name.equals(other.name))
		return false;
	return true;
}

所以,不能只改动euqals(),也要改动hashcode来让两个都判定一致。

同时这样重写也能提高效率。

因为,hashcode()和equals()遵循以下规则:

如果两个对象相同(即用equals比较返回true),那么它们的hashCode值一定要相同!!!

如果两个对象的hashCode不同,那么他们肯定不同(即用equals比较返回false)

所以,都重写后有时候只需要判断hashCode一次就可以了。

小补充:String对equals和hashcode的重写:

String中的equals

1.同一对象 (true)

2.同一类型&&长度相等&&值相等 ( true)

2021.11.30

1.请你介绍一下map的分类和常见的情况

java为数据结构中的映射定义了一个接口java.util.Map;它有四个实现类,分别是HashMap Hashtable LinkedHashMap 和TreeMap.

Map主要用于存储健值对,根据键得到值,因此不允许键重复(重复了覆盖了),但允许值重复

Hashmap (3)是一个最常用的Map,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度,

  • 关于遍历:遍历时,取得数据的顺序是完全随机的。
  • 关于null:HashMap最多只允许一条记录的键为Null;允许多条记录的值为 Null;
  • 不支持线程同步,线程不安全:HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致。如果需要同步,
  • 可以用锁来解决这个问题,或者ConcurrentHashMap:可以用 Collections的synchronizedMap方法使HashMap具有同步的能力,或者使用ConcurrentHashMap。

Hashtable(2)与 HashMap类似,它继承自Dictionary类,不同的是:它不允许记录的键或者值为空;

  • 它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了 Hashtable在写入时会比较慢。

LinkedHashMap 是HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.也可以在构造时用带参数,按照应用次数排序。

  • 在遍历的时候会比HashMap慢,不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比 LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关

TreeMap实现SortMap接口**,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器**,当用Iterator 遍历TreeMap时,得到的记录是排过序的。

一般情况下,我们用的最多的是HashMap,在Map 中插入、删除和定位元素,HashMap 是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。如果需要输出的顺序和输入的相同,那么用LinkedHashMap 可以实现,它还可以按读取顺序来排列.

2.请你讲讲Java里面的final关键字是怎么用的?

思考方法如下

  • final修饰一个类时会怎么样

final修饰一个类时,表示这个类没办法被其他类继承,类中的成员可以根据需要用final修饰,但被final修饰的类里的方法默认是被final修饰的。

  • 使用final方法的原因有什么

第一个原因 final修饰的类不能被继承,它的方法不能被重写,所以便相当于方法被锁定了,无法重写。

第二个原因,是因为final方法转换为内嵌调用,以便于提高效率,但现在,当数据量过于庞大时,这个的作用就微乎其微。

  • 基本数据类型和引用类型的变量加上final修饰分别会怎么样

同时,一个基本数据类型被final修饰,值便不能改变,一个引用数据类型被final修饰,那么对它初始化候就不能指向另外一个对象。

3.请你谈谈关于Synchronized和lock

Synchronized时java的关键字,你可用它来修饰一个方法或者是一个代码块,保证一定时间里只有一个线程能使用它。

  • 与Synchronized相比,lock是一个接口而Synchronized时java里的内置的语言表示。
  • Synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁的发生,而lock不会,lock需要在finally代码块里释放锁。
  • lock可以让等待锁的线程等待中断,而Synchronized不行,等待的进程会一直等待,无法响应中断。
  • lock可以知道有没有获取成功锁,但Synchronized不能。

2021.12.1

1.Hibernate Query接口 iterate()和list()方法的区别

答:

  • 第一个区别:对于缓存的利用

    list():对于缓存只存不读,如果读取的数据较多的话,读取的效率比较慢。它只能在开启缓存查询时进行缓存查询。

    iterate():不用开启缓存查询,默认可以读写一级缓存和二级缓存,读取数据较多的话,速度比较快。

  • 第二个区别:读取一个对象的速度

    list():一次获取一个对象完整属性值,速度较慢,但获取的全面

    iterate():一次只获取对象的部分属性值,先获取主键信息,然后再逐步获取,list获取代理对象有时候只需要1次,而iterate()获取对象需要N+1次

2.抽象类和接口的区别:

被abstract修饰的类即为抽象类,抽象类里的方法可以抽象方法或者非抽象方法,抽象方法具体的实现要交给继承他的函数,或则主函数里声明对象然后在具体代码快里实现。接口和抽象类十分相似,但同时也有许多区别:

  • 一个类只能继承一个抽象类,但可以实现多个接口
  • 抽象类里可以有普通成员变量和静态成员变量,而接口里成员变量默认是被 public static final修饰,且必须被他们修饰。
    • 抽象类里可以有构造方法,普通方法,静态方法,可以用public protected修饰,但接口里的所有方法必须用 public abstract修饰。

2021.12.2

1.请说明Comparable和Comparator接口的作用以及它们的区别。

Java提供了只包含一个compareTo()方法的Comparable接口。这个方法可以个给两个对象排序。具体来说,它返回负数,0,正数来表明输入对象小于,等于,大于已经存在的对象。
Java提供了包含compare()和equals()两个方法的Comparator接口。compare()方法用来给两个输入参数排序,返回负数,0,正数表明第一个参数是小于,等于,大于第二个参数。equals()方法需要一个对象作为参数,它用来决定输入参数是否和comparator相等。只有当输入参数也是一个comparator并且输入参数和当前comparator的排序结果是相同的时候,这个方法才返回true。

2.请列举你所知道的Object类的方法并简要说明。

Object()默认构造方法。clone() 创建并返回此对象的一个副本。equals(Object obj) 指示某个其他对象是否与此对象“相等”。finalize()当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。getClass()返回一个对象的运行时类。hashCode()返回该对象的哈希码值。 notify()唤醒在此对象监视器上等待的单个线程。 notifyAll()唤醒在此对象监视器上等待的所有线程。toString()返回该对象的字符串表示。wait()导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。wait(long timeout)导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量。wait(long timeout, int nanos) 导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。

3. 请解释一下extends 和super 泛型限定符

2021.12.3

请列举你所知道的Object类的方法并简要说明。

Object()默认构造方法:

  • clone() 创建并返回此对象的一个副本。
  • equals(Object obj) 指示某个其他对象是否与此对象“相等”。
  • finalize()当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
  • getClass()返回一个对象的运行时类。
  • hashCode()返回该对象的哈希码值。
  • notify()唤醒在此对象监视器上等待的单个线程。
  • notifyAll()唤醒在此对象监视器上等待的所有线程。toString()返回该对象的字符串表示。
  • wait()导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。
  • wait(long timeout)导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量。
  • wait(long timeout, int nanos) 导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。

12.4

1.请说明List、Map、Set三个接口存取元素时,各有什么特点?

  • list在存储元素时,可以允许有重复元素
  • Map在存储数据的时候,在key-value模式下允许一对一 或者一对多
  • set在存储元素的时候,不允许有内存地址相同的元素
  • Set和Map都有基于哈希储存和排序树的两种储存方式,哈希表的时间复杂度O(1) 基于排序树会根据键值来删除和添加元素,以达到去重的目的。

2.阐述ArrayList、Vector、LinkedList的存储性能和特性

ArrayList和vector 都是使用数组的方式来储存元素,数组储存元素,内存消耗相对于链表更大,插入元素的效率相对于链表比较慢,但是遍历的速度相对于链表比更快。

而LinkedList是基于双向链表实现的,内存的利用率比较大,插入元素的速率比数组快,遍历的速度慢。

而同样是用数组来储存元素,ArrayList的速度和性能应该是远超Vector的,Vector作为java的遗留容器,已经不推荐使用。但是Vector是线程安全的,在涉及多线程的场景,优先使用Vector。但是ArrayList的对象可以通过Collections方法来将他转换成线程安全的容器后使用。

3.请你说明HashMap和Hashtable的区别?

HashMap和Hashtable都继承了Map接口,它们具有以下特点:

  • HashMap不是线程安全的,它适用于单线程,但是不适用于多线程,而Hashtable可以用于多线程。
  • HashMap允许键值和值都为null,而Hashtable不允许
  • Hashtable一般被认为是一个遗留的类

4.请说说快速失败(fail-fast)和安全失败(fail-safe)的区别

想了解什么是快速失败(fail-fast)和安全失败(fail-safe),我们就需要先理解什么是失败?

这个问题中的失败指的是,遍历集合对象的时候,如果集合对象中途被其他线程更改就会引发失败。抛出Concurrent Modification Exception的异常。

这个的实现原理就是,集合实例会有一个modCount 变量,它的默认值是expectedmodCount,每次集合在遍历元素的时候,都会检查是否这个变量还是默认值,是的话继续遍历,不是的话抛出异常。所以引起了失败,这个失败就是快速失败。在java.util包下的类都是快速失败

那什么是安全失败呢?

这是java.util.concurrent包下的新机制。

用这个包下的容器的时候,迭代器会优先拷贝这个容器的内容,然后在拷贝的内容里遍历,这样,即使有线程改动了容器里的内容,也不会影响遍历结果。

5.请你说说Iterator和ListIterator的区别?

Iterator和ListIterator的区别是:
Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。
Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。
ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。

6.请简单说明一下什么是迭代器?

Iterator提供了统一遍历操作集合元素的统一接口, Collection接口实现Iterable接口,
每个集合都通过实现Iterable接口中iterator()方法返回Iterator接口的实例, 然后对集合的元素进行迭代操作.
有一点需要注意的是:在迭代元素的时候不能通过集合的方法删除元素, 否则会抛出ConcurrentModificationException 异常. 但是可以通过Iterator接口中的remove()方法进行删除.

2021.12.6

1. 请解释为什么集合类没有实现Cloneable和Serializable接口?

答:克隆和序列化是根据语义和含义来定义具体的实现的,所以需要实现类根据需求自己实现。

序列化指的是把java对象转化为字节序列的过程,而反序列化指的是把字节序类变成java对象的过程。

序列化的优先:

  • 将对象转化为字节序列储存到硬盘里,当jvm停机后,被序列化的对象还会储存在磁盘里,等待着下一次jvm的启动,把字节序列转化成java对象。
  • 序列化也更方便在网络间传输
  • 可以在进程间传递对象

2.请说明Java集合类框架的基本接口有哪些?

java的集合框架里有以下接口:

  • Collection:可以代表一组对象每个对象都是它的元素
  • Set:不能含有重复的元素的Collection
  • List:有顺序的Collection,元素可以重复
  • Map:代表映射,不能有重复的键值

图解—散列表(hash表)的基本原理以及hash冲突(碰撞)–易懂版

散列表为什么诞生,它用于做什么?

先说说数组:数组的优点是查找比较快,但是添加和删除效率比较低。

再说说链表:链表的优点是添加和删除效率比较快(相对于数组),但是遍历需要一个指针从头节点往后找。

两者都各有优点和缺点,那么有没有一种方法,既可以添加和删除比较快,查找元素也比较快呢?

于是,便引出了我们今天的主角----散列表(hash表)。

其实散列表在java里还是比较常见的,HashMap就实现了散列表。

散列表如何实现

举个例子:我们创建一个数组将它成为散列表,我们将它的长度设置为11,它的索引就是从0-10。

我们先在要向它里面插入以下几个数据:4 8 9 16

散列表可以用数组来实现。如图所示:

未命名文件

如上图我们依次将这些数对 12取余,将这些数添加到对应的关键字里。(当然除了取余还有很多种方法求出关键数,这里我们就用取余法)

散列冲突及解决

但是当我们添加16时,我们发现,16和4在散列表的位置冲突了,我们必须给16安排到别的位置去。

有以下方法解决:

1.开放定址法(再散列法): 

基本思想:当关键字key的哈希地址p=H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi ,将相应元素存入其中。 这种方法有一个通用的再散列函数形式: 

            Hi=(H(key)+di)% m   i=1,2,…,n

其中H(key)为哈希函数,m 为表长,di称为增量序列。增量序列的取值方式不同,相应的再散列方式也不同。 

1.线性探测再散列:

dii=1,2,3,…,m-1         冲突发生时,顺序查看表中下一单元,直到找出一个空单元或查遍全表。

2.二次探测再散列:

di=12,-12,22,-22,…,k2,-k2    ( k<=m/2 )         冲突发生时,在表的左右进行跳跃式探测,比较灵活。

3.伪随机探测再散列:

di=伪随机数序列。  具体实现时,应建立一个伪随机数发生器,(如i=(i+p) % m),并给定一个随机数做起点。

上面的方法不过过多赘述了,接下来我们来说一个经常使用的方法,也是hashmap(1.7)的散列表的创建形式:

数组+链表法创建散列表

上面我们容易发生hash冲突的原因就是一个关键字只能储存一个元素。那么我们结合数组和链表,每个数组元素储存一个链表不就解决了这个问题嘛。

发生碰撞,只需要把元素放到链表的下一位即可。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DKMNAw76-1643859558665)(https://cdn.jsdelivr.net/gh/EngageRing/images01@master/codeImages/未命名文件(1)].2k9lmpk7mpw0.png)

另外,散列表还可以作为缓存缓存我们的数据,我们实现对员工信息查询的功能,不需要每次都向数据库查询,我们可以把常用数据储存在散列表里,下次查询就可以直接查询散列表。

ConcurrentHashMap原理,jdk7和jdk8版本的区别

jdk7:

  • 数据结构: ReentrantLock+Segment+HashEntry,一个Segment中包含一个类似于HashMap的结构,数组+链表

  • 元素查询:二次hash,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部

  • 锁: Segment分段锁Segment继承了ReentrantLock,锁定操作的Segment,其他的Segment不受影响,并发度为segment个数,可以通过构造函数指定,数组扩容不会影响其他的segment

  • get方法无需加锁,volatile保证,扩容的时候,扩容segment加锁 非扩容的segment可以正常访问,数组本身用volatile修饰,读操作不会读到脏数据

  • 1.7 分段加锁 只锁住一个segment segment有多少个就允许多少个segment并发操作

jdk8:

  • 数据结构: synchronized+CAS+Node+红黑树,Node的val和next都用volatile修饰,保证可见性查找,替换,赋值操作都使用CAS
  • 锁:锁链表的head节点,不影响其他元素的读写,锁粒度更细,效率更高,扩容时,阻塞所有的读写操作、并发扩容
  • 读操作无锁:
  • Node的val和next使用volatile修饰,读写线程对该变量互相可见数组用volatile修饰,保证扩容时被读线程感知

concurrentHashmap 1.8 扩容的时候允许多个线程一起扩容

1.8由于sy的性能得到提升,currenthash不再使用reentrantlock 而是使用syn+cas锁

读操作依旧没有锁

CopyOnWriteArrayList的底层原理是怎样的

1.首先CopyOnWriteArraylist内部也是用过数组来实现的,在向CopyOnWriteArrayLlist添加元素时,会复制一个新的数组
写操作在新数组上进行,读操作在原数组上进行
2.并且,写操作会加锁,防止出现并发写入丢失数据的问题

3.写操作结束之后会把原数组指向新数组
4.CopyOnWriteArrayList允许在写操作时来读取数据,大大提高了读的性能,因此适合读多写少的应用场景,但是CopyOnWriteArrayList会比较占内存,同时可能读到的数据不是实时最新的数据,所以不适合实时性要求很高的场景

final

  • 基本使用

修饰类:表示类不可被继承

修饰方法:表示方法不可被子类覆盖,但是可以重载

修饰变量:表示变量—旦被赋值就不可以更改它的值。

修饰的变量是基本数据类型的话,不可以改变他的值。而修饰引用类型的话不可以改变它的引用,但是可以改变它引用对象的值。

QQ截图20220106150322

  • 修饰类变量或者局部变量时

(1)如果final修饰的是类变量,只能在静态初始化块中指定初始值或者声明该类变量时指定初始值。

​ 如果final修饰的是成员变量,可以在非静态初始化块、声明该变量或者构造器中执行初始值。

(2)修饰局部变量

使用前一定要赋值(可以先分配空间再赋值),且复制完无法修改。

QQ截图20220106150352

HashMap和HashTable有什么区别?其底层实现是什么?

区别︰
(1) HashMap方法没有synchronized修饰,线程非安全,HashTable线程安全;

(2) HashMap允许key和value为null,而HashTable不允许

⒉.底层实现:数组+链表实现

  • jdk8开始链表高度到8、数组长度超过64,链表转变为红黑树,元素以内部类Node节点存在·计算key的hash值,二次hash然后对数组长度取模,对应到数组下标,

  • 如果没有产生hash冲突(下标位置没有元素),则直接创建Node存入数组,

  • 如果产生hash冲突,先进行equal比较,相同则取代该元素,不同,则判断链表高度插入链表,链表高度达到8,并且数组长度到64则转变为红黑树,长度低于6则将红黑树转回链表

  • key为null,存在下标0的位置

Java中的异常体系

Java中的所有异常都来自顶级父类Throwable。

Throwable下有两个子类Exception和Errqr。

  • Error是程序无法处理的错误,一旦出现这个错误,则程序将被迫停止运行。
  • Exception不会导致程序停止,又分为两个部分RunTimeException运行时异常和CheckedException检查异常。RunTimeException常常发生在程序运行过程中,会导致程序当前线程执行失败。CheckedException常常发生在程序编译过程中,会导致程序编译不通过。

List和Set的区别

  • List:有序,按对象进入的顺序保存对象,可重复,允许多个Null元素对象,可以使用lterator取出所有元素,在逐—遍历,还可以使用get(int index)获取指定下表的元素
  • Set:无序,不可重复,最多允许有一个Null元素对象,取元素时只能用lterator接口取得所有元素,在逐一遍历各个元素

Jdk1.7到Jdk1.8 HashMap发生了什么变化(底层)?

  • 1.7中底层是数组+链表,1.8中底层是数组+链表+红黑树,加红黑树的目的是提高HashMap插入和查询整体效率

  • 1.7中链表插入使用的是头插法,1.8中链表插入使用的是尾插法,因为1.8中插入key和lvalue时需要判断链表元素个数,所以需要遍历俳表统计铤表元素个数,所以正好就直接使用尾插法

  • 1.7中哈希算法比较复杂,存在各种右移与异或运算,1.8中进行了简化,因为负载的哈希算法的目的就是提高散列性,来提供HshMap的整体效率,而1.8中新增了红黑树,所以可以适当的简化哈希算法,节省CPU资源

Jdk1.7到Jdk1.8 java虚拟机发生了什么变化?

1.7中存在永久代,1.8中没有永久代,替换它的是元空间,元空间所占的内存不是在虚拟机内部,而是本地内存空间,这么做的原因是,不管是永久代还是元空间,他们都是方法区的具体实现,之所以元空间所占的内存改成本地内存,官方的说法是为了和Rockit统一,不过额外还有一些原因,比如方法区所存储的类信息通常是比较难确定的,所以对于方法区的大小是比较难指定的,太小了容易出现方法区溢出,太大了又会占用了太多虚拟机的内存空间,而转移到本地内存后则不会影响虚拟机所占用的内存

说一下hashmap的put方法

  • 根据Key通过哈希算法与与运算得出数组下标
  • 如果数组下标位置元素为空,则将key和value封装为Entry对象(JDK1.7中是Entry对象,JDK1.8中是Node对象)并放入该位置
  • 如果数组下标位置元素不为空,则要分情况讨论
    • 如果是JDK1.7,则先判断是否需要扩容,如果要扩容就进行扩容,如果不用扩容就生成Entry对象,并使用头插法添加到当前位置的链表中
    • 如果是JDK1.8,则会先判断当前位置上的Node的类型,看是红黑树Node,还是链表Node
      • 如果是红黑树Node,则将key和value封装为一个红黑树节点并添加到红黑树中去,在这个过程中会判断红黑树中是否存在当前key,如果存在则更新value
      • 如果此位置上的Node对象是链表节点,则将key和value封装为一个链表Node并通过尾插法插入到链表的最后位置去,因为是尾插法,所以需要遍历铤表,在遍历链表的过程中会判断是否存在当前key,如果存在则更新value,当遍历完链表后,将新链表Node插入到链表中,插入到链表后,会看当前链表的节点个数,如果超过了8,那么则会将该链表转成红黑树
      • 将key和value封装位Node插入到链表或红黑树中后,再判断是否需要进行扩容,如果需要就扩容,如果不需要就介绍PUT方法

抽象类与接口的区别

接口和抽象类的区别

基本区别

我们要从 普通函数,成员变量,继承与实现三个角度考虑。

  • 抽象类可以存在普通成员函数,而接口中只能存在public abstract方法。
  • 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的。
  • 抽象类只能继承一个,接口可以实现多个。

更深入来讲

接口是明确了一种相似的行为(因飞行行为将鸟和飞机联想,而鸟不是飞机),而抽象类是一种继承(是的关系 飞机和战斗机的关系),求同存异。

  • 接口的设计目的,是对类的行为进行约束(更准确的说是一种"有"约束,因为接口不能规定类不可以有什么行为),也就是提供一种机制,可以强制要求不同的类具有相同的行为。它只约束了行为的有无,但不对如何实现行为进行限制。
  • **而抽象类的设计目的,是代码复用。**当不同的类具有某些相同的行为(记为行为集合A),且其中一部分行为的实现方式一致时(A的非真子集,记为B),可以让这些类都派生于一个抽象类。在这个抽象类中实现了B,避免让所有的子类来实现B,这就达到了代码复用的目的。而A减B的部分,留给各个子类自己实现。正是因为A-B在这里没有实现,所以抽象类不允许实例化出来(否则当调用到A-B时,无法执行)。
  • 抽象类是对类本质的抽象,表达的是is a的关系,比如: BMw is a car。抽象类包含并实现子类的通用特性,将子类存在差异化的特性进行抽象,交由子类去实现。
  • 而接口是对行为的抽象,表达的是like a的关系。比如: Bird like a Aircraft(像飞行器一样可以飞),但其本质上is a Bird。接口的核心是定义行为,即实现类可以做什么,至于实现类主体是谁、是如何实现的,接口并不关心。
  • 使用场景:当你关注一个事物的本质的时候,用抽象类;当你关注一个操作的时候,用接口。
  • 抽象类的功能要远超过接口,但是,定义抽象类的代价高。因为高级语言来说(从实际设计上来说也是每个类只能继承一个类。在这个类中,你必须继承或编写出其所有子类的所有共性。虽然接口在功能上会弱化许多,但是它只是针对一个动作的描述。而且你可以在一个类中同时实现多个接口。在设计阶段会降低难度

泛型中extends和super的区别

泛型中extends和super的区别:

  • <? extends T>表示包括T在内的任何T的子类
  • <? super T>表示包括T在内的任何T的父类

谈一下你对面向对象的理解

相对面向过程而言,面向对象这种编程的方式更具有扩展性,易于扩展和维护。相对于面向过程,面向对象这种编程形式更低耦合。

而且面向对象比面向过程更为突出的在这三个方面。

QQ截图20220109172637

QQ截图20220109154821

重写和重载的区别

  • 重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,但是一旦方法返回值或者修饰符设置为不同,那么参数一定不能相同,否则会报错。

  • 重写:发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;

    如果父类方法访问修饰符为private则子类就不能重写该方法。

pub1ic int add(int a, string b)
pub1ic string add(int a,string b)//编译报错

redis相关面试题

1.分别描述下RDB和AOF机制

RDB: Redis DataBase

在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

  • 优点:

1、整个Redis数据库将只包含—个文件dump.rdb,方便持久化。

2、容灾性好,方便备份。

3、性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所以是IO最大化。使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了redis 的高性能

4.相对于数据集大时,比AOF的启动效率更高。

  • 缺点:

1、数据安全性低。RDB是间隔一段时间进行持久化,如果持久化之间redis发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候)

2、由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。

AOF: Append Only File

以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录

  • 优点:

1、数据安全,Redis中提供了3种同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。。

2、通过append模式写文件,即使中途服务器宕机也不会破坏已经存在的内容,可以通过redis-check-aof 工具解决数据一致性问题。

3、AOF机制的rewrite模式。定期对AOF文件进行重写,以达到压缩的目的

  • 缺点:

1、AOF文件比 RDB文件大,且恢复速度慢。

2、数据集大的时候,比 rdb启动效率低。

3、运行效率没有RDB高

总结

  • AOF文件比RDB更新频率高,优先使用AOF还原数据。AOF比RDB更安全也更大
  • RDB性能比AOF好
  • 如果两个都配了优先加载AOF
  • RDB主要是对数据进行备份,而AOF主要对命令进行备份,备份之前会进行一次压缩提高效率

2.redis为什么是单线程还速度快?

Redis基于Reactor模式开发了网络事件处理器,这个处理器叫做文件事件处理器file event handler。这个文件事件处理器,它是单线程的,所以Redis 才叫做单线程的模型,它采用I0多路复用机制来同时监听多个Socket,根据Socket上的事件类型来选择对应的事件处理器来处理这个事件。可以实现高性能的网络通信模型,又可以跟内部其他单线程的模块进行对接,保证了Redis 内部的线程模型的简单性。
文件事件处理器的结构包含4个部分:多个Socket、l0多路复用程序、文件事件分派器以及事件处理器(命令请求处理器、命令回复处理器、连接应答处理器等)。
多个Socket 可能并发的产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个
Socket,会将Socket放入一个队列中排队,每次从队列中取出一个Socket给事件分派器,事件分派器把Socket给对应的事件处理器。
然后一个Socket的事件处理完之后,IO多路复用程序才会将队列中的下一个Socket给事件分派器。文件事件分派器会根据每个Socket当前产生的事件,来选择对应的事件处理器来处理。

单线程快的原因:

  • 纯内存操作
  • 核心是基于非阻塞的IO多路复用机制
  • 单线程反而避免了多线程的频繁上下文切换带来的性能问题

3.redis的持久化机制

RDB: Redis DataBase将某一个时刻的内存快照(Snapshot),以二进制的方式写入磁盘。

手动触发:

save命令,使Redis处于阻塞状态,直到RDB持久化完成,才会响应其他客户端发来的命令,所以在生产环境—定要慎用

bgsave命令,fork出一个子进程执行持久化,主进程只在fork过程中有短暂的阻塞,子进程创建之后主进程就可以响应客户端请求了

(为了保证bgsave持久化操作中的fork进程的数据是当前快照时的内存数据,不产生脏数据,主进程在fork进程产生后再进行写操作时,会将当前内存做一个副本,在副本里进行操作)

自动触发:

save m n :在m秒内,如果有n个键发生改变,则自动触发持久化,通过bgsave执行,如果设置多个、只要满足其一就会触发,配置文件有默认配置(可以注释掉)

flushall:用于清空redis所有的数据库,flushdb清空当前redis所在库数据(默认是0号数据库),会清空RDB文件,同时也会生成dump.rdb、内容为空

主从同步:全量同步时会自动触发bgsave命令,生成rdb发送给从节点

4.Redis的过期键的删除策略

Redis是key-value数据库,我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。

redis设置key的过期时间:1、EXPIRE。2 SETEX

Redis中同时使用了惰性过期定期过期两种过期策略。

  1. 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
  2. 定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

(expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)

实现原理:
1、定期删除:每隔一段时间,执行一次删除过期key的操作。
2、懒汉式删除:当使用get、getset等指令去获取数据时,判断key是否过期。过期后,就先把key删除,再执行后面的操作。
Redis是将两种方式结合来使用。
懒汉式删除
定期删除:平衡执行频率和执行时长。
定期删除时会遍历每个database(默认16个),检查当前库中指定个数的key(默认是20个)。随机抽查这些key,如果有过期的,就删除。
程序中有一个全局变量记录到秒到了哪个数据库。

5.Redis分布式锁底层是如何实现的?

1.首先利用setnx来保证:如果key不存在才能获取到锁,如果key存在,则获取不到锁

2.然后还要利用lua脚本来保证多个redis操作的原子性

3.同时还要考虑到锁过期,所以需要额外的一个看门狗定时任务来监听锁是否需要续约

4.同时还要考虑到redis节点挂掉后的情况,所以需要采用红锁的方式来同时向N/2+1个节点申请锁,都申请到了才证明获取锁成功,这样就算其中某个redis节点挂掉了,锁也不能被其他客户端获取到

6.Redis和Mysql如何保证数据一致

1.先更新Mysql,再更新Redis,如果更新Redis失败,可能仍然不一致

2,先删除Redis缓存数据,再更新Mysql,再次查询的时候在将数据添加到缓存中,这种方案能解决1方案的问题,但是在高并发<下性能较低,而且仍然会出现数据不一致的问题,比如线程1删除了Redis缓存数据,正在更新Mysql,此时另外一个查询再查询,那么就会把Mysql中老数据又查到Redis中

3,延时双删,步骤是:先删除Redis缓存数据,再更新Mysql,延迟几百毫秒再删除Redis缓存数据,这样就算在更新Mysql时,有其他线程读了Mysql,把老数据读到了Redis中,那么也会被删除掉,从而把数据保持一致(主要用这个)

7.redis集群方案

主从
哨兵模式:

sentinel,哨兵是redis集群中非常重要的一个组件,主要有以下功能:

  • 集群监控:负责监控redis master和slave进程是否正常工作。

  • 消息通知:如果某个redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员。

  • 故障转移:如果master node挂掉了,会自动转移到slave node 上。

  • 配置中心:如果故障转移发生了,通知client客户端新的master 地址。

哨兵用于实现redis集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。

  • 故障转移时,判断一个master node是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举
  • 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的
  • 哨兵通常需要3个实例,来保证自己的健壮性。
  • 哨兵+redis 主从的部署架构,是不保证数据零丢失的,只能保证redis集群的高可用性。
  • 对于哨兵+ redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。

Redis Cluster是一种服务端Sharding技术,3.0版本开始正式提供。采用slot(槽)的概念,一共分成16384个槽。将请求发送到任意节点,接收到请求的节点会将查询请求发送到正确的节点上执行

Redis有哪些数据结构?分别有哪些典型的应用场景?

Redis的数据结构有:

1.字符串:可以用来做最简单的数据缓存,可以缓存某个简单的字符串,也可以缓存某个json格式的字符串,Redis分布式锁的实现就利用了这种数据结构,还包括可以实现计数器、Session共享、分布式ID

2.哈希表:可以用来存储一些key-value对,更适合用来存储对象

3.列表: Redis的列表通过命令的组合,既可以当做栈,也可以当做队列来使用,可以用来缓存类似微信公众号、微博等消息流数据

4.集合:和列表类似,也可以存储多个元素,但是不能重复,集合可以进行交集、并集、差集操作,从而可以实现类似我和某人共同关注的人、朋友圈点赞等功能

5.有序集合:集合是无序的,有序集合可以设置顺序,可以用来实现排行榜功能

Math.round(11.5) 等于多少 Math.round(-11.5)等于多少?

答:Math.round(11.5)的返回值是12,Math.round(-11.5)的返回值是-11。四舍五入的原理是在参数上加0.5然后进行下取整

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值