百度移动开发面经合集
1、对线程安全的理解
线程安全是指在多线程环境下,某个函数、类或数据结构能够正确地处理多个线程的并发访问,而不会出现数据竞争、不一致或其他不可预期的行为。线程安全的实现通常需要考虑以下几点:
原子性:操作是不可分割的,要么全部执行,要么全部不执行。
可见性:一个线程对共享数据的修改能够及时被其他线程看到。
有序性:程序的执行顺序符合预期,避免指令重排导致的问题。
实现线程安全的方法包括:
使用同步机制(如锁、信号量)。
使用线程安全的数据结构(如
ConcurrentHashMap
)。避免共享状态(如使用局部变量或线程本地存储)。
2、对链表的使用和理解,链表有哪几种并举例应用场景
链表是一种线性数据结构,由节点组成,每个节点包含数据和指向下一个节点的指针。链表的类型包括:
单向链表:每个节点只有一个指针指向下一个节点。
应用场景:实现栈、队列、简单的动态集合。
双向链表:每个节点有两个指针,分别指向前一个和后一个节点。
应用场景:需要双向遍历的场景(如浏览器的前进后退、LRU缓存)。
循环链表:尾节点指向头节点,形成环。
应用场景:轮询调度、环形缓冲区。
链表的优点是插入和删除高效(O(1)),缺点是随机访问慢(O(n))。
3、TCP 相对于 UDP 有哪些优点,两者的不同点
TCP的优点:
可靠传输:通过确认、重传、流量控制和拥塞控制确保数据不丢失、不重复、按序到达。
面向连接:通信前需要建立连接(三次握手),通信后释放连接(四次挥手)。
适合对可靠性要求高的场景(如文件传输、网页浏览)。
TCP vs UDP:
特性 | TCP | UDP |
---|---|---|
可靠性 | 可靠 | 不可靠 |
连接 | 面向连接 | 无连接 |
传输效率 | 较低(开销大) | 较高(开销小) |
数据顺序 | 保证顺序 | 不保证顺序 |
应用场景 | HTTP、FTP、SSH | 视频流、DNS、游戏 |
4、讲述一下TCP 的传送数据的过程
建立连接(三次握手):
客户端发送
SYN=1, seq=x
。服务端回复
SYN=1, ACK=1, seq=y, ack=x+1
。客户端发送
ACK=1, seq=x+1, ack=y+1
。
数据传输:
发送方将数据分割为报文段,按序发送。
接收方确认收到数据(ACK)。
发送方未收到ACK时会重传。
释放连接(四次挥手):
客户端发送
FIN=1, seq=u
。服务端回复
ACK=1, ack=u+1
。服务端发送
FIN=1, seq=v
。客户端回复
ACK=1, ack=v+1
。
5、原子性和一致性
原子性(Atomicity):事务中的操作要么全部成功,要么全部失败(如转账:A减钱和B加钱必须同时完成)。
一致性(Consistency):事务执行前后,数据库从一个一致状态变到另一个一致状态(如满足约束条件)。
6、数据库的索引和事务是什么
索引:加速查询的数据结构(如B+树索引)。优点:提高查询速度;缺点:占用空间,降低插入/更新速度。
事务:一组操作的逻辑单元,满足ACID特性(原子性、一致性、隔离性、持久性)。
7、数据库查询操作有哪些可以提高查询效率的优化
使用索引。
避免
SELECT *
,只查询需要的列。优化JOIN操作(小表驱动大表)。
避免全表扫描(如避免对索引列使用函数)。
分库分表(大数据量时)。
使用缓存(如Redis)。
8、手写快排的代码
public class QuickSort {public static void quickSort(int[] arr, int low, int high) {if (low < high) {int pivotIndex = partition(arr, low, high);quickSort(arr, low, pivotIndex - 1); // 递归排序左子数组quickSort(arr, pivotIndex + 1, high); // 递归排序右子数组}}private static int partition(int[] arr, int low, int high) {int pivot = arr[high]; // 选择最右元素作为基准int i = low - 1; // i是小于基准的元素的边界for (int j = low; j < high; j++) {if (arr[j] < pivot) {i++;swap(arr, i, j); // 将小于基准的元素放到左侧}}swap(arr, i + 1, high); // 将基准放到正确位置return i + 1;}private static void swap(int[] arr, int i, int j) {int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}public static void main(String[] args) {int[] arr = {3, 6, 8, 10, 1, 2, 1};quickSort(arr, 0, arr.length - 1);System.out.println(Arrays.toString(arr)); // 输出: [1, 1, 2, 3, 6, 8, 10]}
}
9、展示曾经写过的项目代码并讲述功能
无
为什么选择做安卓
我选择做安卓开发的原因主要有以下几点:
用户基数大:Android 是全球市场份额最大的移动操作系统,拥有庞大的用户群体。
开放性和灵活性:相比 iOS,Android 系统更开放,允许开发者更自由地定制和优化应用。
技术生态丰富:Android 开发涉及 UI/UX、性能优化、多线程、网络通信等多个领域,技术挑战性强。
跨平台趋势:Kotlin、Flutter、Jetpack Compose 等技术的发展让 Android 开发更具扩展性。
此外,我对移动端交互和性能优化有浓厚兴趣,Android 开发能让我深入理解系统底层机制(如 Binder、Handler 等),提升综合技术能力。
安卓了解多少,技术非技术都可以
技术方面
开发语言:Java(传统)、Kotlin(官方推荐,更简洁安全)。
核心组件:Activity(界面)、Service(后台)、BroadcastReceiver(广播)、ContentProvider(数据共享)。
UI 框架:XML + View 体系、Jetpack Compose(声明式 UI)。
架构模式:MVC → MVP → MVVM(DataBinding + ViewModel + LiveData)。
性能优化:内存泄漏(LeakCanary)、卡顿分析(Systrace)、APK 瘦身(ProGuard)。
新技术:协程(Kotlin Flow)、KMM(跨平台)、Android 12/13 新特性(如 Material You)。
非技术方面
市场现状:Android 占据全球约 70% 移动市场份额,但国内厂商深度定制(如 MIUI、EMUI)。
开发者生态:Google Play 审核较宽松,但国内需适配各大应用商店。
挑战:碎片化严重(系统版本、屏幕尺寸、厂商 ROM 差异)。
聊项目,登录采用的redis+cookie的分布式session解决方案,具体聊聊
在分布式系统中,Session 需共享,避免用户登录状态丢失(如服务器重启或负载均衡切换)。
实现方案
登录流程:
用户输入账号密码,服务端校验通过后生成唯一
SessionID
。将
SessionID:UserInfo
存入 Redis(设置过期时间)。通过
Set-Cookie
将SessionID
返回给浏览器。
后续请求:
浏览器自动携带
Cookie(SessionID)
。服务端从 Redis 查询
SessionID
,获取用户信息。
安全性优化:
Cookie 设置
HttpOnly
防 XSS,Secure
强制 HTTPS。Redis 存储的 Session 可加密(如 JWT)。
优点
无状态:服务端无需存储 Session,适合横向扩展。
高可用:Redis 集群保障 Session 不丢失。
登录过程当中对密码的校验,存储讲讲
校验流程
前端对密码做初步校验(如长度、复杂度)。
服务端二次校验(防恶意请求),例如:
if (password.length() < 8 || !containsLetterAndDigit(password)) {throw new InvalidPasswordException(); }
存储方案
哈希加密:使用
BCrypt
(推荐)或PBKDF2
,避免明文存储。String hashedPassword = BCrypt.hashpw(rawPassword, BCrypt.gensalt());
加盐(Salt):防止彩虹表攻击,每个用户密码使用独立随机盐值。
面向对象的三大特性具体讲解下
封装(Encapsulation)
隐藏内部实现,暴露安全接口。
继承(Inheritance)
子类复用父类属性和方法(
extends
)。
多态(Polymorphism)
同一方法在不同子类中有不同实现(覆盖
@Override
+ 父类引用指向子类对象)。
讲讲java的权限控制(protect继承关系,default是包访问权限)
修饰符 | 类内 | 同包 | 子类 | 其他包 |
---|---|---|---|---|
private | ✅ | ❌ | ❌ | ❌ |
default | ✅ | ✅ | ❌ | ❌ |
protected | ✅ | ✅ | ✅ | ❌ |
public | ✅ | ✅ | ✅ | ✅ |
protected
:子类可访问(即使跨包)。default
(包权限):仅同包内可见。
java基本类型的以及占空间大小
类型 | 大小 | 取值范围 |
---|---|---|
boolean | 1字节 | true /false |
byte | 1字节 | -128 ~ 127 |
short | 2字节 | -32768 ~ 32767 |
int | 4字节 | -2³¹ ~ 2³¹-1 |
long | 8字节 | -2⁶³ ~ 2⁶³-1 |
float | 4字节 | IEEE 754 单精度浮点 |
double | 8字节 | IEEE 754 双精度浮点 |
char | 2字节 | Unicode 字符(0~65535) |
注意:JVM 可能对
boolean
优化为 1 bit,但数组中占 1 字节。
boolean类型占多大内存,int占多少,double(8字节)
如上
java循环的使用
(1) for
循环
(2) while
循环
(3) do-while
循环
(4) 增强 for
(遍历集合)
java的反射机制,单例模式被反射机制破环,具体是反射的哪些Api进行破环的
Java 反射可以过 Class.getDeclaredConstructor()
和 Constructor.setAccessible(true)
绕过单例模式的私有构造方法限制,从而创建多个实例。
防御反射攻击的方法
枚举单例(推荐):JVM 保证枚举实例的唯一性,反射无法破坏。
构造方法中抛出异常
简单介绍单例模式的实现方式都有哪些
实现方式 | 特点 | 线程安全 | 防反射 |
---|---|---|---|
饿汉式 | 类加载时初始化,可能浪费资源 | ✅ | ❌ |
懒汉式 | 第一次调用时初始化,需加锁解决线程问题 | ❌(需同步) | ❌ |
双重检测锁 | 延迟加载 + 同步块优化性能 | ✅ | ❌ |
静态内部类 | 利用类加载机制保证线程安全,延迟加载 | ✅ | ❌ |
枚举 | 最安全,JVM 保障唯一性,天然防反射和序列化破坏 | ✅ | ✅ |
双重检测锁的模式可以解决懒汉式单例的存在的多线程问题,请问枚举可以解决单例模式存在的
单例模式的优势
线程安全:JVM 在加载枚举类时保证实例唯一。
防反射攻击:
Constructor.newInstance()
会直接抛出异常。防序列化破坏:枚举的序列化由 JVM 特殊处理,不会创建新实例。
java当中的集合八股
集合框架分类
Collection
List
:有序可重复(ArrayList
、LinkedList
)。Set
:无序不重复(HashSet
、TreeSet
)。Queue
:队列(LinkedList
、PriorityQueue
)。
Map
HashMap
:数组 + 链表/红黑树。TreeMap
:基于红黑树有序。ConcurrentHashMap
:线程安全。
高频考点
ArrayList
vsLinkedList
HashMap
的哈希冲突解决(拉链法 + 红黑树)。ConcurrentHashMap
的分段锁(JDK7)和 CAS + synchronized(JDK8)。
HashMap的扩容机制
触发条件:当元素数量超过
容量 × 负载因子(默认 0.75)
时扩容。扩容过程:
新建一个 2 倍大小的数组。
重新计算每个键值对的哈希值,分配到新数组(JDK8 优化:高位掩码判断位置是否变化)。
JDK8 优化:链表长度 ≥8 时转红黑树(提高查询效率)。
Java多线程当中的锁
锁的分类
synchronized
修饰方法或代码块,JVM 级别实现。
可重入,非公平锁。
ReentrantLock
API 级别锁,需手动
lock()
/unlock()
。支持公平锁、可中断、条件变量(
Condition
)。
ReadWriteLock
读写分离(
ReentrantReadWriteLock
),提高读多写少场景性能。
StampedLock
乐观读锁,避免写线程饥饿。
在项目当中使用过哪些锁,怎么使用的
略
new对象的时候,JVM底层的一个过程(类加载机制)
当执行 new MyClass()
时,JVM 底层会经历以下步骤:
(1) 类加载(若未加载)
加载(Loading):
通过类加载器(
ClassLoader
)查找.class
文件,将字节码加载到方法区。生成
Class
对象(存储在堆中,作为方法区数据的访问入口)。
验证(Verification):检查字节码合法性(如魔数、语法)。
准备(Preparation):为静态变量分配内存并赋默认值(如
int
初始化为0
)。解析(Resolution):将符号引用(如类名、方法名)转为直接引用(内存地址)。
初始化(Initialization):执行静态代码块(
static{}
)和静态变量赋值。
(2) 对象实例化
分配内存:
指针碰撞(堆内存规整时):移动指针分配连续空间。
空闲列表(堆内存不规整时):从空闲内存块列表中选择合适区域。
内存空间初始化:将所有字段置为默认值(如
int
为0
,引用为null
)。设置对象头:存储 Mark Word(哈希码、GC 分代年龄等)和类元数据指针(指向方法区的
Class
对象)。执行构造方法(
<init>
):调用用户定义的构造函数,完成字段赋值。
(3) 对象引用关联
将堆中对象的内存地址赋给栈中的引用变量(如
MyClass obj
)。
讲讲网络当中Https
HTTPS = HTTP + SSL/TLS,核心是通过加密和证书保障安全:
(1) 加密流程(TLS 握手)
客户端发起请求:
发送支持的加密算法列表和随机数(Client Random)。
服务器响应:
返回选择的加密算法、服务器证书(含公钥)和随机数(Server Random)。
证书验证:
客户端用 CA 机构的公钥验证证书合法性(防中间人攻击)。
生成会话密钥:
客户端用服务器公钥加密一个 Pre-Master Secret 并发送。
双方通过
Client Random + Server Random + Pre-Master
生成对称密钥(Session Key)。
加密通信:
后续数据传输使用 Session Key 对称加密(如 AES)。
(2) 关键点
非对称加密:用于密钥交换(RSA/ECDHE)。
对称加密:用于数据传输(AES)。
数字证书:验证服务器身份,由 CA 机构签发。
刚刚提到的Https是基于Http,Http使用的是Tcp,那么Tcp的四次挥手当中第三次挥手是谁向谁进行挥手
在 TCP 四次挥手中:
第一次挥手:客户端 → 服务器,发送
FIN=1
(客户端主动关闭)。第二次挥手:服务器 → 客户端,发送
ACK=1
(确认收到FIN
)。第三次挥手:服务器 → 客户端,发送
FIN=1
(服务器准备关闭)。第四次挥手:客户端 → 服务器,发送
ACK=1
(确认收到FIN
)。
关键问题答案:
第三次挥手是服务器向客户端发送
FIN
,表示服务器数据已发送完毕,准备关闭连接。
为什么需要四次挥手?
TCP 是全双工的,双方需独立关闭自己的发送通道。
第二次和第三次挥手不能合并:服务器可能还有数据要发送(延迟确认)。
总结:
new
对象过程:类加载 → 分配内存 → 初始化 → 构造方法调用。HTTPS:通过 TLS 握手建立安全通道,混合使用非对称加密和对称加密。
TCP 挥手:第三次挥手是服务器通知客户端关闭连接。
数据结构当中栈和队列在项目使用过嘛
略
算法题:两个栈实现一个队列
队列(Queue)是 先进先出(FIFO) 的数据结构,而栈(Stack)是 后进先出(LIFO) 的数据结构。我们可以利用 两个栈(stack1
和 stack2
) 来模拟队列的操作:
核心思想
入队(
enqueue
):直接压入stack1
。出队(
dequeue
):如果
stack2
为空,则把stack1
的所有元素弹出并压入stack2
(此时stack2
的栈顶就是最早入队的元素)。如果
stack2
不为空,直接弹出栈顶元素。
.工作内容简单介绍(10min)
略
2.介绍一下TCP三握四次挥手(10min)
三次握手(建立连接)
SYN(Client → Server):
客户端发送
SYN=1, seq=x
(随机序列号)。
SYN+ACK(Server → Client):
服务端返回
SYN=1, ACK=1, seq=y, ack=x+1
(确认客户端的x
,并发送自己的y
)。
ACK(Client → Server):
客户端发送
ACK=1, seq=x+1, ack=y+1
(确认服务端的y
)。
✅ 目的:确保双方都有发送和接收能力,防止历史连接干扰。
四次挥手(关闭连接)
FIN(Client → Server):
客户端发送
FIN=1, seq=u
(表示不再发送数据)。
ACK(Server → Client):
服务端返回
ACK=1, ack=u+1
(确认收到FIN
)。
FIN(Server → Client):
服务端发送
FIN=1, seq=v
(表示服务端也准备关闭)。
ACK(Client → Server):
客户端返回
ACK=1, ack=v+1
(确认服务端的FIN
)。
✅ 为什么需要四次?
TCP 是全双工的,双方需独立关闭自己的发送通道。
2.1为什么第四次挥手要超时等待2MSL
MSL(Maximum Segment Lifetime):报文最大生存时间(Linux 默认 60s)。
目的 1:确保最后一个
ACK
能到达服务端。如果服务端没收到
ACK
,会重发FIN
,客户端在2MSL
内还能响应。
目的 2:让本次连接的所有报文在网络中消失,避免影响后续新连接。
2.2如果有大量的time_wait,close_wait,你会如何检测分析,原因?出现阶段?以及如何避免
(1)TIME_WAIT
(客户端状态)
出现阶段:主动关闭连接的一方(如 HTTP 客户端、微服务调用方)。
原因:
短连接频繁建立和关闭(如爬虫、API 频繁调用)。
没有复用 TCP 连接(如 HTTP/1.1 未启用
Keep-Alive
)。
解决:
复用连接(HTTP/1.1
Keep-Alive
、连接池)。调整内核参数(慎用):
(2)CLOSE_WAIT
(服务端状态)
出现阶段:被动关闭连接的一方(如服务端未正确调用
close()
)。原因:
代码 Bug:未关闭 Socket(如 Java 未调用
socket.close()
)。线程阻塞:处理请求时卡住,无法执行关闭逻辑。
解决:
检查代码确保资源释放(
try-with-resources
或finally
块)。优化服务端性能(避免阻塞)。
2.3什么情况下syn请求丢弃,失效?如何分析,如何解决,不考虑防火墙
(1)SYN 被丢弃的原因
半连接队列(SYN Queue)满:
服务端未及时处理
SYN
,导致队列溢出(默认大小net.ipv4.tcp_max_syn_backlog
)。
SYN Flood 攻击:
攻击者发送大量
SYN
但不完成握手,耗尽服务端资源。
网络问题:
中间路由器丢弃
SYN
(如拥塞、TTL 过期)。
(2)如何分析?
服务端统计:
抓包分析:
(3)解决方案
增大半连接队列:
启用 SYN Cookie(防攻击):
优化服务端:
加快握手处理(如负载均衡、扩容)。
总结
问题 | 原因 | 解决方案 |
---|---|---|
TIME_WAIT 过多 | 短连接频繁关闭 | 复用连接、调整 tcp_tw_reuse |
CLOSE_WAIT 过多 | 服务端未正确关闭 Socket | 检查代码、优化资源释放 |
SYN 被丢弃 | 队列满、攻击、网络问题 | 增大队列、SYN Cookie、抓包分析 |
3.项目深挖,包括项目实现背景(即为什么去做这个项目),项目功能如何实现,项目实现中的一些细节和设计模式,项目对应功能如何测试,如何部署。遇到问题如何发散解决(40min)
略
3.进程线程区别
对比项 | 进程(Process) | 线程(Thread) |
---|---|---|
定义 | 操作系统资源分配的基本单位 | CPU 调度的基本单位(属于进程) |
内存占用 | 独立内存空间(隔离性强) | 共享进程内存(高效但需同步) |
创建开销 | 大(需分配独立资源) | 小(共享进程资源) |
通信方式 | 进程间通信(IPC):管道、共享内存、消息队列 | 直接读写共享变量(需加锁) |
崩溃影响 | 一个进程崩溃不影响其他进程 | 一个线程崩溃可能导致整个进程退出 |
并发性 | 多进程并行(多核) | 多线程并行(轻量级并发) |
关键点:
线程是进程的子任务,共享进程的堆内存,但有自己的栈和寄存器。
多线程需处理竞态条件(如
synchronized
、ReentrantLock
)。
4OSI七层模型
层数 | 名称 | 功能 | 协议/设备 |
---|---|---|---|
7 | 应用层 | 用户接口(HTTP、FTP) | HTTP、SMTP、DNS |
6 | 表示层 | 数据格式转换(加密、压缩) | SSL、JPEG |
5 | 会话层 | 建立/管理会话(RPC) | NetBIOS |
4 | 传输层 | 端到端可靠传输(TCP/UDP) | TCP、UDP |
3 | 网络层 | 路由寻址(IP 地址) | IP、ICMP、路由器 |
2 | 数据链路层 | 帧传输(MAC 地址) | Ethernet、交换机 |
1 | 物理层 | 比特流传输(电缆、光纤) | 网卡、集线器 |
简化记忆:
上三层(应用、表示、会话):面向用户。
下四层(传输、网络、数据链路、物理):面向数据。
5详细介绍线程
线程的生命周期(状态)
NEW:刚创建,未调用
start()
。RUNNABLE:可运行(可能在等待 CPU)。
BLOCKED:等待锁(如
synchronized
)。WAITING:无限期等待(如
wait()
)。TIMED_WAITING:超时等待(如
sleep(1000)
)。TERMINATED:执行完毕。
创建线程的方式
继承
Thread
类:实现
Runnable
接口:线程池(
ExecutorService
):
线程同步
synchronized
:ReentrantLock
:
1.get和post的区别
1. GET vs POST 的区别
对比项 | GET | POST |
---|---|---|
语义 | 获取资源(幂等) | 提交数据(非幂等) |
数据位置 | URL 查询参数(明文) | 请求体(可加密) |
长度限制 | 受 URL 长度限制(约 2KB) | 无限制(服务器可配置) |
缓存 | 可被缓存 | 不可缓存 |
安全性 | 参数暴露在 URL(不安全) | 数据在请求体中(相对安全) |
使用场景:
GET:搜索、分页(如
?page=1
)。POST:登录、表单提交(如 JSON 数据)。
5.互联网这两年前景不好,谈一下你的看法
短期挑战:
政策监管加强(如数据安全、反垄断)。
资本趋于理性,野蛮增长时代结束。
全球经济下行影响投资和消费。
长期机会:
技术驱动:AI、云计算、物联网等仍是未来核心。
数字化转型:传统行业(医疗、制造)仍需互联网技术赋能。
全球化市场:中国互联网企业出海(如 TikTok、SHEIN)。
结论:行业从“高速增长”转向“高质量发展”,对技术深度和复合能力要求更高。
6.为什么觉着互联网会比其他行业就业广
技术渗透性强:
几乎所有行业都需要互联网技术(金融→FinTech、零售→电商)。
岗位多样性:
技术(开发、测试)、产品、运营、设计、数据分析等岗位覆盖全链条。
薪资竞争力:
平均薪资高于传统行业(如制造业)。
低准入门槛:
相比医生、律师等职业,互联网更依赖技能而非学历证书。
远程办公机会:
支持全球化协作(如程序员可远程为海外公司工作)。
7.app测试和web测试有何不同
对比项 | App 测试 | Web 测试 |
---|---|---|
运行环境 | 移动设备(iOS/Android) | 浏览器(Chrome、Firefox) |
兼容性 | 需测试不同机型、OS版本、分辨率 | 主要测试浏览器兼容性 |
网络依赖 | 弱网、断网测试更重要 | 网络影响较小 |
安装与更新 | 需测试安装、升级、卸载流程 | 无需安装,即时更新 |
性能指标 | 内存占用、CPU、电量、启动速度 | 页面加载速度、服务器响应时间 |
工具 | Appium、XCUITest、Espresso | Selenium、Cypress、Playwright |
核心差异:
App 测试更关注硬件交互(如摄像头、GPS)和移动端特性(如手势操作)。
Web 测试更注重跨浏览器一致性和前端渲染性能。
8.你是怎么区分前端和后端的
对比项 | 前端(Frontend) | 后端(Backend) |
---|---|---|
职责 | 用户界面(UI)和交互逻辑 | 数据处理、业务逻辑、数据库交互 |
技术栈 | HTML/CSS/JavaScript、React/Vue | Java/Python/Go、Spring/Django |
关注点 | 用户体验、页面性能、兼容性 | 高并发、安全性、API 设计 |
调试工具 | Chrome DevTools、Lighthouse | Postman、Jmeter、日志分析 |
输出结果 | 浏览器渲染的页面 | 数据库记录、API 响应(JSON/XML) |
简单判断:
前端代码运行在浏览器,后端代码运行在服务器。
前端请求数据(如
fetch('/api/data')
),后端提供数据(如返回JSON
)。
内存分页与分段的区别及优缺点
分页(Paging)
原理:
内存划分为固定大小的页(如 4KB),进程的地址空间分为同等大小的页框。
通过页表映射虚拟地址到物理地址。
优点:
解决外部碎片(物理内存利用率高)。
支持虚拟内存(页面可换入换出磁盘)。
缺点:
页内可能产生内部碎片(最后一页未用满)。
分段(Segmentation)
原理:
按逻辑单元(代码段、数据段、堆栈段)划分内存,每段长度可变。
通过段表记录基址和长度。
优点:
符合程序逻辑(便于共享和保护段)。
缺点:
产生外部碎片(需内存紧凑或动态分区)。
对比总结
特性 | 分页 | 分段 |
---|---|---|
划分单位 | 固定大小页 | 可变长度段 |
碎片问题 | 内部碎片 | 外部碎片 |
管理复杂度 | 简单(页表) | 复杂(段表+保护机制) |
适用场景 | 通用内存管理(OS 主流方案) | 需要逻辑隔离(如早期系统) |
现代 OS(如 Linux):结合分页和分段,实际使用分页机制,段基址固定为 0。
Android方向
四大组件生命周期(Activity、Service等)
(1) Activity 生命周期
核心方法:
onCreate()
:初始化 UI 和数据。onStart()
:Activity 可见但未前台交互。onResume()
:Activity 进入前台,可交互。onPause()
:失去焦点(如弹窗),需快速释放资源。onStop()
:完全不可见(可能被销毁)。onDestroy()
:释放所有资源。
场景:
屏幕旋转:
onDestroy()
→onCreate()
(默认重建)。按 Home 键:
onPause()
→onStop()
。
(2) Service 生命周期
启动方式:
startService()
:onCreate()
→onStartCommand()
→ (运行中)→onDestroy()
。
bindService()
:onCreate()
→onBind()
→ (通信中)→onUnbind()
→onDestroy()
。
(3) BroadcastReceiver
动态注册:
onReceive()
执行后立即销毁。
静态注册:
常驻,通过 AndroidManifest.xml 声明。
(4) ContentProvider
生命周期由系统管理,主要方法:
onCreate()
:初始化数据源(如数据库)。query()
/insert()
/update()
/delete()
:CRUD 操作。
Handler机制原理(Looper、MessageQueue)
核心组件
Handler
:发送和处理消息(sendMessage()
/handleMessage()
)。MessageQueue
:消息队列(单链表,按时间排序)。Looper
:循环从MessageQueue
取消息并分发。
工作流程
Looper.prepare():创建
Looper
和MessageQueue
(主线程已默认创建)。Looper.loop():无限循环调用
MessageQueue.next()
获取消息。Handler.sendMessage():将
Message
插入MessageQueue
。Handler.handleMessage():在目标线程处理消息。
性能优化:内存泄漏(Cursor未关闭、Bitmap未回收)、ANR原因
自定义View流程(measure/layout/draw)
iOS方向
Runtime机制:消息转发、Category实现
消息转发
Objective-C 的消息转发机制是在对象无法响应某个方法时提供的一种补救措施。消息转发分为三个阶段:
动态方法解析:
+resolveInstanceMethod:
或+resolveClassMethod:
,可以在这里动态添加方法实现。备用接收者:
-forwardingTargetForSelector:
,可以将消息转发给其他对象处理。完整转发:
-methodSignatureForSelector:
和-forwardInvocation:
,可以封装 NSInvocation 进行更灵活的转发。
Category 实现
Category 是通过运行时动态将方法添加到类中(编译时为类生成 category_t 结构体,运行时合并到类的方法列表中)。特点:
可以添加方法,但不能添加实例变量(可通过关联对象间接实现)。
方法会覆盖原类方法(同名方法调用优先级:Category > 原类 > 父类)。
实现原理:
编译后生成
_category_t
结构体,包含方法、属性、协议列表。运行时通过
attachCategories
函数将 Category 数据合并到类中。
多线程:GCD与NSOperation对比、线程安全
特性 | GCD (Grand Central Dispatch) | NSOperation |
---|---|---|
抽象层级 | C API,更底层 | Objective-C 对象,更高层 |
任务封装 | Block | NSOperation 子类 |
依赖管理 | 需手动控制(如 barrier) | 直接通过 addDependency: 支持 |
状态控制 | 无直接状态管理 | 可监听 isReady/isFinished/isCancelled |
优先级 | 通过 QoS 设置 | 可设置 queuePriority |
取消任务 | 不可取消 | 可取消 |
适用场景 | 简单异步任务 | 复杂任务依赖关系 |
线程安全
保证线程安全的常见方式:
串行队列:将读写操作放到同一队列(如
dispatch_queue_serial
)。锁:
@synchronized
、NSLock
、os_unfair_lock
、pthread_mutex
。原子操作:
atomic
属性(仅保证读写原子性,不保证线程安全)。内存屏障:
OSMemoryBarrier
避免指令重排。
设计模式:Delegate vs Notification vs KVO的差异及应用场景
特性 | Delegate | Notification | KVO |
---|---|---|---|
通信方向 | 一对一(反向传值) | 一对多(广播) | 一对多(监听属性变化) |
耦合度 | 较高(需协议绑定) | 低(松散耦合) | 最低(无需显式调用) |
实现复杂度 | 中(需实现协议方法) | 低(仅注册观察) | 高(需处理移除观察者) |
适用场景 | UITableView 事件处理 | 跨模块通信(如登录状态广播) | 数据模型变化监听(如进度更新) |
性能开销 | 低(直接调用) | 中(需维护观察者列表) | 高(依赖 Runtime 派发) |
最佳实践:
需要返回值时用 Delegate。
跨组件通信用 Notification。
监听对象属性变化用 KVO(注意移除观察者避免崩溃)。
单例模式的实现与优缺点
优点
全局唯一访问点,便于管理资源(如配置、缓存)。
减少重复实例化开销。
缺点
测试困难:全局状态可能导致测试依赖。
内存常驻:单例生命周期与应用一致。
隐藏耦合:滥用会导致代码难以维护。
替代方案:依赖注入(通过参数传递实例)。
MVC/MVVM在Android中的实践
MVC
Model:数据层(Room、Retrofit)。
View:XML 布局文件。
Controller:Activity/Fragment(实际中常承担过多逻辑)。
问题:Activity 臃肿,单元测试困难。
MVVM(推荐)
ViewModel:持有数据,暴露 LiveData/Flow。
View:Activity/Fragment 观察数据变化。
Data Binding:可选的 XML 绑定(减少样板代码)。