顺丰科技已上岸面经(收集牛客近年来面经)

本人双非本科在秋招入职多益网络,然后本次春招,努努力了一把成功拿到了顺丰科技的offer。
以下为我在牛客上收集到的近年来的面试题目以及答案,希望对大家有用。

顺丰科技

第一篇

用过模糊查询吗?说一说like什么时候可以用索引?

对于前后都有百分号的sql语句 这个语句是不走索引的

EXPLAIN SELECT * FROM `user` WHERE username LIKE '%ptd_%';  //这个语句是不走索引的

对于只有后面使用% 使用到了索引

EXPLAIN SELECT * FROM `user` WHERE username LIKE 'ptd_%';

对于%在前面的, 这个语句也是走全表扫描,没有走索引。

EXPLAIN SELECT * FROM `user` WHERE username LIKE '%ptd_';

综上所述: 当百分号出现在后面的时候 是走索引的。


bean的生命周期?

  1. 实例化 Instantiation
  2. 属性赋值 Populate
  3. 初始化 Initialization
  4. 销毁 Destruction

https://www.jianshu.com/p/1dec08d290c1


数据库的隔离级别,分别会产生什么问题?

  • 读未提交
  • 读已提交
  • 可重复读
  • 串行化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XEm8q75N-1618384899949)(F:\aboutIT\learningFromduoyi\images\image-20210314212016215.png)]


线程和进程之间的区别? 进程和线程

进程

进程是程序的一次执行过程,是一个动态的概念,是程序在执行过程中分配和管理资源的基本单位,每一个进程都有一个自己的地址空间,至少有五种状态:初始态,执行态,等待状态,就绪状态,终止状态。

线程

线程是CPU调度和分派的基本单位,它可以同属于一个进程的其他的线程共享进程所拥有的全部资源。

  • 线程是进程的一部分,一个线程只能属于一个进程,而一个进程可以拥有多个线程,至少有一个线程。

区别:

  1. 进程是一段正在执行的程序,是资源分配的基本单元,而线程是CPU调度的基本单元。
  2. 进程间相互独立进行,进程之间不能共享资源。一个进程至少有一个线程,同一进程的各线程共享整个进程 的资源(寄存器,堆栈,上下文)
  3. 线程的创建和切换的开销比进程小。

如何判断链表是否能成环?

设置两个节点,第一个节点的步长为1。第二个步长为2

while,让两个节点不停的走。

只要两个节点相等了就保证了是有环的。

Node p1 = head;//先都指向头结点
Node p2 = head;
int times = 0;//相遇0次
while(p2!=null&&p2.next!=null)//如果快游标到结尾就退出循环
{
     
	p1=p1.next;//一次走一步
	p2=p2.next.next;//一次走两步
	if(p1==p2)
	{
     
		return isCycle;//相遇即证明有环
	}
}

return noCycle;

