计算机基础面试题手册

梳理高频技术问题,帮助你按主题复习和查漏补缺。

计算机基础阅读 02026年5月30日 10:11

什么是 ASCII 编码?字符集范围是多少?

ASCII 是一种用数字表示英文字符和控制符的编码标准。标准 ASCII 使用 7 位二进制,一共能表示 128 个值,范围是 0-127;其中 0-31 和 127 是控制字符,32-126 是可打印字符。它不支持中文,但 Unicode 前 128 个码位与 ASCII 保持兼容,所以它仍是理解字符编码的基础。追问ASCII 的字符范围怎么分?0-31 是控制字符,例如换行 LF、回车 CR、制表 TAB;32-126 是可打印字符;127 是 DEL 删除字符。标准 ASCII 和扩展 ASCII 有什么区别?标准 ASCII 是 7 位,只有 128 个字符。扩展 ASCII 通常使用 8 位,但 128-255 的含义并不统一。ASCII 和 Unicode 是什么关系?Unicode 是更大的字符集。为了兼容历史系统,Unicode 的前 128 个字符和 ASCII 完全一致。写段代码def ascii_type(ch): n = ord(ch) if 0 <= n <= 31 or n == 127: return 'control' if 32 <= n <= 126: return 'printable' return 'non-ascii'
计算机基础阅读 02026年5月30日 10:11

ASCII 码在编程中有哪些常见应用?

ASCII 码在编程里最常见的用途,是把“字符”当成稳定的数字范围来处理:判断字母数字、做大小写转换、过滤可打印字符、解析协议文本,以及处理只允许英文符号的编码场景。它不适合处理中文,但在命令行、HTTP 头、日志、配置、Base64、URL 编码里仍然很常见。追问为什么可以用范围判断字母和数字?因为 ASCII 中数字 0-9 连续排列,字母 A-Z、a-z 也连续排列。大小写转换为什么经常提到 32?标准 ASCII 里大写字母和对应小写字母相差 32。但实际项目更推荐语言内置方法,避免误伤非英文字符。ASCII 在网络协议里还重要吗?重要。很多协议的关键文本部分仍以 ASCII 兼容方式定义,例如 HTTP 方法名、Header 名、状态行、SMTP 命令等。写段代码def is_ascii_digit(ch): return '0' <= ch <= '9'def to_ascii_upper(ch): return chr(ord(ch)-32) if 'a' <= ch <= 'z' else ch
计算机基础阅读 02026年5月30日 10:11

ASCII 码的历史发展过程是怎样的?

ASCII 的发展主线很清楚:为了解决计算机和通信设备各用各的字符表示方式,美国在 1960 年代推动统一标准;后来 ASCII 成为网络、编程语言和 Unicode 的基础。追问ASCII 是什么时候出现的?ASCII 最早在 1963 年由美国国家标准协会发布,目标是统一英文字符、数字、符号和控制字符的编码方式。1967 年修订后,控制字符体系更完整。它为什么会成为标准?早期计算机、打字机、电传设备需要互相通信,如果字符编码不统一,同一个字节就可能显示成不同内容。扩展 ASCII 为什么会带来问题?8 位扩展 ASCII 增加了 128 个字符,但不同厂商和地区定义不一致,推动了 Unicode 的出现。ASCII 现在还有用吗?有。UTF-8 完全兼容 ASCII,很多协议、代码、配置文件仍以 ASCII 字符为基础。
计算机基础阅读 02026年5月30日 10:11

ASCII 码有哪些优缺点?还适合现在使用吗?

ASCII 的优点是简单、稳定、兼容性强;缺点是字符太少,无法支持中文、日文、表情和复杂符号。现在它更像“基础层”:很多协议和编码仍兼容 ASCII,但现代应用通常会直接使用 UTF-8。追问ASCII 为什么说简单高效?它本质上是 7 位编码,只定义 128 个字符。英文、数字、常见符号和控制字符都能用固定 1 字节处理。ASCII 的兼容性好在哪里?几乎所有操作系统、编程语言、网络协议和文件格式都认识 ASCII。Unicode 和 UTF-8 也保留了 ASCII 的 0-127 区间。最大缺点是什么?字符集太小,不能表示中文、日文、韩文、数学符号、表情符号,也不适合国际化软件。扩展 ASCII 能解决问题吗?只能部分解决。不同厂商和地区的扩展版本不统一,反而容易造成乱码。
计算机基础阅读 05月29日 00:12

