得物二面,题型很典型

题目来源:来源忘记了,写完解析忘记贴地址了

前文

本期是【捞捞面经】系列文章的第 3 期,持续更新中.....。

捞捞面经》系列正式开始连载啦,据说看了这个系列的朋友都拿到了大厂offer~

捞捞面经

注:养成先看真题,自己模拟回答,再看解析参考(别忘随手一键三连哦~)

基础题:

1.Java 中类加载过程?

2.HashMap 和 HashSet 的区别

3.有没有遇到过死锁?怎么解决的?

4.Java 中常见的锁有哪些,其特点简单说说?

5.乐观锁了解吗?有哪些实现方式?

6.悲观锁有哪些?有哪些实现方式?

7.谈谈常见的设计模式?

8.代理模式有哪两种?动态代理有哪两种?

9.MySQL 隔离级别有哪些?

10.RR隔离级别幻读解决了吗?怎么解决幻读?

11.操作系补码是啥,为啥这么做?

情景题:

12.单词频率统计怎么做,怎么优化?

本文解析收录:https://github.com/xlcoding/NowcoderTop

1. Java中类加载过程

Java中的类加载过程主要包括以下几个步骤:

  1. 加载(Loading):加载阶段主要完成以下三个动作:
  • 通过一个类的全限定名来获取定义此类的二进制字节流。将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的数据访问入口。
  1. 验证(Verification):验证阶段主要包括四个检验过程:
  • 文件格式验证:检验导入类数据流是否符合Class文件格式规范。元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求。字节码验证:通过数据流和控制流分析,确定程序语义是合法、符合逻辑的。符号引用验证:确保解析动作能正确执行。
  1. 准备(Preparation):为类变量(即被static修饰的变量)分配内存并设置类变量初始值的阶段,这些变量使用的内存都将在方法区中进行分配。
  2. 解析(Resolution):将常量池内的符号引用替换为直接引用的过程。符号引用就是一组符号来描述所引用的目标,直接引用就是直接指向目标的指针、相对偏移量或者是一个能直接定位到目标的句柄。
  3. 初始化(Initialization):执行类构造器<clinit>()方法的过程。这个方法不需要定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的。

2. HashMap 和 HashSet的区别

HashMap和 HashSet 是Java集合框架中的两种不同类型的集合,它们的主要区别在于存储的数据和使用的场景

  1. HashMap 是一个实现了 Map 接口的类,它存储的是键值对(Key-Value)结构的数据,每个键都是唯一的,用于快速查找值。HashMap 允许键和值为 null。
  2. HashSet 是一个实现了 Set 接口的类,它存储的是无序、不重复的元素集合。HashSet 的内部实际上是一个HashMap,它只使用HashMap的键来实现数据存储,由于键不能重复,所以 HashSet 中的元素也就不会重复。

在使用上,如果需要存储一对键值对,并且需要根据键快速查找值,那么应该使用HashMap。如果需要存储一组元素,但不关心元素的顺序,只需保证元素不重复,那么应该使用HashSet。

3. 有没有遇到过死锁?怎么解决的?

死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种相互等待的现象,若无外力作用,它们都将无法推进下去。

举个例子,比如有两个线程 A 和 B,线程 A 持有资源1并等待资源 2,而线程B持有资源 2 并等待资源 1,这就形成了一个循环等待,导致死锁。

在实际平时业务场景开发中,通常就是程序死锁,或者数据库 MySQL 死锁之类的。

结合具体场景分析:

1)假设我们有一个在线购物网站,用户可以将商品添加到购物车,然后进行结账。这个过程涉及到两个资源:商品库存和用户的购物车

现在假设有两个用户 A 和 B,他们同时想购买同一件商品。用户 A 已经将商品添加到购物车,正在等待从库存中扣除。与此同时,用户B也将同一件商品添加到购物车,也在等待从库存中扣除。这时,用户A的购物车等待用户 B 释放库存,而用户B的购物车等待用户A释放库存,形成了死锁。

为了避免这种情况,我们可以采取以下策略:

  1. 设置超时退出:如果用户在一定时间内没有完成支付,那么系统自动将商品从购物车中移除,释放库存。
  2. 使用锁顺序:我们可以规定,用户必须先获取商品库存,然后再添加到购物车。这样,所有用户都按照同一顺序获取资源,就不会形成死锁。
  3. 死锁检测与恢复:系统定期检测是否存在死锁,如果检测到死锁,可以选择取消一个用户的操作,让其他用户继续。

