云从面试 java开发方向 两轮技术面+1轮HR面

本人8月8号重庆现场面试,第一轮问些项目问题,java基础、虚拟机、并发、mysql、redis、计算机网络的相关知识,一部分没有回答上来。
第二轮技术面主要就项目随机询问技术问题,没有手撕代码,基本上都答上来了,面试官感觉我答得不错,当场给过了。
8月14日HR面,问了些项目、实验室相关的,自己大***团经历,大学最遗憾的事情,期望薪酬、地点等等,HR给人态度很好,感觉自己也答得不错,要等后续消息。
最后贡献第一轮面试的面经,都是本人没答上来的😓😓😓😓😓

1http报文传输?

l HHTTP1.0HTTP1.1的区别:

HTTP1.0的一个事务可以分为四个过程:建立连接、浏览器发出请求消息、服务器发出相应消息、关闭连接。每次连接只处理一个请求和响应。对每个文件的访问,浏览器与服务器都要建立一次单独的连接。

HTTP1.1在一个TCP连接上可以传输多个HTTP请求和响应,多个请求和响应可以重叠,增加了更多的请求头和响应头,如:HostIf-Unmodified-Since请求头

每个水表相关信息6个字节,电表信息4个字节

数据大可以用json传递

短连接:连接、传输数据、关闭连接

短连接指SOCKET连接后发送接收完数据马上断开连接。

长连接:连接、传输数据、保持连接、传输数据、、、、、关闭连接

长连接指建立SOCKET连接后不管是否使用都保持连接,但安全性较差。

之所以出现粘包和半包现象,是因为TCP当中,只有流的概念,没有包的概念。

半包:接收方没有接收到一个安装的包,只接受了部分,这种情况主要是由于TCP为提高传输效率,将一个包分配的足够大,导致接收方不能一次接收完(在长连接和短连接中都会出现)

粘包:指发送方发送的若干数据到接收方接收时粘成一包,从接收缓存区看,后一包数据的头紧接着前一包数据的尾。

发送方引起粘包的原因:TCP协议本身造成,TCP为提高传输效率,发送方往往要收集足够多的数据后才发送一包数据。

接收方引起粘包的原因:由于接收方用户进程不及时接收数据,从而导致粘包现象。

分包:出现粘包时接收方要进行分包处理(在长链接中出现)。

Socket内部默认收发缓存区大小是8kb,实际中往往需要考虑效率问题,重新配置了这个值。

问题描述:在并发量比较大的情况下,就会出现一次接受并不能完整获取所有数据?

解决方法:1、通过包头、包长、包体的协议形式,当服务器端获取到指定包长时才说明获取完整。2、获取到指定包结束标识时,说明包获取完整。

什么时候需要考虑粘包的情况?

1、 当短连接时,不需要考虑粘包情况。

2、 如果发送数据无结构,如文件传输,这样发送方只管发送,接收方只管接受存储就好,不用考虑粘包。

3、 如果双方建立连接,需要在连接受一段时间内发送不同数据结构时,接收方创建一处理线程,对接收到的数据包进行预处理,将粘包分开。

注:粘包情况分两种:一种是粘在一起的包是完整的数据包,另一种情况是粘在一起的包有不完整的包。

备注:
一个包没有固定长度,以太网限制在461500字节,1500就是以太网的MTU,超过这个量,TCP会为IP数据报设置偏移量进行分片传输,现在一般可允许应用层设置8kNTFS系)的缓冲区,8k的数据由底层分片,而应用看来只是一次发送。windows的缓冲区经验值是4k,Socket本身分为两种,流(TCP)和数据(UDP),你的问题针对这两种不同使用而结论不一样。甚至还和你是用阻塞、还是非阻塞Socket来编程有关。
1
、通信长度,这个是你自己决定的,没有系统强迫你要发多大的包,实际应该根据需求和网络状况来决定。对于TCP,这个长度可以大点,但要知道,Socket内部默认的收发缓冲区大小大概是8K,你可以用SetSockOpt来改变。但对于UDP,就不要太大,一般在102410K。注意一点,你无论发多大的包,IP层和链路层都会把你的包进行分片发送,一般局域网就是1500左右,广域网就只有几十字节。分片后的包将经过不同的路由到达接收方,对于UDP而言,要是其中一个分片丢失,那么接收方的IP层将把整个发送包丢弃,这就形成丢包。显然,要是一个UDP发包佷大,它被分片后,链路层丢失分片的几率就佷大,你这个UDP包,就佷容易丢失,但是太小又影响效率。最好可以配置这个值,以根据不同的环境来调整到最佳状态。

