服务端开发之Java备战秋招面试篇8

努力了那么多年,回头一望,几乎全是漫长的挫折和煎熬。对于大多数人的一生来说,顺风顺水只是偶尔,挫折、不堪、焦虑和迷茫才是主旋律。我们登上并非我们所选择的舞台,演出并非我们所选择的剧本。继续加油吧!

目录

1.List和set区别?

2.Hashmap多线程会出现什么问题?

3.Innodb中有几种锁?排它锁和共享锁有什么区别?

4.Sql语句可以设置走行级锁吗?

5.Redis中缓存雪崩和缓存穿透和缓存击穿解释一下?

6.数据库用到了联合索引吗?如何设计的?

7.为什么B+树作为索引的底层效率高?

8.一个sql语句去查询的话经过那些步骤大致流程是怎么样的?

9.没有索引的话如何去查询?

10.MVCC解决什么问题?如何去执行的?

11.Redis几种数据类型,zset你是如何使用的,场景有哪些?

12.arraylist的底层实现?

13.垃圾什么时候会被进行回收?

14.synchronized是属于乐观锁还是悲观锁?属于公平锁还是非公平锁?

15.递归的一些特点?

16.HTTP , TCP ,Socket之间的关系?

17.端口的作用?

18.MySQL中事物的四种隔离级别,解决的问题,性能比较?

19.写个快排,写个DFS?

20.泛型、反射的理解和应用场景?

21.发起一个HTTP请求到收到一个响应中间所发送的事情?

22.使用Socket编程的流程?

23.volatile保证了什么,具体的内存屏障,volatile加在基本类型和对象上的区别?

24.垃圾回收的算法及详细介绍?


1.List和set区别?

(1)List、Set都继承自Collection接口;List的特点:元素有放入顺序,且可重复;Set的特点:元素无放入顺序,且不可重复(注意:元素虽然无放入顺序,但是元素在Set中的位置是由该元素的HashCode决定的,其位置是固定的)。List支持for循环,也就是通过下标来遍历,也可以用迭代器,但是Set只能用迭代器,因为他无序,无法使用下标取值;
(2) List接口有三个实现类:LinkedList,ArrayList,Vector。Set接口有两个实现类:HashSet(底层由HashMap实现),LinkedHashSet
(3) Set:检索元素效率低,删除和插入效率高,插入和删除不会引起元素位置改变。List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。

2.Hashmap多线程会出现什么问题?

(1) 丢失元素问题:当多线程同时put值的时候,若发生hash碰撞,可能多个元素都落在链表的头部,从而造成元素覆盖(hashcode相同而eques值不同的元素),线程A put一个元素a ,线程B put一个元素b,a,b 发生hansh碰撞,本应该在map是链表的形式存在,但是可能线程A和线程B同时put到链表的第一个位置,从而后来者覆盖前者元素造成元素丢失。

(2) put 造成链表形成闭环,get的时候出现死循环。该情况是出现在多线线程操作map扩容时会发生,因为扩容之后链表中元素的会发生逆转, 所以会产生循环链表。jdk1.8中改进了resize方法,改进之后的方法不再进行链表的逆转, 而是保持原有链表的顺序, 如果在多线程环境下, 顶多会在链表后边多追加几个元素而已, 不会出现环的情况。

3.Innodb中有几种锁?排它锁和共享锁有什么区别?

InnoDB按照锁的粒度分为全局锁,表级锁,行级锁。

其中,全局锁对数据库中的所有表进行加锁;表级锁包括表锁(共享读锁、独占写锁)、元数据锁(不同客户端执行DML和DDL语句,锁会冲突,避免读写的不一致性。)、意向锁(不需要逐行检查是否有行锁,只需要根据意向锁进行判定是否可以加表锁即可);行级锁包括行锁(共享锁/排它锁)、间隙锁(更新一条之前不存在的记录,会在两条记录之间加上间隙锁)、临键锁(行锁+间隙锁)。

行锁分 共享锁 和 排它锁。

共享锁又称:读锁。当一个事务对某几行上读锁时,允许其他事务对这几行进行读操作,但不允许其进行写操作,也不允许其他事务给这几行上排它锁,但允许上读锁。

排它锁又称:写锁。当一个事务对某几个上写锁时,不允许其他事务写,但允许读。更不允许其他事务给这几行上任何锁。包括写锁。

4.Sql语句可以设置走行级锁吗?

