【Java校招面试】实战面经(六)


前言

“实战面经”是本专栏的第二个部分,本篇博文是第六篇博文,如有需要,可:

  1. 点击这里,返回本专栏的索引文章
  2. 点击这里,返回上一篇《【Java校招面试】实战面经(五)》
  3. 点击这里,前往下一篇《【Java校招面试】实战面经(七)》

一、如何在N个数中1前K小个数?

1. 利用快排
利用快排中的划分Partition()函数,找到第k小的数a[k-1],则a[0…k-1]是前k小的数(无序)
即选择一个关键字,通过一趟快排,划分出比关键字小的m个数,如果m > k,则再对前m个数做,否则,对关键字后的一位再做一趟快排,直到选出最小的k个数。

时间复杂度为O(n)

2. 利用堆
建立一个元素个数为k的大根堆,然后用剩下的n – k个元素与堆顶进行比较,如果小于堆顶,就用该元素替换堆顶,然后进行堆调整。
时间复杂度为O(n + k)

3. 利用hash散列
先利用hash散列进行降维,分为m份,然后求出每份的前k小,然后再根据堆方法,求出这mk个数中的前k小。


二、TCP断开连接时为什么需要四次挥手?

在这里插入图片描述

为了确保数据能够完整传输。
断开TCP连接需要四次挥手的原因是因为TCP是全双工协议,即双方都可以向对方发送数据。在断开连接时,双方都要发送确认信息,以确保对方已经收到自己的断开请求。因此,需要四次挥手来完成断开连接的过程。


三、虚拟地址到物理地址的映射过程

1. 分页内存管理
将虚拟内存空间和物理内存空间皆划分成大小相同的页面,例如4KB、8KB和16KB等。并将页作为内存空间的最小分配单位,一个程序的一个页面(虚拟页面)可以存放在任何一个物理页面中。

2. 映射过程
先从内存中读取页表,然后根据虚拟地址查表得到物理地址,如果内存中没有当前所需的页,就会发生缺页中断,将所需的页调入内存中,并更新页表。


四、传输用户账号密码时如何加密?

在前后端之间传输用户帐号密码时,为确保信息的安全性和隐私,应采取以下措施对数据进行加密:

1. HTTPS(安全超文本传输协议): 在客户端与服务器之间使用 HTTPS 而非 HTTP 进行通信。HTTPS 协议使用 SSL/TLS 加密,能确保传输数据的机密性、完整性和身份验证。在浏览器中输入账号密码时,启用 HTTPS 可防止他人拦截和窃取数据。

2. 客户端加密: 在传输之前,可以使用客户端 JavaScript 对用户密码进行加密。加密可以使用哈希函数(例如,SHA-256 或 SHA-512)或基于公钥的加密方式(如 RSA)。对于哈希函数,请注意添加随机的“盐”(Salt)以使生成的哈希更具安全性。

3. 服务器端加密: 当服务器接收到加密过的账号和密码后,在存储数据至数据库前,应再次进行加密。常见的做法是使用哈希+盐的方式对密码进行安全哈希。服务器应该存储最终的哈希值以及盐,而非明文密码。对于用户鉴权,应将收到的待验证密码作同样的加密操作并与存储的哈希值进行比较。

下面是一些关键点:

  • 确保域名已经获取了 SSL/TLS 证书,并已在 Web 服务器上进行了安装和配置,以确保 HTTPS 的正常使用。
  • 使用现代安全的加密库和算法。避免使用已知的缺陷和不安全的加密算法。
    保持代码库和所依赖的加密库的更新,确保补丁及时修复。
  • 以上方法概括了前后端传输时有关加密的一些关键点。注意到只有通过 HTTPS 保护数据传输过程,客户端和服务端加密确保了存储数据的安全。建议始终跟踪当前的安全实践和加密策略,以确保用户数据安全。

五、JSON为什么不存在数据库里?

JSON 数据并非不能存储在数据库中。实际上,许多现代数据库(特别是 NoSQL 数据库,如 MongoDB、Couchbase 和 Cassandra, 或一些支持 JSON 数据类型的关系型数据库如 PostgreSQL 和 MySQL)支持 JSON 格式的数据存储。