2)在MySQL中,死锁主要发生在并发事务中,当两个或多个事务同时锁定了对方需要的资源时,就会发生死锁。

举个例子,假设我们有一个银行转账的业务场景,涉及到两个表:账户表(account) 和 交易记录表(transaction)。在一个转账事务中,我们需要先在账户表中减少转出账户的余额,然后增加转入账户的余额,最后在交易记录表中添加一条记录。

现在假设有两个并发的转账事务A和B,事务A先锁定了账户表,准备更新转出账户的余额,而事务B先锁定了交易记录表,准备添加一条记录。这时,事务A需要锁定交易记录表才能继续,而事务B需要锁定账户表才能继续,就形成了死锁。

为了避免这种情况,我们可以采取以下策略:

  1. 保证事务的原子性:在事务中,所有的操作要么全部成功,要么全部失败。这样可以保证在任何时候,数据都是一致的。
  2. 使用锁顺序:我们可以规定,所有的事务都必须按照同一顺序锁定资源。比如,先锁定账户表,然后锁定交易记录表。这样,就不会有事务在等待其他事务释放资源。
  3. 设置超时退出:如果一个事务在一定时间内没有获取到所有需要的锁,那么就回滚事务,释放已经获取的锁。
  4. 死锁检测与恢复:MySQL可以自动检测死锁,并自动回滚其中一个事务,以解除死锁。

4. Java中常见的锁有哪些,其特点简单说说?

Java中的锁主要有以下几种:

  1. Synchronized:Synchronized是Java中最基本的同步互斥机制,它可以保证同一时刻只有一个线程可以访问被synchronized修饰的代码块或方法。Synchronized在JVM层面实现,属于重量级锁。
  2. ReentrantLock:ReentrantLock是Java并发包java.util.concurrent.locks中的一个类,它提供了与synchronized相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。ReentrantLock被称为可重入锁,是因为同一个线程可以多次获取同一个锁。
  3. ReadWriteLock:ReadWriteLock是一个读写锁接口,它维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时持有,写入锁是独占的。
  4. StampedLock:StampedLock 是 Java8 引入的一种新的所机制,可以看做是读写锁的一个改进版本,它通过乐观读来解决读写锁的"写饥饿"问题。
  5. SpinLock(自旋锁):自旋锁是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。
  6. CountDownLatch/CyclicBarrier/Semaphore:这些并不是锁,但是它们提供的机制在某些情况下可以实现类似锁的效果,用于控制并发线程的执行。

可以看出它们各有各的特点和使用场景,因此需要根据具体的需求来选择使用哪种锁。

5、乐观锁了解吗?有哪些实现方式?

乐观锁认为数据在一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测。

乐观锁的实现一般有两种方式:

  1. 版本号机制:在数据表中加入一个版本号字段,每次读取数据时,将版本号一并读出,数据每更新一次,对应的版本号加1。当我们提交更新的时候,判断数据库表对应记录的当前版本号与我们读出来的版本号进行比对,如果数据库表当前版本号与我们读出来的版本号相等,则予以更新,否则认为是过期数据。
  2. CAS算法:CAS(Compare And Swap)算法涉及到三个操作数,分别是内存值V、预期原值A、新值B。当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做并返回false。CAS是一种无锁算法,可以避免多线程的互斥等待,提高系统的并发性。

6、悲观锁有哪些?有哪些实现方式?

悲观锁和乐观锁恰好相反,它假设最坏的情况,认为数据在并发处理过程中一定会发生修改,所以在数据处理前就会先加锁,确保数据处理的过程中不会被其他线程修改。

悲观锁的实现方式主要有以下几种:

  1. 数据库锁:数据库本身就提供了一些锁机制,包括行锁、表锁、页锁等,可以在查询语句后加 FOR UPDATE来实现悲观锁。
  2. 共享锁和排他锁:共享锁(S锁)是允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。排他锁(X锁)是允许获取排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。
  3. Java中的synchronized和ReentrantLock:另外可以使用 synchronized 关键字或ReentrantLock 来实现悲观锁,保证同一时刻只有一个线程可以执行某个方法或代码块。

6. 谈谈常见的设计模式?

7. 代理模式有哪两种?动态代理有哪两种?

代理模式主要有两种:静态代理和动态代理。

  1. 静态代理:在程序运行前,就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
  2. 动态代理:在程序运行时,运用反射机制动态创建而成。