InnoDB 引擎是支持行级锁的,而 MyISAM 引擎并不支持行级锁。

5.Redis中缓存雪崩和缓存穿透和缓存击穿解释一下?

① 缓存穿透:大量请求根本不存在的key,请求根本不存在的资源(DB本身就不存在,Redis更是不存在)

解决方案:

  • 对空值进行缓存 
  • 使用布隆过滤器( 使用BitMap作为布隆过滤器,将目前所有可以访问到的资源通过简单的映射关系放入到布隆过滤器中(哈希计算),当一个请求来临的时候先进行布隆过滤器的判断,如果有那么才进行放行,否则就直接拦截)

② 缓存雪崩:redis中大量key集体过期(下文详解)

③ 缓存击穿:redis中一个热点key过期(大量用户访问该热点key,但是热点key过期)

缓存雪崩和缓存穿透解决方案:

  • 进行预先的热门词汇的设置,进行key时长的调整
  • 实时调整,监控哪些数据是热门数据,实时的调整key的过期时长
  • 使用锁机制(当热点key过期,那么就使用锁机制防止大量的请求直接打在DB)

6.数据库用到了联合索引吗?如何设计的?

联合索引有个规则,叫最左前缀匹配规则,即 SQL 语句中用到了联合索引中的最左边的索引,那么这条 SQL 语句就可以利用这个联合索引去进行匹配。避免回表。两个单列查询返回行较多,同时查返回行较少,联合索引更高效。

7.为什么B+树作为索引的底层效率高?

B+树可以理解是扁且宽的。也就是层数少,每层的节点数目很多。但是每层的节点多归多,却不存储数据,只起到索引效果,所有的数据都存在叶子节点上头。
B+ 树所有的 Data 域在叶子节点,一般来说都会进行一个优化,就是将所有的叶子节点用指针串起来。这样遍历叶子节点就能获得全部数据,这样就能进行区间访问啦。

下来说Mysql如何衡量查询效率:主要是通过磁盘IO次数判断
B+树中,层数少,只在叶子节点存数据的特点就能极大的保证磁盘IO次数少,进而说,效率高~
 

8.一个sql语句去查询的话经过那些步骤大致流程是怎么样的?

(1)客户端发送查询语句给服务器。
(2)服务器首先检查缓存中是否存在该查询,若存在,返回缓存中存在的结果。若是不存在就进行下一步。
(3)服务器进行SQL的解析、语法检测和预处理,再由优化器生成对应的执行计划。
(4)Mysql的执行器根据优化器生成的执行计划执行,调用存储引擎的接口进行查询。
(5)服务器将查询的结果返回客户端。

9.没有索引的话如何去查询?

同上一题。

10.MVCC解决什么问题?如何去执行的?

MVCC,多版本并发控制,指的是维持一个数据的多个版本,使得读写没有冲突。在程序中实现事务内存。在数据库管理系统中,实现对数据库的并发访问,主要解决了不可重复读的问题。MVCC是利用记录的版本链和ReadView,来控制并发事务访问相同记录时的行为。

ReadView即一致性视图,用来判断版本链中的哪个版本是当前事务可见的。

RC和RR隔离级别之间一个非常大的区别就是——它们生成ReadView的时机不同!!
RC(READ COMMITTED)——在一个事务中,每次读取数据前都生成一个ReadView。
RR(REPEATABLE READ)——在一个事务中,只在第一次读取数据时生成一个ReadView。

11.Redis几种数据类型,zset你是如何使用的,场景有哪些?

它支持数据结构,如 字符串,散列,列表,集合,带有范围查询的排序集(sorted sets),位图(bitmaps),超级日志(hyperloglogs),具有半径查询和流的地理空间索引。

在springboot项目里可以添加依赖,然后使用redisTemplate模板存值,有序集合(zset):有序集合和集合有着必然的联系,它和set一样是不可重复的,区别在于多了score值,用来代表排序的权重。也就是当你需要一个有序的,不可重复的集合列表时,就可以考虑使用这种数据类型。排行榜:有序集合经典使用场景。例如小说视频等网站需要对用户上传的小说视频做排行榜,榜单可以按照用户关注数,更新时间,字数等打分,做排行。
 

12.arraylist的底层实现?

JDK1.8中ArrayList()初始化后的底层数组长度为0,且在添加第一个元素时,底层数据长度变为10,之后扩容按原来的1.5倍进行扩容。本质上就是通过定义新的更大的数组,将旧数组中的内容拷贝到新数组,来实现扩容