线程池参数,如何创建线程池。

ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue) {
     
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
  • corePoolSize: 指定了线程池中线程的数量
  • maximumPoolSize: 指定了线程池中最大的线程数量
  • keepAliveTime:当前线程池数量超过corePoolSize时,多余的空闲线程的存活时间,即多次时间时间内会被销毁
  • unit:KeepAliveTime的单位
  • workQueue:任务队列,被提交但尚未被执行的任务
  • threadFactory:线程工厂,用于创建线程,一般用于默认的即可
  • handler:拒绝策略,当任务太多来不及处理,如何拒绝任务

可以使用ThreadPoolExecutor来创建,也可以使用Executors来创建

Executors来创建的方式

  • ExecutorService Pool = Executors.newSingleThreadExecutor();// 单个线程
  • ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建一个固定大小的线程池
  • ExecutorService threadPool = Executors.newCachedThreadPool();//可伸缩的线程池

说一说ejb的过程。

http://www.blogjava.net/kawaii/archive/2007/02/06/98395.html


手撕二分查找

public class BinarySearch {
     

    // 二分查找,找到并返回下标
    public static int binarySearch(int[] arr, int n) {
     

        //定义一些变量
        int left = 0;
        int right = arr.length - 1;
        int mid = 0;

        while (left <= right) {
     
            // 求中间值
            mid = (left + right) / 2;
            if (arr[mid] == n) {
     
                return mid;
            } else if (arr[mid] <= n) {
     
                left = mid + 1;
            } else {
     
                right = mid - 1;
            }
        }
        // 表示未找到
        return -1;
    }

}

第二篇

一面

服务端的分页怎么做的?

使用的是mybatis plus自带的分页插件pageHelper 实现的是物理分页。

  • 逻辑分页:数据库返回的不是分页结果,而是全部数据,然后再由程序员通过代码获取分页数据,全部读取的List中。用lambda进行筛选操作。
  • 物理分页:在操作数据库的时候直接使用sql语句的limit关键字。直接返回的是分页好的结果。

对于搜索部分的分页,使用的是Elasticsearch的Scroll(游标)滚动API。

当Elasticsearch响应请求时,它必须确定docs的顺序,排列响应结果。如果请求的页数较少(假设每页20个docs), Elasticsearch不会有什么问题,但是如果页数较大时,比如请求第20页,Elasticsearch不得不取出第1页到第20页的所有docs,再去除第1页到第19页的docs,得到第20页的docs。

解决的方法就是使用Scroll。因为Elasticsearch要做一些操作(确定之前页数的docs)为每一次请求,所以,我们可以让Elasticsearch储存这些信息为之后的查询请求。这样做的缺点是,我们不能永远的储存这些信息,因为存储资源是有限的。所以Elasticsearch中可以设定我们需要存储这些信息的时长。

第一次请求筛选条件之后,直接返回一个scroll_id 。下次请求的时候带上这个id。会直接返回对应的筛选条件的下一页的数据。

  • 因为搜索的时候,我们只关心搜索的最匹配结果,而不在乎最后一页的效果。因此我们搜索的时候可以使用滚动API
  • 百度的搜索也是如此。

如何查看sql执行计划?

使用explain

explain  select id from user

在这里插入图片描述

  • id: select标识符,这是select的查询序列号
  • select_type:查询类型
    • SIMPLE: 简单select(不适用union或子查询)
    • PRIMARY: 最外面的SELECT
    • UNION: UNION中的第二个或后面的select语句
    • DEPENDENT UNION :NION中的第二个或后面的SELECT语句,取决于外面的查询
    • UNION RESULT: UNION的结果
    • SUBQUERY: 子查询中的第一个SELECT
    • DEPENDENT SUBQUERY:子查询中的第一个SELECT,取决于外面的查询
    • DERIVED:导出表的SELECT(FROM子句的子查询)
  • table: 输出的行所用的表
  • partitions:如果查询是基于分区表的话,显示查询将访问的分区。
  • type:
    • range:只检索给定范围的行,使用一个索引来选择行。 走了索引
    • index:该联接类型与ALL相同,除了只有索引树被扫描。这通常比ALL快,因为索引文件通常比数据文件小。 走了索引
    • ALL: 对于每个来自于先前的表的行组合,进行完整的表扫描,说明查询就需要优化了。没走索引
  • possible_keys:指出MySQL能使用哪个索引在该表中找到行
  • key: 显示MySQL实际决定使用的键(索引)。如果没有选择索引,键是NULL。
  • key_len: 显示MySQL决定使用的键长度。如果键是NULL,则长度为NULL。在不损失精确性的情况下,长度越短越好
  • ref: 显示使用哪个列或常数与key一起从表中选择行
  • rows: 显示MySQL认为它执行查询时必须检查的行数。多行之间的数据相乘可以估算要处理的行数。
  • filtered: 显示了通过条件过滤出的行数的百分比估计值。

项目中的数据库用的是什么? mysql中有哪些索引?聚集索引和非聚集索引解释下?

项目中大部分用到的数据库是mysql数据库。 (索引创建)

mysql中的索引:

  • 普通索引: 没有任何限制,允许在定义索引的列中插入重复的值和空值

    • create index index_name on table_name (column(length));
      
  • 唯一索引: 索引列的值必须是唯一的,允许有空值,如果是组合索引,列值的组合必须唯一

    • create unique index index_name on table_name (column(length));
      
  • 前缀索引: 截取字段的指定长度前半部分来作为索引。

  • 组合索引: 多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。

    • alter table table_name add index index_name(column,column,column..);
      
  • 主键索引:主键索引是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值,一般是在创建表的时候指定主键,主键默认就是主键索引

    • primary key(column)

聚集索引: 表数据按照索引的顺序来存储的,也就是说索引项的顺序与表中记录的物理顺序一致。叶子节点即存储了真实的数据行,不再有另外单独的数据页。一张表只能创建一个聚集索引,因为真实的物理顺序只有一种

  • InnoDB(聚集索引)的数据文件只有数据结构文件.frm和数据文件.idb 其中.idb存放的数据和索引信息是存放在一起的。

非聚集索引: 表数据存储顺序与索引顺序无关。对于非聚集索引,叶结点包含索引字段值及指向数据页数据行的逻辑指针。,其行数量与表数据量一致。

  • MyISAM(非聚集索引)的索引文件.MYI和数据文件.MYD是分开存储的是相对独立的。

总结

聚簇索引和非聚簇索引的区别是:

聚簇索引(innoDB)的叶子结点就是数据结点

非聚簇索引(myisam)的叶子结点任然是索引文件,知识这个索引文件包含指向对应数据块的指针

对于非聚簇索引来说,每次通过索引检索到所需行号后,还需要通过叶子上的磁盘地址去磁盘内取数据(回行)消耗时间。为了优化这部分回行取数据时间,InnoDB引擎采用了聚簇索引。

聚簇索引,即将数据存入索引叶子页面上。对于Innodb引擎来说,叶子页面不再存改行对应的地址,而是直接存储数据。

这样便避免了回行操作所带来的时间消耗。使得InnoDB在某些查询上比MyISAM还要快!


为什么索引能够加快查询速度?

索引可以将无序内容转换为有序的一个集合,就如同新华字典,如果没有目录,那么查询一个汉字就需要很长时间了。

而且索引的数据结构是二叉树。B+树,相当于二分查找,加快查询速率。

  • 索引可以提高查询速度,但是会降低增删改的速度

B+树需要平衡,如果知识二叉树可能存在链式的结构,这样的话查询的速度也就不存在了。

正因为B+树是一棵平衡树,如果我们要对这棵树增删改的话,那肯定会破坏它的原有结构

要维持平衡树,就必须做额外的工作。导致额外的开销。

写两个sql吧,第一个:将学生按数学成绩由高到低排序

第二个:按语数外三门课的总成绩排序

第一个

select name from T group by match DESC

第二个

select sum(id) from account group by id

如果from子句两个表用“,”隔开,解释下该子句的意思?

DESC:降序 ASC :升序

如果from子句,是两个表用逗号隔开,那就是对两个表做一个笛卡尔集。

线程间同步怎么实现?

  • 通过synchronized
  • 通过Object的wait与notify
  • 通过condition的await和signal
  • 通过辅助类CountDownLatch
  • 通过辅助类CyclicBarrier

sychronized关键词修饰一个类的static方法与普通成员方法,两个线程同时分别调用这两个方法阻塞吗?为什么不阻塞?

static修饰的变量 全局共享的只有一份 所以阻塞

普通成员变量,每个线程特有的,加了synchronized只对当前线程加锁,其他线程操作自己的成员方法。


线程池使用过吗?怎么创建线程池?ThreadPoolExecutor的构造参数?拒绝策略介绍下?默认拒绝策略?

使用过线程池。

  • 减少资源的开销,通过复用线程,降低创建销毁线程造成的消耗。
  • 多个线程并发执行任务,提高系统的响应速度
  • 同一分配,调优和监控线程,提高线程的可管理性

创建线程:

Executors :

  • newSingleThreadExecutor 创建一个的线程的线程池
  • newFixedThreadPool 创建指定对象的线程池
  • newCachedThreadPool 创建可变大小的线程池

ThreadPoolExecutor(int corePoolSize, //核心线程池大小

​ int maximumPoolSize,//最大线程池大小

​ long keepAliveTime,//超时没有人调用就会释放

​ TimeUnit unit,//超时单位

​ BlockingQueue workQueue,//阻塞队列

​ ThreadFactory threadFactory,//线程工厂:创建线程,一般不用动

​ RejectedExecutionHandler handler) {//拒绝策略

拒绝策略

  1. AbortPolicy: 直接抛出异常,阻止系统正常运行 (默认的拒绝策略)
  2. CallerRunsPolicy: 只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。
  3. DiscardOldestPolicy: 丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。
  4. DiscardPolicy: 该策略默默地丢弃无法处理的任务,不予任何处理。如果允许任务丢失,这是最好的一种方案。

中间件使用过哪些?比如:Redis,Dubbo,zookeeper,Elasticsearch 微服务了解吗?

  • Redis

设置过期时间: expire key time(以秒为单位)

setex(String key,int seconds,String value) 字符串独有的方式

ttl key : 查看剩余过期时间

  1. 除了字符串自己独有设置过期时间的方法外,其他方法都需要依靠expire方法来设置过期时间
  2. 如果没有设置过期时间,那缓存就是永不过期
  3. 如果设置了过期时间,之后又想让缓存永不过期,使用persist key

3种过期策略

  • 定时删除
    • 含义: 在设置key的过期时间的同时,为该key创建一个定时器,让定时器key的过期时间来临时,对key进行删除
    • 优点:保证内存被尽快释放
    • 缺点:
      • 若过期key很多,删除这些key会占用很多的cpu时间,在CPU紧张的情况下,CPU不能把所有的时间用来做要紧的事,还需要花时间删除这些key
      • 定时器的创建耗时,若为每一个设置过期时间的key创建一个定时器(将会有大量的定时器产生),性能严重影响
      • 没人用
  • 惰性删除
    • 含义:key过期的时候不删除,每次从数据库获取key的时候去检查是否过期,若过期,则删除,返回null
    • 优点:删除操作只发生在从数据库取出key的时候发生,而且只删除当前key,所以对CPU时间的占用是比较少的,而且此时的删除是已经到了非做不可的地步(如果此时还不删除的话,我们就会获取到了已经过期的key了)
    • 缺点:若大量的key在超出时间后,很久一段时间内,都没有被获取,那么可能发生内存泄漏(无用的垃圾占用了大量的内存)
  • 定期惰性删除
    • 含义:每隔一段时间执行一次删除(在redis.conf配置文件设置hz,1刷新的频率)过期key操作
    • 优点:
      • 通过限制删除操作的时长和频率,来减少删除操作对CPU时间的占用-处理”定时删除“的缺点
      • 定期删除过期key–处理”惰性删除“的缺点
    • 缺点:
      • 在内存友好方面,不如”定时删除“
      • 在CPU时间友好方面,不如”惰性删除“
    • 难点
      • 合理的设置删除操作的执行时长(每次删除执行多长时间)和执行频率(每隔多长时间做一次删除)

Redis的持久化

RDB

  • RDB: 过期key对RDB没有任何影响
    • 从内存数据库持久化到RDB文件
      • 持久化key之前,会检查是否过期,过期的key不能进入RDB文件
  • 从RDB文件恢复数据到内存数据库
    • 数据载入数据库之前,会对key先进行过期检查,如果过期,不导入数据库(主库情况)

AOF

  • AOF:过期key对AOF没有任何影响
    • 从内存数据库持久化数据到AOF文件
      • 当key过期后,还没有被删除,此时进行持久化操作(该key是不会进入aof文件,因为没有发生修改命令)
      • 当key过期后,在发生删除时,程序会向aof文件追加一条del命令(在将来的aof文件恢复的时候该过期的键就会被删掉)
    • AOF重写
      • 重写时,会先判断key是否过期,已过期的key不会重写到aof文件

Elasticsearch

反问

二面

项目中的服务分页如何做的?

使用的是mybatis plus自带的分页插件pageHelper 实现的是物理分页。

  • 逻辑分页:数据库返回的不是分页结果,而是全部数据,然后再由程序员通过代码获取分页数据,全部读取的List中。用lambda进行筛选操作。
  • 物理分页:在操作数据库的时候直接使用sql语句的limit关键字。直接返回的是分页好的结果。

对于搜索部分的分页,使用的是Elasticsearch的Scroll(游标)滚动API。


使用的数据库是Mysql还是Oracle?如何查看sql执行计划? explain命令的执行结果介绍下?索引为什么能加快查询速度?

使用的是mysql。

使用explain

explain  select id from user

在这里插入图片描述

  • id: select标识符,这是select的查询序列号
  • select_type:查询类型
    • SIMPLE: 简单select(不适用union或子查询)
    • PRIMARY: 最外面的SELECT
    • UNION: UNION中的第二个或后面的select语句
    • DEPENDENT UNION :NION中的第二个或后面的SELECT语句,取决于外面的查询
    • UNION RESULT: UNION的结果
    • SUBQUERY: 子查询中的第一个SELECT
    • DEPENDENT SUBQUERY:子查询中的第一个SELECT,取决于外面的查询
    • DERIVED:导出表的SELECT(FROM子句的子查询)
  • table: 输出的行所用的表
  • partitions:如果查询是基于分区表的话,显示查询将访问的分区。
  • type:
    • range:只检索给定范围的行,使用一个索引来选择行。 走了索引
    • index:该联接类型与ALL相同,除了只有索引树被扫描。这通常比ALL快,因为索引文件通常比数据文件小。 走了索引
    • ALL: 对于每个来自于先前的表的行组合,进行完整的表扫描,说明查询就需要优化了。没走索引
  • possible_keys:指出MySQL能使用哪个索引在该表中找到行
  • key: 显示MySQL实际决定使用的键(索引)。如果没有选择索引,键是NULL。
  • key_len: 显示MySQL决定使用的键长度。如果键是NULL,则长度为NULL。在不损失精确性的情况下,长度越短越好
  • ref: 显示使用哪个列或常数与key一起从表中选择行
  • rows: 显示MySQL认为它执行查询时必须检查的行数。多行之间的数据相乘可以估算要处理的行数。
  • filtered: 显示了通过条件过滤出的行数的百分比估计值。

索引可以将无序内容转换为有序的一个集合,就如同新华字典,如果没有目录,那么查询一个汉字就需要很长时间了。

而且索引的数据结构是二叉树。B+树,相当于二分查找,加快查询速率。

  • 索引可以提高查询速度,但是会降低增删改的速度

B+树需要平衡,如果知识二叉树可能存在链式的结构,这样的话查询的速度也就不存在了。

正因为B+树是一棵平衡树,如果我们要对这棵树增删改的话,那肯定会破坏它的原有结构

要维持平衡树,就必须做额外的工作。导致额外的开销。


问几个Java基础吧,Java虚拟机你了解吗?介绍一下,能说多少说多少?假设有一个String str = new String(“Hello World”); 这条语句创建了几个对象,分别在JVM的哪个区域?

大概了解吧。

  • 线程共享的区域:堆,方法区,运行时常量池,
  • 线程私有:虚拟机栈,本地方法栈,程序计数器

讲一下程序计数器的作用

讲一下堆的划分 新生代 老年代 如何转换

讲一下可达性算法


String str = new String(“Hello World”); 可能是一个 也可能是两个

  • 这个语句会创建两个或者一个对象,如果堆中未存在"Hello World"这个字符串的化,执行以上语句会在堆中产生一个"Hello World"的字符串,并且在线程的栈中新建一个str的指针,指向堆中的"Hello World" 以上情况会有两个对象

  • 如果堆中已存在这个字符串的化,则不会新建一个"Hello World"对象,直接在栈中生成一个str指针指向对应的"Hello World"对象,因此新建了一个对象。


假设项目中需要用到线程同步,你会考虑怎么实现?

  • 通过synchronized
  • 通过Object的wait与notify
  • 通过condition的await和signal
  • 通过辅助类CountDownLatch
  • 通过辅助类CyclicBarrier

Java中的锁了解吗?介绍下?什么是乐观锁,悲观锁,在Java中分别有哪些实现类?

悲观锁:synchronized?

乐观锁: juc包下的类


线程池用过吗?如何创建线程池?Executors类可以创建哪些线程池?什么时候会开启核心线程以外的线程?什么时候会用到拒绝策略?

Executors :

  • newSingleThreadExecutor 创建一个的线程的线程池
  • newFixedThreadPool 创建指定对象的线程池
  • newCachedThreadPool 创建可变大小的线程池

开启核心线程以外的线程: 当核心线程满了之后,如果还有额外的线程进入,会先往阻塞队列里面插入,当阻塞队列满了的时候,就会开启核心线程以外的线程。

当执行的线程数大于 最大线程数 + 阻塞队列的时候会用到拒绝策略。


Spring源码看过吗?

AOP 代理模式

IOC 工厂模式

https://blog.csdn.net/qq_37126357/article/details/100689342


最后问个场景题,现在要查看数据库,数据两位2千万行,使用多线程实现,你有什么思路吗?不能重复读取,数据全部读取完之后才进行操作。假设有一个线程查询失败如何处理?

这个有谁懂吗?解释下。具体我不知道怎么讲。


第三篇

一面

项目介绍,项目里Redis的作用?

  • 储存验证码 可以设置失效时间
  • 防止抖动问题,一分钟内只能请求五次
  • 记录键值对 对应文章同个用户只能点赞一次
  • 缓存查询比较慢的数据 表的统计信息
  • 不像对一个接口频繁的操作,可以使用redis做批量

数据库索引?

上面有讲到!!


线程和进程的区别?

进程

进程是程序的一次执行过程,是一个动态的概念,是程序在执行过程中分配和管理资源的基本单位,每一个进程都有一个自己的地址空间,至少有五种状态:初始态,执行态,等待状态,就绪状态,终止状态。

线程

线程是CPU调度和分派的基本单位,它可以同属于一个进程的其他的线程共享进程所拥有的全部资源。

  • 线程是进程的一部分,一个线程只能属于一个进程,而一个进程可以拥有多个线程,至少有一个线程。

区别:

  1. 进程是一段正在执行的程序,是资源分配的基本单元,而线程是CPU调度的基本单元。
  2. 进程间相互独立进行,进程之间不能共享资源。一个进程至少有一个线程,同一进程的各线程共享整个进程 的资源(寄存器,堆栈,上下文)
  3. 线程的创建和切换的开销比进程小。

servlet和jsp的区别?

基本介绍

Servlet:

Servlet 是一种服务器端的Java应用程序,具有独立于平台和协议的特性,可以生成动态的Web页面。它担当客户请求(Web浏览器或其他HTTP客户程序)与服务器响应(HTTP服务器上的数据库或应用程序)的中间层。 Servlet是位于Web服务器内部的服务器端的Java应用程序,与传统的从命令行启动的Java应用程序不同,Servlet由Web服务器进行加载,该Web服务器必须包含支持Servlet的Java虚拟机。

Jsp:

JSP 全名为Java Server Pages,中文名叫java服务器页面,其根本是一个简化的Servlet设计。JSP技术使用Java编程语言编写类XML的tags和scriptlets,来封装产生动态网页的处理逻辑。网页还能通过tags和scriptlets访问存在于服务端的资源的应用逻辑。JSP将网页逻辑与网页设计的显示分离,支持可重用的基于组件的设计,使基于Web的应用程序的开发变得迅速和容易。 JSP(JavaServer Pages)是一种动态页面技术,它的主要目的是将表示逻辑从Servlet中分离出来。

相同点

jsp经编译后就变成了servlet,jsp本质就是servlet,jvm只能识别java的类,不能识别jsp代码,web容器将jsp的代码编译成jvm能够识别的java类。

不同点

JSP侧重视图,Sevlet主要用于控制逻辑。

Servlet中没有内置对象 。

JSP中的内置对象都是必须通过HttpServletRequest对象,HttpServletResponse对象以及HttpServlet对象得到。


ThreadLocal了解吗?

​ 使用ThreadLocal维护变量时,其为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立的改变自己的副本,而不会影响其他线程对应的副本。

ThreadLocal内部实现机制:

  • 每个线程内部会维护一个类似HashMap的对象,成为ThreadLocalMap,里面会包含若干个Entry(k-v键值对),相应的线程被称为Entry的主线程
  • Entry的Key是一个ThreadLocal实例,Value是一个线程持有的对象。Entry的作用是为其主线程建立起一个ThreadLocal实例与一个线程特有的对象之间的对应关系。
  • Entry对key的引用是弱引用,Entry对Value的引用是强引用

https://blog.csdn.net/weixin_43691723/article/details/105810442


操作字符串的类有哪些? 他们的区别是什么?

  • String : 字符串常量,字符串长度不可变。Java 中 String 是 immutable(不可变)的。
  • StringBuffer:
    • 字符串变量(Synchronized,即线程安全)。如果要频繁对字符串内容进行修改,出于效率考虑最好使用 StringBuffer,如果想转成 String 类型,可以调用 StringBuffer 的 toString() 方法。
    • 程安全的可变字符序列。在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。可将字符串缓冲区安全地用于多个线程。
    • 主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。
      • append 方法始终将这些字符添加到缓冲区的末端;
      • insert 方法则在指定的点添加字符。
  • StringBuilder
    • 字符串变量(非线程安全)。在内部,StringBuilder 对象被当作是一个包含字符序列的变长数组。
    • 是一个可变的字符序列,是 JDK5.0 新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。

https://www.cnblogs.com/coderD/p/13828832.html


HashMap和HashTable的区别?

是否线程安全

  • HashMap不是线程安全的,HashTable是线程安全的;【HashTable内部的方法基本都使用了synchronized关键字修饰】
  • 注意:现在HashTable在我们的开发中很少很少使用。如果你要保证线程安全,推荐使用ConcurrentHashMap

效率

  • 因为线程安全的问题,HashMap要比HashTable的效率高一点。

对于Null Key和Null Value的支持

  • HashMap中,null可以作为key,但是这样的key只能有一个;可以有一个或者多个键对应的value为null;
  • HashTable中不支持key为null,如果put使用null,那么就会抛出NullPointerException异常;

初始容量和每次扩容的大小不同

  • HashMap创建的时候如果不指定容量大小,初始容量大小为16,之后每次扩充,容量变为原来的2倍;

  • HashTable创建的时候如果不指定容量大小,初始容量大小为11,之后每次扩充,容量会变为2n + 1;

  • HashMap创建的时候给定初始容量大小,HashMap 会将其扩充为2的幂次方大小(HashMap 中的tableSizeFor()方法保证,下面给出了源代码)。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。

  • HashMap 中带有初始容量的构造函数:

    public HashMap(int initialCapacity, float loadFactor) {
     
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }
     public HashMap(int initialCapacity) {
     
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
  • 下面这个方法保证了 HashMap 总是使用2的幂作为哈希表的大小。
    /**
     * Returns a power of two size for the given target capacity.
     */
    static final int tableSizeFor(int cap) {
     
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

底层数据结构

  • JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间;
  • Hashtable 没有这样的机制。

https://blog.csdn.net/ycg33/article/details/100069585


对象序列化是什么?Es里面的数据需要序列化吗?

序列化是将对象转换为一系列字节,这样可以轻松的将对象保存到持久存储或跨通信链接流。然后,字节流可以反序列化-转换为原始对象副本。(可能是二进制也可能不是二进制,取决于实现。)

Elasticsearch是面向文档的,这意味着它可以存储整个对象或文档。然而它不仅仅是存储,还会索引(index) 每个文档的内容使之可以被搜索。在es中,你可以对文档进行索引,搜索,排序,过滤。

  • es使用json作为文档序列化格式,它简洁,简单且容易阅读。

数组与集合之间的转换?

String[] arrags = {
     "hello", "world", "java", "zhiyin"};
//数组转换为集合
List<String> list = new ArrayList<>(Arrays.asList(arrags));
String[] array = list.toArray(new String[list.size()]);

finally这个关键词的用法?

finally 关键字是对 Java 异常处理模型的最佳补充。finally 结构使代码总会执行,而不管有无异常发生。

使用 finally 可以维护对象的内部状态,并可以清理非内存资源。 如果没有 finally,您的代码就会很费解。

finally通常用于try catch语句块中,完整的异常处理语句一般都有finally语句。无论有无异常发生,finally语句一定会被执行。


线程创建的方法有哪些?

使用过线程池。

  • 减少资源的开销,通过复用线程,降低创建销毁线程造成的消耗。
  • 多个线程并发执行任务,提高系统的响应速度
  • 同一分配,调优和监控线程,提高线程的可管理性

创建线程:

Executors :

  • newSingleThreadExecutor 创建一个的线程的线程池
  • newFixedThreadPool 创建指定对象的线程池
  • newCachedThreadPool 创建可变大小的线程池

ThreadPoolExecutor(int corePoolSize, //核心线程池大小

​ int maximumPoolSize,//最大线程池大小

​ long keepAliveTime,//超时没有人调用就会释放

​ TimeUnit unit,//超时单位

​ BlockingQueue workQueue,//阻塞队列

​ ThreadFactory threadFactory,//线程工厂:创建线程,一般不用动

​ RejectedExecutionHandler handler) {//拒绝策略


run和start方法的区别?

Start方法可启动线程,而run方法只是thread的一个普通方法,调用run方法不能实现多线程。

start方法

​ start方法用来启动线程,实现了多线程运行,这时无需等待run方法体代码体执行,而是直接执行下面的代码。

  • 通过调用Thread类的start方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行。
  • 一旦得到了cpu时间片,就开始执行run() 方法,这里方法run() 称为线程体
  • 它包含了要执行的这个线程的内容,Run方法运行结束,此线程即终止。

Run() 方法

run() 方法只是Thread类的一个普通方法,如果直接调用Run方法,程序中依然只有主线程这一个线程。

其程序执行路径还是只有一条,还是要等待run方法体执行完毕后才可继续执行下面的代码。

这样就没有达到多线程的目的。

通过start能够异步的调用run() 方法,但直接调用run()方法是同步的


你的缺点的什么?

对很重要的事情 紧张。比如这次面试,我都很紧张,但是会提前准备很多东西来化解紧张,
hr就问到我这个问题了,我是这样说的。

二面

项目里优化的地方,围绕项目问。

  • mysql分页查询走索引?>
  • 搜索使用Elasticsearch
  • 使用Redis缓存机制?
  • 数据库设计?
  • 防抖处理?

Token的问题

token的原理和作用

token是用于验证身份的,在web系统中验证前端是否能够访问后端系统

token在服务端产生,如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 token 证明自己的合法地位。token是服务端生成的一串字符串,以作前端进行请求的一个令牌,当第一次登录后,服务器生成一个token并返回给前端,之后前端只需带上这个token前来请求数据即可,无需再次带上用户名和密码。

token相比其他验证方式,主要是无需每次都去验证用户名密码,同时由于token保存在前端,服务端无需保存每个用户的登录状态,只需要验证访问时带着的token是不是自己签发的,可以减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。

token可以解决的问题

  • 避开同源策略:
    • web页面中,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源,这样也给不同系统的交互验证带来了较大的问题,但是token完全由应用管理,不涉及服务端,只要使用同样的密钥与算法,token就是有效的。
  • 避免CSRF攻击
    • 使用cookie保存用户信息,可能会造成黑客恶意伪造用户的请求,该请求中所有的用户验证信息都是存在于 cookie 中,因此黑客可以在不知道这些验证信息的情况下直接利用用户自己的 cookie 来通过安全验证。但是token放入header之后,是无法被直接伪造的。
  • token可以在多个应用直接共享
    • 在多个系统协同工作的时候,token可以被共享到多个服务中,避免用户多次登录

Token和Cookie的区别

  1. token和Cookie一样都是首次登录时,由服务器下发,都是交互时进行验证的功能,作用都是无状态的HTTP提供的持久机制。
  2. token存在哪都行,localstorage或者Cookie
  3. token和cookie举例,token就是说你告诉我你是谁就可以。
cookie 举例:服务员看你的身份证,给你一个编号,以后,进行任何操作,都出示编号后服务员去看查你是谁。
token  举例:直接给服务员看自己身份证
  1. 对于token而言,服务器不需要查看你是谁,不需要保存你的会话。当用户logout的时候cookie和服务器的session都会注销,但是当logout的时候token只是注销浏览器信息,不查库。
  2. token的优势在于,token由于服务端不存储会话,所以可扩展性强,token还可用于App中。
总结:

Token 完全由应用管理,所以它可以避开同源策略
Token 可以避免 CSRF 攻击
Token 可以是无状态的,可以在多个服务间共享

如果你的用户数据可能需要和第三方共享,或者允许第三方调用 API 接口,用 Token,如果之上自己的那就无所谓了。 

分布式session不一致,如何解决?

redis分布式储存。


SpringBoot有哪些注解?SpringBootApplication注解发生什么?SpringMVC工作原理?

@Service:

注解在类上,表示这是一个业务层bean
@Controller:

注解在类上,表示这是一个控制层bean
@Repository:

注解在类上,表示这是一个数据访问层bean
@Component:

注解在类上,表示通用bean ,value不写默认就是类名首字母小写

@Autowired:

按类型注入.默认属性required= true;当不能确定 Spring 容器中一定拥有某个类的Bean 时, 可以在需要自动注入该类 Bean 的地方可以使用@Autowired(required = false), 这等于告诉Spring:在找不到匹配Bean时也不抛出BeanCreationException 异常。

@Configuration:

注解在类上,表示这是一个IOC容器,相当于spring的配置文件,java配置的方式。 IOC容器的配置类一般与 @Bean 注解配合使用,用@Configuration 注解类等价与 XML 中配置 beans,用@Bean 注解方法等价于 XML 中配置 bean。@Bean: 注解在方法上,声明当前方法返回一个Bean

@Value:

注解在变量上,从配置文件中读取

RestController:

@RestController 是一个结合了 @ResponseBody 和 @Controller 的注解(像:resetful接口调用时,返回的是json等就使用)

@PathVariable和@RequestParam:

都是将request里的参数的值绑定到contorl里的方法参数里的,区别在于,URL写法不同。

使用@RequestParam时,URL是这样的:http://host:port/path?参数名=参数值

使用@PathVariable时,URL是这样的:http://host:port/path/参数值

当请求参数username不存在时会有异常发生,可以通过设置属性required=false解决,例如: @RequestParam(value=“username”,required=false)

不写的时候也可以获取到参数值,但是必须名称对应。参数可以省略不写

@suppresswarnings:抑制警告

@Transactional:事务注解


@SpringBootApplication:

约定优于配置  @SpringBootApplication=@ComponentScan+@Configuration+@EnableAutoConfiguration。 放在主程序入口类上, 主程序入口类(启动类) 放在root 包下,这样程序启动时所有的相关配置,类都能扫描,查找到


Redis有哪些数据类型,应用场景?Redis怎么获取key

  • String: 此类型和memcache相似,作为常规的key-value缓存应用
    • 例如:微博数,粉丝数等
    • 注:一个键最大能存储512MB
  • Hash
    • Redis hash是一个String类型的field和value的映射表,hash特别适合存储对象(因为对象可能会包含很多属性)
    • 常用命令: hget hset hgetall
    • 主要用来存储对象
  • List
    • list列表是简单的字符串列表,按照插入顺序排序(内部实现为LinkedList),可以选择将一个链表插入到头部或尾部
    • 常用命令::lpush(添加左边元素),rpush,lpop(移除左边第一个元素),rpop,lrange(获取列表片段,LRANGE key start stop)等。
    • 应用场景: Redis list的应用场景非常多,也是Redis最重要的数据结构之一,比如twitter的关注列表,粉丝列表等都可以用Redis的list结构来实现。
  • Set
    • 在微博中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis还为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。
  • ZSet
    • 常用命令:zadd,zrange
    • 实现方式:Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,跳跃表按score从小到大保存所有集合元素。使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。时间复杂度与红黑树相同,增加、删除的操作较为简单。
    • 应用场景: 排行榜

keys * -----> 获取所有的键


项目上线后出故障了,怎么排查(日志)

第一步: 评估bug的影响范围

  • 分析bug影响的用户数量:检查bug是否业务核心环节的功能问题,是的话则影响的用户量比较多
  • 分析bug影响的严重程度:检查bug是否涉及到用户的个人信息泄露、资金财产损失等比较敏感的功能,涉及的话则认为bug比较严重

第二步:解决线上的问题

针对线上问题最重要的是要解决,在评估完影响范围后,就需要制定对应的措施来解决问 题并恢复系统的正常使用。

  • 影响范围比较小的bug: 了解bug出现的场景,业务操作,努力复现bug–> 查看日志
  • 影响范围比较大的bug:将问题影响范围降到最低。

第三步:回溯线上问题

当线上问题解决后,我们还需要对问题进行总结回溯,避免同样的问题再次发生。线上

问题回溯主要从如下几个方面进行:

  • 检查其他的业务是否有同类型的问题,有问题的话提前解决,避免遗漏上线
  • 分析bug的根本原因,考虑如何避免此类问题再次发生
  • 给测试人员检查

Redis的哨兵机制?

概念:

顺丰科技已上岸面经(收集牛客近年来面经)_第1张图片

顺丰科技已上岸面经(收集牛客近年来面经)_第2张图片

哨兵模式

如果主机此时回来了,只能归并到新的主机下,当作从机,这就是哨兵模式的规则!

优点:

  1. 哨兵集群,基于主从复制模式所有的主从配置优点,它全有
  2. 主从可以切换,故障可以转移,系统的可用性就会更好
  3. 哨兵模式就是主从模式的升级,手动到自动,更加健壮!

缺点:

  1. Redis 不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦!
  2. 实现哨兵模式的配置其实是很麻烦的,里面有很多的选择

前后端分别运用了哪些技术?

SpringBoot + MyBatis Plus + MySQL + js + bootStarp.


restful规范

对于资源的具体操作类型,由HTTP动词表示。

常用的HTTP动词有下面五个(括号里是对应的SQL命令)。

  • GET(SELECT):从服务器取出资源(一项或多项)。
  • POST(CREATE):在服务器新建一个资源。
  • PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
  • PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
  • DELETE(DELETE):从服务器删除资源。

下面是一些例子

  • GET /zoos:列出所有动物园
  • POST /zoos:新建一个动物园
  • GET /zoos/ID:获取某个指定动物园的信息
  • PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
  • PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
  • DELETE /zoos/ID:删除某个动物园
  • GET /zoos/ID/animals:列出某个指定动物园的所有动物
  • DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物

指定过滤信息

如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。

下面是一些常见的参数。

  • ?limit=10:指定返回记录的数量
  • ?offset=10:指定返回记录的开始位置。
  • ?page=2&per_page=100:指定第几页,以及每页的记录数。
  • ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
  • ?animal_type_id=1:指定筛选条件

状态码

服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。

  • 200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
  • 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
  • 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
  • 204 NO CONTENT - [DELETE]:用户删除数据成功。
  • 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
  • 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
  • 403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
  • 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
  • 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
  • 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
  • 422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
  • 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

http://www.ruanyifeng.com/blog/2014/05/restful_api.html


put和patch方法的区别?

PATCH: 局部更新

PUT: 全量更新

假设我们有一个UserInfo,里面有userIduserNameuserGender等10个字段。可你的编辑功能因为需求,在某个特别的页面里只能修改userName,这时候的更新怎么做?

人们通常(为徒省事)把一个包含了修改后userName的完整userInfo对象传给后端,做完整更新。但仔细想想,这种做法感觉有点二,而且真心浪费带宽(纯技术上讲,你不关心带宽那是你土豪)。

于是patch诞生,只传一个userName到指定资源去,表示该请求是一个局部更新,后端仅更新接收到的字段。

put虽然也是更新资源,但要求前端提供的一定是一个完整的资源对象,理论上说,如果你用了put,但却没有提供完整的UserInfo,那么缺了的那些字段应该被清空


怎么给前端返回一个视图对象?前后端要分开部署吗?

前后端分离,了解下这里不细讲


第四篇

讲一下Elasticsearch的倒排索引?

顺丰科技已上岸面经(收集牛客近年来面经)_第3张图片
顺丰科技已上岸面经(收集牛客近年来面经)_第4张图片

Elasticsearch分别为每个字段都建立了一个倒排索引。比如,在上面“张三”、“北京市”、22 这些都是Term,而[1,3]就是Posting List。Posting list就是一个数组,存储了所有符合某个Term的文档ID。

当然是建索引了,为Terms建立索引,最好的就是B-Tree索引


说一下线程的创建方式?

使用过线程池。

  • 减少资源的开销,通过复用线程,降低创建销毁线程造成的消耗。
  • 多个线程并发执行任务,提高系统的响应速度
  • 同一分配,调优和监控线程,提高线程的可管理性

创建线程:

Executors :

  • newSingleThreadExecutor 创建一个的线程的线程池
  • newFixedThreadPool 创建指定对象的线程池
  • newCachedThreadPool 创建可变大小的线程池

ThreadPoolExecutor(int corePoolSize, //核心线程池大小

​ int maximumPoolSize,//最大线程池大小

​ long keepAliveTime,//超时没有人调用就会释放

​ TimeUnit unit,//超时单位

​ BlockingQueue workQueue,//阻塞队列

​ ThreadFactory threadFactory,//线程工厂:创建线程,一般不用动

​ RejectedExecutionHandler handler) {//拒绝策略

线程池的使用流程?具体参数是什么?说下线程池参数的队列具体是什么?LinkedBlockingQuene具体说说,什么时候阻塞?

ThreadPoolExecutor(int corePoolSize, //核心线程池大小

​ int maximumPoolSize,//最大线程池大小

​ long keepAliveTime,//超时没有人调用就会释放

​ TimeUnit unit,//超时单位

​ BlockingQueue workQueue,//阻塞队列

​ ThreadFactory threadFactory,//线程工厂:创建线程,一般不用动

​ RejectedExecutionHandler handler) {//拒绝策略


队列

所有 BlockingQueue 都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互:

  • 如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。
  • 如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。
  • 如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。

三种队列

  1. 直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
  2. 无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
  3. 有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。

i++ 操作安全吗?具体怎么改成线程安全?AutomicInteger底层用了什么原理?CAS会造成什么问题?

将i改成AutomicInteger类型。用到了CAS

CAS会造成ABA问题 ----> 携带版本号


锁都了解哪些?使用ReentrantLock应该注意什么?

锁有:

  • ReentrantLock: Lock一般锁一块代码,可以搭配condition使用。
  • synchronized: synchronize用于方法和代码块,可以锁对象和类以及方法

锁有:乐观锁,悲观锁,自旋锁,读写锁。

synchronized有三种状态:偏向锁、轻量级锁、重量级锁

锁有四种级别,按照量级从轻到重分为:无锁、偏向锁、轻量级锁、重量级锁。

每个对象一开始都是无锁的,随着线程间争夺锁,越激烈,锁的级别越高,并且锁只能升级不能降级。


第五篇

一面

以前工作的项目有哪些问题? 怎么解决?

  • 数据不走索引?
  • 搜索速度慢?
  • redis 查询数据总数—> 扯一下mysql的innodb

用过什么类库 (集合类、JUC包下的类) 挑几个说一说(我说了hashmap,hashset,concurrenthashmap还有longAddr)

HashMap:

  • 1.7 数组+链表
  • 1.8 数组+链表+ B+数。

HashSet底层使用的是HashMap的key

concurrentHashMap: 分段锁机制

CountDownLatch


讲讲mybatis

  • 讲讲#与$
  • 讲讲一级缓存二级缓存
  • 讲讲源码(不多)

数据库索引是怎么工作的,为啥可以提高查询效率,mysql数据库索引是什么结构?为啥要用b+树

InnoDB聚集索引,数据文件和主键索引在同个文件。

类似二分搜索的形式 ,大大缩短了查询的时间。

数据库索引是B+数。

二叉树可能形成链形结构,查询复杂度还是O(n)。 不能自平衡


发送一个请求,整个请求过程,不限于浏览器和服务器(从浏览器开始,走了那些协议–>tomcat解析http请求封装成Httprequest–>进入到spring mvc那几个流程 -->mybatis解析sql–>最后返回数据渲染页面)

这个题面试官没让我说这么详细,大致说一下流程。

  1. 域名解析协议DNS
  2. 浏览器发起HTTP请求
  3. 传输层TCP/UDP
  4. 网络层ARP协议。
  5. 应用层SMTP

http是有状态还是无状态?服务器是怎么识别哪个请求(session和cookie,session在服务端有个JsessionID…就那一套东西)

http是无状态的统个session+cookie去辅助

  1. 协议对于事务处理没有记忆能力【事物处理】【记忆能力】

  2. 对同一个url请求没有上下文关系【上下文关系】

  3. 每次的请求都是独立的,它的执行情况和结果与前面的请求和之后的请求是无直接关系的,它不会受前面的请求应答情况直接影响,也不会直接影响后面的请求应答情况【无直接联系】【受直接影响】

  4. 服务器中没有保存客户端的状态,客户端必须每次带上自己的状态去请求服务器【状态】

    Web应用=http协议+session、cookies等状态机制+其他辅助的机制。


快速排序算法?

 private static void quickSort(int[] arr, int leftIndex, int rightIndex) {
     
        if (leftIndex >= rightIndex) {
     
            return;
        }

        int left = leftIndex;
        int right = rightIndex;
        //待排序的第一个元素作为基准值
        int key = arr[left];

        //从左右两边交替扫描,直到left = right
        while (left < right) {
     
            while (right > left && arr[right] >= key) {
     
                //从右往左扫描,找到第一个比基准值小的元素
                right--;
            }

            //找到这种元素将arr[right]放入arr[left]中
            arr[left] = arr[right];

            while (left < right && arr[left] <= key) {
     
                //从左往右扫描,找到第一个比基准值大的元素
                left++;
            }

            //找到这种元素将arr[left]放入arr[right]中
            arr[right] = arr[left];
        }
        //基准值归位
        arr[left] = key;
        //对基准值左边的元素进行递归排序
        quickSort(arr, leftIndex, left - 1);
        //对基准值右边的元素进行递归排序。
        quickSort(arr, right + 1, rightIndex);
    }

二面

HashMap原理

大多数情况下可以直接定位到值,具有很快的访问速度。 非线程安全。(ConcurrentHashMap)

初始化的时候默认的容量是16 (必须是2的指数此幂)

Java7 —> 数组 + 链表

Java8 ----> 数组 + 链表 + 红黑树 (性能提升10%左右)


JVM内存区域,对象从产生到销毁的过程?

Student stu = new Student(“zhangsan”); 
stu.add(); 
stu=null; 
  1. 用户创建了一个Student对象,运行时JVM首先会去方法区寻找该对象的类型信息,没有则使用类加载器classloader将Student.class字节码文件加载至内存中的方法区,并将Student类的类型信息存放至方法区。
  2. 接着JVM在堆中为新的Student实例分配内存空间,这个实例持有着指向方法区的Student类型信息的引用,引用指的是类型信息在方法区中的内存地址。
  3. 在此运行的JVM进程中,会首先起一个线程跑该用户程序,而创建线程的同时也创建了一个虚拟机栈,虚拟机栈用来跟踪线程运行中的一系列方法调用的过程,每调用一个方法就会创建并往栈中压入一个栈帧,栈帧用来存储方法的参数,局部变量和运算过程的临时数据。上面程序中的stu是对Student的引用,就存放于栈中,并持有指向堆中Student实例的内存地址。
  4. JVM根据stu引用持有的堆中对象的内存地址,定位到堆中的Student实例,由于堆中实例持有指向方法区的Student类型信息的引用,从而获得add()方法的字节码信息,接着执行add()方法包含的指令。
  5. 将stu指向null
  6. JVM GC

SpringMVC的过程?

  1. DispatcherServlet表示前置控制器,是整个SpringMVC的控制中心。用户发出请求,DispatcherServlet接收请求并拦截请求,我们假设请求的url为:http://localhost:9999/SpringMVC/input-product… 如上url拆分成三部分:
    • http://localhost:9999 服务器域名
    • SpringMVC部署在服务器(http://localhost:9999)上的web站点
    • input-product表示控制器

通过分析,如上url表示为:请求位于服务器localhost:9999上的SpringMVC站点的input-product控制器

  1. HandlerMapping为处理器映射。DispatcherServlet调用HandlerMapping,HandlerMapping根据请求url查找Handler
  2. HandlerExecution表示具体的Handler,其主要作用是根据url查找控制器,如上url被查找控制器为:input-product
  3. HandlerExecution将解析后的信息传递给DispatcherServlet,如解析控制器映射等
  4. HandlerAdapter表示处理器适配器,其按照特定的规则去执行Handler
  5. Handler让具体的Controller执行
  6. .Controller将具体的执行信息返回给HandlerAdapter,如ModelAndView
  7. HandlerAdapter将视图逻辑名或模型传递给DispatcherServlet
  8. DispatcherServlet调用视图解析器(ViewResolver)来解析HandlerAdapter传递的逻辑视图名
  9. 视图解析器将解析的逻辑视图名传给DispatcherServlet
  10. DispatcherServlet根据视图解析器解析的视图结果,调用具体的视图
  11. 最终视图呈现给用户
    顺丰科技已上岸面经(收集牛客近年来面经)_第5张图片

一个Java实例插入到数据库,mybatis的过程?

直接CRUD那一套讲就行了吧


InnoDB特性,索引的数据结构。

聚集索引 。B+数

  • 支持事务
  • 支持行锁
  • 支持redo log

InnoDB特性

  1. insert buffer
  2. 两次写
  3. 自适应哈希索引
  4. 异步IO
  5. 刷新邻进页(flush neighbor page)

Mybatis一级缓存和二级缓存?

Mybatis中有一级缓存和二级缓存,默认情况下一级缓存是开启的,而且不能关闭。

一级缓存是指SqlSession级别的缓存 即:同一个SqlSession中进行相同的SQL语句查询时,第二次后查询不会从数据库查询, 而是直接从缓存中获取,一级缓存最多缓存1024条SQL。

二级缓存是指可以跨SqlSession缓存 即: 是mapper级别的缓存,对于mapper级别的缓存不同的sqlSession是可以共享的。


SpringBoot原理?

它是一个服务于spring框架的框架,能够简化配置文件,快速构建web应用,内置tomcat,无需打包部署,直接运行。

  • 默认有 resources 文件夹存放配置文件
  • 默认打包方式为 jar
  • spring-boot-starter-web 中默认包含 spring mvc 相关依赖以及内置的 tomcat 容器

默认提供 application.properties/yml 文件
默认通过 spring.profiles.active 属性来决定运行环境时读取的配置文件


SpringBootApplication 本质上是由 3 个注解组成,分别是

@Configuration
@EnableAutoConfiguration
@ComponentScan
@Configuration:
在启动类里面标注了@Configuration,意味着它其实也是一个 IoC

容器的配置类@ComponentScan:
ComponentScan 默认会扫描当前 package 下的的所有加
了@Component 、@Repository、@Service、@Controller的类到 IoC 容器中;


SpringMVC拦截器 过滤器都用到了,如何实现?

过滤器

​ 依赖于servlet容器。在实现上基于函数回调,可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。使用过滤器的目的是用来做一些过滤操作,获取我们想要获取的数据,比如:在过滤器中修改字符编码;在过滤器中修改HttpServletRequest的一些参数,包括:过滤低俗文字、危险字符等。

拦截器

依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。在实现上基于java的反射机制,属于面向切面编程(AOP)的一种运用。由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。但是缺点是能对controller请求进行拦截,可以拦截静态资源,但是拦截不了jsp页面

拦截器

**思路:**即将请求的url地址进行解析,除了登录外的请求都要进行拦截或者过滤,这些请求在通过登录的判断,来决定最后的结果

  • 实现HandlerInterceptor接口,实现preHandle,postHandle方法
  • 配置拦截器
 
    <mvc:interceptors>
        
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="com.invoicing.interceptor.LoginInterceptor">bean>

        mvc:interceptor>
    mvc:interceptors>

因为拦截器无法拦截jsp页面,所以,直接登录jsp页面无法拦截。


过滤器

  • 导入maven依赖 javax.servlet-api
  • 编写过滤器
@WebServlet(urlPatterns = {
     "/*"})
public class MyFilter implements Filter {
     
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
     

    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
     
        HttpServletRequest httpServletRequest=(HttpServletRequest) servletRequest;
        System.out.println("123");
        //放行URL
        if (httpServletRequest.getRequestURI().indexOf("/login") >= 0) {
     
            filterChain.doFilter(servletRequest,servletResponse);
        }

        Object user = httpServletRequest.getSession().getAttribute("user");
        if (user==null){
     
            httpServletRequest.getRequestDispatcher("/login.jsp").forward(servletRequest,servletResponse);
        }else {
     
            //放行请求
            filterChain.doFilter(servletRequest,servletResponse);
        }

    }

    @Override
    public void destroy() {
     

    }
}

HTTP三次握手四次挥手?

三次握手

第一次握手:Client什么都不能确认,Server确认了对方发送正常,自己接受正常。

第二次握手:Client确认了:自己发送,接受正常;对方发送,接受正常; Server确认了:自己接收正常;对方发送正常

第三次握手:Client全部确认 Server全部确认

四次挥手

确保客户端和服务端 都想结束通讯了。


第六篇

数据库 一二三范式的作用?

  • 第一范式

第一范式的目标是确保每列的原子性,如果每列都是不可分割的最小数据单元(也称为最小的原子单元),则满足第一范式1NF 属性不可分割

顺丰科技已上岸面经(收集牛客近年来面经)_第6张图片

  • 第二范式

每个表只描述一件事情,首先满足第一范式,并且表中非主键列不存在对主键的部分依赖。

第二范式要求每个表只描述一件事情

  • 第三范式

不存在对非主键列的传递依赖

满足第二范式,并且表中的列不存在对非主键列的传递依赖。除了主键订单编号外,顾客姓名依赖于非主键顾客标号。

顺丰科技已上岸面经(收集牛客近年来面经)_第7张图片

Mname 是依赖于sdept的 不直接依赖于Sno主键

可分解为以下

顺丰科技已上岸面经(收集牛客近年来面经)_第8张图片


java字符串的==和equals区别?

  • ==比较两个变量本身的值,即两个对象在内存中的首地址。
  • equals()”比较字符串中所包含的内容是否相同。

  • hashCode主要用于提升查询效率,来确定在散列结构中对象的存储地址;
  • 重写equals()必须重写hashCode(),二者参与计算的自身属性字段应该相同;
  • hash类型的存储结构,添加元素重复性校验的标准就是先取hashCode值,后判断equals();
  • equals()相等的两个对象,hashcode()一定相等;
  • 反过来:hashcode()不等,一定能推出equals()也不等;
  • hashcode()相等,equals()可能相等,也可能不等。

HashMap原理,哈希冲突解决方法

数组+链表

哈希冲突使用链表展示


垃圾回收机制?讲一讲分代回收算法?

可达性分析算法

通过GC ROOT的对象作为搜索起始点,通过引用向下搜索,所走过的路径称为引用链。通过对象是否有到达引用链的路径来判断对象是否可被回收(可作为GC ROOT的对象:虚拟机栈中引用的对象,方法区中类静态属性引用的对象,方法区中常量引用的对象,本地方法栈中JNI引用的对象)

顺丰科技已上岸面经(收集牛客近年来面经)_第9张图片

通过可达性算法,成功解决了引用计数所无法解决的循环依赖问题,只要你无法与GC Root建立直接或间接的连接,系统就会判定你为可回收对象。那这样就引申出了另一个问题,哪些属于GC Root。

  • 标记清除 老年代
  • 标记整理 清楚 老年代
  • 复制算法 新生代

TCP/UDP的区别?

UDP:在传输数据之前不需要先建立连接,远地的主机收到UDP报文后也不需要任何确认。虽然UDP不提供可靠的交付,但是却非常的高效(QQ语言,QQ视频,直播等等)

TCP:在传送数据时需要先建立连接,结束后要释放连接。TCP不提供广播,多播等服务。增加了许多开销。用于文件传输,发送和接收邮件,远程登录等场景

顺丰科技已上岸面经(收集牛客近年来面经)_第10张图片


快排原理?是不是稳定的?为什么?JAVA中排序函数是不是用快排?快排的时间复杂度一样的还有哪些算法?

顺丰科技已上岸面经(收集牛客近年来面经)_第11张图片

  1. 分别设置左右两个指针,见(1);
  2. 将左指针指的数字(2)作为基准,此时左指针指为空,见(2);
  3. 右指针向左移,直到遇到比base小的值停,见(3),把右指针此时的值赋值给左指针,见(4);
  4. 左指针开始向右移,直到遇到比base大的值停,见(5),再把左指针的值赋给右指针,右指针再像左移…
  5. 直到左右指针重合,把base的值赋值给这个指针位置。循环结束一次,此时2的左面都是比2小的数字,2的右面都是比2大的数字。
  6. 分别对2左右两边进行上述操作,递归进行。

不是稳定的算法

在排序之前,有两个数相等.但是在排序结束之后,它们两个有可能改变顺序.

在一个待排序队列中,A和B相等,且A排在B的前面,而排序之后,A排在了B的后面.这个时候,我们说这种算法是不稳定的.


时间复杂度一样的有:

  • 堆排序 不稳定
  • 归并排序 不稳定

第七篇

项目中用到的组件,相同作用的类似组件?

  • Redis

设置过期时间: expire key time(以秒为单位)

setex(String key,int seconds,String value) 字符串独有的方式

ttl key : 查看剩余过期时间

  1. 除了字符串自己独有设置过期时间的方法外,其他方法都需要依靠expire方法来设置过期时间
  2. 如果没有设置过期时间,那缓存就是永不过期
  3. 如果设置了过期时间,之后又想让缓存永不过期,使用persist key

3种过期策略

  • 定时删除
    • 含义: 在设置key的过期时间的同时,为该key创建一个定时器,让定时器key的过期时间来临时,对key进行删除
    • 优点:保证内存被尽快释放
    • 缺点:
      • 若过期key很多,删除这些key会占用很多的cpu时间,在CPU紧张的情况下,CPU不能把所有的时间用来做要紧的事,还需要花时间删除这些key
      • 定时器的创建耗时,若为每一个设置过期时间的key创建一个定时器(将会有大量的定时器产生),性能严重影响
      • 没人用
  • 惰性删除
    • 含义:key过期的时候不删除,每次从数据库获取key的时候去检查是否过期,若过期,则删除,返回null
    • 优点:删除操作只发生在从数据库取出key的时候发生,而且只删除当前key,所以对CPU时间的占用是比较少的,而且此时的删除是已经到了非做不可的地步(如果此时还不删除的话,我们就会获取到了已经过期的key了)
    • 缺点:若大量的key在超出时间后,很久一段时间内,都没有被获取,那么可能发生内存泄漏(无用的垃圾占用了大量的内存)
  • 定期惰性删除
    • 含义:每隔一段时间执行一次删除(在redis.conf配置文件设置hz,1刷新的频率)过期key操作
    • 优点:
      • 通过限制删除操作的时长和频率,来减少删除操作对CPU时间的占用-处理”定时删除“的缺点
      • 定期删除过期key–处理”惰性删除“的缺点
    • 缺点:
      • 在内存友好方面,不如”定时删除“
      • 在CPU时间友好方面,不如”惰性删除“
    • 难点
      • 合理的设置删除操作的执行时长(每次删除执行多长时间)和执行频率(每隔多长时间做一次删除)

Redis的持久化

RDB

  • RDB: 过期key对RDB没有任何影响
    • 从内存数据库持久化到RDB文件
      • 持久化key之前,会检查是否过期,过期的key不能进入RDB文件
  • 从RDB文件恢复数据到内存数据库
    • 数据载入数据库之前,会对key先进行过期检查,如果过期,不导入数据库(主库情况)

AOF

  • AOF:过期key对AOF没有任何影响
    • 从内存数据库持久化数据到AOF文件
      • 当key过期后,还没有被删除,此时进行持久化操作(该key是不会进入aof文件,因为没有发生修改命令)
      • 当key过期后,在发生删除时,程序会向aof文件追加一条del命令(在将来的aof文件恢复的时候该过期的键就会被删掉)
    • AOF重写
      • 重写时,会先判断key是否过期,已过期的key不会重写到aof文件

Elasticsearch

倒排索引

JVM组成,程序计数器的功能?双亲委派机制

功能

  • 确定代码的执行函数,指定执行
  • 当切换为别的线程的时候,记录当前线程的执行位置

双亲委派

每一个类都有一个对应它的类加载器。系统中的 ClassLoder 在协同工作的时候会默认使用 双亲委派模型 。即在类加载的时候,系统会首先判断当前类是否被加 载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派该父类加载器的 loadClass() 处理,因此所有的请求最终都应该 传送到顶层的启动类加载器 BootstrapClassLoader 中。当父类加载器无法处理时,才由自己来处理。当父类加载器为null时,会使用启动类加载器 BootstrapClassLoader 作为父类加载器。

BootstrapClassLoader “jre/lib”

extensionClassLoader “jre/lib/ext”

AppClassLoader 主要负责加载应用程序的主函数类

双亲委派模型带来的好处

​ 双亲委托模型保证了Javac程序的稳定运行,可以避免类的重复加载(JVM区分不同的类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的两个 不同的类),也保证了Java的核心API不被篡改。如果不使用双亲委托模型,而是每个类加载自己的话会出现一些问题:编写一个java.lang.object类的话,那么 程序运行的时候,系统就会出现多个不同object类


数据库索引相关

项目中大部分用到的数据库是mysql数据库。

mysql中的索引:

  • 普通索引: 没有任何限制,允许在定义索引的列中插入重复的值和空值

    • create index index_name on table_name (column(length));
      
  • 唯一索引: 索引列的值必须是唯一的,允许有空值,如果是组合索引,列值的组合必须唯一

    • create unique index index_name on table_name (column(length));
      
  • 前缀索引: 截取字段的指定长度前半部分来作为索引。

  • 组合索引: 多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。

    • alter table table_name add index index_name(column,column,column..);
      
  • 主键索引:主键索引是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值,一般是在创建表的时候指定主键,主键默认就是主键索引

    • primary key(column)

聚集索引: 表数据按照索引的顺序来存储的,也就是说索引项的顺序与表中记录的物理顺序一致。叶子节点即存储了真实的数据行,不再有另外单独的数据页。一张表只能创建一个聚集索引,因为真实的物理顺序只有一种

  • InnoDB(聚集索引)的数据文件只有数据结构文件.frm和数据文件.idb 其中.idb存放的数据和索引信息是存放在一起的。

非聚集索引: 表数据存储顺序与索引顺序无关。对于非聚集索引,叶结点包含索引字段值及指向数据页数据行的逻辑指针。,其行数量与表数据量一致。

  • MyISAM(非聚集索引)的索引文件.MYI和数据文件.MYD是分开存储的是相对独立的。

总结

聚簇索引和非聚簇索引的区别是:

聚簇索引(innoDB)的叶子结点就是数据结点

非聚簇索引(myisam)的叶子结点任然是索引文件,知识这个索引文件包含指向对应数据块的指针

对于非聚簇索引来说,每次通过索引检索到所需行号后,还需要通过叶子上的磁盘地址去磁盘内取数据(回行)消耗时间。为了优化这部分回行取数据时间,InnoDB引擎采用了聚簇索引。

聚簇索引,即将数据存入索引叶子页面上。对于Innodb引擎来说,叶子页面不再存改行对应的地址,而是直接存储数据。

这样便避免了回行操作所带来的时间消耗。使得InnoDB在某些查询上比MyISAM还要快!


ssm注解,了解多少?

@Controller:作用于表现层(spring-mvc的注解),它注解的类进行前端请求的处理,转发,重定向。包括调用Service层的方法。

@Service:作用于业务逻辑层(service层)

@Repository:作用于持久层(dao层),它注解的类作为DAO对象(数据访问对象,Data Access Objects),这些类可以直接对数据库进行操作

@Component:是一个通用的Spring容器管理的单例bean组件,最普通的组件,可以被注入到spring容器进行管理。@Component是通用注解,其他三个注解是这个注解的拓展,并且具有了特定的功能。

@RequestMapping:是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。该注解有六个属性:

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping

@Mapper和**@MapperScan**的区别

@Mapper:作用是为了把mapper这个DAO交给Spring容器管理,目前的我的理解是将该注解放在dao层的接口上,对应了它的.xml的映射文件。一个接口,就要在接口上方加一个mapper注解。

@MapperScan:如上面所讲,当存在多个mapper接口时,在每个接口上方都要加一个@Mapper注解,这样显得十分的繁琐,为此我们可以直接在SpringBoot的启动类的上方加一个@MapperScan注解,来解决这个问题。此注解可以添加对包的的扫描,我们可以直接将mapper接口所在的包写在此注解里,这样就会自动扫描所有的mapper接口。


实现多线程的方法,线程池的拒绝策略?

Executors创建

ThreadPoolExecutor

  1. AbortPolicy: 直接抛出异常,阻止系统正常运行 (默认的拒绝策略)
  2. CallerRunsPolicy: 只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。
  3. DiscardOldestPolicy: 丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。
  4. DiscardPolicy: 该策略默默地丢弃无法处理的任务,不予任何处理。如果允许任务丢失,这是最好的一种方案。

redis的应用场景,为什么这么快? redis场景

  1. 缓存数据:最常用,对经常需要查询且变动不是很频繁的数据常称作热点数据;
  2. 消息队列:相当于消息订阅系统,比如ActiveMQ、RocketMQ。如果对数据有较高一致性要求时,还是建议使用MQ;
  3. 计数器:比如统计点击率、点赞量,redis具有原子性,可以避免并发问题;
  4. 电商网站信息:大型电商平台初始化页面数据的缓存。比如去哪儿网购买机票的时候首页的价格和你点进去的价格会有差异;
  5. 热点数据:比如新闻网站实时热点、微博热搜等,需要频繁更新。总数据量比较大的时候直接从数据库查询会影响性能;
  6. 分布式会话:也称为单点登录,保证分布式环境下session一致性;
  7. 秒杀、抢购等场景;
  8. 排行榜,比如在线答题得分排行等;
  9. 分布式锁:防止大并发产生问题;
  10. 数据过期处理:利用redis设置的超时时间,可以精确到毫秒。

redis为什么这么快? redis快

首先,采用了多路复用io阻塞机制
**然后,数据结构简单,操作节省时间 ** 跳跃表
最后,运行在内存中,自然速度快


设计模式? 单例,工厂

这个记得看


TCP/UDP的区别? 应用,三次握手 四次挥手?

UDP:在传输数据之前不需要先建立连接,远地的主机收到UDP报文后也不需要任何确认。虽然UDP不提供可靠的交付,但是却非常的高效(QQ语言,QQ视频,直播等等)

TCP:在传送数据时需要先建立连接,结束后要释放连接。TCP不提供广播,多播等服务。增加了许多开销。用于文件传输,发送和接收邮件,远程登录等场景

顺丰科技已上岸面经(收集牛客近年来面经)_第12张图片

第一次握手:Client什么都不能确认,Server确认了对方发送正常,自己接受正常。

第二次握手:Client确认了:自己发送,接受正常;对方发送,接受正常; Server确认了:自己接收正常;对方发送正常

第三次握手:Client全部确认 Server全部确认

四次挥手

确保客户端和服务端 都想结束通讯了。


第八篇

一面

面向对象三大特性

  • 封装
  • 继承
  • 多态

ArrayList和LinkedList区别

ArrayList基于动态数组实现的。LinkedList基于双向链表。可以归结为链表和数组的区别

  • 数组支持随机访问,但插入删除的代价很高,需要移动大量的元素
  • 链表不支持随机访问,但是插入和删除只需改变指针位置

TreeMap

基于红黑树实现的。

是有序的

性能比其他两种Map差

使用 https://www.jianshu.com/p/e11fe1760a3d


HashMap ConcurrentHashMap

HashMap

数组+链表+B+树

默认大小16 扩容因子0.75


ConcurrentHashMap segement锁机制。


线程池参数,创建线程的方式

ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue) {
     
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
  • corePoolSize: 指定了线程池中线程的数量
  • maximumPoolSize: 指定了线程池中最大的线程数量
  • keepAliveTime:当前线程池数量超过corePoolSize时,多余的空闲线程的存活时间,即多次时间时间内会被销毁
  • unit:KeepAliveTime的单位
  • workQueue:任务队列,被提交但尚未被执行的任务
  • threadFactory:线程工厂,用于创建线程,一般用于默认的即可
  • handler:拒绝策略,当任务太多来不及处理,如何拒绝任务

可以使用ThreadPoolExecutor来创建,也可以使用Executors来创建

Executors来创建的方式

  • ExecutorService Pool = Executors.newSingleThreadExecutor();// 单个线程
  • ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建一个固定大小的线程池
  • ExecutorService threadPool = Executors.newCachedThreadPool();//可伸缩的线程池

jvm内存划分

新生代内存结构

复制算法好处

内存划分

  • 新生代
    • 伊甸园
    • FROM
    • TO
  • 老年代

复制算法

优点:

  • 没有标记和清除过程,实现简单,运行高效。
  • 复制过去以后保证空间的连续性,不会出现"碎片"问题。

缺点:

  • 此算法的缺点也是很明显的,就是需要两倍的内存空间。
  • 对于G1这种分拆成大量region的GC,复制而不是移动,意味着GC需要维护region之间对象引用关系,不管是内存占用或者时间开销也不小。

spring事务 事务传播机制

简单的理解就是多个事务方法相互调用时,事务如何在这些方法间传播。

举个栗子,方法A是一个事务的方法,方法A执行过程中调用了方法B,那么方法B有无事务以及方法B对事务的要求不同都会对方法A的事务具体执行造成影响,同时方法A的事务对方法B的事务执行也有影响,这种影响具体是什么就由两个方法所定义的事务传播类型所决定。

事务类型 解释
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED) 支持当前事务,如果没有事务会创建一个新的事务
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS) 支持当前事务,如果没有事务的话以非事务方式执行
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY) 支持当前事务,如果没有事务抛出异常
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW) 创建一个新的事务并挂起当前事务
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED) 以非事务方式执行,如果当前存在事务则将当前事务挂起
NEVER(TransactionDefinition.PROPAGATION_NEVER) 以非事务方式进行,如果存在事务则抛出异常
NESTED(TransactionDefinition.PROPAGATION_NESTED) 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。

redis主从复制相关,如果redis挂了怎么处理,分布式与微服务了解吗

主从复制

在 redis2.8 版本之前主从复制过程如下图:

顺丰科技已上岸面经(收集牛客近年来面经)_第13张图片

复制过程说明:

  • slave 服务启动,slave 会建立和 master 的连接,发送 sync 命令
  • master 启动一个后台进程将数据库快照保存到 RDB 文件中
注意:此时如果生成 RDB 文件过程中存在写数据操作会导致 RDB 文件和当前主 redis 数据不一致,所以此时 master 主进程会开始收集写命令并缓存起来。
  • master 就发送 RDB 文件给 slave
  • slave 将文件保存到磁盘上,然后加载到内存恢复
  • master 把缓存的命令转发给 slave
注意:后续 master 收到的写命令都会通过开始建立的连接发送给 slave。

当 master 和 slave 的连接断开时 slave 可以自动重新建立连接。如果 master 同时收到多个 slave 发来的同步连接命令,只会启动一个进程来写数据库镜像,然后发送给所有 slave。

完整复制的问题:

在 redis2.8 之前从 redis 每次同步都会从主 redis 中复制全部的数据,如果从 redis 是新创建的从主 redis 中复制全部的数据这是没有问题的,但是,如果当从 redis 停止运行,再启动时可能只有少部分数据和主 redis 不同步,此时启动 redis 仍然会从主 redis 复制全部数据,这样的性能肯定没有只复制那一小部分不同步的数据高。

Redis主机挂了

方案:切换主库的身份

# 连接从库
[root@localhost redis-4.0.12]# redis-cli -p 6380

# 取消从库身份
127.0.0.1:6380> slaveof no one
# 连接从库
[root@localhost redis-4.0.12]# redis-cli -p 6381

# 重新设置从库
127.0.0.1:6381> slaveof 127.0.0.1 6380

B树和B+树区别

  • B树的每个结点都存储了key和data,B+树的data存储在叶子节点上。
    节点不存储data,这样一个节点就可以存储更多的key。可以使得树更矮,所以IO操作次数更少。
  • 树的所有叶结点构成一个有序链表,可以按照关键码排序的次序遍历全部记录
    由于数据顺序排列并且相连,所以便于区间查找和搜索。而B树则需要进行每一层的递归遍历。相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。

sql语句执行计划

使用explain

explain  select id from user

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8D2xbdUE-1618387946730)(F:\aboutIT\learningFromduoyi\images\image-20210314221608646.png)]

  • id: select标识符,这是select的查询序列号
  • select_type:查询类型
    • SIMPLE: 简单select(不适用union或子查询)
    • PRIMARY: 最外面的SELECT
    • UNION: UNION中的第二个或后面的select语句
    • DEPENDENT UNION :NION中的第二个或后面的SELECT语句,取决于外面的查询
    • UNION RESULT: UNION的结果
    • SUBQUERY: 子查询中的第一个SELECT
    • DEPENDENT SUBQUERY:子查询中的第一个SELECT,取决于外面的查询
    • DERIVED:导出表的SELECT(FROM子句的子查询)
  • table: 输出的行所用的表
  • partitions:如果查询是基于分区表的话,显示查询将访问的分区。
  • type:
    • range:只检索给定范围的行,使用一个索引来选择行。 走了索引
    • index:该联接类型与ALL相同,除了只有索引树被扫描。这通常比ALL快,因为索引文件通常比数据文件小。 走了索引
    • ALL: 对于每个来自于先前的表的行组合,进行完整的表扫描,说明查询就需要优化了。没走索引
  • possible_keys:指出MySQL能使用哪个索引在该表中找到行
  • key: 显示MySQL实际决定使用的键(索引)。如果没有选择索引,键是NULL。
  • key_len: 显示MySQL决定使用的键长度。如果键是NULL,则长度为NULL。在不损失精确性的情况下,长度越短越好
  • ref: 显示使用哪个列或常数与key一起从表中选择行
  • rows: 显示MySQL认为它执行查询时必须检查的行数。多行之间的数据相乘可以估算要处理的行数。
  • filtered: 显示了通过条件过滤出的行数的百分比估计值。