动态代理又分为两种:JDK动态代理和CGLIB动态代理。

  1. JDK动态代理:其代理对象必须是某个接口的实现,它是通过反射机制来生成一个实现代理接口的匿名类,再通过代理方法回调委托类的方法。这种方式的优点是可以在不修改源码的情况下对委托类的方法进行增强。
  2. CGLIB动态代理:针对类来实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以该类或方法最好不要声明成final。CGLIB(Code Generation Library),是一个开源项目,是一个强大的、高性能的代码生成包,它广泛被许多AOP的框架使用,例如Spring AOP和synaop等,为他们提供方法的interception(拦截)。

总的来说,代理模式主要用于在不改变原有代码的基础上,增强方法的功能,常用于日志记录,权限控制,事务处理等。

8. MySQL隔离级别有哪些?

MySQL支持以下四种事务隔离级别:

  1. 读未提交(Read Uncommitted):最低的隔离级别,允许读取尚未提交的数据变更,可能导致脏读、幻读或不可重复读。
  2. 读已提交(Read Committed):大多数数据库系统的默认隔离级别(但不包括MySQL和PostgreSQL)。只能读取已经提交的数据。可以防止脏读,但幻读和不可重复读仍可能发生。
  3. 可重复读(Repeatable Read):MySQL的默认隔离级别。在同一事务内的查询都会基于一个快照,保证在同一事务内多次读取同样记录的结果是一致的。但是理论上,该级别仍然无法防止幻读,尽管在InnoDB存储引擎中已经做了处理,实际上可以防止幻读。
  4. 串行化(Serializable):最高的隔离级别,完全服从ACID的隔离级别。所有的事务将依次逐个执行,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响性能,一般不会在实际应用中使用。

9.RR隔离级别幻读解决了吗?怎么解决幻读?

幻读:是指在一个事务内读取到了别的事务插入的数据,导致前后读取的数据不一致。例如,你在一个事务中先后两次执行了相同的查询语句,第一次查询返回了5条记录,但第二次查询却返回了6条记录,这就是幻读。

  1. 在标准的 SQL 隔离级别定义中,可重复读隔离级别并不能解决幻读问题。因为标准定义下的重复读隔离级别,虽然可以防止同一条记录被其他事务修改,但无法防止其他事务插入新的满足查询条件的记录,因此可能出现幻读。只有在串行化(Serializable)隔离级别下,才能完全防止幻读。
  2. 但在 MySQL 的 InnoDB 存储引擎中,可重复读(Repeatable Read)隔离级别实际上已经解决了幻读的问题。
  3. 对于读操作,由于使用了一致性读(consistent read)和多版本并发控制(MVCC),可以保证在同一事务中多次读取同样记录的结果是一致的,避免了不可重复读和幻读的问题。对于写操作,InnoDB 使用了 next-key locking 机制,这种机制会锁定记录行以及索引记录的“间隙”,防止其他事务在这些“间隙”中插入新的记录,从而避免了幻读。

但是,虽然在可重复读隔离级别下,InnoDB 可以有效地防止幻读,但如果事务在读取数据后又进行了写操作(例如,先SELECT,然后UPDATE),可能会出现因为并发操作而导致的数据不一致问题。这种情况下,可能需要使用更高的隔离级别(如串行化)或者显式地使用锁(如SELECT FOR UPDATE)来确保数据的一致性。

10. 操作系统补码是啥,为什么这么做?

补码是一种二进制数的表示形式,主要用于表示负数。在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理,即CPU只需要做加法运算即可。

补码的计算方法是:正数的补码就是其本身;负数的补码是在其原码的基础上,符号位不变,其余各位取反(得到反码),然后整个数加1。

例如,假设我们有一个8位的二进制数,+7的原码和补码都是00000111,而-7的原码是10000111,其反码是11111000,再加1得到补码11111001。

使用补码不仅解决了0的符号以及存在两个编码的问题(+0和-0),而且还使得负数的编码更为简单。在使用补码的情况下,减法可以转换为加法,大大简化了计算机硬件的设计。

情景题:

1. 单词频率统计怎么做,怎么优化?

这个问题得看你数据量有多大咯。。。

数据量:几十万或者几万个单词

如果只有几十万或者几万个单词,那么在一台普通的计算机上就可以很好地处理这个问题了。这种情况下,我们可以使用以下的方法:

  1. 使用哈希表或字典:哈希表或者字典可以用来统计每个单词的频率。每次遇到一个单词,都可以在O(1)的时间内找到它的频率并更新它。
  2. 排序并统计:如果需要找出频率最高的N个单词,可以在统计完所有单词的频率后,对单词按照频率进行排序,然后选出前N个。这个步骤的时间复杂度是O(n log n),其中n是单词的总数。