但在一些数据存储场景下,存储原始的 JSON 数据可能不是最优选择。原因可能有以下几点:

1. 结构化查询: 关系型数据库如 SQL Server、Oracle 和 MySQL 是为高效处理结构化数据而设计的。如果直接将 JSON 数据存储在这些关系型数据库中,可能导致低效的查询性能。另一方面,针对 JSON 数据格式的 NoSQL 数据库在这方面表现得更好,因为它们支持更灵活的数据存储和查询。

2. 数据规范化: 在关系型数据库中,通常偏向于采用规范化的数据模型(即通过使用连接去掉重复数据),以减少数据冗余、节省存储空间以及实现数据一致性。JSON 数据是一种自包含的、非规范化的数据结构,可能不适用于具有规范化需求的关系型数据库。

3. 数据类型和验证: 关系型数据库通常支持严格的数据类型和数据约束,从而可以确保数据的完整性。JSON 数据格式没有这种功能,因此在这些应用场景下,需要额外地实现这些验证和完整性保证。

4. 在原始JSON格式下存储至关系型数据库时,如果需要对JSON格式内的字段进行建立索引和查询,将带来额外的性能和计算负担。因为处理包含在JSON内的查询将更复杂和耗时。

需要强调的是: 选择存储 JSON 数据或将其转换为特定数据模型取决于具体的需求和使用场景。在一些现代数据库中, 对 JSON 数据的存储和查询具有很好的支持,因此可以根据实际需求进行判断。


六、Redis是什么?

Redis是REmote DIctionary Server的缩写,即远程字典服务器。它以字典结构存储数据,并允许其他应用通过TCP协议读写字典中的内容。同大多数脚本语言中的字典一样,Redis字典中的键值除了可以是字符串,还可以是其他数据类型。


七、Redis的过期策略

1. 定时删除: 在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除。

2. 惰性删除: key过期的时候不删除,每次从数据库获取key的时候去检查是否过期,若过期,则删除,返回null。

3. 定期删除: 每隔一定时间就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。

缺点:
  1) 定时删除: 定时器会消耗CPU资源,降低系统吞吐量;
  2) 惰性删除: 如果过期的key很久没有被再次访问,那么会被无用的数据占用大量的内存空间。
  3) 定期删除: 因为是随机的,所以可能过期的key很长时间没有被随机到。
都有缺点,所以需要淘汰策略。


八、Redis的淘汰策略

淘汰策略起作用于内存不足以容纳新写入的数据时:

1. noeviction: 新写入操作会报错。
2. allkeys-lru: 在键空间中,移除最近最少使用的key。
3. allkeys-random: 在键空间中,随机移除某个key。
4. volatile-lru: 在设置了过期时间的键空间中,移除最近最少使用的key。
5. volatile-random: 在设置了过期时间的键空间中,随机移除某个key。
6. volatile-ttl: 在设置了过期时间的键空间中,移除最早过期时间的key。


九、写一个LRUCache

《实战面经(五)》第八题


十、ThreadLocal适合的场景、实现、注意事项

1. 适合的场景: ThreadLocal适合用来保存线程生命周期内的上下文信息。

2. 实现: Thread类里面有一个threadLocals对象,指向这个线程的ThreadLocalMap对象,里面存储了这个线程中所有的ThreadLocal对象。

3. 注意事项: 由于ThreadLocalMap的Entry中Key是弱引用,可以被回收,回收之后键值对变成<null, Value>。为了避免内存泄漏,用完之后要手动调用remove方法通过Key,把整个Entry释放掉。


十一、CountDownLatch、Semaphore和CyclicBarrier

1. CountDownLatch: 是一个计数器闭锁,通过它可以完成类似于阻塞当前线程的功能,即:一个线程或多个线程一直等待,直到其他线程执行的操作完成。CountDownLatch用一个给定的计数器来初始化,该计数器的操作是原子操作,即同时只能有一个线程去操作该计数器。调用该类await方法的线程会一直处于阻塞状态,直到其他线程调用countDown方法使当前计数器的值变为零,每次调用countDown计数器的值减1。当计数器值减至0时,所有因调用await()方法而处于等待状态的线程就会继续往下执行。