二面

可以继承string类吗,既然不能继承string,怎么扩展它的方法?

不可以,因为String类有final修饰符,而final修饰的类是不能被继承的,实现细节不允许改变。

扩展?

StringBuffer

StringBuilder


stringbuffer和stringbuilder区别

线程安全

  • StringBuffer: 线程安全 synchronized 修饰的
  • StringBuilder:线程不安全

缓冲区

StringBuffer代码

private transient char[] toStringCache;

@Override
public synchronized String toString() {
     
    if (toStringCache == null) {
     
        toStringCache = Arrays.copyOfRange(value, 0, count);
    }
    return new String(toStringCache, true);
}

StringBuild代码

@Override
public String toString() {
     
    // Create a copy, don't share the array
    return new String(value, 0, count);
}

可以看出,StringBuffer 每次获取 toString 都会直接使用缓存区的 toStringCache 值来构造一个字符串。

而 StringBuilder 则每次都需要复制一次字符数组,再构造一个字符串。

所以,缓存冲这也是对 StringBuffer 的一个优化吧,不过 StringBuffer 的这个toString 方法仍然是同步的。

性能

既然 StringBuffer 是线程安全的,它的所有公开方法都是同步的,StringBuilder 是没有对方法加锁同步的,所以毫无疑问,StringBuilder 的性能要远大于 StringBuffer。