TCP 可靠传输靠哪些机制保证?RTO 和 SACK 是怎么工作的?

TCP 可靠传输靠六个机制保证:序列号+确认应答(保证有序、检测丢包)、超时重传+快重传(丢包恢复)、校验和(检测数据损坏)、流量控制(防接收方溢出)、拥塞控制(防网络过载)。其中序列号和重传是核心,其他都是辅助。追问超时重传的 RTO 怎么算?为什么不能固定值?网络延迟波动大,固定值不靠谱——设小了正常延迟就触发多余重传,设大了真丢包要等很久才恢复。RTO 基于实测 RTT 动态计算:RTO = SRTT + 4 × RTTVAR,SRTT 是平滑 RTT(历史加权平均),RTTVAR 是 RTT 波动幅度。首次测量 SRTT = RTT,后续每次 SRTT = 7/8 × 旧SRTT + 1/8 × 新RTT。Linux 还有个下限(tcprtomin,默认 200ms),防止 RTO 过小导致过度重传。累积确认有什么问题?SACK 怎么解决的?累积确认只告诉你"到这个序号之前的都收到了",中间丢了哪个说不清。比如发了 1-10 号包,5 号丢了,ACK 会一直回 5(表示期望收到 5),6-10 号收到了但不确认。发送方不知道 6-10 号到底到了没有,可能重传 5-10 全部。SACK(Selective ACK)在 TCP 选项里额外报告已收到的非连续块,发送方就知道只重传 5 号就行。Linux 默认开启 SACK(tcp_sack=1),对高延迟高丢包链路很重要。序列号为什么是按字节编号而不是按包编号?因为 TCP 是字节流协议,应用层不关心包的边界——你写 1000 字节,TCP 可能拆成两个 500 字节的段,也可能合成一个。按字节编号让接收方能精确知道每个字节的位置,不管怎么拆分合成都能正确重组和确认。也方便滑动窗口按字节数控制流量。面试追问:ISN(初始序列号)为什么不用 0 或 1?防止旧连接的报文段被新连接误接收——ISN 基于时钟递增,每隔 4 微秒加 1,不同连接的序列号空间错开。校验和能检测所有错误吗?不能。校验和是 16 位反码求和,只能检测单比特错误和大部分多比特错误,但两个错误恰好互相抵消时校验和不变。以太网的 CRC 能检测更多错误,但端到端路径上中间设备可能修改数据(如 NAT 改 IP 地址),TCP 校验和是端到端验证的最后防线。如果需要更强的完整性保证,要用 TLS 的 MAC 或应用层哈希。写段代码# 模拟 TCP 序列号和累积确认def simulate_seq(): sent = 0 # 下一个要发的字节号(ISN=0 简化) acked = 0 # 已确认的字节号 segments = [(0, 100), (100, 200), (200, 300)] # (起始, 结束) for start, end in segments: print(f"发送: seq={start}, 数据长度={end-start}") # 模拟中间段丢失 print(f"收到: ACK={100} (第1段确认)") print(f"收到: ACK={100} (第2段丢失,重复ACK)") print(f"收到: ACK={100} (第3个重复ACK→快重传)") print(f"重传: seq={100}, 数据长度=100") print(f"收到: ACK={300} (累积确认到第3段)")simulate_seq()
计算机基础阅读 05月29日 00:11

TCP 四次挥手的过程是什么?为什么不能三次挥手?

