java面试准备整理(一)
1. java面向对象有哪些特征
面向对象的三大特征:封装、继承、多态
2. ArrayList和LinkedList的区别
都实现了List
接口。ArrayList
是基于索引的数据接口,底层是数组。LinkedList
(链表)是以元素列表的形式存储数据。
3. 高并发中的集合有哪些问题
1. 第一代线程安全集合类
Vector
、Hashtable
。使用synchronized
。效率低下。
2. 第二代
ArrayList
、HashMap
。使用1
2Collections.synchronizedList(list);
Collections.synchronizedMap(m);
3. 第三代
1 | java.util.concurrent.* |
Java 中的高并发环境下使用集合可能会遇到以下问题:
线程安全性问题:Java 中的大多数集合类(如
ArrayList
、HashMap
等)在多线程环境下不是线程安全的,因此可能会导致数据不一致或者其他意外行为。性能问题:在高并发环境下,如果使用线程安全的集合类(如
ConcurrentHashMap
、CopyOnWriteArrayList
等),可能会引入额外的性能开销,因为它们通常会使用同步或者复制等机制来保证线程安全。迭代器失效问题:在使用迭代器遍历集合时,如果其他线程对集合进行了修改,可能会导致迭代器失效,抛出
ConcurrentModificationException
异常。死锁问题:如果在高并发环境下不正确地使用集合,可能会出现死锁问题,例如两个线程分别持有集合中的不同锁,并试图获取对方持有的锁。
内存泄漏问题:如果不正确地使用集合,可能会导致内存泄漏问题,例如在高并发环境下频繁地向集合中添加元素但未及时删除,可能会导致集合持有过多的引用而无法被及时回收。
为了解决这些问题,可以采用以下方法:
- 使用线程安全的集合类,如
ConcurrentHashMap
、CopyOnWriteArrayList
等。 - 使用同步机制来保证多线程访问的安全性,例如使用
synchronized
关键字或者ReentrantLock
类。 - 避免在迭代集合时对集合进行修改,可以使用迭代器的安全删除操作或者将集合复制一份后再进行操作。
- 注意合理设计并发访问的逻辑,避免死锁等并发问题的发生。
- 注意集合的内存使用情况,避免内存泄漏问题的发生。
4. JDK1.8的新特性有哪些
由于要兼容Lambda表达式,接口里实现了默认方法,接口中带了实现。
函数式接口
支持了多重注解
Java 8 引入了许多重要的新特性,其中一些最显着的包括:
Lambda 表达式:Lambda 表达式是一种匿名函数,它可以作为参数传递给方法或者作为函数式接口的实例使用,极大地简化了代码的编写和阅读。
Stream API:Stream API 提供了一种声明式的方式来操作集合,可以轻松地进行过滤、映射、排序、聚合等操作,大大提高了代码的简洁性和可读性。
函数式接口:Java 8 引入了
@FunctionalInterface
注解,用于标识函数式接口,即只包含一个抽象方法的接口。这种接口可以被 Lambda 表达式直接实现,使得函数式编程更加方便。方法引用:方法引用允许通过方法的名字来引用方法,而不需要像 Lambda 表达式那样提供方法的完整定义,可以进一步简化代码。
默认方法:接口中可以定义默认方法,这些方法可以在接口中提供默认的实现,从而在接口的实现类中直接使用,而无需重新实现。
Optional 类:Optional 类是一个容器对象,用于包装可能为 null 的值,可以避免空指针异常,并提供了一些便利的方法来处理可能为 null 的值。
新的日期和时间 API:Java 8 引入了
java.time
包,提供了全新的日期和时间 API,解决了旧的java.util.Date
和java.util.Calendar
API 的许多问题,例如线程安全性和易用性。CompletableFuture 类:CompletableFuture 类提供了一种便利的方式来进行异步编程,可以轻松地处理异步任务的完成和组合。
这些新特性使得 Java 8 成为一个重要的版本,为 Java 编程带来了许多新的可能性和优势。
5. Java接口和抽象类有哪些区别
语法的区别
语义的区别:什么时候用接口,什么时候用抽象类。
抽象类面对的是一个概念,动物,哺乳动物,植物,有具体概念的建议用抽象类去实现。
接口描述的是某些共同特征,比如flyable
6. hashcode和equals如何使用
在Java中,hashCode()
和equals()
方法是用于处理对象相等性和哈希值计算的两个重要方法。
hashCode()
方法:hashCode()
方法用于返回对象的哈希码(hash code),它是一个32位的整数。- 哈希码用于快速定位对象在哈希表中的位置,因此它对于实现高效的数据结构如HashMap、HashSet等非常重要。
- 在实现
hashCode()
方法时,通常需要遵循以下约定:- 如果两个对象根据
equals()
方法相等,则它们的哈希码必须相等。 - 但是,两个对象的哈希码相等并不意味着它们相等,即对于不同的对象,哈希码可能相同(哈希冲突)。
- 如果两个对象根据
- 如果重写了
equals()
方法,则通常也需要重写hashCode()
方法,以保持对象相等性和哈希码一致。
equals()
方法:equals()
方法用于比较两个对象是否相等。在Java中,默认的equals()
方法实现是比较对象的引用是否相同(即比较内存地址)。- 通常需要重写
equals()
方法来定义对象相等的规则。重写equals()
方法时,需要遵循以下约定:- 自反性:对于任何非空引用值
x
,x.equals(x)
必须返回true
。 - 对称性:对于任何非空引用值
x
和y
,如果x.equals(y)
返回true
,则y.equals(x)
也必须返回true
。 - 传递性:对于任何非空引用值
x
、y
和z
,如果x.equals(y)
返回true
,y.equals(z)
返回true
,则x.equals(z)
也必须返回true
。 - 一致性:对于任何非空引用值
x
和y
,如果对象中的信息没有改变,多次调用x.equals(y)
应该始终返回相同的结果。 - 对于任何非空引用值
x
,x.equals(null)
必须返回false
。
- 自反性:对于任何非空引用值
- 重写
equals()
方法时,需要确保与hashCode()
方法一致,即如果两个对象根据equals()
方法相等,则它们的哈希码也必须相等。
综上所述,hashCode()
和equals()
方法在Java中常用于实现对象的相等性比较和哈希值计算,这对于在集合中存储对象、使用哈希表等数据结构非常重要。
7. java代理的几种实现方式
静态代理
两种动态代理
在Java中,代理是一种常见的设计模式,用于控制对其他对象的访问。代理可以在访问对象时添加额外的逻辑,例如日志记录、性能监控、安全控制等。Java中实现代理的几种方式包括:
静态代理:
- 静态代理是在编译时确定代理类和被代理类的关系。代理类和被代理类在编译期间就已经确定,代理类通常实现与被代理类相同的接口或者继承相同的父类。
- 静态代理的实现比较简单,可以直接在代理类中调用被代理类的方法,并在方法前后添加额外的逻辑。
动态代理(JDK动态代理):
- JDK动态代理是在运行时动态生成代理类的方式。它是通过Java反射机制实现的,在运行时创建代理对象并动态处理对被代理方法的调用。
- JDK动态代理要求被代理的类必须实现接口,代理类通过实现
InvocationHandler
接口来处理被代理方法的调用。
CGLIB代理:
- CGLIB代理是针对类的代理,它通过动态生成被代理类的子类来实现代理。因此,被代理类不需要实现接口,而是通过继承来实现代理。
- CGLIB代理通过字节码技术在运行时创建代理类的子类,并重写其中的方法,在方法前后添加额外的逻辑。
Java动态代理与CGLIB代理的比较:
- Java动态代理适用于接口代理,要求被代理的类必须实现接口,因此更加灵活,但是无法对类的非接口方法进行代理。
- CGLIB代理适用于类的代理,不要求被代理的类实现接口,因此更加灵活,可以代理类的任何方法,但是无法代理
final
方法和private
方法。
总的来说,静态代理、JDK动态代理和CGLIB代理是Java中常用的代理实现方式,它们各自具有不同的特点和适用场景,可以根据具体需求选择合适的代理方式。
8. java中 == 和 equals 有哪些区别
==
基本数据类型比较值,引用数据类型比较的地址。equals
比较两个对象的内容是否相等
9. java中异常处理机制是什么
抛出异常 throw
捕获异常 try catch finally
声明异常 throws
10. java中重写和重载有哪些区别
重载overload:同一个类中,参数不同的
重写override:子类和父类中,重写的方法和被重写的方法
11. String、StringBuffer、StringBuilder区别及使用场景
String是只读字符串String
、StringBuffer
和StringBuilder
是Java中用于处理字符串的三个类,它们在设计和使用上有一些区别,适用于不同的场景:
String
:String
是不可变的(immutable)类,一旦创建就不能被修改。这意味着每次对String
进行修改时,都会创建一个新的String
对象,旧的对象会被丢弃,这样会导致性能开销和内存浪费。- 由于
String
是不可变的,因此它是线程安全的,可以被多个线程安全地共享。
StringBuffer
:StringBuffer
是可变的(mutable)类,它提供了对字符串进行修改的方法,例如append()
、insert()
、delete()
等。因此,对于频繁的字符串拼接或者修改操作,使用StringBuffer
会比直接使用String
更高效。StringBuffer
是线程安全的,它的方法都是同步的,适用于多线程环境。
StringBuilder
:StringBuilder
与StringBuffer
类似,也是可变的字符串序列。但是,与StringBuffer
不同的是,StringBuilder
的方法都是非同步的,因此在单线程环境下性能更好。- 由于
StringBuilder
的方法不是同步的,因此它不适用于多线程环境。
使用场景:
- 当需要处理不可变的字符串时,例如字符串常量,可以使用
String
类。 - 当需要在单线程环境下进行频繁的字符串操作时,例如字符串拼接、修改等,可以使用
StringBuilder
。 - 当需要在多线程环境下进行字符串操作时,可以使用
StringBuffer
,因为它是线程安全的,但是性能相对较低,如果不需要线程安全的话,也可以使用StringBuilder
。
综上所述,根据具体需求和线程安全性要求,可以选择使用String
、StringBuffer
或者StringBuilder
来处理字符串。
12. 怎样声明一个类不会被继承,什么场景下会用
用final修饰。例如Math类
13. 自定义异常在生产中如何应用
自定义异常在生产环境中的应用可以提高代码的健壮性、可维护性和可读性,使得异常处理更加规范和易于管理。合理地使用自定义异常可以有效地提高系统的稳定性和可靠性,从而提升用户体验和系统性能。
继承合适的父类:自定义异常类应当继承自Exception类或者其子类,例如RuntimeException,以便在异常处理时能够统一处理或者捕获。
14. Redis线程模型有哪些,单线程为什么快
Redis的线程模型主要有单线程模型和多线程模型两种。
单线程模型:
- Redis最初采用的是单线程模型。单线程模型指的是Redis服务器在一个线程中顺序地处理所有请求,所有的命令都在一个事件循环中执行。
- 单线程模型的优势在于简单高效,因为不需要考虑线程同步和线程切换的开销,避免了多线程并发访问共享数据可能引发的竞态条件和死锁问题。
- Redis单线程模型适用于大多数情况下的数据存储和处理,特别是在单个CPU核心的情况下,单线程模型的性能表现通常比较优秀。
多线程模型:
- Redis 6.0 版本引入了多线程模型(Redis主从分离模式),可以充分利用多核CPU的性能,提升服务器的吞吐量和并发处理能力。
- 多线程模型将网络IO、命令处理和数据读写等操作分配给多个工作线程处理,可以提高系统的并发处理能力,适用于高并发的场景。
为什么单线程模型在某些情况下会比多线程模型快呢?
线程切换开销:在多线程模型中,由于涉及到线程的创建、销毁和切换,会产生额外的开销,而单线程模型避免了这些开销。
CPU缓存优化:单线程模型可以更好地利用CPU缓存,因为数据和指令都在同一个线程中执行,避免了多线程中频繁的数据共享和同步操作可能引发的缓存失效问题。
线程同步和锁竞争:在多线程模型中,由于涉及到多个线程对共享资源的访问和修改,需要进行线程同步和锁竞争,而单线程模型避免了这些问题。
虽然单线程模型在某些情况下性能优于多线程模型,但是对于高并发、大规模的应用场景,多线程模型能够更好地利用多核CPU的性能,提高系统的并发处理能力。因此,选择合适的线程模型需要根据具体的应用场景和需求进行权衡和选择。
15. Redis持久化机制:RDB和AOF
Redis(远程字典服务器)是一种流行的内存数据库,它支持多种持久化机制来保证数据的安全性和可靠性。两种主要的持久化机制是RDB(Redis Database)和AOF(Append-Only File)。
RDB(Redis Database):
- RDB 是 Redis 的一种快照持久化方式,它周期性地将 Redis 在内存中的数据以快照的形式保存到硬盘上的一个文件中。
- 生成 RDB 文件的时机可以通过配置文件中的保存条件来设置,比如在一定的时间间隔内或者在达到一定数量的写操作后。
- 由于 RDB 是将整个数据集保存到文件中,因此它的恢复速度比较快,适用于对数据一致性要求不是很高、需要定期备份或者迁移数据的场景。
AOF(Append-Only File):
- AOF 是通过将 Redis 的写操作以追加的方式记录到一个文件中来实现持久化的机制。
- AOF 文件记录了 Redis 服务器接收到的所有写命令,通过重放这些命令可以恢复数据。
- AOF 文件的内容是可读的,因此在故障发生时可以比较容易地手动修复,但相对于 RDB,AOF 文件通常会更大一些。
- AOF 提供了不同的同步选项,可以在性能和数据安全之间进行权衡,比如可以选择每个命令都同步到磁盘,或者每秒同步一次等。
在实际应用中,可以根据业务需求和对数据一致性、恢复速度等方面的要求选择适合的持久化机制,甚至可以同时使用 RDB 和 AOF 来提高数据的安全性。
16. redis的过期键有哪些删除策略
Redis 中有几种删除过期键的策略,它们分别是:
定期删除(定时任务):
Redis 默认会每秒检查一定数量的过期键,然后删除其中已过期的键。这个数量可以通过配置文件中的hz
参数进行设置,默认为 10。惰性删除:
当客户端尝试访问某个键时,Redis 会先检查该键是否过期,如果过期则会立即删除。这种方式称为惰性删除。定期删除 + 惰性删除结合:
定期删除和惰性删除可以结合使用,定期删除确保过期键不会一直占据内存,而惰性删除则保证了内存的及时回收。过期键删除策略配置:
在 Redis 的配置文件中,可以通过设置maxmemory-policy
参数来调整过期键的删除策略。常见的策略包括:volatile-lru
:从已设置过期时间的键中挑选最近最少使用的键进行删除。volatile-ttl
:从已设置过期时间的键中挑选即将过期的键进行删除。volatile-random
:从已设置过期时间的键中随机选择键进行删除。allkeys-lru
:从所有键中挑选最近最少使用的键进行删除,包括设置过期时间和未设置过期时间的键。allkeys-random
:从所有键中随机选择键进行删除。noeviction
:禁止删除任何键,当内存用完时,所有写操作都会返回错误。
通过选择合适的删除策略,可以根据实际需求来平衡内存使用和数据可用性。
17. redis缓存如何回收
Redis 中的缓存回收通常是指对内存的回收,以确保 Redis 不会因为内存占用过高而导致性能下降或者服务不可用。主要的缓存回收方法包括:
内存淘汰策略:
Redis 提供了多种内存淘汰策略,用于在内存达到最大限制时选择要移除的键。常见的淘汰策略包括:- LRU(最近最少使用):移除最近最少使用的键。
- LFU(最少频繁使用):移除最少频繁使用的键。
- TTL(过期时间):移除最早过期的键。
设置内存最大限制:
可以在 Redis 配置文件中设置maxmemory
参数,限制 Redis 实例使用的最大内存量。当达到这个限制时,Redis 将会根据配置的内存淘汰策略来回收内存。手动删除:
可以通过手动删除键来释放内存。当某些键不再需要缓存时,可以通过DEL
命令删除这些键。持久化策略:
使用持久化机制(如 RDB 或 AOF)时,可以通过合理的设置持久化策略来控制数据的持久化频率,从而间接地控制内存的占用。过期键自动删除:
Redis 中的键可以设置过期时间,一旦过期时间到达,Redis 将自动删除这些键以释放内存空间。内存优化:
可以通过一些优化策略来减少 Redis 实例的内存占用,比如使用数据结构优化、压缩数据、分片数据等方式。
综合利用这些方法,可以有效地管理 Redis 的内存使用,确保其在高性能的同时不会因为内存溢出而导致服务不可用。
18. redis集群方案有哪些
在搭建 Redis 集群时,有几种常见的方案:
Redis Sentinel:
Redis Sentinel 是 Redis 官方提供的高可用性解决方案,它可以监控 Redis 实例的健康状态,并在主节点宕机时自动将从节点提升为主节点,以保证系统的可用性。但是 Redis Sentinel 本身并不支持数据分片,只能用于高可用性方案。Redis Cluster:
Redis Cluster 是 Redis 官方提供的分布式方案,它将数据分片存储在多个节点上,并提供了自动数据分片和数据复制等功能,可以实现水平扩展和高可用性。Redis Cluster 在设计上遵循了主从复制、数据分片和节点互相通信等原则,提供了一套完整的分布式解决方案。第三方方案:
除了官方提供的解决方案之外,还有一些第三方工具和方案可以实现 Redis 的集群化,比如 Twemproxy、Codis 等。这些方案可能会提供一些额外的功能或者更加灵活的配置选项,但也可能会增加系统的复杂度和维护成本。
在选择适合自己的 Redis 集群方案时,需要考虑到系统的需求、性能要求、高可用性要求以及团队的技术栈和维护成本等因素。
19. redis事务是怎么实现的
Redis 的事务是通过 MULTI/EXEC 命令组合来实现的,具体过程如下:
MULTI 命令:
客户端发送 MULTI 命令给 Redis 服务器,表示开始一个事务。Redis 服务器收到 MULTI 命令后,会将当前客户端设置为事务状态,并将后续的命令都放入一个队列中,等待执行。命令入队:
在 MULTI 和 EXEC 之间的所有命令都会被放入事务队列中,而不是立即执行。这些命令不会立即改变数据库状态,而是暂时保存在队列中。命令执行:
客户端发送 EXEC 命令给 Redis 服务器,表示执行事务中的所有命令。Redis 服务器收到 EXEC 命令后,会按照命令入队的顺序,逐个执行事务队列中的命令。事务原子性:
在执行事务期间,Redis 会将这些命令作为一个整体进行执行,要么全部执行成功,要么全部执行失败。这保证了事务的原子性,即要么所有命令都成功执行,要么都不执行。执行结果返回:
执行事务队列中的命令后,Redis 会将每个命令的执行结果按照顺序返回给客户端。如果在执行事务过程中出现了错误,Redis 会返回一个错误给客户端,同时回滚之前已执行的命令。
通过这种方式,Redis 的事务可以确保一系列命令的原子性执行,但需要注意的是,Redis 的事务并不支持回滚操作,因此在发生错误时,需要由客户端自行处理错误并进行补偿操作。
20. redis 主从复制的原理
Redis 主从复制是一种常用的高可用性和读写分离方案,它的原理如下:
建立连接:
首先,从节点(Slave)会向主节点(Master)发送一个 PSYNC 或 SYNC 命令,请求建立复制连接。同步数据:
一旦连接建立成功,主节点会将自己的数据发送给从节点,从节点会接收并保存这些数据。如果是首次进行复制,主节点会发送整个数据集;如果是增量复制,主节点会发送从上次复制点之后的数据变化。增量复制:
在初始同步完成后,主节点会继续将所有写命令发送给从节点,从节点会根据这些写命令来更新自己的数据集。这样,从节点就能保持和主节点一样的数据状态。持续连接和同步:
一旦复制建立,主从节点之间会维持一个持久的连接。主节点会持续地将写命令发送给从节点,确保从节点始终与主节点保持同步。主从切换:
当主节点宕机或者不可用时,可以手动或自动地将从节点提升为主节点,以保证系统的高可用性。在自动故障转移中,Redis Sentinel 或其他监控工具会监测主节点的状态,并在主节点不可用时自动将某个从节点升级为主节点。
通过主从复制,Redis 实现了数据的备份和读写分离,提高了系统的可用性和性能。从节点可以处理读请求,分担主节点的压力,同时在主节点故障时可以快速接管,保证服务的连续性。