send()函数返回了实际发送的长度,在网络不断的情况下,它绝不会返回(发送失败的)错误,最多就是返回0。对于TCP你可以字节写一个循环发送。当send函数返回SOCKET_ERROR时,才标志着有错误。但对于UDP,你不要写循环发送,否则将给你的接收带来极大的麻烦。所以UDP需要用SetSockOpt来改变Socket内部Buffer的大小,以能容纳你的发包。明确一点,TCP作为流,发包是不会整包到达的,而是源源不断的到,那接收方就必须组包。而UDP作为消息或数据报,它一定是整包到达接收方。

2、关于接收,一般的发包都有包边界,首要的就是你这个包的长度要让接收方知道,于是就有个包头信息,对于TCP,接收方先收这个包头信息,然后再收包数据。一次收齐整个包也可以,可要对结果是否收齐进行验证。这也就完成了组包过程。UDP,那你只能整包接收了。要是你提供的接收Buffer过小,TCP将返回实际接收的长度,余下的还可以收,而UDP不同的是,余下的数据被丢弃并返回WSAEMSGSIZE错误。注意TCP,要是你提供的Buffer佷大,那么可能收到的就是多个发包,你必须分离它们,还有就是当Buffer太小,而一次收不完Socket内部的数据,那么Socket接收事件(OnReceive),可能不会再触发,使用事件方式进行接收时,密切注意这点。这些特性就是体现了流和数据包的区别。


2、自动装箱拆箱

1 //自动装箱

2 Integer total = 99;

3

4 //自定拆箱

5 int totalprim = total;

装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型



private static final Integer[] SMALL_VALUES = new Integer[256];

SMALL_VALUES是一个静态的Integer数组对象,也就是说最终valueOf返回的都是一个Integer对象。

1、i >= 128 || i < -128 =====> new Integer(i)
2、i < 128 && i >= -128 =====> SMALL_VALUES[i 128]

SMALL_VALUES本来已经被创建好,也就是说在i >= 128 || i < -128是会创建不同的对象,在i < 128 && i >= -128会根据i的值返回已经创建好的指定的对象。

public class Main {

public static void main(String[] args) {


Integer i1 = 100;

Integer i2 = 100;

Integer i3 = 200;

Integer i4 = 200;


System.out.println(i1==i2);  //true

System.out.println(i3==i4);  //false

}

}

1、i1和i2会进行自动装箱,执行了valueOf函数,它们的值在(-128,128]这个范围内,它们会拿到SMALL_VALUES数组里面的同一个对象SMALL_VALUES[228],它们引用到了同一个Integer对象,所以它们肯定是相等的。

2、i3和i4也会进行自动装箱,执行了valueOf函数,它们的值大于128,所以会执行new Integer(200),也就是说它们会分别创建两个不同的对象,所以它们肯定不等。

1 Integer num1 = 100;

2 int num2 = 100;

3 Long num3 = 200l;

4 System.out.println(num1 num2);  //200

5 System.out.println(num3 == (num1 num2));  //true

6 System.out.println(num3.equals(num1 num2));  //false

1、当一个基础数据类型与封装类进行==、 、-、*、/运算时,会将封装类进行拆箱,对基础数据类型进行运算。
2、对于num3.equals(num1 num2)为false的原因很简单,我们还是根据代码实现来说明:

1 @Override

2 public boolean equals(Object o) {

3     return (o instanceof Long) && (((Long) o).value == value);

4 }

它必须满足两个条件才为true:
1、类型相同
2、内容相同
上面返回false的原因就是类型不同。

1、  需要知道什么时候会引发装箱和拆箱

当一个基础数据赋值给封装类,会对基础数据类型装箱

当一个基础数据类型与封装类进行==、 、-、*、/运算时,会将封装类进行拆箱
2、装箱操作会创建对象,频繁的装箱操作会消耗许多内存,影响性能,所以可以避免装箱的时候应该尽量避免。

3、equals(Object o) 因为原equals方法中的参数类型是封装类型,所传入的参数类型(a)是原始数据类型,所以会自动对其装箱,反之,会对其进行拆箱

4、当两种不同类型用==比较时,包装器类的需要拆箱, 当同种类型用==比较时,会自动拆箱或者装箱

3、三次握手四次挥手停顿? 没查到