什么是线程安全问题?synchronized底层实现

sycnronized的作用

  • 原子性:synchronized保证语句块内操作是原子的
  • 可见性:synchronized保证可见性(通过“在执行unlock之前,必须先把此变量同步回主内存”实现)
  • 有序性:synchronized保证有序性(通过“一个变量在同一时刻只允许一条线程对其进行lock操作”)

sycnronized的使用

  • 修饰实例方法,对当前实例对象加锁
  • 修饰静态方法,多当前类的Class对象加锁
  • 修饰代码块,对synchronized括号内的对象加锁

https://blog.csdn.net/weixin_38481963/article/details/88384493


hashmap的实现,arraylist和linkedlist区别

大多数情况下可以直接定位到值,具有很快的访问速度。 非线程安全。(ConcurrentHashMap)

初始化的时候默认的容量是16 (必须是2的指数此幂)

Java7 —> 数组 + 链表

Java8 ----> 数组 + 链表 + 红黑树 (性能提升10%左右)


ArrayList基于动态数组实现的。LinkedList基于双向链表。可以归结为链表和数组的区别

  • 数组支持随机访问,但插入删除的代价很高,需要移动大量的元素
  • 链表不支持随机访问,但是插入和删除只需改变指针位置

springboot特性,自动配置原理