TCP 四次挥手是断开连接的过程:主动方发 FIN,被动方回 ACK,被动方再发 FIN,主动方回 ACK。之所以要四次而不是三次,是因为 TCP 全双工——每个方向必须单独关闭。第二次挥手时被动方可能还有数据没发完,不能把 FIN 和 ACK 合并,只能先确认,等数据发完再关自己的方向。追问TIME_WAIT 为什么要等 2MSL?两个原因。第一,确保最后的 ACK 能到——如果 ACK 丢了,被动方会重传 FIN,TIME_WAIT 状态的主动方可以重新回 ACK。如果主动方直接 CLOSED,收到重传的 FIN 只能回 RST,被动方会报连接异常。第二,让网络中属于这个连接的旧报文段全部消亡——MSL 是报文最大生存时间(RFC 793 建议 2 分钟,Linux 实际是 60 秒),等 2MSL 确保来回两个方向的旧包都过期,不会干扰后续新连接。TIME_WAIT 过多怎么办?短连接场景下(比如 HTTP/1.0 每个请求一个连接),主动关闭方会积累大量 TIMEWAIT,每个占一个五元组(源IP、源端口、目的IP、目的端口、协议),端口耗尽后新连接无法建立。解决方法:启用 tcp_tw_reuse(允许 TIMEWAIT 端口给新连接用,依赖时间戳判断旧包)、改用长连接(HTTP/1.1 Keep-Alive)、让客户端主动关闭(服务端被动关闭不产生 TIME_WAIT)。注意 tcp_tw_recycle 在 NAT 环境下会导致连接失败,Linux 4.12 后已移除此选项。为什么不能三次挥手?假设第二次和第三次合并——被动方收到 FIN 后立刻发 FIN+ACK。问题是被动方可能还有数据没发完:它收到主动方的 FIN 只是说"我不发了",不代表"你也不能发了"。被动方需要继续发送剩余数据,发完才能关自己的方向。如果提前发 FIN,这些数据就丢了。只有在被动方恰好也没有数据要发时,才能合并成三次挥手——这就是延迟 ACK 场景下偶尔观察到三次挥手的原因。CLOSE_WAIT 很多是什么问题?CLOSEWAIT 是被动方收到 FIN 后、还没发 FIN 之前的状态。大量 CLOSEWAIT 说明应用层没有调用 close()——通常是因为代码 bug:忘了关 socket,或者线程池满了来不及处理。一个典型的坑是:HTTP 服务端在请求处理异常时没关连接,客户端超时断开后,服务端停留在 CLOSEWAIT。排查方法:netstat 统计 CLOSEWAIT 数量,配合 lsof 找到对应进程,检查代码里的 socket 关闭逻辑。写段代码# 查看系统 TCP 连接状态分布import subprocessresult = subprocess.run(['netstat', '-tan'], capture_output=True, text=True)states = {}for line in result.stdout.splitlines()[2:]: parts = line.split() if len(parts) >= 6: state = parts[5] states[state] = states.get(state, 0) + 1for state, count in sorted(states.items(), key=lambda x: -x[1]): print(f"{state:20s} {count}")
计算机基础阅读 05月29日 00:10

TCP 拥塞控制的四个算法是什么?超时重传和快重传怎么区分?

TCP 拥塞控制有四个算法:慢启动、拥塞避免、快重传、快恢复。核心思想是"先试探再加码"——不清楚网络能承受多少时就少发,确认没问题再加速;一旦发现拥塞就立刻降速。控制对象是 cwnd(拥塞窗口),和 rwnd 取较小值作为实际发送窗口。追问慢启动为什么叫"慢"?明明指数增长很快"慢"是相对没有拥塞控制的情况——TCP 早期一上来就发满窗口,容易打爆网络。慢启动从 cwnd=1 MSS 开始,每收到一个 ACK 就 +1 MSS,一个 RTT 内翻倍(1→2→4→8→16)。看起来快,但初始只有 1 MSS,比直接发满窗口保守得多。cwnd 到达 ssthresh 后切换到拥塞避免的线性增长(每 RTT +1 MSS),不再翻倍。ssthresh 的初始值通常很大,第一次丢包后才设为 cwnd/2。3 个重复 ACK 为什么就重传?不等超时吗?等超时太慢了。RTO 通常是几百毫秒到几秒,而 3 个重复 ACK 说明后续包已经到了,只是中间那个丢了——网络还在工作,只是丢了一个包。快重传收到第 3 个重复 ACK 就立即重传,不用等 RTO,恢复速度差了一个数量级。为什么是 3 个不是 1 个?因为偶尔乱序也会产生重复 ACK,1 个就重传太激进,3 个基本确认是丢包。超时重传和快重传的处理有什么不同?超时重传意味着网络可能严重拥塞——连 ACK 都回不来了,所以处理更激进:ssthresh = cwnd/2,cwnd 直接降到 1 MSS,重新慢启动。快重传说明网络还通着(后续包到了),只是丢了一个包,所以处理温和:ssthresh = cwnd/2,cwnd 降到 ssthresh 而不是 1,进入快恢复。这就是为什么快重传后不回慢启动——丢一个包不代表网络崩溃。BBR 和传统拥塞控制有什么不同?传统算法(Reno/Cubic)基于丢包判断拥塞——丢包就降速,不丢就加速。问题是在高延迟高丢包的网络(无线、跨国)里,丢包不一定是拥塞,可能是链路本身的特性,传统算法会白白降速。BBR(Google 2016 年提出)不看病包,直接测量带宽和 RTT,把发送速率调整到带宽-延迟积(BDP),目标是把管道填满但不溢出。实测:YouTube 启用 BBR 后跨洲链路吞吐量提升 10-100 倍。写段代码# 模拟慢启动和拥塞避免的 cwnd 变化def simulate_cwnd(ssthresh=16, max_rtt=20): cwnd = 1 # 初始 1 MSS for rtt in range(1, max_rtt + 1): if cwnd < ssthresh: cwnd *= 2 # 慢启动:指数增长 phase = "慢启动" else: cwnd += 1 # 拥塞避免:线性增长 phase = "拥塞避免" print(f"RTT {rtt:2d}: cwnd={cwnd:3d} MSS [{phase}]")simulate_cwnd()
计算机基础阅读 05月29日 00:09

