android面试题2
android面试题
一 线程池
工具类
import java.util.concurrent.*
import java.util.concurrent.atomic.AtomicInteger/*** 线程池工具类:简化线程池使用,提供常用配置*/
object ThreadPoolUtils {// CPU核心数(自动适配设备)private val CPU_COUNT = Runtime.getRuntime().availableProcessors()// 核心线程数:CPU核心数 + 1(常规场景适配)private val CORE_POOL_SIZE = CPU_COUNT + 1// 最大线程数:CPU核心数 * 2 + 1(应对突发任务)private val MAX_POOL_SIZE = CPU_COUNT * 2 + 1// 非核心线程空闲存活时间(30秒)private const val KEEP_ALIVE_TIME = 30L// 任务队列容量private const val QUEUE_SIZE = 128// 通用线程池实例private val executor: ExecutorServiceinit {// 线程工厂:自定义线程名称,便于调试val threadFactory = ThreadFactory { runnable ->val threadNumber = AtomicInteger(1).getAndIncrement()Thread(runnable, "ThreadPool-$threadNumber").apply {isDaemon = false // 非守护线程,确保任务完成}}// 初始化线程池executor = ThreadPoolExecutor(CORE_POOL_SIZE,MAX_POOL_SIZE,KEEP_ALIVE_TIME,TimeUnit.SECONDS,LinkedBlockingQueue(QUEUE_SIZE), // 任务队列(可改为ArrayBlockingQueue指定固定容量)threadFactory,ThreadPoolExecutor.DiscardOldestPolicy() // 队列满时丢弃最旧任务,避免崩溃)}/*** 提交任务到线程池*/fun execute(task: Runnable) {executor.execute(task)}/*** 获取单线程池(用于串行任务,按提交顺序执行)*/fun getSingleThreadExecutor(): ExecutorService {return Executors.newSingleThreadExecutor()}/*** 获取固定线程数的线程池* @param nThreads 线程数量*/fun getFixedThreadPool(nThreads: Int): ExecutorService {return Executors.newFixedThreadPool(nThreads)}/*** 关闭线程池(应用退出时调用)*/fun shutdown() {if (!executor.isShutdown) {executor.shutdown()}}
}
使用
// 在主线程中提交子线程任务
ThreadPoolUtils.execute {// 执行耗时操作(如车载场景中的本地文件读取、数据解析)loadCarStatusData()
}或者
// 获取单线程池,任务按顺序执行
val singleExecutor = ThreadPoolUtils.getSingleThreadExecutor()
singleExecutor.execute {// 任务1:下载地图数据downloadMapData()
}
singleExecutor.execute {// 任务2:解析地图数据(会在任务1完成后执行)parseMapData()
}// 在Application或主Activity的onDestroy中调用
ThreadPoolUtils.shutdown()
二 数据结构和算法
一、基础数据结构:数组(Array)与链表(LinkedList)
问题1:在车载空调APP中,若需要存储“不同区域的温度设置(如主驾、副驾、后排)”,用数组还是链表更合适?为什么?
答案:优先用数组,核心原因如下:
- 访问效率:车载空调的温度设置需要高频“随机访问”(如用户点击主驾区域,直接读取/修改对应位置的温度值),数组的随机访问时间复杂度是 O(1),而链表需遍历查找,时间复杂度 O(n),无法满足车载UI的实时响应需求。
- 场景适配:车载空调的分区数量是固定的(通常3-4个区域),数组的固定长度特性完全匹配,无需链表的“动态扩容”能力;且数组内存连续,占用资源更少,适合车载系统对内存的严格要求。
- 实际代码映射:Android中
ArrayList(基于数组)的get(int index)方法可直接定位分区温度,而LinkedList的get(int index)会触发遍历,在空调调节的高频交互中可能导致UI卡顿。
二、高频应用:哈希表(HashMap/HashTable)
问题2:你在开发空调APP时,若需要缓存“每个用户的个性化空调设置(如用户ID→温度、风速、模式)”,为什么选择HashMap而非HashTable?HashMap的核心原理(哈希冲突、扩容机制)是什么?
答案:
1. 选择HashMap的原因(结合车载场景):
- 性能优先:车载系统对响应速度要求高,HashMap是非线程安全的,无同步锁开销,查询/插入效率(平均O(1))远高于HashTable(全程加锁,效率低);而用户个性化设置的读写通常在单线程(主线程或专属缓存线程)中进行,无需线程安全保障。
- 功能适配:HashMap支持
null键和null值(如默认用户的设置可存为null),而HashTable不支持;且HashMap支持泛型(如HashMap<String, AirConditionConfig>),代码可读性更高,符合Android开发规范。
2. HashMap核心原理:
- 哈希冲突解决:通过“数组+链表/红黑树”实现。当不同key计算出相同哈希值(冲突)时,会在数组对应位置形成链表;当链表长度超过8且数组容量≥64时,链表转为红黑树(查询时间从O(n)优化为O(logn)),避免极端场景下的性能退化(如车载缓存大量用户设置时)。
- 扩容机制:默认初始容量16,负载因子0.75。当元素数量超过“容量×负载因子”(如16×0.75=12)时,触发扩容(容量翻倍),并重新计算所有元素的哈希值(rehash),确保哈希冲突概率维持在低水平,保障缓存查询效率。
三、有序结构:二叉树与红黑树
问题3:车载空调APP中,若需要按“温度调节频率”排序显示常用温度(如22℃、24℃、26℃的使用次数),为什么用TreeMap(红黑树实现)而非HashMap?红黑树的核心特性是什么?
答案:
1. 选择TreeMap的原因:
- 天然排序:TreeMap基于红黑树实现,可按key的自然顺序(如温度值大小)或自定义比较器(如按使用次数降序)自动排序,无需额外处理排序逻辑;而HashMap是无序的,若需排序需手动遍历+Collections.sort(),效率低(O(nlogn)),且无法实时维护有序状态(如用户新增一次24℃调节后,需重新排序)。
- 场景匹配:车载常用温度的数量通常较少(5-10个),TreeMap的查询/插入时间复杂度(O(logn))完全满足需求,且有序特性可直接用于UI列表展示(如“常用温度TOP3”),减少代码工作量。
2. 红黑树的核心特性(保证有序+高效):
- 每个节点非红即黑;
- 根节点是黑色;
- 所有叶子节点(null)是黑色;
- 红色节点的两个子节点必为黑色(无连续红节点);
- 从任意节点到其所有叶子节点的路径,黑色节点数量相同(“黑高一致”)。
这些特性确保红黑树的高度始终维持在O(logn),避免退化为链表,保障TreeMap在高频更新(如用户频繁调节温度)时的性能稳定。
四、队列(Queue):阻塞队列与非阻塞队列
问题4:在空调APP中,若需要处理“异步温度调节请求(如用户连续点击+/-按钮,需按顺序执行调节)”,为什么用BlockingQueue(如ArrayBlockingQueue)而非普通Queue?阻塞队列的核心作用是什么?
答案:
1. 选择BlockingQueue的原因(车载异步场景适配):
- 线程安全+顺序保障:用户连续点击温度按钮时,请求会在主线程产生,但温度调节的实际逻辑(如与车载硬件通信)需在子线程执行,BlockingQueue是线程安全的,可确保主线程“生产请求”和子线程“消费请求”的顺序一致(FIFO),避免调节指令混乱(如先+1℃再-1℃,不会变成先-1℃再+1℃)。
- 阻塞等待机制:当队列空时,子线程会阻塞等待新请求(无需轮询,减少CPU占用);当队列满时(如用户短时间点击10次),主线程会阻塞或抛出异常(可配置),避免请求堆积导致内存溢出,符合车载系统的稳定性要求。
2. 阻塞队列的核心作用:
- 实现“生产者-消费者”模型的解耦(主线程生产请求,子线程消费请求);
- 自带线程安全和流量控制(队列容量限制),无需手动加锁(如synchronized),简化代码且避免死锁风险;
- 常见实现类:ArrayBlockingQueue(固定容量,性能高)、LinkedBlockingQueue(动态容量),车载场景优先选ArrayBlockingQueue(固定容量可预知,避免内存波动)。
五、实战延伸:数据结构在车载开发中的性能优化
问题5:你在空调APP中使用数据结构时,遇到过哪些性能问题?如何优化的?(结合实际经验的高频追问)
答案(可结合你的项目经历调整):
在开发空调分区温度缓存时,初期用HashMap<String, Int>存储“分区ID→温度”,但在车载系统低内存场景下,出现过哈希冲突频繁(链表长度超过10)导致查询延迟的问题,优化方案如下:
- 初始化容量优化:根据实际分区数量(4个),将HashMap初始容量设为8(而非默认16),负载因子保持0.75,减少内存占用的同时,降低扩容频率(无需rehash);
- 冲突解决优化:自定义分区ID的哈希算法(如将“主驾”“副驾”等字符串转为固定int值,而非默认的Object.hashCode()),减少哈希冲突概率,避免链表转为红黑树的开销;
- 缓存淘汰策略:添加缓存过期机制(如用户30分钟未操作,清空HashMap),释放内存,符合车载系统“低内存优先”的特性。
参考
https://blog.51cto.com/u_16163510/11567226