4、说说数据结构堆和栈

堆是一种经过排序的树形数据结构,每个节点都有一个值,所以堆在数据结构中通常可以看成一棵树的数组对象。

1、 堆中某个节点的值总是不大于或不小于其父节点的值。

2、 堆总是一颗完全二叉树。

堆分两种情况,将根节点最大的堆叫做最大堆/大根堆,根节点最小的堆叫做最小堆/小根堆。

栈是限定仅在表尾进行插入和删除操作的线性表。我们把允许插入和删除的一端称为栈顶,另一端称为栈底,不含任何数据元素的栈称为空栈。栈的特殊之处在于它限制了这个线性表的插入和删除位置,它始终只在栈顶进行。而且栈是一种具有后进先出的数据结构,又称为后进先出的线性表,简称 LIFOLast In First Out)结构。堆栈中定义了一些操作。两个最重要的是PUSHPOPPUSH操作在堆栈的顶部加入一个元素。POP操作相反,在堆栈顶部移去一个元素,并将堆栈的大小减一。

队列是只允许在一端进行插入操作、而在另一端进行删除操作的线性表。允许插入的一端称为队尾,允许删除的一端称为队头。它是一种特殊的线性表,特殊之处在于它只允许在表的前端进行删除操作,而在表的后端进行插入操作,和栈一样,队列是一种操作受限制的线性表。而且队列是一种先进先出的数据结构,又称为先进先出的线性表,简称 FIFOFirst In First Out)结构。解决假溢出的办法就是后面满了,就再从头开始,也就是头尾相接的循环。我们把队列的这种头尾相接的顺序存储结构称为循环队列。

内存分配中的堆区和栈区
栈是系统自动分配空间的,而堆则是程序员根据需要自己申请的空间。由于栈上的空间是自动分配自动回收的,所以栈上的数据的生存周期只是在函数的运行过程中,运行后就释放掉,不可以再访问。而堆上的数据只要程序员不释放空间,就一直可以访问到,不过缺点是一旦忘记释放会造成内存泄露。
申请效率的比较:栈由系统自动分配,速度较快。但程序员是无法控制的。堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。

申请大小限制

Windows下,栈是向低位扩展的数据结构,是一块连续的被训区域,大小为2M,如果申请空间超过栈的剩余空间,将提示overflow,栈所获得的空间较小
堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统用链表来存储空闲内存地址的,自然不连续,而链表的遍历方向是由低地址向高地址。对大小受限于计算机系统中的虚拟内存,所以对获得的空间比较灵活,也比较大。

5hash算法?

哈希表就是 依据关键字可以根据一定的算法(哈希函数)映射到表中的特定位置的思想建立的表。因此哈希表最大的特点就是可以根据f(K)函数得到其在数组中的索引。

EqualHashCode的三条要求:

1.  在对象没有被修改的前提下,执行多次调用,该hashCode方法必须始终返回相同的整数。

2.  如果两个对象执行equals方法结果为true,则分别调用hashCode方法产生的整数结果是相等的。

3.  非必要要求:两个对象执行equals方法结果为false,则分别调用hashCode方法产生的整数结果是不相等的。

哈希碰撞

在计算hash地址的过程中会出现对于不同的关键字出现相同的哈希地址的情况,即key1 key2,但是f(key1) = f(key2),这种情况就是Hash 冲突。具有相同关键字的key1key2称之为同义词。
通过优化哈希函数可以减少这种冲突的情况(如:均衡哈希函数),但是在通用条件下,考虑到于表格的长度有限及关键值(数据)的无限,这种冲突是不可避免的,所以就需要处理冲突。

冲突处理的四种方式

l  开放地址:出现冲突后按照一定算法查找一个空位置存放

l  再哈希法:出现冲突后采用其他哈希函数计算,直到不再冲突为止

l  链地址法:链接地址法不同于前两种方法,它是在出现冲突的地方存储一个链表,所有同义词记录存储在其中。

l  建立公共溢出区:设HashTable为基本表,OverTable为溢出表,所有关键字和基本表中关键字为同义词的记录,不管它们由哈希函数得到的哈希地址是什么,一旦发生冲突,都填入溢出表。

6java中常用的队列

l ArrayBlockingQueue: ArrayBlockingQueue是一个数组队列,由代码看其维护了一个Object[] items数组,然后同步保证安全

FIFO原则和同步安全访问