这个被问到了不太懂,我只是大概讲了下。有点复杂,看源码


redis数据类型,set和zset区别

redis数据类型

  • String
  • Hash
  • List
  • set
  • Zset

spring事务配置和隔离级别

事务配置

  • 一种是在配置文件(xml)中做相关的事务规则声明
  • 另一种是基于 @Transactional 注解的方式。

隔离级别

  • 读未提交
  • 读已提交
  • 可重复读
  • 串行化

删除表格中某行数据的语句? 建立索引的sql语句?索引的优化?

delete from Table where id = 1 ?

create index (index_name) on table_name(column_name)


sql语句: 统计几个班男女生的人数?(SQL实现)

select 性别,count(*) 人数 from student group by 性别;


单点登录

  1. 用户访问app系统,app系统是需要登录的,但用户现在没有登录。
  2. 跳转到CAS server,即SSO登录系统,以后图中的CAS Server我们统一叫做SSO系统。 SSO系统也没有登录,弹出用户登录页。
  3. 用户填写用户名、密码,SSO系统进行认证后,将登录状态写入SSO的session,浏览器(Browser)中写入SSO域下的Cookie。
  4. SSO系统登录完成后会生成一个ST(Service Ticket),然后跳转到app系统,同时将ST作为参数传递给app系统。
  5. app系统拿到ST后,从后台向SSO发送请求,验证ST是否有效。
  6. 验证通过后,app系统将登录状态写入session并设置app域下的Cookie。