2. Semaphore: 与CountDownLatch相似,不同的地方在于Semaphore的值被获取到后是可以释放的,并不像CountDownLatch那样一直减到底。它也被更多地用来限制流量,类似阀门的 功能。如果限定某些资源最多有N个线程可以访问,那么超过N个主不允许再有线程来访问,同时当现有线程结束后,就会释放,然后允许新的线程进来。类似于锁的lock与 unlock过程(acquirerelease)。

3. CyclicBarrier: 它允许一组线程相互等待,直到到达某个公共屏障点(common barrier point)。通过它可以完成多个线程之间相互等待,只有当每个线程都准备就绪后,才能各自继续往下执行后面的操作。类似于CountDownLatch,它也是通过计数器来实现的。当某个线程调用await方法时,该线程进入等待状态,且计数器加1,当计数器的值达到设置的初始值时,所有因调用await进入等待状态的线程被唤醒,继续执行后续操作。因为CycliBarrier在释放等待线程后可以重用,所以称为循环barrier。


十二、生产者/消费者的例子

LinkedBlockDeque实现

1. 生产者

	public class Producer implements Runnable {
	    private final LinkedBlockingDeque<String> deque;
	    private int productCount;
	    public Producer(LinkedBlockingDeque<String> deque) {
	        this.deque = deque;
	        productCount = 0;
	    }
	 
	    @Override
	    public void run() {
	        try {
	            while (true) {
	                Thread.sleep(1500);
	                System.out.println(produce() + " left.");
	            }
	        } catch (InterruptedException ex) {
	            Logger.getLogger(Producer.class.getName()).log(Level.SEVERE, null, ex);
	        }
	    }
	 
	    public int produce() {
	        String productName = "p" + ++productCount;
	        deque.offerFirst(productName);
	        System.out.print("Producer: " + productName + " produced. ");
	        return deque.size();
	    }
	}

2. 消费者

	public class Consumer implements Runnable {
	    private final LinkedBlockingDeque<String> deque;
	    public Consumer(LinkedBlockingDeque<String> deque) {
	        this.deque = deque;
	    }
	 
	    @Override
	    public void run() {
	        try {
	            while (true) {
	                Thread.sleep(2000);
	                System.out.println(consume() + " left.");
	            }
	        } catch (InterruptedException ex) {
	            Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex);
	        }
	    }
	 
	    public int consume() {
	        String product = deque.pollLast();
	        System.out.print("Consumer: product " + product + " consumed. ");
	        return deque.size();
	    }
	}

3. 主类

	public class Starter {
	    private static final LinkedBlockingDeque<String> DEQUE = new LinkedBlockingDeque<>();
	    public static void main(String[] args) {
	        Executor threadPool = Executors.newFixedThreadPool(2);
	        threadPool.execute(new Producer(DEQUE));
	        threadPool.execute(new Consumer(DEQUE));
	    }
	}

十三、JDK1.7和JDK1.8的HashMap有什么区别?

1. 链表和红黑树的结构调整: 在 JDK 1.8 中的HashMap实现中,引入了红黑树数据结构。在 JDK 1.7 中,HashMap在解决哈希冲突时只使用链表。而在 JDK 1.8 中,为减少哈希冲突导致的查询性能下降,当链表长度超过阈值(默认为 8)时,会把链表转换为红黑树。当红黑树节点减少到一定数量(小于 6 个)时,会将红黑树转换回链表。这个优化有助于提高哈希表在处理冲突严重情况下的查询性能。

2. 节点类结构调整: 在 JDK 1.8 中的HashMap实现里,相较于 JDK 1.7,在节点类中加入了红黑树节点所需要的属性,如色彩属性(红色或黑色)和指向父节点、左子节点、右子节点的引用。

3. 哈希计算优化: 在 JDK 1.8 的HashMap中,为了降低哈希冲突的概率,引入了一个新的哈希函数。通过将哈希码的高位与低位进行异或运算,使不同哈希码的差异更加明显,减少哈希冲突的概率。这有助于提高哈希表的整体性能。

