即时通讯项目中怎么实现历史消息的下拉分页加载?业务场景一般在即时通讯项目(比如聊天室)中,我们会采用下拉分页的方式让用户加载历史消息记录。
区别于标准分页每次只展示当前页面的数据,下拉分页加载是 增量加载 的模式,每次下拉时会请求加载一小部分新数据,并放到已加载的数据列表中,从而形成无限滚动的效果,确保用户体验流畅。
比如用户有 10 条消息记录,以 5 条为单位进行分页,刚进入房间时只会加...
让你设计一个线程池,怎么设计?这种设计类问题还是一样,先说下理解,表明你是知道这个东西的用处和原理的,然后开始 阐述。
基本上就是按照现有的设计来说,再添加一些个人见解。
线程池讲白了就是存储线程的一个容器,池内保存之前建立过的线程来重复执行任务,减少创建和销毁线程的开销,提高任务的响应速度,并便于线程的管理。
我个人觉得如果要设计一个线程池的话得考虑池内工作线程的管理、任务编排执行、线程池...
Dubbo 和 Spring Cloud Gateway 有什么区别?回答重点Dubbo 是一个 RPC(远程过程调用)框架,主要用于服务之间的通信。它提供高性能的 RPC 调用、负载均衡、服务发现、服务注册、服务治理等功能。
适用于需要高性能 RPC 调用的分布式系统,常用于内部服务通信。
Spring Cloud Gateway 是一个 API 网关,用于处理外部客户端请求并将其路由到后...
Go 语言中如何访问私有成员?重点回答在 Go 语言中,以小写字母开头的标识符是私有成员,私有成员(字段、方法、函数等)遵循语言的可见性规则,仅在定义它的包内可见,包外无法访问这些私有成员。如果想要访问私有成员,主要包括以下三种方式:
在同一个包内,可以直接访问小写字母开头的私有成员。
在其他包中,无法直接访问私有成员,但可以通过公开的接口来间接访问私有成员。
使用反射来绕过 Go 语言的...
Go 语言的接口是怎么实现的?回答重点在 Go 语言中,接口(interface)是一种动态类型,允许定义对象的行为,而不需要指定具体的实现。
它本质上是一个动态类型和动态值的组合:
动态类型:接口持有的具体数据的类型。
动态值:接口持有的具体数据的值或引用。
接口通过这两部分,实现对不同类型的统一操作。
Go 采用鸭子类型的设计哲学,不需要显式声明实现关系。只要一个类型的方法集满足接口...
Go 语言中怎么实现闭包?闭包的主要应用场景是什么?回答重点在 Go 语言中,闭包(Closure)是一个函数值,它可以引用其外部作用域中的变量。在 Go 中实现闭包的方法非常简单,我们可以通过在一个函数内部定义另一个函数,并让其访问外部函数的变量来实现。
即函数可以访问被引用的变量并对其赋值,函数被“绑定”到变量上。
下面是一个简单的 Go 语言闭包示例:
12345678910111...
Go 语言中通过指针变量 p 访问其成员变量 title,有哪几种方式?回答重点在 Go 语言中,通过指针变量 p 访问其成员变量 title 主要有以下两种方式:
1)使用 (*p).title 访问成员变量。
2)由于 Go 提供了指针的简写支持,还可以直接使用 p.title 来访问成员变量。
这两种方式其实是等价的,Go 编译器会帮你处理其中的细节。
有 4 种情况可以使用简洁支持:...
Go 语言使用断言时会发生拷贝吗?回答重点在 Go 语言中,类型断言是否发生拷贝取决于接口内部持有的数据类型:
值类型:当接口持有的是值类型(例如 int、float、struct 等),进行类型断言时会发生拷贝,因为接口存储的是这个值的副本,断言后得到的是该值的拷贝。
引用类型:当接口持有的是引用类型(例如指针、切片、映射、通道等),进行类型断言时不会发生拷贝,因为接口存储的是一个引用,...
Go 语言中触发异常的场景有哪些?重点回答在 Go 语言中,使用 error 类型来处理错误,并通过 panic 和 recover 来处理程序的异常情况。以下是一些可能触发 panic(即异常)的场景:
数组或切片越界
空指针解引用
调用 panic 函数
非法类型断言
数学错误
内存越界或非法操作
运行时错误
使用不安全的库或代码
在上述1、2、4和5是在写代码中最常遇见的异常场景。...
不分配内存的指针类型能在 Go 语言中使用吗?回答重点在 Go 语言中,不分配内存的指针类型可以使用,但是只能用该指针本身,不可以用*去解引用出具体的值,会导致 panic 。这是因为 Go 允许声明指针变量,但如果不分配内存(没有指向有效的地址),该指针会是 nil。访问 nil 指针会导致运行时错误。
简单的说,Go 中声明一个指针变量是非常直接的,你可以使用 *Type 来声明一个指针...
Go 语言中 defer 的变量快照在什么情况下会失效?重点回答在 Go 语言中,defer 的变量快照是指在 defer 语句定义时所捕获的变量的状态。但有些情况下,defer 语句中的变量快照可能会失效,导致不如预期那样行为,如下:
1)匿名函数闭包:当 defer 语句中使用的匿名函数捕获了外部变量时。如果变量的值在 defer 语句定义后发生变化,defer 执行时会使用变化后的值。...
Go 语言中的局部变量是分配在栈上还是堆上?回答重点Go 语言中的局部变量既可能分配在栈上,也可能分配在堆上
如果变量的生命周期局限于函数作用域,并且不会逃逸到函数外,则分配在栈上。
如果局部变量的生命周期超出函数作用域(如通过指针返回给外部使用),编译器会将变量分配在堆上,确保变量在作用域外仍然有效,这种机制称为“逃逸分析”。
扩展知识栈和堆的区别栈分配:
栈是线程私有的,分配和释放内存...
Go 语言中 init() 函数在什么时候执行?回答重点init() 函数在 Go 程序执行之前自动调用,会在 main() 函数执行之前。
它用于初始化包级别的变量,用来设置初始状态或者执行一次性初始化操作(它不能有参数,也不能返回值)。每个包中的 init() 函数在该包的其他代码执行之前运行,每个包可以有多个 init() 函数。
执行顺序:
包的初始化顺序:如果一个包被多个包依赖,...
GO语言中非接口的任意类型T都能调用*T的方法么?反过来呢?回答重点都可以的。在Go语言中,对于非接口的任意类型T,确实可以调用 * T(指向T的指针)的方法。这是因为当你尝试在一个T类型的值上调用一个 * T 方法时,Go编译器会隐式地获取该值的地址,然后调用相应的方法。这种行为被称为指针接收者的方法调用的自动解引用。
示例代码1234567891011121314151617181920...
Go 语言中所有的 T 类型都有 *T 类型吗?重点回答不是。在 Go 语言中,几乎所有的类型 T 都可以有一个对应的指针类型 *T,不过接口类型的指针是无效的。
扩展知识1)普通情况对于大多数类型(包括基础类型、自定义类型、结构体、切片、映射、通道等),你可以使用 *T 来表示类型 T 的指针。以下是一些示例:
基础类型:
12var a int = 10var p *int = &...
Go 语言切片的容量是如何增长的?回答重点在 Go 语言中,切片的容量是一种动态增长的机制。当切片的长度达到或超过容量时,Go 语言会自动扩展其底层数组的容量,一般由append触发。切片容量增长(growslice)的具体规则在不同版本的规则不同。
对于 go1.18 之前来说:
如果期望容量大于当前容量的两倍就会使用期望容量;
如果当前切片的长度小于 1024 的话, growslic...
线程的生命周期在 Java 中是如何定义的?回答重点在 Java 中,线程的生命周期可以细化为以下几个状态:
New(初始状态):线程对象创建后,但未调用 start() 方法。
Runnable(可运行状态):调用 start() 方法后,线程进入就绪状态,等待 CPU 调度。
Blocked(阻塞状态):线程试图获取一个对象锁而被阻塞。
Waiting(等待状态):线程进入等待状态,需...
Java 中线程之间如何进行通信?回答重点在 Java 中,线程之间的通信是指多个线程协同工作,主要实现方式包括:
1)共享变量:
线程可以通过访问共享内存变量来交换信息(需要注意同步问题,防止数据竞争和不一致)。
共享的也可以是文件,例如写入同一个文件来进行通信。
2)同步机制:
synchronized:Java 中的同步关键字,用于确保同一时刻只有一个线程可以访问共享资源,利用 ...
Java 线程安全的集合有哪些?回答重点
类名
描述
线程安全模型
使用场景
Vector
线程安全的动态数组
每个方法加锁
较低并发需求的场景(不推荐使用)
Hashtable
线程安全的哈希表
每个方法加锁
较低并发需求的场景(不推荐使用)
ConcurrentHashMap
线程安全的哈希表(高并发)
分段锁,支持高并发
高并发的场景,如缓存、分布式锁等
Copy...
你了解 Java 线程池的原理吗?回答重点线程池是一种池化技术,用于预先创建并管理一组线程,避免频繁创建和销毁线程的开销,提高性能和响应速度。
它几个关键的配置包括:核心线程数、最大线程数、空闲存活时间、工作队列、拒绝策略。
主要工作原理如下:
默认情况下线程不会预创建,任务提交之后才会创建线程(不过设置 prestartAllCoreThreads 可以预创建核心线程)。
当核心...
Java 线程池有哪些拒绝策略?回答重点一共提供了 4 种:
1)AbortPolicy,当任务队列满且没有线程空闲,此时添加任务会直接抛出 RejectedExecutionException 错误,这也是默认的拒绝策略。适用于必须通知调用者任务未能被执行的场景。
2)CallerRunsPolicy,当任务队列满且没有线程空闲,此时添加任务由即调用者线程执行。适用于希望通过减缓任务提交速...
如何合理地设置 Java 线程池的线程数?回答重点线程池的线程数设置需要看具体执行的任务是什么类型的。
任务类型可以分:CPU 密集型任务和 I/O 密集型任务。
CPU 密集型任务CPU 密集型任务,就好比单纯的数学计算任务,它不会涉及 I/O 操作,也就是说它可以充分利用 CPU 资源(如果涉及 I/O,在进行 I/O 的时候 CPU 是空闲的),...
Java 并发库中提供了哪些线程池实现?它们有什么区别?回答重点Java 并发库中提供了 5 种常见的线程池实现,主要通过 Executors 工具类来创建。
1)FixedThreadPool:创建一个固定数量的线程池。
线程池中的线程数是固定的,空闲的线程会被复用。如果所有线程都在忙,则新任务会放入队列中等待。
适合负载稳定的场景,任务数量确定且不需要动态调整线程数。
2)CachedT...
什么是 Java 的 Timer?回答重点Java 的 Timer 是一个用于调度任务的工具类,用于在未来某个时刻执行任务或周期性地执行任务。Timer 类一般与 TimerTask 搭配使用,其中 TimerTask 是一个需要执行的任务。
适用于简单的定时任务,如定时更新、定期发送报告等。
扩展知识Timer 使用TimerTask 是 Timer 需要执行的任务,它是一个实现了 Run...
Java 中的 DelayQueue 和 ScheduledThreadPool 有什么区别?回答重点DelayQueue 是一个阻塞队列,而 ScheduledThreadPool 是线程池,不过内部核心原理都是差不多的。
DelayQueue 是利用优先队列存储元素,当从队列中获取任务的时候,如果最老的任务已经到了执行时间,可以从队列中出队一个任务,反之可以获得 null 或者阻塞等待任...
你了解时间轮(Time Wheel)吗?有哪些应用场景?回答重点时间轮(Time Wheel) 是一种用于管理和调度大量定时任务的数据结构。它是一种高效的定时任务调度算法,主要用于优化任务调度的效率,特别是在需要处理大量定时任务时。
时间轮是一种环形的数据结构,通过将时间划分为若干个时间片(槽),每个时间片负责管理一定时间段(如秒、分钟等内的任务。
工作原理:
时间轮的中心是一个环形结构,...
你使用过哪些 Java 并发工具类?回答重点比如:ConcurrentHashMap、AtomicInteger、Semaphore、CyclicBarrier、CountDownLatch、BlockingQueue 等等。
这个问题只要把你知道的一些并发类名字说出来就行了,然后等面试官选择其中一个去询问即可(一般需要结合简历中项目的业务场景,所以需要根据自己的业务提前准备)。
具体的并发...
什么是 Java 的 Semaphore?回答重点Semaphore (信号量)是 Java 并发包中的一个同步工具类,用于管理一组许可(permits)。每个许可可以被一个线程持有,当许可被持有时,其他线程需要等待才能获取许可。即可以限制同时访问特定资源的线程数量,确保在特定时刻只有有限数量的线程能够访问资源。
扩展知识基本概念:
许可(Permits):表示可以访问资源的线程数量。Sem...
什么是 Java 的 CyclicBarrier?回答重点CyclicBarrier 是一个同步辅助类,允许一组线程在执行某个任务时相互等待,直到所有线程都到达屏障(barrier)之后才继续执行。它的设计目的是解决多线程并发任务中需要同步的场景,确保所有参与的线程都在特定的点上完成执行,然后一起继续后续的任务。
扩展知识基本概念
屏障(Barrier):一个线程在调用 await() 方法...
什么是 Java 的 CountDownLatch?回答重点CountDownLatch 是 JUC 中的一个同步辅助类,它允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。CountDownLatch 通过一个计数器来实现,计数器的初始值由构造方法传入。每当一个线程完成工作后,计数器会递减。当计数器到达零时,所有等待的线程会被唤醒并继续执行。
主要功能:
等待事件完成:通过 a...