至此,跨域单点登录就完成了。以后我们再访问app系统时,app就是登录的。接下来,我们再看看访问app2系统时的流程。

  1. 用户访问app2系统,app2系统没有登录,跳转到SSO。
  2. 由于SSO已经登录了,不需要重新登录认证。
  3. SSO生成ST,浏览器跳转到app2系统,并将ST作为参数传递给app2。
  4. app2拿到ST,后台访问SSO,验证ST是否有效。
  5. 验证成功后,app2将登录状态写入session,并在app2域下写入Cookie。

https://www.jianshu.com/p/75edcc05acfd


第九篇

Redis的应用场景

  1. 缓存数据:最常用,对经常需要查询且变动不是很频繁的数据常称作热点数据;
  2. 消息队列:相当于消息订阅系统,比如ActiveMQ、RocketMQ。如果对数据有较高一致性要求时,还是建议使用MQ;
  3. 计数器:比如统计点击率、点赞量,redis具有原子性,可以避免并发问题;
  4. 电商网站信息:大型电商平台初始化页面数据的缓存。比如去哪儿网购买机票的时候首页的价格和你点进去的价格会有差异;
  5. 热点数据:比如新闻网站实时热点、微博热搜等,需要频繁更新。总数据量比较大的时候直接从数据库查询会影响性能;
  6. 分布式会话:也称为单点登录,保证分布式环境下session一致性;
  7. 秒杀、抢购等场景;
  8. 排行榜,比如在线答题得分排行等;
  9. 分布式锁:防止大并发产生问题;
  10. 数据过期处理:利用redis设置的超时时间,可以精确到毫秒。