在这种情况下,由于数据量相对较小,所以IO速度和存储空间通常不会成为问题。因此,我们主要需要关注的是算法的效率。使用哈希表或字典可以保证统计频率的步骤非常快,而排序则是一个相对较慢的操作,但由于n不大,所以总的时间复杂度仍然是可以接受的。

数据量:几百万个

如果只有几百万个单词,那么我们可以直接在单台机器上进行处理,无需使用MapReduce等分布式处理框架。以下是具体的步骤:

  1. 使用哈希表或字典:同上。
  2. 使用堆或优先队列:如果需要找出频率最高的N个单词,可以使用堆或者优先队列来存储当前频率最高的N个单词。每次遇到一个新的单词,就和堆顶的单词比较,如果新的单词的频率更高,就将堆顶的单词替换为新的单词。

数据量:几十亿个

对于几十亿个单词的频率统计,可以采取以下步骤:

  1. 使用MapReduce模型:首先,可以将数据分割成多个小块,然后使用多台机器并行处理这些小块数据。这就是MapReduce模型的基本思想。在Map阶段,每台机器处理自己的数据块,并统计每个单词的频率。在Reduce阶段,将所有机器的结果合并,得到每个单词的总频率。
  2. 使用哈希表或者字典:在每台机器上,可以使用哈希表或者字典来统计单词的频率(同上)
  3. 使用堆或者优先队列:同上

那有什么什么好的办法可以优化呢?

优化方面,可以考虑以下几点:

  1. 使用更快的IO方式:如果数据是存储在硬盘上的,那么读取数据的速度可能会成为瓶颈。可以考虑使用更快的IO方式,例如使用SSD硬盘,或者使用内存映射文件。
  2. 使用压缩存储:如果内存空间不足,可以考虑使用压缩存储。例如,可以使用Trie树来存储单词,这样可以大大减少存储空间。
  3. 使用更好的哈希函数:哈希冲突会影响哈希表的性能。可以考虑使用更好的哈希函数,以减少哈希冲突。
  4. 使用多线程或者多进程:如果CPU资源充足,可以考虑使用多线程或者多进程来并行处理数据,以提高处理速度。
#晒一晒我的offer##发现了面试通关密码##java##牛客在线求职答疑中心#
全部评论

相关推荐

