这段时间面了几家公司,也跟不同的面试官切磋了一些面试题,有的没啥难度,有的则是问到了我的知识盲区,没办法,Android能问的东西太多了,要全覆盖到太难了,既然没法全覆盖,那么只好亡羊补牢,将这些没答上来的题目做下记录,让自己如果下次遇到了可以答上来
TCP与UDP有哪些差异
这道题回答的不全,仅仅只是将两个协议的概念说了一下,但是真正的差异却没有真正答上来,后来查询了一下资料,两者的差异如下
- TCP是传输控制协议,是面向连接的协议,发送数据前需要建立连接,TCP传输的数据不会丢失,不会重复,会按照顺序到达
- 与TCP相对的,UDP是无连接的协议,发送数据前不需要建立连接,数据没有可靠性
- TCP的通信类似于打电话,需要确认身份后才可以通话,而UDP更像是广播,不关心对方是不是接收,只需要播报出去即可
- TCP支持点对点通信,而UDP支持一对一,一对多,多对一,多对多
- TCP传输的是字节流,而UDP传输的是报文
- TCP首部开销为20个字节,而UDP首部开销是8个字节
- UDP主机不需要维持复杂的连接状态表
TCP的三次握手
这道题以及下面那道虽然说上来了,但是也没有说的很对,仅仅只是说了下每次握手或者挥手的目的,中间的过程没有说出来,以下是三次握手以及四次挥手的详细过程
- 第一次握手:客户端将SYN置为1,随机生成一个初始序列号seq发送给服务端,客户端进入SYN_SENT状态
- 第二次握手:服务端收到客户端的SYN=1之后,知道客户端请求建立连接,将自己的SYN置1,ACK置1,产生一个ack=seq+1,并随机产生一个自己的初始序列号,发送给客户端,服务端进入SYN_RCVD状态
- 第三次握手:客户端检查ack是否为序列号+1,ACK是否为1,检查正确之后将自己的ACK置为1,产生一个ack=服务器的seq+1,发送给服务器;进入ESTABLISHED状态;服务器检查ACK为1和ack为序列号+1之后,也进入ESTABLISHED状态;完成三次握手,连接建立
TCP的四次挥手
- 第一次挥手:客户端将FIN设置为1,发送一个序列号seq给服务端,客户端进入FIN_WAIT_1状态
- 第二次挥手:服务端收到FIN之后,发送一个ACK为1,ack为收到的序列号加一,服务端进入CLOSE_WAIT状态,这个时候客户端已经不会再向服务端发送数据了
- 第三次挥手:服务端将FIN置1,发送一个序列号给客户端,服务端进入LAST_ACK状态
- 第四次挥手:客户端收到服务器的FIN后,进入TIME_WAIT状态,接着将ACK置1,发送一个ack=序列号+1给服务器,服务器收到后,确认ack后,变为CLOSED状态,不再向客户端发送数据。客户端等待2* MSL(报文段最长寿命)时间后,也进入CLOSED状态。完成四次挥手
从浏览器输入地址到最终显示页面的整个过程
这个真的知识盲区了,谁会平时没事在用浏览器的时候去思考这个问题呢,结果一查居然还是某大厂的面试题,算了也了解下吧
- 第一步,浏览器查询DNS,获取域名对应的ip地址
- 第二步,获取ip地址后,浏览器向服务器建立连接请求,发起三次握手请求
- 第三步,连接建立好之后,浏览器向服务器发起http请求
- 第四步,服务器收到请求之后,根据路径的参数映射到特定的请求处理器进行处理,并将处理结果以及相应的视图返回给浏览器
- 第五步,浏览器解析并渲染视图,若遇到js,css以及图片等静态资源,则重复向服务器请求相应资源
- 第六步,浏览器根据请求到的数据,资源渲染页面,最终将完整的页面呈现在浏览器上
为什么Zygote进程使用socket通信而不是binder
应用层选手遇到偏底层问题就头疼了,但是这个问题还是要知道的,毕竟跟我们app的启动流程相关
- 原因一:从初始化时机上,
Binder
通信需要在Android运行时以及Binder
驱动已经初始化之后才能使用,而在这之前,Zygote
已经启动了,所以只能使用socket
通信 - 原因二:从出现的先后顺序上,
Zygote
相比于Binder
机制,更早的被设计以及投入使用,所以在Android的早期版本中,Android就已经使用socket
来监听其他进程的请求 - 原因三:从使用上,
socket
通信不依赖于Binder
机制,它是一种简单通用的IPC机制,也不需要复杂的接口定义 - 原因四:从兼容性上来讲,
socket
是一种跨平台的IPC机制,可以在不同的操作系统和环境中使用。 - 原因五:从性能上来讲,由于使用
Zygote
通信并不是频繁的操作,所以使用socket
通信不会对系统性能造成显著影响 - 原因六:从安全性上来讲,使用
socket
可以确保只有系统中特定的服务如system_server
才能与Zygote通信,从而提升一定的安全性
使用Binder的好处有哪些
上面那个问题问好了紧接着就是这道题,我嗯嗯啊啊的零碎说了几个,肯定也是不过关的,回头查了下资料,使用Binder
的优势如下
- 从效率上来讲,
Binder
比较高效,相比较于其他几种进程的通信方式(管道,消息队列,Socket,共享内存),Binder
只需要拷贝一次内存就好了,而除了共享内存,其余都都要拷贝两次内存,共享内存虽然不需要拷贝,但是实现方式复杂,所以综合考虑Binder
占优势 - 使用的是更加便于理解,更简单的面向对象的IPC通信方式
Binder
既支持同步调用,也支持异步调用Binder
使用UID和PID来验证请求的来源,这样可以确保每个Binder事务可以精确到发起者,为进程间的通信提供了保障Binder
是基于c/s架构,架构清晰明确,Server端与Client端相对独立Binder
有一套易于使用的API供进程间通信,将复杂的内部实现隐藏起来
如果一个线程连续调用两次start,会怎样?
会怎样?谁知道呀,正常人谁会没事去调用两次start
呢?但是这个还真有人问了,我只能说没遇到过,后来回去自己试了下才知道
如上述代码所示,有一个线程,然后连续调用了两次start
方法,当我们运行一下这段代码后,得到的结果如下
可以发现线程有正常运行,但同时也因为多调了一次start
而抛出了异常,这个异常在start
方法里面就能看到
有一个状态为started
,正常第一次启动线程时候,started
为false,所以是不会抛出异常的,started
为true的地方是在下面这个位置
调用了native方法nativeCreated
后,started
状态位才变成true,这个时候如果再去调用start
方法,那么必然会抛出异常
如何处理协程并发的数据安全
之前遇到过这么个问题,并发处理的协程之间是否可以保证数据安全,这个由于之前有实验过,所以想都没想就说可以保证数据安全,但面试官只是呵呵了一下,我捉摸着难道不对吗,后来回去试了一下才发现,不一定就能保证数据安全,看下面这段代码
这段代码里面在runBlocking
中创建了1000个协程,每一个协程都对变量count
做自增操作,最后把结果打印出来,我们预期的是打印出的结果就是1000,实际结果如下
看到的确就是1000,没啥毛病,多试几次也是一样的,但是如果换一种写法试试看呢
原本都是runBlocking
里面的子协程,现在将这些协程变成非runBlocking
的子协程,结果是不是还是1000呢,看下结果
明显不是了,所以并发处理的协程,并不能保证数据安全,那么如何可以让数据安全呢,有以下几个办法
原子类
这个好理解,同处理线程安全差不多
channel
receive
函数只有等到阻塞队列里面有数据的时候才会执行,没有数据的时候会一直等待,所以这就能保证这些协程可以并发执行,不过要注意的是这里的Channel
一定要设置队列大小,不然程序会一直阻塞,receive
一直在等待队列里面有数据
mutex
使用互斥锁的方式,withLock
函数内部执行了获取锁跟释放锁逻辑,将变量count
保护起来,实现数据安全,除此之外,还可以使用lock
与unLock
函数来实现,代码如下
总结
总的来讲自己在系统层面,偏底层的那些问题上,还是掌握的不多,这个也跟自己多年徘徊在应用层开发有关,底层知识用到的不多,自然也就忽略了,但是如果面试的话,就算是面的应用层,也是需要知道一些底层方面的知识,不然面试官随便问几个,你不会,别人会,岗位不就被别人拿走了吗