Springboot和Spring的区别

Spring

包含一些很好用的功能,如依赖注入和开箱即用的模块:SpringJDBC、SpringMVC、SpringSecurity、SpringAOP、SpringORM、SpringTest 。

SpringBoot

是Spring的扩展,消除了设置Spring应用程序所需的XML配置,为更快,更高效的开发生态系统铺平道路,以下是一些特征:

  1. 创建独立的Spring应用‘
  2. 嵌入式Tomcat,Jetty
  3. 提供starters简化构建配置
  4. 尽可能的自动配置Spring应用
  5. 完全没有代码生成和XML配置要求

从配置分析

  • Spring创建web应用程序所需的最小依赖项
<dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-webartifactId>
    <version>5.1.0.RELEASEversion>
dependency>
<dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-webmvcartifactId>
    <version>5.1.0.RELEASEversion>
dependency>
  • SpringBoot创建来启动的最小依赖项
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId> spring-boot-starter-webartifactId>
    <version>2.0.6.RELEASEversion>
dependency>

Spring对于测试库: SpringTest, JUnit。需要将这些作为依赖项导入。但是在SpringBoot中,我们只需要添加Spring-boot-starter-test依赖来自动包含这些库

SpringBoot为不同的Spring模块提供了许多依赖项,一些最常用的是:

spring-boot-starter-data-jpa spring-boot-starter-security spring-boot-starter-test spring-boot-starter-web spring-boot-starter-thymeleaf。


MVC配置

  • Spring需要定义调度程序Servlet,映射和其他支持配置。我们可以使用web.xml或Initializer来完成操作。

如果使用servlet创建之后,还是需要使用 @EnableWebMvc注释添加到@Configuration类,并定义视图解析器从控制器返回。

  • SpringBoot一旦我们添加了Web启动程序,只需要在application配置文件中配置就行了。

这意味着SpringBoot将查看应用程序中存在的依赖项,属性和bean,并根据这些依赖项,对属性和bean进行配置。当然,如果我们想要添加自己的自定义配置,那么SpringBoot自动配置将会退回。

配置模板引擎

现在我们来看下如何在Spring和Spring Boot中配置Thymeleaf模板引擎

  • Spring中,为视图解析器添加thymeleaf-spring5依赖和一些配置。(可以是类也可以是xml文件)
  • SpringBoot只需要导入 spring-boot-starter-thymeleaf的依赖,来启动Web应用程序中的Thymeleaf支持。

应用程序启动引导配置

  • Spring 引导配置 Spring支持传统的 web.xml引导方式以及最新的 Servlet3+方法。

配置 web.xml方法启动的步骤

  1. Servlet容器(服务器)读取 web.xml
  2. web.xml中定义的 DispatcherServlet由容器实例化
  3. DispatcherServlet通过读取 WEB-INF/{servletName}-servlet.xml来创建WebApplicationContext。最后, DispatcherServlet注册在应用程序上下文中定义的 bean

使用 Servlet3+方法的 Spring启动步骤 (容器搜索实现)

  1. ServletContainerInitializer的类并执行
  2. SpringServletContainerInitializer找到实现所有类
  3. WebApplicationInitializer创建具有XML或上下文 @Configuration类 WebApplicationInitializer创建 DispatcherServlet与先前创建的上下文。

SpringBoot引导配置

Spring Boot应用程序的入口点是使用@SpringBootApplication注释的类

默认情况下, SpringBoot使用嵌入式容器来运行应用程序。在这种情况下, SpringBoot使用 publicstaticvoidmain入口点来启动嵌入式 Web服务器。

此外,它还负责将 Servlet, Filter和 ServletContextInitializerbean从应用程序上下文绑定到嵌入式 servlet容器。SpringBoot的另一个特性是它会自动扫描同一个包中的所有类或 Main类的子包中的组件

打包和部署

这两个框架都支持 Maven和 Gradle等通用包管理技术。但是在部署方面,这些框架差异很大。例如,Spring Boot Maven插件在 Maven中提供 SpringBoot支持。它还允许打包可执行 jar或 war包并 就地运行应用程序。

在部署环境中 SpringBoot 对比 Spring的一些优点包括:

  1. 提供嵌入式容器支持
  2. 使用命令java -jar独立运行jar
  3. 在外部容器中部署时,可以选择排除依赖关系以避免潜在的jar冲突
  4. 部署时灵活指定配置文件的选项
  5. 用于集成测试的随机端口生成

数据库外键? 如何实现数据库乐观锁?

数据库乐观锁实现方式:

  1. 借助数据库表增加一个版本号的字段version(数字类型),每次更新一行记录,都使得该行版本号加一,开始更新之前先获取version的值,更新提交的时候带上之前获取的version值与当前version值作比较,如果不相等则说明version值发生了变化则检测到了并发冲突,本次操作执行失败,如果相等则操作执行成功。
update table set columnA = 1,version=version+1 where id=#{id} and version = #{oldVersion}
  1. 借助行更新时间时间戳,检测方法则与方式1相似,即更新操作执行前先获取记录当前的更新时间,在提交更新时,检测当前更新时间是否与更新开始时获取的更新时间时间戳相等。
  2. 前面2种方式都是提交的时候检测版本有没有改变,只要有变化都会失败,而有一类场景当字段只需要满足一个区间范围并不关心是否有数据更新冲突,且本身进行更新并且作为判断条件时,可不借助其他字段,对字段本身作判断即可。例如一个较常见的场景:库存的扣减,只要扣减后的值大于等于零即可。
update product set rest = rest– #{deduct} where name = ‘abc’ and rest >= #{deduct}

缺点

  • 需要对表的设计增加额外的字段,增加了数据库的冗余,另外,当应用并发量高的时候,version值在频繁变化,则会导致大量请求失败,影响系统的可用性。

  • 我们通过上述sql语句还可以看到,数据库锁都是作用于同一行数据记录上,这就导致一个明显的缺点,在一些特殊场景,如大促、秒杀等活动开展的时候,大量的请求同时请求同一条记录的行锁,会对数据库产生很大的写压力。

悲观锁实现

只需要添加for update语句就能执行行锁

select * from table where id = 1 for update;

缺点

  • 在高并发环境下,容易造成大量请求阻塞,影响系统可用性
  • 悲观锁使用不当还可能产生死锁的情况

为什么不建议使用数据库外键?

外键优点

  • 保证数据的完整性和一致性;
  • 级联操作方便;
  • 将数据完整性判断托付给了数据库完成,减少了程序的代码量;

性能问题

假设一张表名为user_tb。那么这张表里有两个外键字段,指向两张表。那么,每次往user_tb表里插入数据,就必须往两个外键对应的表里查询是否有对应数据。如果交由程序控制,这种查询过程就可以控制在我们手里,可以省略一些不必要的查询过程。但是如果由数据库控制,则是必须要去这两张表里判断。

并发问题

在使用外键的情况下,每次修改数据都需要去另外一个表检查数据,需要获取额外的锁。若是在高并发大流量事务场景,使用外键更容易造成死锁。

扩展性问题

做平台迁移方便,比如你从Mysql迁移到Oracle,像触发器、外键这种东西,都可以利用框架本身的特性来实现,而不用依赖于数据库本身的特性,做迁移更加方便。

分库分表方便,在水平拆分和分库的情况下,外键是无法生效的。将数据间关系的维护,放入应用程序中,为将来的分库分表省去很多的麻烦。

技术问题

使用外键,其实将应用程序应该执行的判断逻辑转移到了数据库上。那么这意味着一点,数据库的性能开销变大了,那么这就对DBA的要求就更高了。很多中小型公司由于资金问题,并没有聘用专业的DBA,因此他们会选择不用外键,降低数据库的消耗。

相反的,如果该约束逻辑在应用程序中,发现应用服务器性能不够,可以加机器,做水平扩展。如果是在数据库服务器上,数据库服务器会成为性能瓶颈,做水平扩展比较困难。


冒泡排序最少使用几个for

单个for的冒泡排序算法:

public static void bubbleSort(int[] arr) {
     
		//定义比较次数,次数为数组长度-1
		int times =arr.length-1;
		for (int i = 0; i < times ; i++) {
     
			//arr[i]是否大于arr[i+1]
			if(arr[i]>arr[i+1]){
     
				//arr[i]比arr[i+1]大,那么交换之
				int temp=arr[i];
				arr[i]=arr[i+1];
				arr[i+1]=temp;
			}
			//关键点,判断是否是最后一次比较,若是,那么重置变量
			if(i==times-1){
     
				i=-1;//i重置为-1,随后for循环会++,因此下次比较时i值为0
				times--;//比较次数递减1
			}
		}
}

接口和抽象类的区别?

抽象类

含有abstract修饰符的class即为抽象类,abstract 类不能创建实例对象。含有abstract方法的类必须定义为abstract class,abstract class类中的方法不必是抽象的。abstract class类中定义抽象方法必须在具体(Concrete)子类中实现,所以,不能有抽象构造方法或抽象静态方法。如果子类没有实现抽象父类中的所有抽象方法,那么子类也必须定义为abstract类型。

接口

接口(interface)可以说成是抽象类的一种特例,接口中的所有方法都必须是抽象的。接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为public static final。

语法上的区别

1.抽象类可以有构造方法,接口中不能有构造方法。

2.抽象类中可以有普通成员变量,接口中没有普通成员变量