TCP 滑动窗口机制是怎么工作的?零窗口和糊涂窗口是什么?

TCP 流量控制靠滑动窗口:接收方在 ACK 里通告自己还能接收多少数据(rwnd),发送方据此控制发送量,不让接收方缓冲区溢出。发送窗口 = min(rwnd, cwnd),rwnd 是接收方给的流量控制信号,cwnd 是发送方自己根据拥塞状况算的。这里只说 rwnd 的部分。追问零窗口是怎么回事?怎么恢复?接收方缓冲区满了,在 ACK 里通告 rwnd=0,发送方就必须停。但接收方应用层读完数据释放缓冲区后,会发一个窗口更新报文告诉发送方可以继续了。问题在于:这个窗口更新报文丢了怎么办?发送方会一直等,死锁。解决方法是发送方启动持续计时器,定期发零窗口探测报文(只含 1 字节数据),迫使接收方回 ACK 并带上最新窗口值。这就是为什么零窗口不会永远卡住。糊涂窗口综合征是什么?接收方应用层每次只从缓冲区读 1 字节,就通告 rwnd=1;发送方就发 1 字节数据——一个 41 字节的包只装了 1 字节有效载荷,带宽利用率 2.4%。这就是糊涂窗口综合征(Silly Window Syndrome)。接收端的解决方法叫 Clark 方案:缓冲区空闲不到 MSS 或总缓冲区的 35% 时不通告新窗口;发送端配合 Nagle 算法,攒够数据再发。两端一起防才能根治。rwnd 和 cwnd 有什么区别?rwnd 是接收方告诉发送方"我还能收多少",防止接收方溢出;cwnd 是发送方自己算的"网络还能承受多少",防止网络拥塞。流量控制(rwnd)是端到端的保护,拥塞控制(cwnd)是全局性的保护。实际发送窗口取两者最小值——任何一个窗口满了都不能多发。面试中经常把这两个混在一起考,要分清楚哪个是对方给的,哪个是自己算的。滑动窗口的"滑动"体现在哪?窗口把发送缓冲区的数据分成四段:已确认 ← 窗口左边界 | 已发送未确认 | 可发送未发送 | 窗口右边界 → 不可发送。收到 ACK 后左边界右移(滑过去),接收方通告新窗口后右边界右移(窗口打开)——这就是"滑动"。如果右边界不变就是窗口关闭,左边界追上右边界就是零窗口。写段代码# 模拟滑动窗口发送def sliding_window_send(data, window_size): sent = 0 # 窗口左边界 acked = 0 # 已确认 while acked < len(data): # 发送窗口内的数据 while sent < min(acked + window_size, len(data)): print(f"发送 data[{sent}]") sent += 1 # 模拟收到 ACK print(f"收到 ACK={acked+1}") acked += 1 # 左边界滑动sliding_window_send(list(range(8)), window_size=3)
计算机基础阅读 05月29日 00:02

TCP Keep-Alive 机制是什么?为什么还需要应用层心跳?