总之,引入红黑树哈希计算优化是 JDK 1.8 中HashMap的两个主要改进。这些改进使得HashMap在处理哈希冲突严重情况下具有更好的性能。


十四、各种排序算法的比较

类别排序方法时间复杂度空间复杂度稳定性
平均最好最坏辅助存储
插入排序直接插入O(n2)O(n)O(n2)O(1)稳定
Shell排序O(n1.3)O(n)O(n2)O(1)不稳定
选择排序直接选择O(n2)O(n2)O(n2)O(1)不稳定
堆排序O(nlog2n)O(nlog2n)O(nlog2n)O(1)不稳定
交换排序冒泡排序O(n2)O(n)O(n2)O(1)稳定
快速排序O(nlog2n)O(nlog2n)O(n2)O(nlog2n)不稳定
归并排序O(nlog2n)O(nlog2n)O(nlog2n)O(n)稳定
基数排序O(d(r+n))O(d(rd+n))O(d(r+n))O(rd+n)稳定

十五、各种HTTP协议的method

方法说明
GET获取资源
POST传输实体主体
PUT传输文件
HEAD获得报文首部
DELETE删除文件
OPTIONS询问支持的方法
TRACE追踪路径
CONNECT要求用隧道协议连接代理

十六、各个容器类之间的关系

在 Java 里,容器类主要可以分为两大类:Collection 接口下的相关容器和 Map 接口下的相关容器。这两类和它们的子类在结构、能力和使用场景上有所区别,我将为你简要介绍这些关系。

在这里插入图片描述

1. Collection 接口系列
Collection 接口是 Java 中最基本的容器类,它的主要子接口包括 ListSetQueue

  1) List 接口: List 接口的实现提供了一个有序的容器,可以包含重复的元素。用户可以使用索引值访问容器中的元素。常用的 List 实现类有:
    ArrayList:基于动态数组实现,访问效率高,插入、删除效率较低。
    LinkedList:基于双向链表实现,插入、删除效率高,访问效率较低。

  2) Set 接口: Set 接口的实现提供了一个无序的容器,不允许出现重复的元素。主要的 Set 实现类包括:
    HashSet:基于哈希表实现,保证元素唯一性,查询、插入和删除速度较快。
    LinkedHashSet:基于 HashSet 实现,并维护一个双向链表,保留了元素的插入顺序。
    TreeSet:基于红黑树实现,元素按自然排序或自定义排序规则进行存储。

  3) Queue 接口: Queue 接口的实现提供了基于先进先出(FIFO)策略的队列。典型的 Queue 实现类有:
    ArrayDeque:基于数组实现,具有双端队列特性,可作为栈和队列使用。
    LinkedList:除了实现了 List 接口外,还实现了 Queue 接口,并具有双端队列特性。
    PriorityQueue:基于堆结构实现,元素按自然排序或自定义排序规则进行存储。

2. Map 接口系列
Map 接口是一个键值对映射关系的容器。根据不同的实现方式,Map 接口有以下子类:

  1) HashMap: 基于哈希表实现的 Map,保证键的唯一性,查询、插入和删除速度较快。HashMap 是线程不安全的。

  2) LinkedHashMap: 基于 HashMap 和双向链表实现的 Map,键唯一,保留键值对的插入顺序。

  3) TreeMap: 基于红黑树实现的 Map,键按自然顺序或自定义顺序进行存储。

  4) Hashtable:HashMap 类似,基于哈希表实现的 Map,但在大部分操作上采用同步方法,因此是线程安全的。在 Java 中现在不太推荐使用,ConcurrentHashMap 是一个更好的线程安全的哈希表实现。

  5) ConcurrentHashMap: 基于哈希表实现的 Map,它在多线程修改时采用分段锁机制,较 Hashtable 有更好的性能。线程安全。

这些容器类各自有不同的应用场景和性能特点,根据使用场景和需求选择合适的容器类来满足数据存储和操作的需求。在选择容器类时,通常需要考虑操作性能要求、数据集的大小、线程安


后记

这份面经题目较之前的少些,但补充了很多之前不全的知识点。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IMplementist

你的鼓励,是我继续写文章的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值