3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。

  1. 抽象类中的抽象方法的访问类型可以是public,protected和(默认类型,虽然

eclipse下不报错,但应该也不行),但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。

  1. 抽象类中可以包含静态方法,接口中不能包含静态方法

  2. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。

  3. 一个类可以实现多个接口,但只能继承一个抽象类。


List和Set的区别?

它们都处于java.util包中,Set、List和Map都是接口,它们有各自的实现类。Set的实现类主要有HashSet和TreeSet,List的实现类主要有ArrayList和LinkedList。

List和Set的区别

1、List,Set都是继承自Collection接口
2、List特点:元素有放入顺序,元素可重复 ,Set特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉,(元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的,加入Set 的Object必须定义equals()方法 ,另外list支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。)
3.Set和List对比:
Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。


附加:自己觉得比较重要的知识点

redis分区

概念: 分区是分割数据到多个Redis实例的处理过程,因此每个实例只保存key的一个子集

分区的优势:

  • 通过利用多台计算机内存的和值,允许我们构造更大的数据库
  • 通过多核和多台计算机,允许我们扩展计算机能力;通过多台计算机和网络适配器,允许我们扩展网络带宽,提高redis性能

分区的不足:

  • 多键操作是不被支持的,比如我们将要批量操作的键被映射到了不同的Redis实例中
  • 多键Redis事务是不被支持的
  • 分区的最小粒度是键,因此我们不能将关联到一个键的很大的数据集映射到不同的实例
  • 当应用分区表的时候,数据的处理是非常复杂的,比如我们需要处理多个rdb/aof文件,将分布在不同实例的文件聚集到一起备份
  • 添加和删除机器是很复杂的,例如Redis集群支持几乎运行时透明的因为增加或减少机器而需要做的realancing,然而像客户端和代理分区这种方式是不支持这种功能的。

分区的实现:

范围分区:

用户根据数据对应的ID从0到10000的用户映射R0 10000-20000用户映射到R1… 依次分区。

存在以下问题:

  • 需要一张表,用来存储ID范围到Redis实例的映射关系,比较难维护。
  • 对于以后的一种新的类型,我们都需要维护一张这样的表。每有一种新的类型我们就需要有一张映射表。
  • 如果我们要存储的数据的key不是整数,而是一组uuid就不好分区了

哈希分区:

哈希可以适应任何形式的key,而不像范围分区一样需要key的形式为int,比较简单。一个公式就可以表达:

id = hash(key)%N

hash可以使用(crc32函数)计算出一个数值型的值。然后按照分区的个数取余就行了N就是分区的个数


es读写一致性

ES数据并发冲突控制是基于乐观锁和版本号机制

当一个document第一次创建的时候,他的_version 内部版本号就是1;以后每次对这个document执行修改或者删除操作,都会对这个__version 版本号自动加1;哪怕是删除,也会对这条数据的版本号加1(假删除)

客户端对es数据做更新的时候,如果带上了版本号那带的版本号与es中文档的版本号一致才能修改成功,否则抛出异常。如果客户端没有带上版本号,首先会读取最新版本号才做更新尝试,这个尝试类似于CAS操作,可能需要尝试很多次才能成功。乐观锁的好处是不需要互斥锁的参与。

es更新之后会向副本节点同步更新数据(同步写入),直到所有副本都更新了才返回成功


Elasticsearch Master节点的职责

  1. 由主节点负责ping所有其他节点,判断是否节点已经挂掉
  2. 创建或删除索引
  3. 决定分片在节点之间的分配

HTTP/HTTPS

HTTP

基于TCP/IP通信协议来传递数据的协议,传输的数据类型为HTML文件,图片文件,查询结果等。

HTTP协议一般用于B/S架构()。浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。

HTTP请求报文构成

  1. 请求行:包括请求方法,URL,协议/版本
  2. 请求头(Request Header)
  3. 请求正文

响应报文构成

  1. 状态行
  2. 响应头
  3. 响应正文

Http存在如下问题:

  • 请求信息明文传输,容易被窃听截取
  • 数据的完整性未校验,容易被篡改
  • 没有验证对方身份,存在冒充危险

HTTPS

HTTPS协议:一般理解为HTTP+SSL/TLS,通过SSL证书来验证服务器的身份,并为浏览器和服务器之间的通信进行加密。

SSL

SSL是位于TCP/IP协议与各种应用层协议之间,为数据通讯提供安全支持。

浏览器在使用HTTPS传输数据的流程是什么?

顺丰科技已上岸面经(收集牛客近年来面经)_第14张图片

  1. 首先客户端通过URL访问服务器建立SSL连接。
  2. 服务端收到客户端请求后,会将网站支持的证书信息(证书中包括公钥)传送一份给客户端
  3. 客户端的服务器开始协商SSL连接的安全等级,也就是信息加密的等级。
  4. 客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥会话密钥加密,并传送给网站。
  5. 服务器利用自己的私钥解密出会话密钥
  6. 服务端利用会话密钥加密与客户端之间的通信

HTTPS的缺点

  • HTTPS协议多次握手,导致页面的加载时间延长近50%
  • HTTPS连接缓存不如HTTP高效,会增加数据开销和功耗
  • 申请SSL证书需要钱,功能越强大的证书费用越高
  • SSL涉及到的安全算法会消耗CPU资源,对服务器资源消耗较大

Java中的数据引用

强引用

把一个对象赋值给一个引用变量,这样引用变量就是一个强引用。当一个对象被引用变量引用时,它处于可达状态,不会被JVM回收。因此是造成Java内存泄漏的主要原因之一。

Object obj = new Object();

软引用

软引用需要用 SoftReference 类来实现,对于只有软引用的对象来说,当系统内存足够时它不会被回收,当系统内存空间不足时它会被回收。软引用通常用在对 内存敏感的程序中。

Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null; // 使对象只被软引用关联

弱引用

弱引用需要用 WeakReference 类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够, 总会回收该对象占用的内存。

Object obj = new Object();
WeakReference<Widget> weakWidget = new WeakReference<Widget>(obj);
obj = null;

虚引用

虚引用需要 PhantomReference 类来实现,它不能单独使用,必须和引用队列联合使用。 虚引用的主要作用是跟踪对象被垃圾回收的状态。(唯一目的是能在这个对象被回收时收到一个通知)

Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj, null);
obj = null;

队列,栈底层

都可以使用ArrayList 和 LinkedList来实现。


Java异常机制

顺丰科技已上岸面经(收集牛客近年来面经)_第15张图片

Java语言按照错误严重性,从throwable根类衍生出Error和Exception两大派类

Error

程序在执行过程中所遇到的硬件或操作系统的错误。错误对程序而言是致命的,将导致程序无法运行。常见的错误有内存溢出,jvm虚拟机自身的非正常运行,calss文件没有主方法。程序本生是不能处理错误的,只能依靠外界干预。Error是系统内部的错误,由jvm抛出,交给系统来处理。

Exception(异常)

是程序正常运行中,可以预料的意外情况。比如数据库连接中断,空指针,数组下标越界。异常出现可以导致程序非正常终止,也可以预先检测,被捕获处理掉,使程序继续运行。

EXCEPTION(异常)按照性质,又分为编译异常(可检测)和运行时异常(不可检测)。

编译时异常

又叫可检查异常,通常时由语法错和环境因素(外部资源)造成的异常。比如输入输出异常IOException,数据库操作SQLException。其特点是,Java语言强制要求捕获和处理所有非运行时异常。通过行为规范,强化程序的健壮性和安全性。

运行时异常

又叫不检查异常RuntimeException,这些异常一般是由程序逻辑错误引起的,即语义错。比如算术异常,空指针异常NullPointerException,下标越界IndexOutOfBoundsException。运行时异常应该在程序测试期间被暴露出来,由程序员去调试,而避免捕获。


所以,java语言处理运行时错误有三种方式

  1. 程序不能处理的错误,
  2. 程序应该避免而可以不去捕获的运行时异常
  3. 必须捕获的非运行时异常

阻塞队列原理

阻塞队列是一种可以再多线程环境下使用,并且支持阻塞等待的队列。支持在队列为空时,获取元素的线程会等待队列变为非空,当队列满时,存储元素的线程会等待队列可用。

JDK提供的阻塞队列

  1. ArrayBlockingQueue:

基于数组实现的一个阻塞队列,在创建ArrayBlockingQueue对象时必须指定容量大小。并且可用指定公平性与非公平性,默认情况下为非公平性,即不保证等待时间最长的队列最优先能够访问队列

  1. LinkedBlockingQueue

基于链表实现的一个阻塞队列,在创建LinkedBlockingQueue对象时如果不指定容量大小,则默认大小为Integer.MAX_VALUE

  1. PriorityBlockingQueue:

以上2种队列都是先进先出队列,而PriorityBlockingQueue却不是,它会按照元素的优先级对元素进行排序,按照优先级顺序出队,每次出队的元素都是优先级最高的元素。注意,此阻塞队列为无界阻塞队列,即容量没有上限,前面2中都是有界队列

  1. DelayQueue

基于PriorityQueue,一种延时阻塞队列,DelayQueue中的元素只有当其指定的延时时间到了吗,才能够从队列中获取到该元素。DelayQueue也是一个无界队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会阻塞

  1. SynchronousQueue

SynchronousQueue是一个不存储元素的阻塞队列。每一个put操作必须等待一个take操作,否则不能继续添加元素


三种类型的BlockingQueue

无界队列

队列大小无限制,常用的为无界的LinkedBlockingQueue,使用该队列作为阻塞队列时要尤其当心,当任务耗时较长时可能会导致大量新任务在队列中堆积最终导致OOM。阅读代码发现,Executors.newFixedThreadPool 使用的就是LinkedBlockingQueue,导致cpu和内存飙升导致服务器挂掉。

有界队列

一类是遵循FIFO原则的队列例如ArrayBlockingQueue,另一类是优先级队列PriorityBlockingQueue

PriorityBlockingQueue中的优先级由任务的Comparator决定。

使用有界队列时队列大小需要和线程池大小互相配合,线程池较小有界队列较大时可减少内存消耗,降低CPU使用率和上下文切换,但是可能会限制系统吞吐量。

同步移交队列

如果不希望任务在队列中等待而是希望将任务直接交给工作线程,可使用SynchronousQueue作为等待队列。SynchronousQueue不是一个真正的队列,而是一种线程之间移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。只有在使用无界线程池或者有饱和策略时才建议使用该队列。

https://blog.csdn.net/qq_35909080/article/details/87002367


滑动窗口

固定窗口

  1. 如果说窗口过小,那么当传输比较大的数据的时候需要不停的对数据进行确认,这个时候会造成很大的延迟。
  2. 如果说窗口的大小定义的过大。我们假设发送方一次发送100个数据,但是接收方只能处理50个数据。这样每次都会只对这50个数据进行确认。发送方下一次还是发送100个数据,但是接收方还是只能处理50个数据。这样就有不必要的数据来拥塞我们的链路。

所以我们引入了滑动窗口机制,窗口的大小并不是固定的而是根据我们之间的链路的带宽的大小,链路是否拥塞,接收方是否能处理这么多数据,三个元素共同决定的

滑动窗口

  1. 允许发送方在停止并等待确认前可以连续多个分组。由于发送方不必每发送每确认,因此该协议可以加速数据的传输。
  2. 在接收窗口向前滑动时(与此同时 也发送了确认),发送窗口也会同步向前滑动,收发两端的窗口按照以上规律不断的向前滑动,可以动态调整窗口大小。

https://blog.csdn.net/h2604396739/article/details/85239439?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.control&dist_request_id=&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.control


zookeeper基本数据类型

  • 临时节点:客户端和服务器端断开连接后,创建的节点自己删除。
  • 临时顺序节点:客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号,顺序编号有小到大。
  • 持久节点:客户端和服务器端断开连接后,创建的节点不删除。
  • 持久顺序节点:客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号,顺序编号有小到大。
  • -e 创建的是临时节点
  • -s 创建的是顺序节点

https://www.jianshu.com/p/40b0bb3c21e7


我自己的面经

上面部分为我在牛客上收集到的近年来部分面经,感觉对我这次面试帮助挺大的。下面是我自己面试被问到的问题!有点久了,有些忘记了。

  • JVM内存模型
  • 垃圾回收算法,机制
  • 有什么锁?乐观锁,悲观锁
  • redis集群搭建。
  • elasticsearch倒排索引
  • elasticsearch分页如何做、
  • HashMap底层,以及ConcurrentHashMap的分段锁机制
  • B+树的特点,以及和B树的区别
  • nginx有什么用?搭建负载均衡有几种方式?
  • kafka消息队列返回0 1 -1分别代表什么?
  • Springboot启动加载流程
  • dubbo了解吗
  • redis如何保证高可用
  • mysql如何排查慢查询
  • 场景题1:分析公司内部数据库为走所有的情况?
  • 场景题2:老板叫你收集某市某年的建筑垃圾你会如何做?

你可能感兴趣的:(后端面试题,顺丰科技,面经,春招)