4399公司2021Java开发工程师（卷一）总体来说难度不大，偏向基础。但是由于基本没有准备，SpringBoot和文件编程不尽人意。[...]
[面试题]4399公司2017秋招开发工程师笔试题
1.灰度等级为256级，分辨率为1024*1024的显示器，至少需要的帧缓存容量为（1MB）灰度 = 256 = 2^8 ，因此需要8比特表示缓存 = 8bit * 1k * 1k = 8Mbit = 8MB2.设一组初始记录关键字序列为(65,56,72,99,86,25,34,66)，则以第一个关键字65为基准而得到的一趟快速排序结果是（34，56，25，65，86，99，72，66）八大排序基本笔试面试必考3.在下列几种排序方法中，空间复杂度最高的是（归并排序）归并排序O(N)快速排序：就地快排O(1)使用递归： 每一次都平分数组，即最优情况是O（logn）退化为冒泡排序，即最差情况是O（n）直接插入、希尔、简单选择、堆排、冒泡都是O(1)基数排序为O(N+R)4.下面哪些可能是重载的基本条件（参数的类型不同、参数的顺序不同、参数的个数不同）（注意：是可能为重载的基本条件）这题强调了可能，因为参数顺序可能是 method(int a,int b)，也可能是指 method(double a,int b)5.为了保证连接的可靠建立，TCP通常采用（三次握手）三次握手，四次挥手。6.下列有关计算机网络叙述正确的是（ABC）A.建立计算机网络的最主要的目的是实现资源共享B.计算机网络是在通信协议控制下实现的计算机互联C.利用Internet网可以使用远程的超级计算机中心的计算机资源D.TCP/IP的网络接口层对应的OSI的物理层7.小明的游戏账号密码是一串8位数字，只记前面5个数字为38034.但他肯定，后面3个数字全是奇数，最后一个数字是5，且后3个数字中相邻数字不相同，小明至少要试多少次才能保证登入（16）3 8 0 3 4 _(奇-后=5) _(1.3.7.9) 54*4=168.五个球从盒子里拿出来，打乱顺序放回去，均不在原位的排列数是多少（44）4 * ( 2 + 3 * 3 ) = 44错排公式：D(n) = (n-1) * ( D(n-1) + D(n-2) )，n >= 3，D(1) = 0 , D(2) = 1+[xA][][][][]把A放到其它位置有4种情况，这里设A在B位。[B][A][xC][xD][xE]若把B放在A位，则变成D(n-2)种情况的错排 [xB][A][xC][xD][xE]若不把B放在A位，因为有这个前提所以B相当于为A的错排，则变成D(n-1)种情况的错排9.数据在计算机内有链式和顺序两种存储方式，在存储空间使用的灵活性上，链式存储比顺序存储要（高）链式存储快，查找慢；顺序查找快，存储慢。10.入栈序列是：a1,a3,a5,a2,a4,a6,出栈序列是：a5,a4,a2,a6,a3,a1，则栈的容量最小是（4）画图即可11.对某二叉树进行先序遍历的结果是ABDEFC,中序遍历的结果是DBFEAC，则后序遍历的结果是（DFEBCA）先序：根→左→右中序：左→根→右后序：左→右→根思路：通过先序的根1确定中序对应的位置，就可以知道根1前面的所有左子树，再递归下去。12.对于有n个结点的二叉树，其高度为（不确定）(第一层高度1)题目说的是二叉树，若为完全二叉树为[log2N]+1；若每层一个节点则高度为N。13.设输入序列为1,2,3，则经过栈的作用后可以得到（5）中不同的输出序列。卡特兰数：f(3) = C36 / 3+1 = ((654)/(321)) / 4 = 20 / 4 = 514.一棵完全二叉树第六层有9个叶结点（根为第一层），则结点个数最多有（109）1 + 2 + 4 + 8 + 16 + 32 + 2*(32-9) = 63 + 46 = 10915.i为整型变量，则以下循环语句的循环次数是（0）for(i=2;i<2;) printf(“%d”,i--);16.64位系统上，定义的变量int* a3占据（48）字节3 2 8字节 = 48字节17.当一进程运行时，系统可基于某种原则，强行将其擞下，把处理器分配给其他进程，这种调度方式是（剥夺方式）优先权原则、短进程优先原则、时间片原则，采用直接剥夺的调度方式。18.i的初值为0，i++在两个进程里面分别执行100次，能得到可能的值包括（0-200）多线程下，i++不是原子性操作，所以可能脏读。19.在操作系统中同时存在多个进程，它们（可以共享允许共享的系统资源 可以调用同一段程序代码）20.【4399论述题】大学期间除了课程作业外做了哪些开发？用的什么语言？目的是什么？图书管理系统，javaweb爬虫，java车辆预约系统，java易网，python...尽量写简历上的项目，最好是比赛的。21.【4399论述题】周围朋友一般因为你的什么特质而佩服你？变相展示自己的出众点：每天保持学习，能持之以恒去图书馆学习。有明确的目标，不断学习前沿技术。经常做博客积累并分享。22.【4399论述题】请描述一个例子，你曾经解决过一个别的同学解决不了的技术问题，你是如何做到的又是变相秀加分项：Sql优化、GC调优、高并发等等加分的。23.【4399论述题】大学期间，你是如何学习提升技术能力的，目前积累程度如何，请举例说明展现自己的自学能力和学习态度，尽量详细写自学过程和坚持不懈。24.描述一个例子，你曾经给自己设定了一个很高的目标，并且很努力的去完成的一件事拿具体实例证明，注意可靠性。也是讲自己的目标、时间规划和学习态度。25.【4399论述题】在工作中，你是更注重团队协作还是个人想法？请举例说明。在一个代表的领导下，进行团队协作。在需求分析阶段可以开展头脑风暴充分展示个人的想法，再统一决定。开发阶段则各自完成自己的任务。26.小陆所在的公司实行周末单双休，即一周只休周日，下一周休周六日，如此轮换。已知2016年8月13-14日是双休，输入年月日，以yyyymmdd的格式，如20160814，输出当天是否休息，（不考虑法定节假日）。要求：不能使用时间、日期相关的库函数参考#include<iostream> #include<string> using namespace std; int main(){ string inputString; long date; cin>> inputString; int year,month,day; int pos = 0; //去除字符串中的逗号 while((pos=inputString.find(","))!=-1){ inputString.erase(pos,1); } //将字符串转换为10进制int型 date = stoi(inputString,0,10); //从日期中提取年月日 year=date/10000; month=date%10000/100; day=date%100; int weekAfter[7] = {0,1,2,3,4,5,6}; int weekBefore[7] = {0,6,5,4,3,2,1}; int mon[12]={31,28,31,30,31,30,31,31,30,31,30,31}; //判断闰年 if(year%100!=0&&year%4==0||year%400==0) mon[1]=29; else mon[1]=28; int dou=2; //记录单休双休，0双休，1单休 int zhou=7; //记录是周几，7周日 int t=0; //记录week中的下标 if(date>20160814) //双休周日 { while(year!=2016||month!=8||day!=14){ day--; t++; if(month > 8 && day<1){ day=mon[month]; month--; }else if(year > 2016 && month<1){ year--; month=12; if(year%100!=0&&year%4==0||year%400==0) mon[1]=29; else mon[1]=28; } } printf("%d\n",t); if(t%7!=0){ dou=t/7+1; }else{ dou=t/7; } dou=dou%2; //0为双休1为单休 zhou=weekAfter[t%7]; //周几，0为周日，6周六，5周五…… }else if(date<20160814){ //20160814之前 while(year!=2016||month!=8||day!=14){ day++; t++; if(day > mon[month]){ month++; day=1; }else if(month>12){ year++; month=1; if(year%100!=0&&year%4==0||year%400==0) mon[1]=29; else mon[1]=28; } } if(t%7!=0){ dou=t/7; }else{ dou=t/7; } dou=dou%2; //0为双休1为单休 zhou=weekBefore[t%7]; //周几，0为周日 }else{ dou = 0; zhou = 0; } if(dou==1){ //单休 if(zhou!=0) //不是周日 cout<<"工作"<<endl; else cout<<"休息"<<endl; }else if(dou==0){ //双休 if(zhou==0||zhou==6){ //是周日或周六 cout<<"休息"<<endl; }else cout<<"工作"<<endl; } }27.将20个球放进12个不同的袋子，每个袋子可以放0-20个球，有多少种放法？分析如何计算，然后编程解答。进阶问题：每个袋子只能放0个、2个或3个球，该如何计算？参考public int ballBag(int bag, int ball) { int[][] dp = new int[bag + 1][ball + 1]; int i, j, k, sum; for (i = 1; i <= bag; i++) { dp[i][0] = 1; }//无论几个包，放入0个球的方法只有一种 for (j = 0; j <= ball; j++) { dp[1][j] = 1; }//无论几个球，放入1个包的方法也只有一种 for (i = 2; i <= bag; i++) { for (j = 1; j <= ball; j++) { sum = 0; for (k = 0; k <= j; k++) { sum += dp[i - 1][k]; } dp[i][j] = sum; } } return dp[bag][ball]; }28.关键字过滤：输入字符串s1和s2，判断s1中是否顺序包含s2中所有字符（大小写敏感），如果否，输出s1，如果是，输出“不可描述”例：输入ABCde，Ad，输出：不可描述输入ABCde，AA输出：ABCde要求：不能使用字符相关的库函数import java.util.Scanner; public class solution { public String findit(String str){ Scanner scanner = new Scanner(System.in); String str1 = scanner.next(); String str2 = scanner.next(); char[] char1 = str1.toCharArray(); char[] char2 = str2.toCharArray(); int p1 = 0; int p2 = 0; while(p1<str1.length() && p2<str2.length()){ while(p1<str1.length() && p2<str2.length() && char1[p1]!=char2[p2]){ p1++; } p1++; p2++; } if(p2 >= char2.length){ return new String("不可描述"); }else{ return str1; } } }29.【附加题】n男n女去相亲，活动结束时两两异性间产生匹配值，用1-100整数表示。为了使活动完美，主办方希望找到一种匹配方案，使得所有异性两两匹配，并且中匹配值最大。1）编程实现匹配算法，并分析时间算法复杂度，估算在你的计算机上n能支持到多大2）当n比较大时，可能接受次优解，请描述可以怎么优化你的算法或使用别的算法二分图的最大权匹配，KM算法。
这一份是比较早的面试题，比较基础也比较在流传。趁六月份开个新帖，每日更新至少一道面试笔试题。[...]
[面试题]阿里巴巴2017秋招研发工程师笔试题
1.有个特殊的餐厅，对客人的要求是如果要离开餐厅一定要看下餐厅内有没有比你迟进来的人，一定要所有比你迟进来的人离开后你才能离开，有一天甲，乙，丙，丁四个客人先后进入了这家餐厅，那么他们离开的顺序不可能是：EA: 丙，乙，甲，丁 B: 甲，乙，丙，丁 C: 乙，甲，丙，丁 D: 乙，丙，甲，丁 E: 丁，丙，甲，乙 F: 丁，丙，乙，甲考点： 堆栈误区： 甲乙丙丁不一定一次性全部进入餐厅，一般错在丁第一个出现，说明甲乙丙丁全部一次性入栈2.在关系型是数据库中，有两个不同的事务同时操作数据库中同一表的同一行，不会引起冲突的是：FA: 其中一个DELETE操作，一个是SELECT操作 B: 其中两个都是UPDATE C: 其中一个是SELECT，一个是UPDATE D: 其中一个SELECT E: 其中一个是DELETE，另一个是UPDATE F: 两个都是DELETE思路： 只要不产生“脏数据”，就是正确的。同理都是SELECT也不会冲突。3.众所周知我们所处的宇宙的质能公式是E=mc^2 ，其中c是真空中的光速。和我们的宇宙平行的另一个宇宙meta，研究显示他们使用的质能公式是E=(2+ √3)^m，当一个物体的质量很大的时候，对应的能量E非常大，数据也非常的长。但meta宇宙里面的智慧生物非常的懒，他们只愿意把E取整，然后记录对应的能量E的最后一位整数，比如m=0时，他们会记录1，m=1时，他们会记录3，m=2时，他们会记录3.现在请问当m=80时，他们会记录多少？3思路： 计算3.73的0、1、2、3次方，就能找到规律1，3，34.页高速缓存是Linux kerne使用的主要的磁盘缓存技术。它允许系统把存放在磁盘上的一些数据保留在内存中，以便减少对磁盘的访问。进程对页高速缓存区中的数据修改之后，数据页被标记为“脏数据”在下列哪些条件下，脏数据不会被写入磁盘？B、FA: 页高速缓存空间不足 B: 突然断电 C: 变脏以来，太久没有过更新 D: 通过系统调用（sync（），fsync（），fdataasync（））来强 E: 行对将对快设备的更新同步到磁盘 F: 内存足够大 G: 磁盘足够大考点： 缓存->磁盘。 ACD是脏数据写入磁盘的条件。5.设一组初始关键字记录关键字为（ 12,15,1,18,2,35,30,11 ），则以 12 为基准记录的一趟快速排序结束后的结果为挖坑法： 11，2，1，12，18，35，30，15左右指针法：2，11，1，12，18，35，30，15(左右先后顺序影响答案)前后指针法：1，2，11，18，15，35，30，126.多项式 P(X)=a+bx+cx^2+dx^3 ，对于任意 x ，计算 P(X) 中最少需要用到乘法操作的次数是多少？3思路： a+bx+cx^2+dx^3=a+x(b+x(c+xd))7.有一个班31人，女同学15人，男同学16人，现在要玩一个结组游戏，每组由男女两名同学构成，每个同学最多只能在一个组。结组的同学可以到老师那里领100元钱，然后按照预先分配的协议进行分配，钱最小可分单元为1元。未能结组的同学无法领到钱，不允许在组之间传递利益。那么一下命题正确的是：男生和女生可以得到一样多的钱误区： 公平则男女15各50R，一个男0R；博弈则为15个女生99R,15个男生1R，一个男生0R8.现代的企业是建立在大规模协作的基础上的，员工之间，团队之间，部门之间，企业之间的协作都是成功的重要因素。好的企业在协作上是高效的。以下说法中不合适的是（B）A: 一个项目能容纳的人员是有限的，当增加到一定规模项目进度反而会变慢。 B: 一个项目协作为了办证信息对称，多方参与的情况下直接召集多方在一起开会就能协调好 C: 协作建立的条件包括互补和共赢 D: 能力结构类似的成员之间较多样互补型员工之间更容易产生竞争关系 E: 协作中的权利和责任应当相称 F: 如果有可能的话，信息交互较多的事务更合适在一个团队内或有一个人完成，相较于进行分工。9.以下程序的运行结果是？A考点： 线程的run()和start()思路： start()启动线程，这时无需等待run方法体代码执行完毕，可以直接继续执行下面的代码；run()直接调用则当作普通方法，这时要等待run方法体执行完毕后，才可继续执行下面的代码10.输入图片大小为200×200，依次经过一层卷积（kernel size 5×5，padding 1，stride 2），pooling（kernel size 3×3，padding 0，stride 1），又一层卷积（kernel size3×3，padding 1，stride 1）之后，输出特征图大小为：97思路：卷积向下取整，池化向上取整输出尺寸=(输入尺寸-kernel尺寸+2*padding)/stride+1(200-5+2*1)/2+1=99.5，向下取整99 (99-3)/1+1=97 (97-3+2*1)/1+1=9711.一个二叉树有100个子节点数为2的节点，100个子节点数为1的节点，那么个子节点数为0的节点（叶节点）的个数为：101思路：度数(边数) = 所有节点数-1;这里度数=2*100+100=300，总节点数=100(2节点数)+100(1节点数)+(0节点数)12.某种类型的双核 CPU 的性能提升了 1/3 ，假定该提升是通过对每条指令缩短执行时间实现的，那么它每条指令执行时间缩短了 (1/4) 。思路：原来：1秒执行1条指令现在：1秒执行4/3条指令，即执行1条指令花费1/(4/3)=3/4秒，节约1-3/4=1/4秒13.一个map-reduce任务由m个mapper和r个reducer构成，计算的效率可以认为正比于mr的乘积（数据管道的个数），在限定任务的mr乘积约等于10000的情况下，假定每个mapper和每个reducer的成本分别为1和7，那么最佳的资源分配最接近于以下哪个方案？思路x*7x=10000,x=37.80≈38即：reducer=38，mapper=10000/38=263.16≈26414.如果你有相关经验，很多景点的餐馆商铺经营方式很有趣。以下描述错误的是：F(有点无语)A: 由于景点的大部分顾客是一次性的，因此商铺的信用在其他条件相同时可能更低 B: 景点的餐馆为了招揽顾客使用托儿会比居民区的餐馆使用托儿效果好 C: 景点常常卖一些当地特产，比如青岛的海边会有卖贝壳的，这些贝壳产自附近的海域 D: 店铺使用托儿的有效原因是，人们常常做出多数人做出的选择，而忽视自己自然状态下的决策 E: 一些景点在出口位置上安排一个商店，商店内的通道曲折，为的是顾客多花些时间看东西 F: 景点内一些玩射箭的场所经营状况会比在居民区附近设置的类似场所好，原因是景点内的游人玩起来更在状态15.一个机器人玩抛硬币的游戏，一直不停的抛一枚不均匀的硬币，硬币有A,B两面，A面的概率为3/4，B面的概率为1/4。问第一次出现连续的两个A年的时候，机器人抛硬币的次数的期望是多少？28/9思路：首先我们假设机器人抛硬币的期望为T，也就是说机器人停止游戏的话需要T次，接着分为三种情况（重复抛次数为T）：第一次抛到B，则需要重新抛置，概率为1/4，次数为T+1第一次抛到A，第二次抛到B，仍然需要重新抛置，概率为1/4*3/4,次数为T+2第一次抛到A，第二次抛到A，只需要两次，概率为3/4*3/4，次数为2从而可以得到数学期望：T = 1/4 (T+1)+3/16(T+2)+9/16 * 2,解得T=28/916.小a和小b一起玩一个游戏，两个人一起抛掷一枚硬币，正面为H，反面为T。两个人把抛到的结果写成一个序列。如果出现HHT则小a获胜，游戏结束。如果HTT出现则小b获胜。小a想问一下他获胜的概率是多少？2/317.假定某同学使用Naive Bayesian（NB）分类模型时，不小心将训练数据的两个维度搞重复了，那么关于NB的说法中正确的是：B、DA: 这个被重复的特征在模型中的决定作用会被加强 B: 模型效果相比无重复特征的情况下精确度会降低 C: 如果所有特征都被重复一遍，得到的模型预测结果相对于不重复的情况下的模型预测结果一样。 D: 当两列特征高度相关时，无法用两列特征相同时所得到的结论来分析问题 E: NB可以用来做最小二乘回归 F: 以上说法都不正确思路：在朴素贝叶斯分类理论系统中，都有一个重要的条件独立性假设：假设所有特征之间相互独立，这样才能将联合概率拆分对于A，冗余特征会使得并没有增加输入信息的前提下增加模型判别的置信度，这显然是不合理的。18.以下哪个行为，不会明显加剧客户端运行过程中的卡顿：CA: 在主线程集中处理耗时的操作 B: 在子线程集中处理耗时的操作 C: 在其它进程集中处理耗时的操作 D: 提高后台线程的优先级 E: 降低主线程的优先级 F: 页面存在多个重叠显示的控件19.以下程序的输出是：3,5思路：f函数为斐波那契数列1 1 2 3 5 8 13 21 34 55...count函数为MIT HAKM算法，统计n的二进制表示中1个数量(https://blog.csdn.net/msquare/article/details/4536388)20.学校图书馆共有 300 万册图书，想统计其中 Computer ， Science ，计算机，科学这几个词出现的次数，并按照自然年度分类，如 2016 年出版的书籍中这几个词各自出现的次数， 2015年······依次类推。思路：将每本书都存在hdfs里作为一个文件，文件名为 时间（4位年份）+书的id+书的名称。使用mapreduce进行运算，map输出为<日期，computer次数；science次数；计算机次数；科学次数>,reduce输出同样，不过作为value的字符串中的次数为总次数。代码如下：public static class MyMapper extends Mapper<LongWritable,Text,Text,Text>{ private static Text outputKey = new Text(); private static Text outputValue = new Text(); @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { //得到hdfs文件名 String filename = ((FileSplit) context.getInputSplit()).getPath().getName(); String date = filename.substring(0, 4); //分别统计computer，science，计算机，科学出现的次数 int computer = 0; int science = 0; int jisuanji = 0; int kexue = 0; String line = value.toString(); String[] words = line.split(" "); for(String word : words){ if(word.equals("computer")) computer++; if(word.equals("science")) science++; if(word.equals("计算机")) jisuanji++; if(word.equals("科学")) kexue++; } String outputVal = "" + computer + ";" + science + ";" + jisuanji + ";" + kexue; outputKey.set(date); outputValue.set(outputVal); context.write(outputKey, outputValue); } } public static class MyReducer extends Reducer<Text, Text, Text, Text> { @Override protected void reduce(Text key, Iterable<Text> values,Context context) throws IOException, InterruptedException { int allComputer = 0; int allScience = 0; int allJisuanji = 0; int allKexue = 0; for(Text value:values){ String val = value.toString(); String[] str = val.split(";"); allComputer += Integer.parseInt(str[0]); allScience += Integer.parseInt(str[1]); allJisuanji += Integer.parseInt(str[2]); allKexue += Integer.parseInt(str[3]); } String finalVal = "" + allComputer + ";" + allScience + ";" + allJisuanji + ";" + allKexue; context.write(key, new Text(finalVal)); } }21.设计接口并且实现一个多线程安全的堆，并且要求可以删除堆中任意元素（非堆顶元素），要求尽量高效，假设已有标准的mutex实现可以使用。deleteElement(int[] heap, int index, int len) { mutex.lock(); heap[index] = heap[len-1]; len--; while(2*index + 1 < len && (heap[index] > heap[2*index+1]|| heap[index] > heap[2*index +2])) { int swapi = heap[2*index+1] < heap[2*index +2]) ? 2*index+1 : 2*index+2; swap(heap,index,swapi); index = swapi; } mutex.unlock(); }22.淘宝上的每个宝贝一般都有个默认的全国邮费（也可能没有），同时也支持到特定省份有特定的邮费，如果到特定的省份没有特别的邮费就用默认的全国邮费。请：1.设计一个存储结构来保存一个宝贝的所有邮费信息；（简单用文字阐述一下做法）2.给定一个宝贝的邮费存储信息和一个省份，编程快速得出宝贝到此省的邮费。 注意：邮费的类型是uint32_t，此外由于商品数量非常大（假定十亿量级），查询量也非常大，对存储和查询的效率要求非常高，因此存储效率和查询效率是考察的重点。//假设34个省的id为0--33 struct postage_info{ uint64_t mask;//总共34各省，用34位表示每个省的邮费是不是默认，是的话对应位为1 struct postage *p; //用一个数组存储非默认省份的价格，假设此数组按province_id的大小已经排好序了 uint32_t default; //默认价格 } struct postage{ int province_id; // 省id int postage; //邮费 } uint32_t get_postage(int province_id, struct postage_info * info){ if(1<<province_id && info->mask) return info->default; else return bsearch(province_id, info->p)//... 使用二分查找获取对应省份的邮费 }
java面向对象：Clone专题1.1 为什么要clone？在实际编程过程中，我们常常要遇到这种情况：有一个对象 A，在某一时刻 A 中已经包含了一些有效值，此时可能会需要一个和 A 完全相同新对象 B，并且此后对 B 任何改动都不会影响到 A 中的值，也就是说，A 与 B 是两个独立的对象，但 B 的初始值是由 A 对象确定的。在 Java 语言中，用简单的赋值语句是不能满足这种需求的。要满足这种需求虽然有很多途径，但实现 clone（）方法是其中最简单，也是最高效的手段。1.2 new 一个对象的过程和 clone 一个对象的过程区别new 操作符的本意是分配内存。程序执行到 new 操作符时，首先去看 new 操作符后面的类型，因为知道了类型，才能知道要分配多大的内存空间。分配完内存之后，再调用构造函数，填充对象的各个域，这一步叫做对象的初始化，构造方法返回后，一个对象创建完毕，可以把他的引用（地址）发布到外部，在外部就可以使用这个引用操纵这个对象。new会调用构造方法，clone不会。clone 在第一步是和 new 相似的，都是分配内存，调用 clone 方法时，分配的内存和原对象（即调用 clone 方法的对象）相同，然后再使用原对象中对应的各个域，填充新对象的域，填充完成之后，clone 方法返回，一个新的相同的对象被创建，同样可以把这个新对象的引用发布到外部。1.3 写clone()方法时，通常都有一行代码，是什么？clone 有缺省行为，super.clone();因为首先要把父类中的成员复制到位，然后才是复制自己的成员。补充说明clone默认方法为浅拷贝，即：新（拷贝产生）、旧（元对象）对象不同，但是内部如果有引用类型的变量，新、旧对象引用的都是同一引用。深拷贝则是全部拷贝原对象的内容，包括内存的引用类型也进行拷贝。（一般用于有父子类关系）package clone; /** * @author CTX */ public class Father implements Cloneable{ private Son son; public Father(Son son){ this.son = son; } @Override protected Object clone() throws CloneNotSupportedException { // return super.clone(); Father newFather = (Father) super.clone(); newFather.son = (Son) this.son.clone(); return newFather; } public Son getSon() { return son; } }package clone; /** * @author CTX */ public class Son implements Cloneable{ @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }package clone; /** * @author CTX */ public class Test { public static void main(String[] args) throws CloneNotSupportedException { Father father1 = new Father(new Son()); Father father2 = (Father) father1.clone(); System.out.println("f1:"+father1.hashCode()+" s1:"+father1.getSon().hashCode()); System.out.println("f2:"+father2.hashCode()+" s2:"+father2.getSon().hashCode()); } } f1:460141958 s1:1163157884 f2:1956725890 s2:356573597JavaSE语法2.1 当一个对象被当做参数传递到一个方法后，此方法可改变这个对象的属性，并可返回变化后的结果，那么这里到底是值传递还是引用传递？是值传递。Java编程语言中只有由值传递参数的。当一个对象实例作为一个参数被传递到方法中时，参数的值就是该对象的引用。对象的内容可以在被调用的方法中改变，但对象的引用是永远不会改变的。注意：在 Java应用程序中永远不会传递对象，而只传递对象引用。但Java应用程序按引用传递对象这一事实并不意味着 Java 应用程序按引用传递参数。参数可以是对象引用，而 Java应用程序是按值传递对象引用的。Java异常处理3.1 调用下面的方法，得到的返回值是什么？public int getNum() { try { int a = 1 / 0; return 1; } catch (Exception e) { return 2; } finally { return 3; } }代码在走到第 3 行的时候遇到了一个 MathException，这时第四行的代码就不会执行了，代码直接跳转到 catch语句中，走到第 6 行的时候，异常机制有这么一个原则如果在 catch 中遇到了 return 或者异常等能使该函数终止的话那么有 finally 就必须先执行完 finally 代码块里面的代码然后再返回值。因此代码又跳到第 8 行，可惜第 8 行是一个return 语句，那么这个时候方法就结束了，因此第 6 行的返回结果就无法被真正返回。如果 finally 仅仅是处理了一个释放资源的操作，那么该道题最终返回的结果就是 2。因此上面返回值是 3。3.2 throw 和 throws 的区别throw：1）throw 语句用在方法体内，表示抛出异常，由方法体内的语句处理。2）throw 是具体向外抛出异常的动作，所以它抛出的是一个异常实例，执行 throw 一定是抛出了某种异常。throws：1）throws 语句是用在方法声明后面，表示如果抛出异常，由该方法的调用者来进行异常的处理。2）throws 主要是声明这个方法会抛出某种类型的异常，让它的使用者要知道需要捕获的异常的类型。3）throws 表示出现异常的一种可能性，并不一定会发生这种异常。实例：void testException(int a) throws IOException,{ try{ ... }catch(Exception1 e){ throw e; }catch(Exception2 e){ System.out.println("出错了！"); } if(a!=b) throw new Exception3("自定义异常"); }JavaSE常用API4.1 华为面试题：什么情况下用“+”运算符进行字符串连接比调用 StringBuffer/StringBuilder对象的 append 方法连接字符串性能更好？通过查看String源码可以发现，String类中已经重载过 +，所以可以用它连接字符串。而且之前通过查阅文章了解到反编译文件中 +被转换为了 StringBulder，所以无论使用哪种方法连接字符串实际上底层还是通过 StringBulder。但是进一步通过反编译程序可以发现对于存在for循环的连接语句，+转换的 StringBulder出现在for循环内部，每执行一遍就会产生一次 StringBulder对象;而创建的 StringBulder则在for循环外部，虽然代码更复杂但是换来了更高的效率和资源开销。4.2 请说出下列输出：class StringEqualTest { public static void main(String[] args) { String s1 = "Programming"; String s2 = new String("Programming"); String s3 = "Program"; String s4 = "ming"; String s5 = "Program" + "ming"; String s6 = s3 + s4; System.out.println(s1 == s2); //false System.out.println(s1 == s5); //true System.out.println(s1 == s6); //false /** * intern(): * 如果字符串常量池中已经包含一个等于此String对象的字符串，则返回代表池中这个字符串的String对象； * 否则，将此String对象包含的字符添加到常量池中，并返回此String对象的引用。 * new String不会放入常量池中 */ System.out.println(s1 == s6.intern()); //true //s2指向堆栈中的引用地址，s2.intern()指向常量池的引用地址，故不相同 System.out.println(s2 == s2.intern()); //false } }4.3 Java日期和时间问题一定要用LocalDataTime而不用Data函数，因为Data格式化日期还需要构造输出格式并且很多构造函数已经被弃用了。并且Date时间类是可变类，这就意味着在多线程环境下对共享 Date变量进行操作时，必须由程序员自己来保证线程安全！而自 Java 8开始推出的 LocalDateTime却是不可变类，是线程安全的，开发人员不用再考虑并发问题。而Data连用的SimpleDataFormat也是线程不安全，应该用DateTimeFormatter。package data_and_time; import java.time.Clock; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.Month; import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalAdjuster; import java.time.temporal.TemporalAdjusters; import java.util.Calendar; /** * @author 唐宋丶 * @create 2020/9/28 */ public class LocaDataTimeDemo { public static void main(String[] args) { /** * 获取当前日期和时间 */ LocalDateTime rightNow = LocalDateTime.now(); System.out.println("当前时刻：" + rightNow); System.out.println("当前年：" + rightNow.getYear()); System.out.println("当前月：" + rightNow.getMonth()); System.out.println("当前日：" + rightNow.getDayOfMonth()); System.out.println("当前时：" + rightNow.getHour()); System.out.println("当前分：" + rightNow.getMinute()); System.out.println("当前秒：" + rightNow.getSecond()); /** * 获取从1970-01-01 00：00至今的毫秒数 */ System.out.println(Calendar.getInstance().getTimeInMillis()); System.out.println(System.currentTimeMillis()); System.out.println(Clock.systemDefaultZone().millis()); /** * 获取本月最后一天 * with(TemporalAdjusters)用于寻找第一天/最后一天 */ System.out.println("本月最后一天：" + rightNow.with(TemporalAdjusters.lastDayOfMonth())); /** * 构造指定年月日 一月 */ LocalDateTime beforeData = LocalDateTime.of(1970, Month.JANUARY,1,0,0,0); System.out.println("构造日期：" + beforeData); /** * 修改日期 */ System.out.println(rightNow); System.out.println("两年前：" + rightNow.minusYears(2)); System.out.println("两月后：" + rightNow.plusMonths(2)); System.out.println("改到22时：" + rightNow.withHour(22)); /** * 格式化日期（部分样式） */ String data1 = rightNow.format(DateTimeFormatter.ofPattern("yyyy/MM/dd")); String data2 = rightNow.format(DateTimeFormatter.ISO_DATE); String data3 = rightNow.format(DateTimeFormatter.ISO_DATE_TIME); String data4 = rightNow.format(DateTimeFormatter.BASIC_ISO_DATE); System.out.println("自定义样式：" + data1); System.out.println("基本样式一：" + data2); System.out.println("基本样式二：" + data3); System.out.println("基本样式三：" + data4); /** * 时间反解析：给你一个陌生的字符串,你可以按照你需要的格式把时间给反解出来 */ LocalDateTime time = LocalDateTime.parse("2020---01---02 03:04",DateTimeFormatter.ofPattern("yyyy---MM---dd HH:mm")); System.out.println("字符串反解析后的时间为：" + time); } } 当前时刻：2020-09-28T20:07:36.469 当前年：2020 当前月：SEPTEMBER 当前日：28 当前时：20 当前分：7 当前秒：36 1601294856473 1601294856481 1601294856481 本月最后一天：2020-09-30T20:07:36.469 构造日期：1970-01-01T00:00 2020-09-28T20:07:36.469 两年前：2018-09-28T20:07:36.469 两月后：2020-11-28T20:07:36.469 改到22时：2020-09-28T22:07:36.469 自定义样式：2020/09/28 基本样式一：2020-09-28 基本样式二：2020-09-28T20:07:36.469 基本样式三：20200928 字符串反解析后的时间为：2020-01-02T03:04Java 8 日期/时间新特性：不可变性：新日期/时间的API中，类都是不可变的，保证线程安全。关注分离点：将日期时间和机器时间明确分离。为日期(Data)、时间(Time)、日期时间(DataTime)、时间戳(unix timestamp)以及时区定义了不同的类。清晰：所有类的方法都被明确定义已完成相同行为，不像一起 format()/parse()都需要专门一个类。实用操作：都实现了加、减、格式化、解析、提取部分等通用任务。可扩展性：不仅仅只能工作在 ISO-8601 日历系统上。Java 8 日期/时间API包：java.time 包：这是新的 Java 日期/时间 API 的基础包，所有的主要基础类都是这个包的一部分，如：LocalDate,LocalTime, LocalDateTime, Instant, Period, Duration 等等。所有这些类都是不可变的和线程安全的，在绝大多数情况下，这些类能够有效地处理一些公共的需求。java.time.chrono 包：这个包为非 ISO 的日历系统定义了一些泛化的 API，我们可以扩展AbstractChronology类来创建自己的日历系统。java.time.format 包：这个包包含能够格式化和解析日期时间对象的类，在绝大多数情况下，我们不应该直接使用它们，因为 java.time 包中相应的类已经提供了格式化和解析的方法。java.time.temporal 包：这个包包含一些时态对象，我们可以用其找出关于日期/时间对象的某个特定日期或时间，比如说，可以找到某月的第一天或最后一天。你可以非常容易地认出这些方法，因为它们都具有“withXXX”的格式。java.time.zone 包：这个包包含支持不同时区以及相关规则的类。Java数据类型5.1 String常用方法
技术面试指导一、必备项0.自我介绍表达流畅、不要太差即可。可以提前准备一份说辞，外企需要准备英文的自我介绍。1.基础坑1：【答案很标准】面试时的回答，一定不要背网上的《面试大全》/《面试宝典》，一定要有自己的思想。这些答案太过千篇一律、标准。而且太老的面试题可能存在错误或者过时。建议：每道题都通过自己的理解或者代码敲一遍，能够用自己话说出来，最好加上自己的一些理解（哪怕有点瑕疵）。或者搬运一些博客/GitHub上大牛的博文。不要死记那些所谓的标准答案（那些答案往往很浅）。总之，就是要向面试官传达“我的答案是我自己写的，我是一个有独立思考的人，而不是网上抄的”。2.技术列表坑2：【精通】3年以内的开发者，几乎没人敢说“精通”哪一门技术建议：掌握程度：掌握、熟练、理解、会使用坑3：【个人掌握的技能过于“标准化”，明显就是培训、或者看某套视频学出来。如：java + 数据库 + web前端 + jsp/servlet + ssm + boot/cloud】建议：一般而言，自学成才的人比培训出来的学生 更具有独立思考的能力，因此在相同的条件下，企业更喜欢没有参加过培训的学生。建议写上2-3门非培训机构标配课程，如service mesh、netty等（最好写与高并发、分布式有关的，技术的名字相对“少见”但又很重要的）。对面试而言，这些“少见”的技术，只要你写上了，并且能把其中任意一个核心知识点说明白，就已经非常加分了。（假设Spring是一个“少见”的技术，那么你只要在面试时解释一下什么是IoC就可以了）坑4：【简历上写一大堆牛B的技术，显得自己很厉害】建议：技术点宁可少写，也别多写。面试官经常都很忙，没时间精心准备对你的面试，甚至有时候是一边神游一边在提问，所以很可能从你简历里随便挑几个你写上的技术来问你。因此简历上写到的技术，都很可能被问到。所以写上去的所有技术都要事先做好面试官提问的准备，对于非主流的技术，面试官一般会让你自己介绍使用场景和方法，可以事先做好准备。3.项目坑4：【项目名叫“xxx电商项目”、“xxx管理系统”，这些“项目”简直就是培训机构的标配，缺乏真实项目的感觉】但对于实习生来说，这个坑真的是痛点。因为除此和课设的“xxx管理系统”之外，很难接手到其他新的项目。大创之类的也不一定刚好是所学语言为主。建议：针对实习生来说，可以学一学Java爬虫，做完后开源出来。提前准备好回答“项目”的剧本 “你做过什么样的项目？”或者根据你简历中的项目来提问，几乎是技术面试官必须做、并且非常喜欢做的事。所以，如果你没有充足的项目经验，就提前准备好台词吧。（用到的技术、使用场景、遇到的困难、如何解决）关于项目，经常会被问到的点是：某个技术本身的不足，以及如何弥补。因为这样问，能够检验你是否真的做过这个“项目”，至少能说明你是否深入思考过。举例如下：你项目中用到了MySQL ：如果数据超过的MySQL的容量怎么处理？（弥补MySQL自身的不足）你做的这个项目是高并发吧？缓存用了吗？在哪些场景 你见过缓存失效？怎么解决？（还是在问你缓存自身的问题如何解决）看你的项目用到了MQ？MQ可以用来解耦合，具体讲讲你项目中到底哪些场景用到了解耦合？（在考你的项目是真的，还是假的）项目的重难点 每个项目都有自己的重难点，这些重难点也就是必问点，举例如下分布式项目：如何共享数据？什么是CAP原则？分布式锁、分布式事务、分布式缓存怎么实现？高并发项目：几级缓存，如何限流，如何熔断，用docker了没？真实性：实际的使用场景简历上写的“用到了人脸识别技术” ：哪些场景用到了？人脸识别是自己公司写的，还是调用的三方API？自己写的话，用的什么算法？调用API的话，每次调用需要付费多少钱？识别时的光线强度有什么要求？多线程、设计模式、算法：用来处理什么业务？场景？大数据的项目：数据从哪来的？项目能否访问？描述方式：技术列表 + 文字 （如果绘图功底不错，可以加上架构图）项目周期：半年以上。在校生的话就写2、3个月的项目周期。 简历上的项目个数：三个及以内（毕业三年内，写1-2个就够了）4.表达沟通能力不用刻意表现，表达清楚就行。问到不会的不甩锅，不抱怨，诚恳听取认真改正，体现自己的素养。二、加分项1.高并发/分布式/调优多线程（juc、aqs、线程安全、锁机制、生产消费者、线程依赖问题）数据处理SQL优化 ， 常见高性能数据库架构（如mysql+mycat+haproxy+keepalived）JVM调优2.实际的解决问题能力这点需要自己在面试时主动将话题引入。例如在回答项目时，主动说一下你在做项目时遇到过什么问题。具体是如何发现、排查、分析、解决问题的。3.绝杀ACM竞赛、蓝桥杯等全国性竞赛（学生专享）有过书籍、论文等出版物在github发布过项目(star很多)博客、微信公众号、 个人在阿里云等部署的可访问项目（这一条大部分人都能做到）。如果是电子简历，附上链接地址；如果是纸质简历，将链接封装在二维码里。研究过JDK/spring/mybatis等源码三、注意事项在描述时，多使用具体“数字”（前提是比模糊的更能体现自己）几个项目、几篇博客、排名第几、几次奖学金、几个证书工资：不要写面议 ，至少给个薪资范围，如1.5w - 2.0w 工资写面议HR可能没办法预算，比较赶的时候就直接pass掉了。而且一般会给工资最低范围多个一点点，如果自我感觉技术还行，可以适当提点薪资下限。简历：1-2页写满，不要放空，不要包书皮，电子版用PDF。简洁大方即可，不用太过绚丽。细节：毕业时间、年龄、工作履历、期望薪资等要相互匹配。例如，不要“毕业5年”，但“工作履历加起来只有3年”。沟通：注意人文素养 ，不要抱怨问题，要体现解决问题、愿意承担责任的态度 建议：体现出个人解决问题的能力、团队感、沟通能力，也可以在简历中表明。四、出奇制胜反对和所有 应聘者 千篇一律建议：积累《阿里巴巴编程规范》（IDEA加插件）、《effective java》反对和所有《面试宝典》千篇一律先用自己的答案写，在看看博客上别人的总结和心得，再结合宝典中的标准答案。源码级解决问题当学习中产生一个问题时，是如何解决的？百度、谷歌、问别人。但是面试官喜欢认识到我们有独立思考的一面，所以我们要尽可能的告知是通过自己查询书籍、阅读源码得出的。比如问到字符串相加时，点明 +号的底层方法是调用 StringBuffer/StringBuilder的 append()方法；如下例子：请讲讲ArrayList如何库容的？我最初看书（看博客、文档），了解到它的底层是动态数组，在add()时如果发现已满，则自动扩容1.5 。再通过查阅源码验证，发现是在数组已满时扩容，并且是通过位移运算符扩容。int newCapacity = oldCapacity + (oldCapacity >> 1);找准时机，秀技能每个人都有一些独到的经验，要想办法在面试的时候讲给面试官。“聊聊自己的项目经验” ,讲项目，讲完的时候加一句：我在做这个项目的时候曾经遇到了一个bug，当时ArrayList.asList()返回一个List，但是执行add()方法时报错了。当时通过阅读源码发现返回的List不是Collection中的那个List而是asList()方法返回后内置的List，它不包括add()方法。比较通用的秀点1.优化类（JVM、SQL优化）JVM优化：项目做完时，我用jmeter进行了压力测试，结果发现相应时间太慢（或者内存利用率太高） ，然后我用jvisualvm分析了下JVM的内存情况。分析后，进一步发现项目中的小对象太多了，并且发现这些小对象都是生命周期比较短的对象。然后我猜测可能就是由于短对象太多，造成了堆中新生代容量不足，进而让很多短对象逃逸到了老年代中。这样一来，新生代和老年代中的对象都会很多，就会加速GC的回收频率，从而降低系统的性能。对此，我调大了新生代的内存大小，并且调高了新生代 逃逸到老年代的阈值。之后再测试，发现性能平稳了许多。（PS：以上短短200字，就告诉了面试官你有性能测试的良好习惯、会发现问题、分析问题，并且会JVM调优！）SQL优化：项目做完时，我用jmeter进行了压力测试，结果发现响应时间太慢。然后我用mysqldumslow工具查找到了项目中执行时间最长的那个SQL语句，因此猜测是这条SQL的性能太低，拖累了整个系统。然后我用explain查看了SQL执行计划，发现这个SQL根本没有写索引并且是大表驱动了小表，所以特别慢。之后，我给它后面的where查询字段加上了索引，并且改为了小表驱动大表。然后再次测试，响应时间就缩短了很多。（PS：这个秀点是“SQL优化”，具体流程是：定位慢SQL->使用explain查询SQL执行计划，用于分析SQL执行慢的原因->SQL优化。上述中的“小表驱动大表”等是SQL优化时的术语。）2.算法类（KMP算法优化字符串匹配等）技术沉淀数据支撑：GitHub、个人博客、专栏、微信公众号、项目发布到阿里云大厂区别bat、美团、阿里、字节调动、google、facebook等大厂的看重点：数据结构和算法、操作系统、网络、设计模式、分布式、逻辑思维心态建设回答正确，但被面试官“否定”：1.答案来自于面试宝典，千篇一律2.面试官自己在秀技术，调整好心态，不要受干扰。正常面试官：引导你来回答，而不是想方设法吓唬你问：有没有用过List? 哪些常见子类？数组?动态扩容，不越界？3.如果问到自己不会的？一定要答出自己的想法/答类似的框架：虽然没用过这个新框架，但是我用过同类产品，并且相信他们是差不多的，然后就答这个同类产品的实现原理。如：对于mybatis/jpa，若出现新dao框架？则表明相信他们的工作原理都是映射文件/注释：实体类-表，然后通过框架本身的api的进行crud....等等。五、SSM和开源框架（部分）面试考点梳理Mybatis了解Mybatis的相关组件和配置规约Mybatis开发时的常用对象1.SqlSessionFactory：SqlSesssion工厂。通过SqlSessionFactory：SqlSesssion中的openSession()产生SqlSesssion对象。2.SqlSesssion：SqlSesssion对象（类似于JDBC中的Connection）3.Executor：MyBatis中所有Mapper语句的执行 都是通过Executor进行的。Mybatis四大核心对象1.StatementHandler(负责sql语句)：数据库的处理对象 select... from where id = #{} ..2.PrameterHandler(负责sql中的参数)：处理SQL中的参数对象3.Executor4.ResultSetHandler：处理SQL的返回结果集Mybatis四大处理器StatementHandler、PrameterHandler、ResultSetHandler、TypeHandler （类型转换器）执行流程//（1）获取配置文件信息 Reader reader = Resources.getResourceAsReader("conf.xml") ; //（2）创建SqlSessionFactory SqlSessionFactory sessionFacotry = new SqlSessionFactoryBuilder().build(reader,"development") ; //（3）通过SqlSessionFactory创建SqlSession SqlSession session = sessionFacotry.openSession() ; //（4）通过SqlSession执行数据库操作（获取XxxMapper再执行相应sql语句） StudentMapper studentMapper = session.getMapper(StudentMapper.class) ; Student student = studentMapper.queryStudentByStuno(1) ; //（5）调用session.commit()提交事务 session.commit(); //（6）session.close()关闭会话 session.close();一对一、一对多、多级缓存等。见之前博客SpringIOC/DI：控制反转/依赖注入含义：IOC帮我们提供了一个工厂。先向工厂中注入对象（配置[xml、注解]），再从工厂中获取对象。IOC可以让我们通过“配置”的方式来创建对象。目的：解耦合问题：Student2 student = new Student2()使用new会造成耦合度较高解决：用工厂模式，使 类->new->对象到 类->工厂模式->对象,但需要手动编写（引申到手撕工厂模式）AOP：面向切面编程（OOP的补充而不是代替）提问：OOP的不足、AOP对此的改进之处、AOP的使用场景有哪些：日志和安全统一校验等、如何实现Spring用到了哪些设计模式工厂模式：创建bean、获取bean单例模式/原型模式：创建bean时，设置作用域。（顺便手写singleton/prototype）监听模式：自定义时间发布，监听模式。如ApplicationListener，当某个动作触发时，就会自动执行一个通知。SpringMVCSpringBoot自动装配原理：约定优于配置 (核心：将一些配置功能，前置到源码底层实现好)自动装配两个特点：1.版本仲裁中心：因此，以后引入依赖时，不用再写版本号。好处：1.不用记 2.避免冲突(防止引入多个引来时，由于各个依赖的版本不兼容造成的冲突)2.提供了很多starter(场景启动器) ：批量jar。假设开发web项目需要用到 json.jar tomcat.jar hibernate-validator.jar, spring-web.jar ...则把他们统一封装到 spring-boot-starter-web启动场景。以后使用web项目，只需要引入spring-boot-starter-web。自动装配的应用时： @EnableAutoConfiguration就是springboot提供自动装配的注解。SpringBoot启动流程后续还有SpringCloud、Eureka、Ribbon、Feign、熔断器以及各种分布式架构等代码部分1.字符串匹配问题，判断是否相等import java.util.Objects; /** * @author 唐宋丶 * @create 2020/9/15 * * 出奇制胜：判断字符是否相等 */ public class Demo01 { public static void main(String[] args) { String str = "abc"; //大多数人写法 System.out.println(str.equals("abc")); //'"abc"' 应该作为方法 "equals()"的调用方，而不是参数 ，需要翻转来避免空指针问题 System.out.println("abc".equals(str)); //Objects工具类提供equals比较方法 System.out.println(Objects.equals("abc",str)); } }Objects.equals()源码如下： /** * Returns {@code true} if the arguments are equal to each other * and {@code false} otherwise. * Consequently, if both arguments are {@code null}, {@code true} * is returned and if exactly one argument is {@code null}, {@code * false} is returned. Otherwise, equality is determined by using * the {@link Object#equals equals} method of the first * argument. * * @param a an object * @param b an object to be compared with {@code a} for equality * @return {@code true} if the arguments are equal to each other * and {@code false} otherwise * @see Object#equals(Object) */ public static boolean equals(Object a, Object b) { return (a == b) || (a != null && a.equals(b)); }2.线程通信：生产者—消费者问题一般采用wait()和notify()/notifyAll()出奇制胜可以采用Semaphore、Google的guava类库的Monitorimport com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.Monitor; import com.google.common.util.concurrent.MoreExecutors; import java.util.LinkedList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; /** * @author 唐宋丶 * * 出奇制胜：通过google guava类库的Monitor完成生产-消费者问题的线程通信 */ public class MonitorDemo { private LinkedList<Integer> buffer = new LinkedList<>(); private static final int MAX = 10; //记录生产的数据编号 private static AtomicInteger count = new AtomicInteger(0); //int i ; i++ ;非原子性操作 ; i++ 拆分成多行 ；线程安全问题 private Monitor monitor = new Monitor(); /** * 生产数据，返回值是void，传入是int */ public void produce(int value) { try { //enterWhen：相当于加锁 monitor.enterWhen(monitor.newGuard(() -> buffer.size() < MAX)); buffer.addLast(value); } catch (InterruptedException e) { e.printStackTrace(); } finally { monitor.leave(); System.out.println("生产完毕，缓冲区大小：" + buffer.size()); } } /** * 消费数据，传入是void，返回值是int */ public int consume() { try { monitor.enterWhen(monitor.newGuard(() -> !buffer.isEmpty())); return buffer.removeFirst(); } catch (InterruptedException e) { e.printStackTrace(); throw new RuntimeException(e); } finally { monitor.leave(); System.out.println("消费完毕，缓冲区大小：" + buffer.size()); } } public static void main(String[] args) { MonitorDemo demo = new MonitorDemo(); ExecutorService executorService = Executors.newFixedThreadPool(6); //将线程池包装 ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(executorService); //向线程池放入3个生产者 for (int i = 0; i < 3; i++) { listeningExecutorService.submit(() -> { while (true) { int result = count.getAndIncrement(); //i++ 0 1 2 demo.produce(result); System.out.println("生产：" + result); } }); } //向线程池放入3个消费者 for (int i = 0; i < 3; i++) { while (true) { int result = demo.consume(); System.out.println("消费" + result); } } } }3.如何设计一个秒杀系统？秒杀含义：流量很大，库存少（10000用户抢购5件商品）从以下架构设计原则回答：限流、缓存、隔离、降级和熔断限流（多重）尽量上级限流：在用户看得到的页面限流，如：加验证码防止非法机器人的刷新/攻击、JavaScript限制IP请求次数、隐藏秒杀入口地址，只有用户正常进入商品页面才显示秒杀按钮，不能直接输入秒杀按钮的链接负载均衡：在网络分层第四层通过搭载lvs集群分发请求到Nignx集群、在网络分层第七层搭载Nginx分流到Tomcat集群中+动静分离：动态请求（需要通过java代码查询数据返回结果）发送到后台服务器；静态请求（图片、视频、大且数据唯一的文件）发送到CDNMQ流量削峰：在请求和服务器之间加个MQ队列，只存放服务器可承受的请求数。缓存（多级）:Redis缓存：Java服务器请求->redis缓存->DB数据库。DB处理：搭建高可用架构：VIP虚拟IP技术（keepalived+haproxy将IP分为多个节点，某个节点挂掉后能自动切换到可用节点）+MyCat读写分离（秒杀系统一般读多写少）+读/写数据库MySQL主从同步隔离：将秒杀的服务器隔离开，确保即使秒杀挂掉了其他操作还能正常执行降级和熔断：熔断：客户端请求时调用service()超时->备用方法(){return "未响应，请重试"}降级：服务端 ：20个服务->减少到10个服务（内部节约资源），通过牺牲非必要服务来提高性能。防止重复消费行为：幂等性支付->支付服务(扣款)->返回（支付成功）过程中若因网络等问题支付成功提示失败有可能导致用户重复付款。幂等性实现：去重表，在每次支付前，先查看去重表中 是否有扣款记录。如果有，则返回文字提示“已扣款，请稍等响应页面”；如果没有，再执行扣款4.简述NignxNginx是一款轻量级的web服务器（反向代理服务器，负载均衡服务器，动静分离服务器）。（1）反向代理正向代理(VPN)：代理客户端去访问服务器。如：(用户→VPN)→google反向代理(Nginx/Apache)：代理服务器接收服务端的请求（处理后再转发给服务器）。如：客户端→(Nginx→Tomcat)（2）负载均衡分流客户端请求，均衡集群服务端压力。Nignx支持 weight轮询（默认）、ip_hash、fair、url_hash四种负载均衡调度算法。（3）动静分离分离静态请求和动态请求，将动态请求发送给Web服务器(TomCat)，并给静态请求做缓存（或CDN加速）。Nginx优点高并发连接： 官方测试Nginx能够支撑5万并发连接，实际生产环境中可以支撑2~4万并发连接数。Nginx为开源软件，成本低廉。稳定性高：用于反向代理时，基本无宕机，非常稳定。支持热部署/热维护：能够在不间断服务的情况下，进行Nignx扩增和维护。5.用多种方法实现“多线程（三线程）交替打印123123”方法一：package DT.thread_test; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @author 唐宋丶 * @create 2020/9/16 * * 建立三个线程，第一个线程打印1、第二个线程打印2、第三个线程打印3；要求三个线程交替打印，123123123123…… * * 通过锁方式一： lock.lock()/unlock(): await() signal() */ public class LoopPrint { private static int num = 1; /** * 一把读写锁,锁定变量的更改 */ Lock lock = new ReentrantLock(); /** * 三个线程通知 线程打印 123123123... * 线程1判断num是否为1，不是等待，是打印并通知线程2 * 线程2判断num是否为2，不是等待，是打印并通知线程3 * 线程3判断num是否为3，不是等待，是打印并通知线程1 */ Condition condition1 = lock.newCondition(); Condition condition2 = lock.newCondition(); Condition condition3 = lock.newCondition(); public static void main(String[] args) { LoopPrint loopPrint = new LoopPrint(); //打印1的线程 new Thread(() -> { while (true){ loopPrint.print1(); } }).start(); new Thread(() -> { while (true){ loopPrint.print2(); } }).start(); new Thread(() -> { while (true){ loopPrint.print3(); } }).start(); } public void print1(){ lock.lock(); try{ if(num != 1){ condition1.await(); } System.out.println(1); Thread.sleep(1000); num = 2; condition2.signal(); }catch (Exception e){ System.out.println(e); }finally { lock.unlock(); } } public void print2(){ lock.lock(); try{ if(num != 2){ condition2.await(); } System.out.println(2); Thread.sleep(1000); num = 3; condition3.signal(); }catch (Exception e){ System.out.println(e); }finally { lock.unlock(); } } public void print3(){ lock.lock(); try{ if(num != 3){ condition3.await(); } System.out.println(3); Thread.sleep(1000); num = 1; condition1.signal(); }catch (Exception e){ System.out.println(e); }finally { lock.unlock(); } } } 方法二：package DT.thread_test; /** * @author 唐宋丶 * @create 2020/9/16 * * * 建立三个线程，第一个线程打印1、第二个线程打印2、第三个线程打印3；要求三个线程交替打印，123123123123…… * * * * 通过锁方式二： Synchronized wait()、notify()/notifyAll() */ public class LoopPrint2 { private static int num = 1; Thread t1 = new Thread(() ->{ while (true){ print1(); } }); Thread t2 = new Thread(() ->{ while (true){ print2(); } }); Thread t3 = new Thread(() ->{ while (true){ print3(); } }); public static void main(String[] args) throws InterruptedException { LoopPrint2 loopPrint2 = new LoopPrint2(); loopPrint2.t1.start(); loopPrint2.t2.start(); loopPrint2.t3.start(); } public void print1(){ //保证当前线程是在t3执行完施放锁以后才执行的 synchronized (t3){ synchronized (this){ if(num == 1){ System.out.println(1); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } num = 2; this.notifyAll(); } } t3.notifyAll(); } } public void print2(){ synchronized (t1){ synchronized (this){ if(num == 2){ System.out.println(2); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } num = 3; this.notifyAll(); } } t1.notifyAll(); } } public void print3(){ synchronized (t2){ synchronized (this){ if(num == 3){ System.out.println(3); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } num = 1; this.notifyAll(); } } t2.notifyAll(); } } }方法三：package DT.thread_test; import java.util.concurrent.Semaphore; /** * @author 唐宋丶 * @create 2020/9/16 * * 建立三个线程，第一个线程打印1、第二个线程打印2、第三个线程打印3；要求三个线程交替打印，123123123123…… * * 通过信号量争夺方式实现交替打印 */ public class SemaphorePrint { /** * 三个信号量争夺一个许可证 */ Semaphore sem1 = new Semaphore(1); Semaphore sem2 = new Semaphore(0); Semaphore sem3 = new Semaphore(0); public static void main(String[] args) { SemaphorePrint semaphorePrint = new SemaphorePrint(); new Thread(() -> semaphorePrint.print1()).start(); new Thread(() -> semaphorePrint.print2()).start(); new Thread(() -> semaphorePrint.print3()).start(); } public void print(String value,Semaphore current, Semaphore next){ while (true){ try { //当前信号量获取许可证 current.acquire(); System.out.println(Thread.currentThread().getName() + ": " + value); Thread.sleep(1000); /** * 在实现中不包含真正的许可对象，并且Semaphore也不会将许可与线程关联起来， * 因此在一个线程中获得的许可可以在另一个线程中释放。可以将acquire操作视为是消费一个许可，而release操作是创建一个许可，Semaphore并不受限于它在创建时的初始许可数量。 * * print1中，next.release释放一个许可，此时已经选中print2的线程，所以下一个获取到许可的信号量必定是线程2，以此类推 */ next.release(); } catch (InterruptedException e) { e.printStackTrace(); } } } public void print1(){ print("1",sem1,sem2); } public void print2(){ print("2",sem2,sem3); } public void print3(){ print("3",sem3,sem1); } }6.一个类的声明能否 既是abstract，又是final？不能。abstract修饰的类为抽象类，不能被实例化，只能通过子类extend继承它；而final修饰的类不能被继承。所以两者在能否被“继承”方面是矛盾语义，不能同时使用。7.如何给final修饰成员变量的初始化赋值？final：用于声明属性，方法和类，分别表示属性不可变（对象属性的引用类型不可变），方法不可覆盖，类不可继承。内部类要访问局部变量，局部变量必须定义成final类型。final修饰的属性没有默认值。此题分为两种情况：情况一、没有static：可以直接通过 =赋值；也可以通过构造方法给final赋值，但要注意对于每个构造方法都必须给final变量赋值。public class FinalDemo01 { //final修饰的变量，没有默认值 //final int num = 10; final int num; public FinalDemo(){ this.num = 10 ; } public FinalDemo(int num ){ //只要存在final修饰的属性的构造方法都必须给final属性赋值 this.num = num ; } //构造方法的作用？ 实例化对象new（任意一个构造方法都可以实现） public static void main(String[] args) { FinalDemo demo = new FinalDemo(); System.out.println(demo.num); } }情况二、有static：只能通过 =初始化值，而不能用构造方法赋值。因为static变量是在构造方法之前执行的。补充：（1）static修饰变量的执行时机：类加载过程中执行，并且会被初始化为默认值，若有赋初值则在后续被赋予。class A { //a -> 0 ->10 static int a = 10; }（2）final修饰的变量执行时机：运行时被初始化（直接赋值，也可以通过构造方法赋值）（3）static final修饰的变量执行时机：在javac时（编译）生成ConstantValue属性，在类加载的过程中根据ConstantValue属性值为该字段赋值。ConstantValue属性值没有默认值，必须显示的通过=赋值。8.为什么对于一个public及final修饰的变量，一般建议声明为static？如果没有static修饰：假设在10个类中都要使用类A中的num变量，就必须在这10个类中先实例化A的对象，然后通过 对象.num来使用num变量，因此至少需要new 10次。加上static可以节约内存。static是共享变量，因此只会在内存中拥有该变量的一份存储，之后就可以直接通过 类名.变量使用（例如 A.num），而不用先new后使用。由于final修饰的变量不可改变，因此不用考虑并发问题。9.InterfaceA是接口，InterfaceA []a = new InterfaceA[2];是否正确？正确。在对象数组a[]中实际存放的不是对象本身，而是对象的引用地址。此题a[]中为 null。class InterfaceA { ... } class ClassA implements InterfaceA { } Public class Test { public static void main(String[] args){ InterfaceA []a = new InterfaceA[2]; //new生成的对象在堆内存中，a[]中存放的是对象引用的地址 a[0] = new ClassA(); a[1] = new ClassA(); } }9.==和equals()的区别？(在面试大全中已经讲解过，这边简单的再过一遍)==为比较运算符，比较的是基本数据类型的值和对象的引用地址（内存地址）equlas()最初是在Object中定义的一个方法。在Object中定义的 equals()就是 ==。只不过一般来说其子类都会重写 equlas()方法，将其重写为比较“内容”是否相等。如：String。10.举例说明如何重写equals()方法假定Person对象的name和age相同，则返回true；否则返回false。public class Person { private String name; private int age ; public Person() { } public Person(String name,int age) { this.age = age; this.name = name; } ... //约定：如果name和age相同，则返回true @Override public boolean equals(Object obj) { if (this == obj) { return true; } //name age if(obj instanceof Person){ Person per = (Person)obj ; //用传入的per，和当前对象this比较 if(this.name.equals(per.getName()) && this.age==per.getAge()){ return true ; } } return false; } //约定：如果重写equals()则要重写hashCode()，防止map等方法导致结果与约定的equals结论不一致 @Override public int hashCode() { return this.name.hashCode() & age; } }11.重写euqals()为什么要重写hashCode()方法用上题例子来说明：public class EqualsDemo { public static void main(String[] args){ Person p1 = new PerSon('zs',23); Person p2 = new PerSon('zs',23); //Map中的key根据对象的hashcode存储 Map<Person,String> map = new HashMap<>(); map.put(p1,'AAA'); map.put(P2,'BBB'); //AAA 根据上题的equals两个p1p2相等，因该为同一个对象。所以两个人的hashcode应该也是一样的，实际上map中应该只能存放一个p(p1=p2)，最终值为BBB覆盖AAA System.out.println(map.get(p1)); //BBB System.out.println(map.get(p2)); } }总结：两个对象相等（引用地址），则hashcode一定相同如果两个对象的hashcod相同，这俩对象不一定相同。两个对象不等（引用地址），hashcode不一定不等如果两个对象的hashcod不同，这俩对象一定不同。判断元素内容是否相等（Map/Set等）：（1）根据hashCode快速定位（提高效率，避免了在大量集合中由于遍历带来的效率问题）（2）若hashCode相等，根据equals判断内容是否相同 （判断正确性，避免偶然）所以，如果要判断元素内容是否相同，就要重写hashCode()和equals()。12.TCP三次握手和四次挥手原因：任何数据传输都无法保证绝对的可靠性初始化状态：客户端处于close关闭状态，服务器处于Listen监听状态。三次握手：SYN和ACK建立连接（双向确认），完成三次握手,客户端与服务器开始传送数据。过程：（1）第一次握手：客户端发送请求报文将 SYN=1 同步序列号和初始化 seq=x发送给服务端，服务端从初始化状态，创建连接，等待客户端，确认接收后的状态为SYN_Receive。这个时候客户端处于等待状态为SYN_Send。（2）第二次握手，服务器接收到报文后（SYN=1，seq=x）收到请求后请求报文变为同步序列号SYN=1，初始化序列号seq=1，确认号ACK=1，ack=x+1，服务器为SYN_Receive状态，发送端的状态为：SYN_Send。（3）第三次握手，客户端收到服务端的数据包（收到响应后），然后发送同步序列号ack=y+1和数据包的序列号seq=x+1和ACK=1确认包作为应答（第三次握手：ACK=1,seq=x+1,ack=y+1），客户端和服务端变化为established状态。四次挥手：数据传送完毕，完成四次挥手关闭连接（1）第一次挥手：客户端设置seq和 ACK ,向服务器发送一个 FIN=1报文段。此时，（第一次挥手，FIN=1，seq=u）客户端进入 FIN_WAIT 状态，表示客户端没有数据要发送给服务端了。（2）第二次挥手：服务端收到了客户端发送的 FIN 报文段，向客户端回了一个 ACK 报文段。（3）第三次挥手：服务端向客户端发送FIN 报文段，请求关闭连接，同时服务端进入 LAST_ACK 状态。（4）第四次挥手：客户端收到服务端发送的 FIN 报文段后，向服务端发送 ACK 报文段,然后客户端进入 TIME_WAIT 状态。服务端收到客户端的 ACK 报文段以后，就关闭连接。此时，客户端等待 2MSL（指一个片段在网络中最大的存活时间）后依然没有收到回复，则说明服务端已经正常关闭，这样客户端就可以关闭连接了。13.HashSet和HashMap之间的关系HashMap：线程不安全，可通过j.u.c(java.util.concurrent)包中的ConcurrentHashMap解决（线程安全，适用于高并发）。JDK1.8之前HashMap由数组+链表组成的，数组是HashMap的主体（默认初始容量为16），数组中的每个元素是链表的形式。JDK1.8以后，当链表长度大于阈值（默认为8）时，将链表转化为红黑树，以减少搜索时间。HashSet（唯一，无序）: 线程不安全，基于HashMap实现的，底层采用 HashMap 来保存元素，源码如下：public HashSet() { map = new HashMap<>(); }hashmap.put(key,value); 对于hashmap，在增加元素时，如果key已经存在，则将该key对应的value值覆盖掉，返回值是：在本次增加元素之前，key对应的value值(首次返回为null)。hashset.add(key,常量值);所有hashset中增加的Value都是一个常量值 PRESENT（new Object()） 对于hashset，在增加元素时，如果集合中不存在，则返回true；如果已经存在则返回false。14.包装类基本类型包装类intIntegerbyteByteshortShortlongLongfloatFloatdoubleDoublebooleanBooleancharCharacterInterger i = 10;//自动装箱int j = i;//自动拆箱Integer + int/Integer == int -> int; int优先高，自动拆箱public class Wapper { public static void main(String[] args) { //自动装箱，int转Integer Integer f1 = 100, f2 = 100, f3 = 200, f4 = 200; //实际上，包装类型间的相等判断应该用equals，而不是'==' System.out.println(f1 == f2);//true System.out.println(f3 == f4);//false } }以下是Integer类中“自动装箱”的源码：public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }其中IntegerCache.low的是值是-128，IntegerCache.high的值是127。也就是说，Integer在自动装箱时，如果判断整数值的范围在[-128,127]之间，则直接使用整型常量池中的值；如果不在此范围，则会new 一个新的Integer()。因此，本题f1和f2都在[-128,127]范围内，使用的是常量池中同一个值。而f3和f4不在[-128,127]范围内，二者的值都是new出来的，因此f3和f4不是同一个对象。15.泛型A<x> z = new a<x>();//根据泛型约束，两个x类型必须一致，不能是父子关系。 List<Object> objs = new ArrayList<> ();//对，根据jdk新特性可以自动推测出后面应该为Object 相当于： List<Object> objs = new ArrayList<Object> (); //对 List<Object> objs = new ArrayList<String> ();//错 List<?> objs = new ArrayList<String> ();//对 相当于： List<? extends Object> objs = new ArrayList<String> ();//对 List<? extends String> objs = new ArrayList<String> ();//对,在考虑泛型时，不用考虑 final16.异常分类以下程序，能否成功运行？public class ExceptionDemo01 { void test() throws NullPointerException{ System.out.println("A..."); } public static void main(String[] args) { new ExceptionDemo01().test(); } }异常分为 运行异常和检查异常运行异常：RuntimeException，运行时才会发现的异常（编译时不会进行检查）。检查异常：CheckedException（泛指除了RuntimeException以外的其他所有异常）：编译时会进行检查。如果是CheckedException,在编写代码时，必须try..catch或者throws二选一。但是RuntimeException在编写时不会进行异常检查，因此本题目中抛出的NullPointerException在编译阶段不会被检查。而在运行时，本题中并不会造成NullPointerException，因此无需任何处理，也不会报错。简言之，本题目在编译阶段不会进行异常检查（本题是RuntimeException），而在运行时又不会发生异常，因此是正确的，会正常运行。17.默认值整数的默认类型是int，小数的默认类型是double。但是在赋值时（在数值范围内），=可以自动转为相应的整数类型（byte/short/int/long）；但却不会转为小数类型，如下所示。byte num = 10 ;//正确,10会自动转为byte类型 float f = 3.4; //错误,3.14是double类型，不会自动转为float float f = (float) 3.4;//正确3.4是双精度数，将双精度型（double）赋值给浮点型（float）属于向下转型（down-casting，也称为窄化）会造成精度损失，因此需要强制类型转换float f =(float)3.4; 或者写成float f =3.4F;。18.volatile可以禁止指令的重排序功能。那么synchronized有这个功能吗？重排序所实现的优化不会影响单线程程序执行结果1. int a = 100 ; 2. int b ; 3. b = 200 ; 4. int c = a * b ;根据重排序，以上代码的实际执行顺序可以是1、2、3、4，也可以是2、3、1、4，还可以是2、1、3、4等，因为这几种可能的最终执行结果都是相同的。（实际上第4句还可以再拆）而synchronized的作用是加锁，可以保证串行执行，即可以让并发环境转为单线程环境。因此加了synchronized就已经是单线程环境了。既然是单线程，那么无论是否进行了重排序，最终的结果都不会有影响，即都可以保证线程安全。所以说，在使用synchronized时根本不用关心“重排序”这个问题，无论它支持或不支持，都已经不重要了。19.“如果一个对象存在着指向它的引用，那么这个对象就不会被GC回收”，这句话对吗？不对。JVM中存在着四种类型的引用：强引用、软引用、弱引用和虚引用。这句话只适用于“强引用”，Object ref = new Object()中的ref就是一个强引用。但除此以外，还有以下三个：软引用：当JVM的内存足够时，GC不会主动回收软引用对象；但当JVM的内存不足时，GC就会去主动回收软引用对象。弱引用：当GC进行垃圾回收时，无论是否当时JVM的内存是否充足，都会去主动回收弱引用对象。虚引用：是否使用虚引用对于一个对象本身来说都没有任何区别。虚引用的价值在于和引用队列一起使用。综上，软引用、弱引用和虚引用都是这句话的反例。20.JVM类加载问题 （待研究）package com.yanqun.pojo; class MyClass{ static int num1 = 100 ; static MyClass myClass = new MyClass(); public MyClass(){ num1 = 200 ; num2 = 200 ; } static int num2 = 100 ; public static MyClass getMyClass(){ return myClass ; } @Override public String toString() { return this.num1 + "\t" + this.num2 ; } } public class MyClassLoader { public static void main(String[] args) { MyClass myc = MyClass.getMyClass() ; System.out.println(myc); } }运行结果：200 100解析：JVM使用“类”的生命周期是：类的加载->连接->初始化->使用->卸载各阶段主要完成的工作如下：1.类的加载：（1）寻找并加载类的二进制数据（class文件）（2）将硬盘上的class文件 加载到jvm内存中2.连接该阶段又包含了验证、准备和解析3个过程，如下。（1）验证校验.class文件的正确性（2）准备给static静态变量分配内存，并初始化static的默认值。因此，本题在此阶段各变量的值如下：static int num1 = 0 ; static MyClass myClass = null ; static int num2 = 0 ;（3）解析:把类中符号引用，转为直接引用举个例子，在加载阶段，JVM还不知道类的具体内存地址，只能使用“com.yanqun.pojo.MyClass ”来替代MyClass类，“com.yanqun.pojo.MyClass ”就称为符号引用；但在解析阶段，JVM就可以将 “com.yanqun.pojo.MyClass ”映射成实际的内存地址，因此就可以用 内存地址来代替MyClass，这种使用 内存地址来使用 类的方法 称为直接引用。3.初始化：给static变量 赋予实际的值因此，本题在此阶段各变量的值如下：static int num1 = 100 ; static MyClass myClass = new MyClass();此句调用了构造方法，构造方法会进行如下赋值： public MyClass(){ num1 = 200 ; num2 = 200 ; } static int num2 = 100 ;根据程序 自上而下执行的特点，num1最终的值是200，num2最终的值是100。（4）使用：对象的初始化、对象的垃圾回收、对象的销毁（5）卸载