TCP Keep-Alive 是操作系统提供的连接存活检测机制:连接空闲一段时间后,内核自动发探测包,根据对端响应判断连接是否还活着。三个核心参数控制行为——空闲多久开始探测(tcpkeepalivetime,默认 7200 秒)、探测间隔(tcpkeepaliveintvl,默认 75 秒)、探测几次放弃(tcpkeepaliveprobes,默认 9 次)。最差情况下,从连接断开到被检测出来要 7200 + 75×9 = 7875 秒,超过 2 小时。追问为什么默认 2 小时这么长?RFC 1122 建议至少 2 小时,是出于对网络风暴的担忧——如果全网所有连接都以短间隔发探测包,本身就是一场 DDoS。2 小时在服务器间稳定网络里够用了,问题出在移动端:运营商 NAT 设备的连接跟踪表有限,空闲 5 分钟(移动 2/3G)到 28 分钟(电信 3G)就淘汰条目,连接就被静默丢弃了,2 小时探测根本来不及救。既然有 Keep-Alive,为什么还要应用层心跳?三个原因。第一,Keep-Alive 只能检测连接是否可达,不能检测对端进程是否卡死——进程死锁时 TCP 连接还活着,Keep-Alive 照样通过。第二,Keep-Alive 的探测包不带业务数据,服务端对探测无感知,无法在应用层做状态同步。第三,参数是系统级的,改了影响所有连接,不如应用层心跳可以按业务精细控制间隔。微信的心跳从 30 秒到 300 秒动态调整,Keep-Alive 做不到。Keep-Alive 探测包长什么样?一个不包含数据的 ACK 包,序列号设为对端期望的序列号减 1,这样对端会发现序列号不匹配,回一个 ACK 带上正确的期望序列号——探测就成功了。如果对端回复 RST,说明进程已崩溃重启;如果连续 9 次无响应,内核判定连接死亡,关闭 socket 并返回 ETIMEDOUT。什么场景下 Keep-Alive 就够了?服务器之间的稳定内网连接。比如微服务间 gRPC 长连接、数据库连接池,网络环境可控,不存在 NAT 超时问题,Keep-Alive 配合较短的探测间隔(比如 60 秒)就能及时清理僵死连接。这些场景不需要应用层心跳的灵活性。写段代码import socketsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 60) # 60秒后开始探测sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 10) # 每10秒探测一次sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 3) # 探测3次放弃
计算机基础阅读 05月29日 00:00

什么是 TCP Nagle 算法?为什么会造成 40ms 延迟?

Nagle 算法的核心规则只有一条:连接上有未被确认的小包时,不再发新的小包,等 ACK 回来再把缓冲区里的数据攒一起发。目的是减少小包数量——Telnet 按一个键就产生一个 41 字节的包,其中 40 字节是 TCP+IP 头部,有效载荷只有 1 字节,带宽利用率不到 3%。1984 年 John Nagle 在 RFC 896 里提出这个方案,解决的就是交互式应用疯狂发小包导致的广域网拥塞。算法默认开启(RFC 1122 推荐),通过 TCP_NODELAY 选项关闭。追问Nagle 和延迟 ACK 怎么会互相卡死?Nagle 在发送端等 ACK,延迟 ACK 在接收端等更多数据再确认,两者同时启用就形成僵持:发送方写了一个小包,等 ACK;接收方收到后不马上回 ACK,等 40ms(Linux 默认)或 200ms(Windows 默认)看还有没有后续数据。典型场景是 write-write-read 模式:第一次写直接发出,第二次写被 Nagle 挡住,接收端延迟 ACK 等 40ms,发送端就卡在这 40ms 上。腾讯云有实际案例,营销平台 10% 的请求耗时稳定卡在 38-42ms,根因就是这对组合。什么时候必须关掉 Nagle?实时交互场景:游戏同步、远程桌面、WebSocket 推送。这些场景宁可多发几个小包也不能容忍额外延迟。Redis 3.x 的主从同步曾因 Nagle 导致从库延迟飙升,后来在源码里给同步 socket 加了 TCP_NODELAY 才解决。Nagle 和 TCP_CORK 有什么区别?Nagle 是"有小包没确认就不发",侧重减少小包数量;CORK 是"攒够一个 MSS 再发",侧重提高吞吐量。CORK 更激进——把数据一直攒到 MSS 或超时(通常 200ms)才放行,适合 HTTP 流水线这类一次要发大量数据的场景。Nginx 在发送响应头时用 CORK,等响应体也凑齐了一起发,减少系统调用次数。怎么确认线上问题是 Nagle 引起的?抓包看时序:发送端有小包发出后,超过 40ms 才收到 ACK 或才发下一个包,大概率是 Nagle + 延迟 ACK。也可以临时在客户端设 TCP_NODELAY 做对比——如果 40ms 档消失了就确认了。注意要区分是 Nagle 的问题还是单纯的网络延迟,对比开关前后的延迟分布比看绝对值更可靠。写段代码// 禁用 Nagle 算法int flag = 1;setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));// Linux 上启用快速 ACK(接收端)setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, &flag, sizeof(flag));