13.垃圾什么时候会被进行回收?

(1)老年代的内存使用率达到了一定阈值,会触发Full GC 

(2)执行System.gc()
        不要频繁使用gc函数。建议是:保持代码健壮(记得将不用的变量置为null),让虚拟机去管理内存。System.gc():提醒虚拟机进行垃圾回收,回不回收由虚拟机决定。若虚拟机决定回收,也不是立刻进行回收,它是异步的。 当然,System.gc()也不是一点用也没有,当执行System.gc()之后,还是能一定程度影响垃圾回收的。

14.synchronized是属于乐观锁还是悲观锁?属于公平锁还是非公平锁?

悲观锁,也是非公平锁。 Synchronized在线程进入ContentionList时,等待的线程会先尝试自旋获取锁,如果获取不到就进入ContentionList,这明显对于已经进入队列的线程是不公平的,还有一个不公平的事情就是自旋获取锁的线程还可能直接抢占OnDeck线程的锁资源。

15.递归的一些特点?

函数定义中所具有的这些特点是判断函数是否为递归函数的基本要素。
换言之,能用递归解决的问题通常具有两个特点:
1 有退出条件(递归出口)
2 外层需要用到内层算出的结果(也可能是内层需要外层的计算结果,但比较少见)
最难的地方是找出外层利用内层结果的方法,这往往需要在思考问题的过程中发现规律,纸笔是不可缺少的。

16.HTTP , TCP ,Socket之间的关系?

HTTP 协议:超文本传输协议,对应于应用层,用于如何封装数据。

TCP/UDP 协议:传输控制协议,对应于传输层,主要解决数据在网络中的传输。

IP 协议:对应于网络层,同样解决数据在网络中的传输。

TCP/IP是传输层协议,主要解决数据如何在网络中传输;而HTTP是应用层协议,主要解决如何包装数据。

web 使用 HTTP 作应用层协议,以封装 HTTP 文本信息,然后使用 TCP/IP 做传输层协议,将数据发送到网络上。

Socket(套接字)是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,才能使用TCP/IP协议。

17.端口的作用?

可以参考这篇博客,网络端口的作用及分类_TerryZjl的博客-CSDN博客  ,写的不错。

在网络技术中,端口(Port)大致有两种意思:一是物理意义上的端口,比如,ADSL Modem、集线器、交换机、路由器用于连接其他网络设备的接口,如RJ-45端口、SC端口等等。二是逻辑意义上的端口,一般是指TCP/IP协议中的端口,端口号的范围从0到65535,比如用于浏览网页服务的80端口,用于FTP服务的21端口等等。
端口的作用:主机通过端口区分不同的网络服务。

18.MySQL中事物的四种隔离级别,解决的问题,性能比较?

四种隔离级别:读未提交、读已提交、可重复读、串行化。

读已提交:解决脏读

可重复读:解决脏读喝不可重复度读。

串行化:解决脏读、不可重复读、幻读等。

读未提交、读已提交、可重复读、串行化性能依次降低。

19.写个快排,写个DFS?

QuickSort如下:

public class Main {
    public static void main(String[] args) {
        int [] arr = {10,3,4,9,7,5,2,6,1,8} ;
        quickSort(arr,0,arr.length-1) ;
        for(int ans : arr){
            System.out.print(ans + " ");
        }
    }

    private static void quickSort(int[] arr, int left, int right) {
        if(left < right){
            int pivot = partition(arr,left, right) ;
            quickSort(arr, left, pivot-1);
            quickSort(arr, pivot+1, right);
        }
    }

    private static int partition(int[] arr, int p, int r) {
        int pivot = arr[p] ;
        int left = p + 1, right = r ;
        while(left <= right){
            while(left <= right && arr[left] < pivot){
                left ++ ;
            }
            while(left <= right && arr[right] > pivot){
                right -- ;
            }
            if(left < right){
                swap(arr, left, right) ;
            }
        }
        swap(arr, p, right) ;
        return right ;
    }

    private static void swap(int[] arr, int p, int right) {
        int tmp = arr[p] ;
        arr[p] = arr[right] ;
        arr[right] = tmp ;
    }
}

DFS实现全排列模板如下:

public class DFS {
    static int [] arr = {1,2,3,4,5} ;
    public static void main(String[] args) {

        dfs(0) ;
    }

