简介:面向准备兴业数金、兴业银行Java在线笔试的考生,这份2020年笔试题整理聚焦Java基础与计算机核心知识,用于考前查漏补缺和熟悉题型。压缩包仅含1个docx文档,大小约14KB,轻量便于在电脑或手机上查阅,文档主要用于题点归纳与问答参考。内容围绕方法重载、JDK辅助工具、三级存储层次与分层存储、多线程同步与互斥实现、MySQL InnoDB引擎、Delete与Truncate差别、堆结构等常见考点展开,并附“最近对称数”编程题思路,读者可据此梳理答题要点、补充概念解释,并对照问答部分完善多线程与存储系统等高频题的回答框架。已有8742人学习下载,适合需要快速过一遍客观题与简答题知识面的求职者。
1. 从「最近的对称数」说起:一道笔试题里藏着的概念地图
「输入一个正整数,输出离它最近的对称数」,很多人第一次看到这类题,下意识打开编辑器写循环,写着写着发现边界比想象中多:9 的下一个对称数是 11,99 的下一个是 101,比它小的和比它大的哪个更近,一位数要不要单独处理。题本身不难,难的是它背后连着一串概念:什么算对称、构造和判断哪条路更稳、Java 在线笔试为什么偏爱这种题。同一份卷子里还有「分层存储分几层」「线程同步和互斥有什么区别」「delete 和 truncate 差在哪」,看着零散,考的其实是同一件事——你能不能用一句话把概念说清楚,再补一句它的边界。把这些捋顺,比背答案管用。
2. 分层存储:为什么计算机要把数据分成三六九等
装电脑时经常有人问:既然内存比硬盘快那么多,为什么不干脆全用内存,硬盘只当仓库?答案藏在三个词里——容量、速度、价格。快的贵且小,慢的便宜且大,这三者不可能同时满足。计算机存储系统的分层结构,就是在这个矛盾里找平衡的产物:把不同容量、不同存取速度、不同价格的存储器按层次叠起来,再靠管理软件和硬件把它们组合成一个整体,让程序和数据按「热度」分布在不同层里。
2.1 Cache、主存、辅存:三个层次各管一段路
打个比方,分层存储像一张书桌、一个书架和一间仓库。正在写的纸摊在桌面上,手一伸就够到;常用的书放书架,起身走两步;不常用的资料堆仓库,找起来费劲但能装。桌面的面积最小、书架次之、仓库最大,而取用的方便程度正好反过来。
对应到计算机里,桌面就是高速缓冲存储器 Cache,它夹在 CPU 和主存之间,存取速度能和 CPU 的工作速度相媲美;书架是主存储器,也就是平时说的内存条,程序运行时的指令和数据放在这里;仓库是辅助存储器,硬盘、固态盘这类,断电不丢数据,容量最大、单位价格最低。
| 层次 | 典型介质 | 容量量级 | 访问延迟量级 | 单位成本 | 断电是否丢失 |
|---|---|---|---|---|---|
| Cache | CPU 内部或近旁的高速存储 | KB 到数 MB | 纳秒级 | 最高 | 是 |
| 主存 | 内存条 | GB 级 | 百纳秒级 | 中等 | 是 |
| 辅存 | 硬盘、固态盘 | TB 级 | 毫秒到微秒级 | 最低 | 否 |
这张表不用背数字,记住方向就行:越往上越快、越贵、越小,越往下越慢、越便宜、越大。原文把整个结构看成两个层次——「Cache—主存」层次和「主存—辅存」层次。前者解决速度问题,让 CPU 不至于干等内存;后者解决容量问题,靠辅存的大容量把可寻址空间撑得比主存大得多,程序看到的空间远超过内存条本身。
2.2 局部性原理:分层为什么不是随意设计
如果程序访问数据完全随机,Cache 里放什么都是碰运气,分层就失去意义了。真正让分层成立的是局部性原理:一段时间内,程序倾向于反复访问同一批数据(时间局部性),也倾向于访问相邻地址的数据(空间局部性)。循环里的计数器被反复读写,是时间局部性;数组元素在内存里连续摆放,是空间局部性。
看一段很朴素的代码就能体会空间局部性的威力:
// 二维数组在内存中按行优先连续存放 int n = 4096; int[][] a = new int[n][n]; // 写法一:按行遍历,地址连续,Cache 命中率高 long sum1 = 0; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { sum1 += a[i][j]; } } // 写法二:按列遍历,每次跨一整行,Cache 行被反复换入换出 long sum2 = 0; for (int j = 0; j < n; j++) { for (int i = 0; i < n; i++) { sum2 += a[i][j]; } }两段代码做的事完全一样,访问的元素个数也相同,差别只在顺序。写法一里a[i][j]随着j递增地址连续,一次读入的 Cache 行能连着用好几次;写法二里内层循环每走一步就跳过一整行,之前读进来的数据还没用上就被挤出去了。数据量大到超过 Cache 容量时,两种写法的时间差距会明显拉开——这不是玄学,是分层存储加局部性共同作用的结果。
那这套机制由谁维护?Cache 的换入换出基本由硬件自动完成,对程序是黑匣子;主存和辅存之间的调度由操作系统管,靠页表、缺页中断这些机制,把辅存当成主存的延伸。原文说「通过管理软件和辅助硬件有机组合成一个整体」,说的就是这层意思:单独的存储器只是零件,被管理起来才成为存储系统。
2.3 分层的代价:命中率、一致性与「平均价格」的计算方式
分层不是只有好处。第一笔代价是命中率,Cache 装得下才叫缓存,装不下就得换,换得频繁就是抖动,程序跑起来忽快忽慢。第二笔代价是一致性,同一份数据可能同时存在于 Cache 和主存里,写操作要决定先写哪、什么时候同步,多核场景下这件事尤其麻烦。第三笔代价是缺页,主存装不下时要去辅存取,一次缺页的延迟可能是几百个纳秒和几毫秒的差距,一次抖动就能把整体性能拉下来。
但账还是划算的。原文里有个容易被忽略的推理:Cache 虽贵,但容量小,摊到整个存储系统上对平均价格影响有限;辅存容量大、价格低,把绝大多数数据放在那里,整体平均价格就被拉低了。也就是说,一个较大的存储系统既有接近 CPU 的速度,又有极大的容量,成本还低——速度问题交给 Cache,容量问题交给辅存,中间用主存衔接。
提示:分层的收益建立在「热点集中」上。如果访问完全随机、毫无局部性,缓存命中率会掉得很难看,这时候加缓存只是加了复杂度。
理解了这一层,「存储系统分几个层次」「为什么分层能提高执行效率」这类问答题就有了骨架:先答三层结构,再答两级层次各自解决什么矛盾,最后答局部性原理和平均价格这笔账。
3. 线程同步与互斥:临界区、互斥量、信号量到底差在哪
两个人共用一台打印机,各自发了一份文档过去,如果不加控制,打出来的可能是半页 A 接半页 B。这就是互斥要解决的问题:共享资源在同一时刻只允许一个使用者。但并发里的麻烦不止「抢」,还有「等」——一个线程必须等到另一个线程把消息送过来才能往下走。这两件事经常被混着说,其实一个管排他,一个管次序。
3.1 同步是「等消息」,互斥是「抢钥匙」
原文给的定义很干脆:线程同步是指线程之间具有一种制约关系,一个线程的执行依赖另外一个线程的消息,没收到消息就等待,直到消息到达才被唤醒;线程互斥是指对共享的进程系统资源,每个线程访问时的排他性,有若干线程都要用同一个共享资源时,任何时刻最多只允许一个线程使用,其他线程必须等待,直到占用者释放。
打个比方,同步像接力赛,第二棒必须等第一棒把棒交到手里才能起跑,这是次序上的依赖;互斥像单人厕所的门锁,谁进去谁锁门,外面的人排队,这是使用权上的排他。原文补了一句关键判断:线程互斥可以看成是一种特殊的线程同步。因为「等别人用完」本身也是一种等待关系,只是它等的不是消息,而是资源的释放。
3.2 用户模式和内核模式:同步手段为什么分两拨
原文把线程间的同步方法分成两类。用户模式包括原子操作和临界区,特点是只在用户态完成,不需要切换内核态与用户态;内核模式包括事件、信号量、互斥量,靠系统内核对象的单一性来做同步,使用时需要在用户态和内核态之间切换。
这个划分不是分类偏好,而是开销问题。用户态代码碰不到内核对象,所以用户模式的同步手段只能在自己的进程里做文章;一旦需要跨进程协调,或者需要内核来担保「只有一个」,就必须调用内核对象,代价是每次等待和唤醒都可能伴随状态切换。切换本身不慢,但架不住频率高,所以原则很朴素:能在用户态解决,就别轻易进内核。
四种手段的分工也由此展开。临界区是对多线程串行化来访问公共资源或一段代码,速度快,适合控制数据访问,但作用范围限于同一进程内;互斥量为协调对共享资源的单独访问而设计,可以带名字,因而能跨进程;信号量控制的是具有有限数量用户的资源,它管的不是「一个」,而是「几个」;事件则是通知机制,用来告诉其他线程某件事已经发生,从而启动后继任务。
3.3 四种手段怎么选:一张对照表
| 手段 | 解决的问题 | 典型场景 | 代价与限制 |
|---|---|---|---|
| 原子操作 | 单个变量读改写不被打断 | 计数器自增 | 能力有限,复杂逻辑撑不住 |
| 临界区 | 一段代码串行执行 | 进程内共享变量保护 | 仅在进程内有效,忙等有开销 |
| 互斥量 | 共享资源同一时刻一人用 | 跨进程独占文件、设备 | 内核对象,涉及态切换 |
| 信号量 | 有限数量的资源配额 | 连接池、限流 | 计数逻辑写错很难查 |
| 事件 | 通知与唤醒 | 生产者通知消费者 | 只传递信号,不保护数据 |
落到 Java 代码上,这几种语义都能找到对应物:
// 互斥:同一时刻只允许一个线程进入临界区 synchronized (lock) { count++; } // 信号量:构造参数 3 表示同时最多 3 个线程持有许可 Semaphore semaphore = new Semaphore(3); semaphore.acquire(); try { handleRequest(); // 受配额限制的共享资源操作 } finally { semaphore.release(); // 务必在 finally 里归还许可 }synchronized的语义是排他,锁住的是对象监视器,同一时刻只有一个线程能进来;Semaphore(3)的语义是配额,它不保证排他,只保证并发数不超过许可数。acquire()拿不到许可就阻塞,release()把许可还回去,写在finally里是为了避免异常路径漏还导致配额永久丢失。把互斥量当成信号量用、或者把信号量当互斥量用,是并发代码里最常见的翻车方式之一:前者写不出「允许 N 个」,后者把「允许 N 个」硬压成「允许 1 个」,白白损失并发度。
注意:互斥量可以看作许可数为 1 的信号量,但反过来把互斥量当信号量用,配额的含义就丢了,排查问题时连「现在有几个线程在里面」都数不清。
4. 四组最容易答偏的易混概念:重载、删除、堆与工具
概念题失分,多半不是没背过,而是背串了。下面四组是这份卷子里最容易在细节上答偏的地方,每一组都按「像在哪、为什么混、怎么分」拆开说。
4.1 重载与重写:名字都带「载」,机制完全不同
易混点:重载是在同一个类里定义多个同名方法,但参数列表不同;重写是子类重新实现父类已有的方法,方法签名相同。两者都常被归到「多态」这个词下面,答题时一紧张就写成同一个东西。
为什么混:中文都带「重」字,英文 overload 和 override 也只差几个字母,教材讲多态时又总把它们放在相邻段落。更麻烦的是,重载的判定规则里「返回值不同不算数」这条,很多人记反了。
怎么区分:看位置和时机。重载发生在同一个类内部,靠参数个数、参数类型、参数顺序三种方式区分,编译器在编译期就根据实参决定调用哪个版本,这叫静态绑定;返回值类型不同不构成重载,只改返回值连编译都过不去。重写发生在父子类之间,要求方法名、参数列表一致,返回值类型兼容,访问权限不能比父类更严,运行期根据对象的实际类型决定调用哪个版本,这叫动态绑定。一句话记:重载看参数、编译期定;重写看对象、运行期定。
4.2 delete 与 truncate:都像「清空」,但一个能反悔一个不能
易混点:原文的标准答法是「delete 删除表中的所有数据,truncate 清空表中的所有数据,并且重新设置自增列的起始值」。很多人只记住后半句,以为两者差别就是自增列归零。
为什么混:把整张表的数据清掉时,两者的结果看起来一模一样,都是表还在、数据没了。差别藏在过程里,而过程平时看不见。
怎么区分:delete 是数据操作语句,可以带 where 条件删一部分,也可以不带条件删全部;它逐行处理,写入事务日志,放在事务里能回滚,会触发触发器,锁的粒度偏细。truncate 是数据定义语句,只能整表清空,不逐行记录,执行快得多,自增计数器会被重置,多数情况下不可回滚,也不触发那些行级触发器。To pick 哪种,看两件事:要不要留退路、要不要按条件删。生产环境里不确认备份就敲 truncate,属于血泪经验级别的踩坑点,一次执行下去,后悔药基本没地方买。
4.3 堆:数据结构里的堆、内存里的堆、堆栈的堆
易混点:原文对堆的描述是「一种特殊的树形数据结构,它满足堆的性质:对于任意节点,左子树和右子树的高度之差最多为 1」。这句话其实把堆的性质和平衡二叉树的性质弄混了——「左右子树高度差不超过 1」是平衡树的定义,不是堆的定义。
为什么混:中文里三个「堆」挤在一起。数据结构里的堆、JVM 运行时数据区里的堆、口语里连读的「堆栈」,说的是三件不同的事,而「堆」这个词在算法题和内存话题里出现频率都极高。
怎么区分:先把数据结构的堆记准,它有两个硬条件——完全二叉树,以及堆序性,即大顶堆里父节点的值不小于子节点,小顶堆里父节点不大于子节点。满足这两条,堆顶就是全局最值,所以它能用来实现优先队列和堆排序。用数组存堆时,下标 i 的左孩子是 2i+1、右孩子是 2i+2、父节点是 (i-1)/2,这个映射关系来自「完全二叉树」这条性质,不是来自平衡。再看 JVM 的堆,它是对象实例分配的运行时数据区,和线程私有的栈(方法调用栈)分工不同;而「堆栈」连读时,多数语境下指的其实是栈。答题时先写「完全二叉树 + 堆序性」两个条件,再补用途,顺序别反。
提示:卷子里把堆的性质写成平衡树性质,是出题或整理时留下的瑕疵。照抄会答错,遇到这类定义,先用两个硬条件自查一遍。
4.4 JDK 辅助工具:编译、打包、诊断各管一摊
易混点:原文列举了 javadoc、jar、jconsole 等,并说明它们是帮助开发者编写、测试和调试程序的工具。答题时容易笼统地说「都是辅助工具」,说不出各自管什么。
为什么混:这些命令都随 JDK 一起安装,名字短、数量多,平时真正敲过的可能只有 javac 和 java。
怎么区分:按用途分四类就不乱了。编译运行类是 javac 和 java,前者把源码编成字节码,后者启动虚拟机执行;打包文档类是 jar 和 javadoc,前者把类文件打成归档包,后者从源码注释生成文档;进程线程诊断类是 jps 和 jstack,一个列出当前运行的 Java 进程,一个导出指定进程的线程栈,用来查卡死;内存监控类是 jmap、jstat 和 jconsole,分别看堆转储、GC 统计和图形化运行状态;调试类是 jdb。给几个常用命令:
# 列出当前机器上的 Java 进程及主类 jps -l # 导出指定进程的线程栈到文件,排查死锁或线程卡住 jstack <pid> > stack.txt # 每秒采样一次 GC 统计,观察堆和回收频率 jstat -gc <pid> 1000jps -l里的-l表示输出完整主类名;jstack的<pid>是进程号,用jps查到后替换;jstat -gc后面跟进程号和采样间隔毫秒数,1000 就是每秒一次。参数不用背全,记住「哪个工具解决哪类问题」比记住每个开关更重要——被问到「JDK 提供哪些辅助工具」时,按这四类各报一两个名字加一句作用,比报一长串名字更有说服力。
5. InnoDB 引擎:为什么它成了 MySQL 的默认选择
建表时不写引擎,MySQL 会替你选一个。多数版本里这个默认值就是 InnoDB,而很多人对它的全部了解只有「支持事务」四个字。事务、行级锁、崩溃恢复这三项能力,各自解决了一类很具体的问题,值得拆开看。
5.1 事务、行级锁、崩溃恢复:三根支柱分别解决什么
事务解决的是「一组操作要么全做、要么全不做」。转账是最常被举的例子:从 A 扣钱和给 B 加钱必须绑在一起,中间任何一步失败,整个动作回滚。InnoDB 提供事务支持,意味着多条语句可以作为一个整体提交或撤销,配合隔离级别控制并发事务之间的可见性。没有事务的引擎,只能靠应用层自己补偿,逻辑一旦复杂就很容易留下不一致的数据。
行级锁解决的是并发写的冲突范围。表锁意味着一个人改一行,整张表都别想写;行级锁只锁涉及的那几行,改不同行的两个事务可以同时进行。并发量上来之后,这个差别直接决定系统是能撑住还是排长队。锁粒度变细也带来代价:锁越多,管理开销越大,死锁的概率也越高,所以行锁不是免费的午餐,它换的是并发度。
崩溃恢复解决的是「断电之后数据还在不在」。InnoDB 靠重做日志和回滚日志实现恢复:已提交但还没写进数据文件的操作,重启后重做;未提交的操作,回滚掉。这个机制让数据库在异常宕机后能回到一个一致的状态,而不是留下改了一半的表。
5.2 和表锁型引擎的对比:并发能力差在哪
| 特性 | InnoDB | 早期非事务型引擎 |
|---|---|---|
| 事务支持 | 支持,可提交可回滚 | 不支持,语句即时生效 |
| 锁粒度 | 行级锁为主 | 表级锁为主 |
| 外键 | 支持 | 不支持 |
| 崩溃恢复 | 有日志,可恢复到一致状态 | 恢复能力弱 |
| 典型场景 | 混合读写、并发写入的业务表 | 读多写少、允许重建的数据 |
这张表不是要判谁优谁劣,而是说明选择依据。非事务型引擎在读多写少、数据可重建的场景里,结构简单、开销小,未必没有位置;但只要业务涉及并发写入、需要回滚、需要外键约束,InnoDB 的三项能力就是刚需,而不是加分项。
5.3 什么时候不该用 InnoDB
说清楚边界比一味推荐更有用。第一种情况是纯只读的报表库,数据由离线任务批量灌入,查询侧不需要事务,也不需要行锁,这时候行锁和外键带来的开销是纯负担。第二种情况是数据本身可随时重建,比如临时中间表、缓存落地的宽表,丢了重跑一遍就行,崩溃恢复的价值被削弱。第三种是对存储空间极其敏感、写入模式又是纯追加的场景,事务日志的额外空间要算进成本。
反过来,只要出现下面任一信号,就别纠结了:一个业务动作要改多张表、并发写同一张表的概率不低、数据丢了没法从上游补回来。这三种情况下用非事务引擎,事后补数据的成本远高于当初省下的那点开销。
6. 这些概念什么时候用得上:答题、面试和干活的三条边界
同一批概念,在三个场合的用法不一样。笔试答题要求「准」,把定义和边界说清楚就能得分;面试追问要求「深」,得能举出反例;实际干活要求「选」,知道什么场景不该用比知道怎么用更重要。
| 场景 | 该拿出来的概念 | 别做的事 |
|---|---|---|
| 笔试题 | 先用一句话给定义,再补一个反例 | 把相近概念混成一段话 |
| 面试追问 | 说清适用前提和代价 | 只背结论,说不出为什么 |
| 写业务代码 | 按并发量、一致性要求选方案 | 不分场景照搬默认配置 |
| 操作生产库 | 先确认备份和回滚路径 | 随手 truncate、随手加锁 |
举一个具体例子。分层存储这个概念,答题时答「三级结构加两个层次」就够了;面试被追问时,要能说出局部性原理和命中率的关系,最好能举出按行遍历和按列遍历的差距;真到写代码时,它变成很具体的动作——热点数据往前放、批量操作减少往返、别在循环里做远程调用。概念本身没变,落点变了。
再比如 truncate 和 delete 的区别,卷面上是选择题,生产环境里是一次操作事故的分水岭。答题答对不等于操作不翻车,这两件事得分开练。
从那以后我每次碰到一道概念题,都强制自己先用一句话说完定义,再补一个反例,最后说清什么时候不适用——三步走完,答偏的概率小很多。这份笔试题里七道问答加两道编程题的参考答法和易错点,都整理在资料里,对着看比零散搜答案省事。希望帮到你。
本文还有配套的精品资源,点击获取