非公平锁,基于ReentrantLock和两个Condition的阻塞和唤醒实现同步;notEmpty在队列中没有元素可获取时阻塞线程,notFull在满队列不可插入时阻塞线程。

l  LinkedBlockingQueue和ArrayBlockingQueue的区别

①LinkedBlockingQueue使用的是单向链表,而ArrayBlockingQueue使用的是数组,故LinkedBlockingQueue使用head节点和last节点维护FIFO原则。

②LinkedBlockingQueue分别使用了takeLockputLock两个锁进行新增和移除元素的操作,这也导致了元素计数器count属性需要声明为AtomicInteger进行原子操作。

③LinkedBlockingQueue默认可以不指定队列大小,使用Integer.MAX_VALUE默认初始化队列大小。

l  PriorityBlockingQueue带优先级的队列(基于完全二叉树的最小堆最大堆排序)

①、初始大小11,但是会自动扩容,最大可以到Integer.MAX_VALUE - 8

②、其队列元素必须实现Comparator接口,以便其基于完全二叉树的最小堆和最大堆排序;

③、PriorityBlockingQueue本身不支持序列化,数组前加了transient修饰,其序列化会转化成PriorityQueue,反序列化时再转换成PriorityBlockingQueue自己。


7、直接(堆外)内存的使用

1、堆外内存定义

内存对象分配在Java虚拟机的堆以外的内存,这些内存直接受操作系统管理(而不是虚拟机),这样做的结果就是能够在一定程度上减少垃圾回收对应用程序造成的影响。使用未公开的UnsafeNIO包下ByteBuffer来创建堆外内存。

2、为什么使用堆外内存

1、减少了垃圾回收

使用堆外内存的话,堆外内存是直接受操作系统管理( 而不是虚拟机 )。这样做的结果就是能保持一个较小的堆内内存,以减少垃圾收集对应用的影响。

2、提升复制速度(io效率)

堆内内存由JVM管理,属于用户态;而堆外内存由OS管理,属于内核态。如果从堆内向磁盘写数据时,数据会被先复制到堆外内存,即内核缓冲区,然后再由OS写入磁盘,使用堆外内存避免了这个操作。

3、堆外内存申请

JDK的ByteBuffer类提供了一个接口allocateDirect(int capacity)进行堆外内存的申请,底层通过unsafe.allocateMemory(size)实现。Netty、Mina等框架提供的接口也是基于ByteBuffer封装的。

注:unsafe.allocateMemory(size)最底层是通过malloc方法申请的,但是这块内存需要进行手动释放,JVM并不会进行回收,幸好Unsafe提供了另一个接口freeMemory可以对申请的堆外内存进行释放。

在Cleaner 内部中通过一个列表,维护了针对每一个 directBuffer 的一个回收堆外内存的线程对象(Runnable),回收操作是发生在 Cleaner 的 clean() 方法中

4、堆外内存释放

其中firstCleaner类的静态变量,Cleaner对象在初始化时会被添加到Clener链表中,和first形成引用关系,ReferenceQueue是用来保存需要回收的Cleaner对象。如果该DirectByteBuffer对象在一次GC中被回收了此时,只有Cleaner对象唯一保存了堆外内存的数据(开始地址、大小和容量),在下一次FGC时,把该Cleaner对象放入到ReferenceQueue中,并触发clean方法。

此时,只有Cleaner对象唯一保存了堆外内存的数据(开始地址、大小和容量),在下一次FGC时,把该Cleaner对象放入到ReferenceQueue中,并触发clean方法。

Cleaner对象的clean方法主要有两个作用:
1、把自身从Clener链表删除,从而在下次GC时能够被回收
2、释放堆外内存




更新,已收到意向书,南京岗,谢谢大家
😁😁😁😁😁#云从科技##Java工程师##面经##校招#
全部评论
好难好难
点赞 回复
分享
发布于 2019-08-16 21:08
薪资你要了多少哇
点赞 回复
分享
发布于 2019-08-16 21:14
阅文集团
校招火热招聘中
官网直投
大佬大佬,干货~
点赞 回复
分享
发布于 2019-08-16 22:47
hr面问了什么内容呢?😀
点赞 回复
分享
发布于 2019-10-30 15:49
大佬,你薪资要的多少呀
点赞 回复
分享
发布于 2019-11-19 11:10
how much
点赞 回复
分享
发布于 2019-11-25 17:32

相关推荐

8 125 评论
分享
牛客网
牛客企业服务