    private static void dfs(int index) {
        if(index >= arr.length){
            for(int ans : arr){
                System.out.print(ans);
            }
            System.out.println();
            return ;
        }
        for(int i=index; i<arr.length; i++){
            swap(i,index) ;
            dfs(index+1);
            swap(i,index) ; //回溯
        }
    }

    private static void swap(int i, int index) {
        int temp = arr[i] ;
        arr[i] = arr[index] ;
        arr[index] = temp ;
    }
}

20.泛型、反射的理解和应用场景?

泛型是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。

反射机制允许程序在执行期间借助于ReflectionAPI取得任何类的内部信息(比如成员变量、构造器、成员方法等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到。加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个Class对象包含了类的完整结构信息,通过这个Class对象得到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以形象的称之为:反射。
 

21.发起一个HTTP请求到收到一个响应中间所发送的事情?

1.浏览器解析用户输入的URL,生成一个HTTP格式的请求。
2.先根据URL域名从本地hosts文件查找是否有映射ip,如果没有将域名发送给电脑所配置的DNS进行域名解析,得到ip的地址。
3.浏览器通过操作系统将请求通过四层网络协议发送出去。
4.途中可能会经过各种路由器,交换机,最终到达服务器。
5.服务器收到请求后,根据请求所指定的端口,将请求传递给绑定了该端口的应用程序,比如8080被tomcat占用了。
6.tomcat接受到请求数据后,按照http协议的格式进行解析,解析得到所要访问的servlet.
7.然后servlet来处理这个请求,如果是SpingMVC中的DispatcherServlet,那么则会找到对应的Controller的方法,并执行该方法得到结果。
8.Tomcat得到响应结果后封装HTTP响应的格式,并将再次通过网络发送给浏览器所在的服务器。
9.浏览器所在的服务器拿到结果后再传递给浏览器,浏览器则负责解析并渲染。
 

22.使用Socket编程的流程?

服务端:

1:加载套接字库,创建套接字(WSAStartup()/socket());

2:绑定套接字到一个IP地址和一个端口上(bind());

3:将套接字设置为监听模式等待连接请求(listen());

4:请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept());

5:用返回的套接字和客户端进行通信(send()/recv()、read()/write());

6:返回,等待另一连接请求;

7:关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup())。

客户端编程的文字步骤:

1:加载套接字库,创建套接字(WSAStartup()/socket());

2:向服务器发出连接请求(connect());

3:和服务器端进行通信(send()/recv());

4:关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup())。
 

23.volatile保证了什么,具体的内存屏障,volatile加在基本类型和对象上的区别?

保证了内存可见性,volatile修饰的变量(本地内存:java虚拟机栈/寄存器)不会被缓存在寄存器,变量在本地内存(虚拟机栈线程私有的空间),一旦变量修改会立即回写至主内存,每一个线程访问主内存上的数据是最新的变量结果。

保证了有序性,Java内存模型不会对volatile指令进行重排序,从而保证对volatile变量的执行顺序,永远按照顺序出现的顺序执行。

volatile只能修饰变量,对基本类型的数据起作用。

volatile修饰对象是否起作用?

对对象不起作用,只能对对象的地址空间进行可见,即地址如果发生改变,其他线程能够立即感知,但是对象本身的属性发生改变,volatile是不能保证其他下线程立即感知。

24.垃圾回收的算法及详细介绍?

程序的运行必然申请内存资源,如果无效的对象不清理一直占用资源,那么肯定会导致内存溢出,所以内存资源的管理就很重要了。

引用计数法:通过引用计数器计数,对象没有引用就可以被回收。

标记清除算法:

  • 标记:从根节点开始标记引用的对象。
  • 清除:未被标记引用的对象就是垃圾对象,可以被清理。

标记压缩(整理)算法:根据老年代的特点提出的一种标记算法,标记过程与标记清除算法一致,在清理阶段则不是简单的清理,而是将存货的对象向一端压缩,然后清理边界以外的垃圾,解决碎片化的问题。

标记-复制算法:为了解决效率问题,标记复制算法出现了。复制算法的核心就是将内存一分为二,每次只用其中的一块,在垃圾回收时,将还存活的对象复制到另一个空间中,然后将内存空间清空,交换两个内存的角色,完成垃圾回收。

垃圾收集器:各种垃圾回收器实现垃圾回收。

猜你喜欢

转载自blog.csdn.net/nuist_NJUPT/article/details/129146627