实在智能(小厂-见习python开发工程师)-2024.9.2&nbsp;一面(20min)1.python的数据类型中哪些是可变的哪些是不可变的2.为什么不可变3.讲一下多进程,多线程,多协程(协程没答出来、、、、)多进程(multiprocessing):在一个操作系统中同时运行多个进程,每个进程都有自己的内存空间和资源,进程之间相互独立优点:充分利用多核cpu,提升计算性能进程独立,内存隔离使得一个进程的崩溃不会影响到其他的进程缺点:创建和销毁的开销比较大多线程:一个进程中同时运行多个线程,线程共享进程的内存空间和资源优点:开销小,信息共享通信速度快缺点:由于内存共享,线程之间的竞争可能导致数据不一致,需要使用锁等机制保证线程安全python中,由于全局解释锁(GIL)的存在,cpu密集型任务的性能提升有限协程:协程是一种轻量级的用户级线程,允许在单个线程中进行并发操作,协程通过yield或async、await语法实现,可以在执行过程中挂起和恢复优点:开销非常小,适合处理大量并发任务由于协程是单线程,避免了线程中的竞争和锁的问题缺点:协程的调度是由程序员控制的,可能导致复杂的控制流单线程运行导致的性能问题4.多进程之间的数据能否共享不能,但是python可以通过一些机制实现数据共享:共享内存:multiprocessing模块提供了共享内存的功能,可以使用value和array来创建共享数据进程间通信(IPC):使用队列queue或者管道pipe实现进程间的通信,这些机制允许一个进程将数据发送到另一个进程5.python装饰器属于一种特殊的函数,用于在不修改函数代码的情况下,增强或者改变函数的行为,通常用于日志记录,权限检查,缓存,装饰器以函数作为参数,并返回一个新函数,通常用@什么什么的语法装饰器链,可以应用于同一个函数装饰器将从内到外依次执行:内置装饰器:@staticmethod、@classmethod和@property,用于定义类中的静态方法、类方法和属性。6.讲一下restfulapi7.如何判断两个对象是不是同一个使用is运算符,比较的是内存中的地址是否相同8.私有属性能否被继承能被继承,但是访问受到名称重整(name&nbsp;mangling)的影响。具体来说,私有属性在子类中不能直接通过其原始名称访问,而是要加一个&lt;_父类名称&gt;在使用的属性之前9.属性能否被修改能被修改可修改性取决于它们的定义方式和访问权限。1.公共属性:如果属性是公共的(没有前缀下划线或双下划线),则可以直接在类的实例中修改2.受保护属性:单下划线,可以在类外部访问和修改3.私有属性:双下划线开头,不能直接在类外部访问,但可以在类内部修改10.讲一下你在实习项目中负责哪些模块11.项目是前后端分离吗12.数组和链表的不同点相同点,各自的优势和劣势相同:1.都是存储数据2.都是线性结构,存在顺序关系3.都可以用来实现其他数据结构不同:数组长度不可变,链表长度可变13.flask的生命周期1.请求创建2.请求上下文创建3.路由匹配4.视图函数调用5.响应生成6.响应上下文创建7.响应发送8.请求结束(清理请求上下文,响应上下文,释放相关资源)9.错误处理(如果在请求处理之后发生错误)(面试官人不错,没有太为难我,期待二面ing)14.python的垃圾回收机制1.引用计数:每个对象都有一个引用计数器,记录有多个引用指向该对象,当对象的引用计数为零时,表示没有任何引用指向该对象,python会立即释放对象占用的内存2.循环垃圾回收:为了处理引用计数无法解决的循环引用问题,定期检查对象之间的引用关系,识别并回收哪些形成循环引用的对象实在智能(小厂-见习python开发工程师)-2024.9.3&nbsp;二面(20min)自我介绍做了哪些项目项目中遇到的难点,有遗憾的地方反问环节(不知道问啥。。。):现在在做的项目,团队有几个人这两次面试时间都好短,感觉在赚kpi
查看11道真题和解析
点赞 评论 收藏
分享
----------------------------------一面面经--------------------------------------------1.苍穹外卖项目的总体介绍,包括技术栈和方案2.用户登录这些有没有用到模块化3.怎么避免多个用户同时去修改同一份数据4.如果性能要求较高的话,如何设计5.Java实现乐观锁的具体实现手段6.库存的扣减是在什么时候,是在支付前还是支付后7.多个用户同时下单一个商品,怎么保证不超卖(前面问的项目问的都挺模棱两可)8.都用过哪些Redis的数据类型,用在哪里9.Redis为什么比MySQL要快10.Redis中有String结构,以Key和Value存储,如果这个Key是个热点key,ops达到10w+,怎么解决11.使用多级缓存,每一层缓存应该存什么内容(和上面的10对应),怎么更新内容,如果比如二级缓存,一级缓存失效了怎么办12.多级缓存有什么开源组件13.进程和线程的区别14.进程间的通信方式有哪些15.Linux用过哪些命令,说一下16.具体说一下哪些命令17.用户打开一个网页的过程,在这之间用了什么协议18.TCP和UDP有什么区别19.MySQL的事务,事务的隔离级别20.给你一个学生表student,有学号、课程、课程分数,写MySQL语句,找出表中及格的学生的学号(所有课程都要及格才算及格)21.Sql注入22.手撕:比较简单,合并两个有序数组23.问了能不能去实习,说一共有三次技术面---------------------------------二面面经---------------------1.Java为什么学习成本更低2.Python和Java的区别3.Redis和MySQL是怎么配合的4.为什么先更新MySQL再删除缓存5.为什么是删除缓存而不是更新6.两个线程更新缓存会出现缓存一致性问题吗,解释一下7.如果缓存删除失败,会出现什么问题8.如果先更新数据库,再删除Redis数据,此时Redis数据删除失败,会有什么后果9.延迟双删策略,靠什么实现的10.延迟双删的延迟如何实现11.Redis实现消息队列有什么缺点12.Redis为什么使用Lua脚本13.优惠券超卖问题的解决方案14.缓存穿透、击穿、雪崩15.缓存击穿使用逻辑过期再介绍一下16.大圆桌有n个位置,黑棋和白棋,a,b两个人轮流下棋子,当对方没有空位置下棋,则我方获胜,如何保证我方获胜--------------------------三面----------------------------三面没记录...已经约了HR面
投递字节跳动等公司10个岗位
点赞 评论 收藏
分享
8 35 评论
分享
牛客网
牛客企业服务