- 4.2 20:12 电话方式,确认时间;
- 4.3 14:03 开始电话面试 时长32min
1. 综合
1.1.可以先自我介绍一下吗?
- 参考:根据自己的情况提前准备好即可。
1.2.学了这么多东西,你觉得你学的最好的是哪一块?
- 参考:这里只能说平时注意自己核心竞争力和技术特色的培养;
- 打个比方:嗯我觉得我在Android这一方面相对比较有把握,然后在*(某个方向)的知识和运用上比较有心得;
1.3.有没有什么实战项目做出来呢?
要点:平时注意多上GitHub、各种网课上或者找导师多扒些项目去做
- 参考思路:
- 最好提前准备好表述,注意
表达的效率
!当被前辈提问时,简单扼要
地把重点说明白!
切忌
拖泥带水含糊不清,没有效率地说了一堆话,很扣分
!
项目最难的地方
,最有价值的问题
和解决方案
,最好自己总结好,交代好项目重点之后顺带提出来,不然面试官前辈会问下面
1.4.
这个问题。
所以务必注重回答问题的完整性
和严谨性
;
1.4. 你觉得在这个项目当中做起来最难的地方是在哪里?
- 参考:总结归纳好所做项目对应的内容重点,简明阐述;
或可从
所用技术及所涉及知识点
、设计思路
、解决方法
等方向归纳回答;
1.5 除了做Android的话,对C/C++这一块了解吗?
- 参考:此问题自然是知道多少回答多少,不过最好是有组织有条理地描述;
这里可以从C++和Java的异同入手回答:
2. 计算机网络(0.4-0.45)
1.Android端跟后台通讯的时候用的是什么协议?
- 参考:用的是基于
`
HttpURLConnection类的HTTP协议
你们对HTTP平常用得熟悉吗?
2.(接1. )HTTP是哪一层上面在用的呢?
- 参考:HTTP是应用层上面的协议;
3.(接2.)那它是基于什么协议去实现的?
- 参考:它是基于TCP连接的;
4.在项目中,你有可能需要维持APP跟后台之间的长时间连接,那么在实际运用中你是如何实现维持长时间连接的?就是你的这种app跟后台服务的通讯,是一种 短连接 还是一种 长连接 的方式呢?
考点:网络的(短连接跟)长连接(即持久连接)问题
- 参考(实现长连接):
在Android中,我们在进行HTTP请求的时候,使用的是Java API的一个叫
HTTPURLConnection
的封装类,
首先将一个web UR
L传给一个URL
对象的构造方法,创建出一个URL
实例,
用这个URL实例调用其openConnection()
方法,会返回一个对象,
将其返回的对象转型为HttpURLConnection
对象并付给一个HttpURLConnection实例
,
接着就可以调用HttpURLConnection
实例的一系列set
方法对这个请求做各种设置,
其中调用方法setRequestProperty("Connection", "Keep-Alive")
即可完成这个请求的长连接
的实现;
当然除了以上Android端的配置意外,我们还需要在服务器
设置好Keep- Alive长连接模式
,
是否能完成一个完整的Keep- Alive连接和服务器设置也相关;
- HttpURLConnection 的 demo:
URL realUrl = new URL(url); HttpURLConnection conn = (HttpURLConnection) realUrl .openConnection(); // 发送POST请求必须设置如下两行 conn.setDoOutput(true); conn.setDoInput(true); // POST方法 conn.setRequestMethod("POST"); // 设置通用的请求属性 conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.connect();
额外:
可以聊一下
什么是长连接、短连接
:
- 在HTTP/1.0中默认使用短连接。
也就是说,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。
当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源(如JavaScript文件、图像文件、CSS文件等),每遇到这样一个Web资源,浏览器就会重新建立一个HTTP会话。- 而从HTTP/1.1起,默认使用长连接,用以保持连接特性。
使用长连接的HTTP协议,会在响应头加入这行代码:
Connection:keep-alive
在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。
Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。
实现长连接需要客户端和服务端都支持长连接。- HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。
长连接,短连接的适用场景?
长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况。
每个TCP连接都需要三次握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,
所以每个操作完后都不断开,次处理时直接发送数据包就OK了,不用建立TCP连接。
例如:数据库的连接用长连接, 如果用短连接频繁的通信会造成socket错误,而且频繁的socket创建也是对资源的浪费。
而像WEB网站的http服务一般都用短链接,
因为长连接对于服务端来说会耗费一定的资源,
而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,
如果用长连接,而且同时有成千上万的用户,如果每个用户都占用一个连接的话,那可想而知吧。
所以并发量大,但每个用户无需频繁操作情况下需用短连好。相关阅读:
5.客户端到服务器拉数据的时候你是用GET还是用POST去取的?
参考:拉数据的时候是用GET去取的,另外:
GET
和POST
本质上是没有区别的,它们都是TCP
链接,
- GET和POST能做(能做——具备实现的能力)的事情一样一样。
技术上要给GET加上request body,给POST带上url参数,也是行的通的。
- 只不过
HTTP
给TCP链接
披上了GET
和POST
的外衣,打上了服务类型的标签,大多数的语言框架也对此作了一个约定俗成,
使得不同服务类型的TCP链接请求在应用时,最好要进行各自不同的代码编写和机制处理,
同时加上浏览器/服务器或多或少地对URL等参数可能的限制,
导致他们在应用过程
中体现出一些不同。
- 然而其实像**“POST请求时数据就要放在BODY中,
GET请求时数据(参数)就要放在URL中而不能放在BODY中”**这样的说法,
只是HTML标准对HTTP协议的用法的约定,HTTP并没有做这样子的要求;
应用过程上的区别
:
00 关于服务器(2点):
- **GET是从服务器上获取数据,
POST是向服务器传送数据;**
- 对于GET方式,
服务器端用Request.QueryString获取变量的值,
对于POST方式,
服务器端用Request.Form获取提交的数据。
01 关于效率和安全性(3点):POST(传输意义上)安全性较高;GET(传输意义上)安全性非常低,但是执行效率比POST好;
- GET产生一个TCP数据包,
浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
POST产生两个TCP数据包,浏览器先发送header,
服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。
也就是说,
GET一步到位,而POST需要两步,这也是GET比POST更快的一个原因;- **GET会将数据缓存起来,
而POST不会,这是GET比POST更快的又一个原因;**
据道友测试,
使用ajax采用GET方式请求静态数据(比如html页面,图片)的时候,
如果两次传输的数据相同,
第二次以后消耗的时间将会在10ms以内(chrome测试),
而POST每次消耗的时间都差不多。
经测试,
chrome和firefox下如果检测到get请求的是静态资源,则会缓存,
如果是数据,则不会缓存,但是IE什么都会缓存起来;- GET把请求的数据放在url上,即HTTP协议头上,url参数可见;
POST把数据放在HTTP的包体内(requrest body),url参数不可见;
所以原则上POST要比get安全,毕竟传输参数时url不可见;
02 关于请求长度限制(1点):
- http协议从未规定GET/POST的请求长度限制是多少;
但是实际应用上,
GET提交的数据的限制,取决于浏览器和web服务器设置的URL请求长度限制;
各种浏览器和web服务器的设定均不一样,
这依赖于各个浏览器厂家的规定或者可以根据web服务器的处理能力来设定。
IE 限制 2k,Firefox 限制 8k(非常老的版本 256byte),
如果超出了最大长度,大部分的服务器直接截断,也有一些服务器会报414错误。
POST默认没有限制。理论上IIS4中最大量为80KB,IIS5中为100KB。
03 其他(2点):
从
本质意义
上讲,GET是安全的,POST不安全:- GET没有更改服务器内容; - POST对服务器就行写入、覆盖,会更改服务器内容;
幂等性(同样的一个操作,它一次或者多次地操作,对系统资源产生的影响是一样的)
GET具备幂等性;
- POST不具备;
(原因参照对服务器是否有修改)
“用GET替换POST来优化网站性能”是一个坑,慎入;何出此言?
- GET与POST都有自己的语义,不能随便混用。
- 据研究,在网络环境好的情况下,
发一次包的时间和发两次包的时间差别基本可以无视。
而在网络环境差的情况下,
两次包的TCP在验证数据包完整性上,有非常大的优点。
- 并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。
相关阅读:
6.在做的过程中有没有遇到过HTTP的错误,比如说HTTP的那些错误码?
参考回答:状态码由三位数字组成,第一位数字表示响应的类型,常用的状态码有五大类:
- 1xx:表示服务器已接收了客户端请求,客户端可继续发送请求
2xx:表示服务器已成功接收到请求并进行处理
- 200 OK:表示客户端请求成功
- 3xx:表示服务器要求客户端重定向
重定向(Redirect)就是通过各种方法将各种网络请求重新定个方向转到其它位置(如:网页重定向、域名的重定向、路由选择的变化也是对数据报文经由路径的一种重定向)
4xx:表示客户端的请求有非法内容
- 400 Bad Request:表示客户端请求有语法错误,不能被服务器所理解 - 401 Unauthonzed:表示请求未经授权,该状态代码必须与 WWW-Authenticate 报头域一起使用 - 403 Forbidden:表示服务器收到请求,但是拒绝提供服务,通常会在响应正文中给出不提供服务的原因 - 404 Not Found:请求的资源不存在,例如,输入了错误的URL - 414 :Request-URI 太长
5xx:表示服务器未能正常处理客户端的请求而出现意外错误
- 500 Internal Server Error:表示服务器发生不可预期的错误,导致无法完成客户端的请求
503 Service Unavailable:表示服务器当前不能够处理客户端的请求,在一段时间之后,服务器可能会恢复正常
相关阅读:
3. Java(0.4-0.5)
1.String、StringBuffer、StringBuilder三个的分别?
综述:
- String:不可变字符串;
- StringBuffer:可变字符串、效率低、线程安全;
- StringBuilder:可变字符序列、效率高、线程不安全;
- 执行速度:StringBuilder > StringBuffer > String;
(StringBuffer 很多方法可以带有synchronized关键字,在做字符串操作时需要做对应的操作,所以运行性能比StringBuilder稍微差些)
- StringBuffer、 StringBuilder的API基本一样:
详析:
- **
String
的值是不可变的,每次对String的操作都会生成新的String对象,
不仅效率低下,而且浪费大量优先的内存空间:**
JVM对于上图是这样处理的,
首先创建一个String对象str,并把“hello”赋值给str,
然后str=str+" world":
其实JVM又创建了一个新的对象也名为str,
然后再把原来的str的值和“ world”加起来再赋值给新的str,
而原来的str就会被JVM的垃圾回收机制(GC)给回收掉了,
所以,最开始的str实际上并没有被更改,
也就是前面说的String对象一旦创建之后就不可更改了(String的值是不可变的)。
这样短短的两个字符串,却需要开辟三次内存空间,不得不说这是对内存空间的极大浪费。
所以,
Java中对String对象进行的操作,
实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,执行速度很慢。
StringBuffer
是可变的、线程安全的字符串操作类,任何对它指向的字符串的操作都不会产生新的对象。
每个StringBuffer对象都有一定的缓冲区容量,
当字符串大小没有超过容量时,不会分配新的容量,
当字符串大小超过容量时,会自动增加容量;
StringBuilder
是可变的、线程不安全的字符串操作类,任何对它指向的字符串的操作都不会产生新的对象。
每个StringBuilder对象都有一定的缓冲区容量,
当字符串大小没有超过容量时,不会分配新的容量,
当字符串大小超过容量时,会自动增加容量;
适用场景:
- String:适用于操作少量的字符串/数据的情况
- StringBuilder:适用于单线程操作字符串缓冲区下操作大量数据的情况
- StringBuffer:适用于多线程操作字符串缓冲区下操作大量数据的情况
StringBuilder/StringBuffer的缓冲区会不会满?
StringBuilder/StringBuffer的缓冲区的大小默认初始化为16个字符,
或者可以使用其他重载的构造方法初始化缓冲区的大小,
缓冲区最大容量的大小视内存而定,一般都是数以M计,有极限但是一般用不满的;相关阅读:
- String、StringBuffer和StringBuilder这三兄弟分别是做什么的?
- 图析:String,StringBuffer与StringBuilder的区别
- StringBuffer与StringBuilder的区别,及实现原理【提及初始化及扩容机制】
- 获取StringBuffer对象的容量
- Java中的String,StringBuilder,StringBuffer三者的区别
- Java中String、StringBuilder、StringBuffer常用源码分析及比较(二):StringBuilder、StringBuffer源码分析
- [[源码]String StringBuffer StringBudlider(2)StringBuffer StringBuilder源码分析](https://www.cnblogs.com/zongjihengfei/p/6005377.html)
2.有没有用过Java对应的数据结构的类?
参考:概述诸多
集合类的特性
,尤其注意底层实现
、内部算法实现
、线程安全
、同类区别
等问题;
ArrayList
类a. 实现了
可变的数组
,允许保存所有元素
,包括null
,并可以根据索引位置对集合进行快速的随机访问;
b. 缺点是向指定的索引位置插入对象或删除对象的速度较慢。
LinkedList
类a.采用
链表结构
保存对象。
b.优点是便于向集合中插入和删除对象,需要向集合中插入、删除对象
时,使用LinkedList类实现的List集合的效率较高:
c. 但对于随机访问集合中的对象
,使用LinkedList类实现List集合的效率较低。
HashSet
类a. 实现
Set接口
,由哈希表(实际上是一个HashMap实例)
支持。
b. 它不保证Set的迭代顺序
,特别是它不保证该顺序恒久不变
。
c .此类允许使用null元素
。
TreeSet
类a. 不仅实现了
Set接口
,还实现了java.util.SortedSet接口
,
b. 因此,TreeSet类实现的Set集合在遍历集合时
按照自然顺序递增排序
;*********%%%%%%%%%%***********
c. 也可以按照指定比较器
递增排序;
d. 即可以通过比较器对用TreeSet类实现的Set集合中的对象进行排序。
HashMap
类
a .继承于AbstractMap
,实现了Map
、Cloneable
、java.io.Serializable
接口;
b. 此实现提供所有可选的映射操作
,并允许使用null值和null键
,但必须保证键的唯一性
。
c .HashMap通过哈希表
对其内部的映射关系进行快速查找
。
d. 此类不保证
映射的顺序,特别是它不保证该顺序恒久不变。
TreeMap
类
a. 不仅实现了Map接口,还实现了java.util.SortedMap接囗,因此,集合中的映射关系具有一定的顺序。
b. 但在添加、删除和定位映射关系
时,TreeMap类比HashMap类性能稍差
。
c. 由于TreeMap类实现的Map集合中的映射关系是根据键对象
按照一定的顺序排列
的,因此不允许键对象是null
。- 可以通过HashMap类创建Map集合,当需要顺序输出时,再创建一个完成相同映射关系的TreeMap类实例。
HashTable
类
a. 继承于Dictionary
,实现了Map
、Cloneable
、java.io.Serializable
接口。
b.Hashtable 的函数都是同步
的,这意味着它是线程安全
的。它的key、value都不可以为null。
此外,Hashtable中的映射不是有序的。c .HashMap通过```哈希表```对其**内部的映射关系**进行```快速查找```。
Vector
类a.
Vector
是矢量队列,它是JDK1.0
版本添加的类。继承于AbstractList
,实现了List
,RandomAccess
,Cloneable
这些接口。b. ```Vector ```继承了```AbstractList```,实现了```List```;所以,它是一个队列,支持相关的添加、删除、修改、遍历等功能。 c. ```Vector``` 实现了```RandmoAccess```接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在Vector中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。 d. ```Vector``` 实现了```Cloneable```接口,即实现```clone()```函数。它能被克隆。 e. 和```ArrayList```不同,```Vector```中的操作是线程安全的。
Stack
类a.
Stack
是栈。它的特性
是:先进后出
(FILO, First In Last Out)。
b. java工具包中的Stack是继承于Vector(矢量队列)的,由于Vector是通过数组实现的,
这就意味着,Stack也是通过数组实现的,而非链表。
当然,我们也可以将LinkedList当作栈来使用;
线程安全
- 线程安全:Hashtable、Vector、Stack
- 线程不安全:HashMap 、ArrayList、LinkedList 、HashSet、TreeSet、TreeMap
- HashMap 四种同步方式的性能比较
- HashSet同步方法:
Set s = Collections.synchronizedSet(new HashSet(...));
synchronizedSet()返回的Set是同步的;key/value 可否为空(关于List,Set,Map能否存储null)
- key/value 可为空:
ArrayList、LinkedList 可以存储多个null;
HashSet(只能有一个null的节点)、
HashMap (key、value都可以为null,只能有一个key == null的节点)、
Vector 底层是数组,所以不会管你元素的内容是什么,可以存储多个null、
- key/value 不可为空:
TreeSet不能有key为null的元素、
TreeMap不能有key为null的元素、
HashTable(key、value都不可以为null)
- 是否有序(遍历时是否按添加元素时的顺序)
( 常见Map 及 List 是否有序总结 )
(java中ArrayList 、LinkList区别)
(知乎:Java遍历HashSet为什么输出是有序的?)
- 有序:LinkedList、ArrayList、TreeSet、Vector、Stack(先进后出)、
TreeMap (根据其键的自然顺序进行排序)、
- 无序:
HashMap中的映射不是有序、
Hashtable、HashSet(“不保证有序”,但也不是“保证无序”,时有时无)补充:
- List接口的实现类
List接口的常用实现类有
ArrayList
与LinkedList
.
Set集合
- Set集合中的对象
不按特定的方式排序
,只是简单地把对象加入集合中
;- Set集合中
不能包含重复对象
;- Set集合由
Set接口
和Set接口的实现类
组成。- Set接口继承了
Collection接口
,因此包含Collection接口的所有方法
。
Set接口常用的实现类有HashSet类
与TreeSet类
。
Map集合
- Map集合没有继承
Collection
接口,其提供的是key到value的映射
;- Map中不能包含相同的
key
,每个key
只能映射一个value
;- key还决定了
存储对象在映射中的存储位置
,但不是由
key对象本身
决定的,而是通过一种“散列技术”
进行处理,产生一个散列码的整数值
;
散列码
通常用作一个偏移量
,该偏移量对应分配给映射的内存区域的起始位置
,从而确定存储对象在映射中的存储位置
;- Map集合包括Map接口以及Map接口的所有实现类。
Map接口
- Map接口提供了将key映射到值的对象。 - 一个映射不能包含重复的key,每个key最多只能映射到一个值。 - Map接口中同样提供了集合的常用方法,除此之外还包括如下表所示的常用方法:![](https://upload-images.jianshu.io/upload_images/9125154-a29023971e4bec63.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
注意:Map集合中允许值对象是null,而且没有个数限制,例如,可通过“map.put("05",null)”语句向集合中添加对象。
Map接口的实现类
- Map接口常用的实现类有```HashMap```和```TreeMap```; - 建议使用```HashMap类```实现Map集合; - 由```HashMap类```实现的Map集合```添加和删除映射关系效率更高```; - HashMap是基于```哈希表```的```Map接口```的实现; - HashMap通过```哈希码```对其内部的映射关系进行```快速查找```; - ```TreeMap```中的映射关系存在```一定的顺序```; - 如果希望Map集合中的对象也存在一定的顺序,应该使用TreeMap类实现Map集合。
相关阅读:
- Java知识梳理 | 详析三大集合类
- 图解集合 4 :HashMap【存储单元Entry】
- Java 集合系列14之 Map总结(HashMap, Hashtable, TreeMap, WeakHashMap等使用场景)
底层实现
与源码分析
详析可以见此:
- Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例
- Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例
- Java 集合系列06之 Vector详细介绍(源码解析)和使用示例
- Java 集合系列07之 Stack详细介绍(源码解析)和使用示例
- Java 集合系列10之 HashMap详细介绍(源码解析)和使用示例
- Java 集合系列11之 Hashtable详细介绍(源码解析)和使用示例
- Java 集合系列12之 TreeMap详细介绍(源码解析)和使用示例
- Java 集合系列16之 HashSet详细介绍(源码解析)和使用示例
- Java 集合系列17之 TreeSet详细介绍(源码解析)和使用示例
- Java HashMap源码分析
- 详解 equals() 方法和 hashCode() 方法
- java中hashCode()方法的作用
- java源码分析之LinkedList
- LinkedList源码分析(基于JDK8)
3.HashMap跟HashTable有什么分别?
HashMap和Hashtable的相同点:
- HashMap和Hashtable都是存储“键值对(key-value)”的散列表,而且都是采用拉链法实现的。
- 添加key-value键值对的基本逻辑;
- 删除key-value键值对的基本逻辑;
HashMap和Hashtable的不同点:
- 继承和实现方式不同
- 线程安全不同
- 对null值的处理不同
- 支持的遍历种类不同
- 通过Iterator迭代器遍历时,遍历的顺序不同
- 容量的初始值 和 增加方式都不一样
- 添加key-value时的hash值算法不同
- 部分API不同
详细见此文章:Java 集合系列14之 Map总结(HashMap, Hashtable, TreeMap, WeakHashMap等使用场景)
相关阅读:
4.HashMap里面的Hash函数是用什么算法去写的?或者是说当中的Hash有没有可能出现冲突的?
基于JDK1.6.0_45
- HashMap 是基于
“拉链法”
实现的散列表
。 存储思想
:通过table
数组存储,数组的每一个元素都是一个Entry
;
而一个Entry
就是一个单向链表
,Entry链表
中的 每一个节点
就保存了key-value键值对数据
。
关于table定义的两行源码:static class Entry<K,V> implements Map.Entry<K,V>
transient Entry[] table;
添加key-value键值对
:
首先根据key值
计算出哈希值
,再计算出数组索引
(即,该key-value节点在table中的索引)。
然后,根据数组索引
找到Entry
(即,单向链表),再遍历单向链表,将key和链表中的每一个节点的key进行对比。
若key
已经存在Entry链表
中(冲突
),则用该value值取代旧的value值;
若key
不存在Entry链表
中,则新建一个key-value节点,并将该节点插入Entry链表的表头位置。
- “首先根据
key值
计算出哈希值
” 一言的更深一步理解:即我们在用HashMap(或者HashMTable)的
put(K key, V value)
方法时,
实参值会传给形参变量key,这时候JVM会为形参key分配一块内存,并赋予其地址,
随后在put方法中,进行int hash = hash(key.hashCode());
,即双重哈希,减少冲突率;
- 默认情况下,Object中的hashCode() 返回对象的 32位JVM内存地址。
也就是说如果对象不重写该方法,则返回相应对象的 32为JVM内存地址。
即key调用hashCode()方法得到其hashCode 值(该方法适用于每个Java对象),
接着再用HashMap中的hash方法进行第二重哈希,计算出哈希值
;
删除key-value键值对
:
删除键值对
的逻辑相比于添加键值对
简单一些。
首先,还是根据key计算出哈希值,再计算出数组索引(即,该key-value在table中的索引)。
然后,根据索引找出Entry(即,单向链表)。
若节点key-value存在与链表Entry中(冲突
),则删除链表中的节点即可。
基于JDK1.8
- JDK1.8对
“拉链法”
进行了 “升级”,
即引入了 红黑树,大程度优化了HashMap的性能;
在增加key-value键值对
时候,
某个数组元素 table[i]
的链表长度
如果大于8
,
则把链表
转换为红黑树
,在红黑树中执行插入操作,
否则仍旧进行链表
的插入操作(似同JDK1.6);
相关阅读:
- 图解集合 4 :HashMap【存储单元Entry】
- Java 集合系列10之 HashMap详细介绍(源码解析)和使用示例
- Java 集合系列14之 Map总结(HashMap, Hashtable, TreeMap, WeakHashMap等使用场景)
- 用拉链法和线性探测法解决哈希冲突
- HashMap中的hash与rehash
5.Java中实现多线程的方式有哪几种?
参考:
不严谨地说,Java中实现多线程的方式有两类,即
继承Thread类
以及实现Runnable接口
;
- 1.
继承Thread类
创建一个类【
普通具体类
或者匿名内部类
皆可!!!】去继承Thread类并重写run()
方法,
使用的时候构造一个这个类的对象去调用start()
方法,【匿名内部类
直接new完就start()
】
即可让run()方法中的代码在子线程中运行;class MyThread extends Thread{ @Override public void run(){ //处理具体逻辑 } }
new MyThread().start();
- 但是使用继承的方式耦合性有点高,
更多的时候我们都会选择使用
实现Runnable接口
的方式来定义一个线程;或者使用匿名内部类
去实现简单的线程开启;
2.
实现Runnable接口
- 2.1 定义一个类,让它实现Runnable接口并重写run()方法,
使用的时候构造一个这个类的对象,
然后将这个类对象当做参数传给Thread类的构造方法,
构造好Thread之后调用start()方法即可让run()方法中的代码在子线程中运行;class MyThread implements Runnable{ @Override public void run(){ //处理具体逻辑 } }
MyThread myThread = new MyThread(); new Thread(myThread).start;//Thread的构造函数接收一个Runnable参数
.
.
- 2.2 使用匿名内部类的方式,调用Runnable的构造方法并重写run()方法,
得到一个匿名内部类,
然后把整个匿名内部类作为参数传给Thread类的构造方法,
构造好Thread之后调用start()方法即可让run()方法中的代码在子线程中运行;new Thread(new Runnable(){ @Override public void run(){ //处理具体逻辑 } }).start();
.
.
- 2.3 准备好一个
Runnable
实例(实例化
还是匿名内部类
的形式都行),再使用绑定
子线程Looper
的一个Handler实例
的post()
或者postDelayed()
,
post这个Runnable
实例到子线程处理,即可完成子线程任务开启!
【Handler实例
要绑定子线程Looper
才是处理子线程任务,
不然绑定mainLooper
就是提交到主线程
处理了!!!】实际上归根到底,
以上也便是实现线程执行单元
(线程的执行单元
就是run
方法)的两种方式,
而创建线程的方式
永远只有一种——构造Thread类
,
方才罗列的两类方法最终归结到new Thread().start();
上来。
实际上在运用中,我们都可以围绕Thread的诸多构造方法,
做各种不一样实现线程执行单元
的方式:
6.Java当中的内存管理是怎么做的?它和C++对应的有什么分别呢?
Java 程序运行时的内存分配策略有三种,分别是
静态分配
、栈式分配
和堆式分配
,
三种方式所使用的内存空间分别是静态存储区(方法区)
、栈区
和堆区
。
静态存储区(方法区)
:主要存放静态变量
。这块「内存」在程序编译时就已经分配好了,
并且在程序整个运行期间都存在。
栈区
:当方法被执行时,方法体内的局部变量(包括基础数据类型、对象的引用)都在栈上创建,
并在方法执行结束时,
这些局部变量所持有的内存将会自动被释放。
因为栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
堆区
:又称动态内存分配,通常就是指程序运行时直接 new 出来的内存,也就是对象的实例,这部分「内存」在不使用时将会被 Java 垃圾回收器来负责回收。- 另外一种划分理解:
JVM会用一段空间来存储执行程序期间需要用到的数据和相关信息,
这段空间就是运行时数据区(Runtime Data Area),也就是常说的JVM内存。
JVM会将它所管理的内存划分为 线程私有数据区 和 线程共享数据区两大类:
线程私有数据区
包含:- **```程序计数器```**:是当前线程所执行的字节码的行号指示器 - **```虚拟机栈```**:是Java方法执行的内存模型 - **```本地方法栈```**:是虚拟机使用到的Native方法服务
线程共享数据区
包含:- **```Java堆```**:用于存放几乎所有的对象实例和数组;
是垃圾收集器管理的主要区域,也被称做“GC堆”;
是Java虚拟机所管理的内存中最大的一块- **``` 方法区```**:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据;
Class文件中除了有类的版本、字段、方法、接口等描述信息外,
还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,
这部分内容将在类加载后进入方法区的运行时常量池中存放;相关阅读:
7.对于虚拟机那些个新生代和持久代的,你有看过他们的策略吗?
参考回答:
(1)
判定对象可回收有两种方法
:
引用计数算法
:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。然而在主流的Java虚拟机里未选用引用计数算法来管理内存,主要原因是它难以解决对象之间相互循环引用的问题,所以出现了另一种对象存活判定算法。可达性分析法
:通过一系列被称为『GC Roots』的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。其中可作为GC Roots的对象:虚拟机栈中引用的对象,主要是指栈帧中的本地变量、本地方法栈中Native方法引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象
(2)
回收算法有以下四种
:
分代收集算法
:是当前商业虚拟机都采用的一种算法,根据对象存活周期的不同,将Java堆划分为新生代和老年代,并根据各个年代的特点采用最适当的收集算法。
新生代
:大批对象死去,只有少量存活。使用『复制算法』,只需复制少量存活对象即可。
复制算法
:把可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用尽后,把还存活着的对象『复制』到另外一块上面,再将这一块内存空间一次清理掉。
老年代
:对象存活率高。使用『标记—清理算法』或者『标记—整理算法』,只需标记较少的回收对象即可。
标记-清除算法
:首先『标记』出所有需要回收的对象,然后统一『清除』所有被标记的标记-整理算法
:首先『标记』出所有需要回收的对象,然后进行『整理』,使得存活的对象都向一端移动,最后直接清理掉端边界以外的内存。相关阅读:
8.Java当中判断两个对象是否相同的时候有哪些方法?
Java中判断两个对象是否相同时有两种方法——用
==
或者equals()
;
==
是比较两个对象在JVM中的地址
。c
equals()
是根类Obeject
中的方法,
查看源码我们可以知道默认的equals()
方法中,
首选也是直接调用==
,比较对象地址:当然真正使用的时候,我们需要在自定义类中对equals()
进行重载,
从而能使重载后的equals()
除了==
的判断作用之外,
还可以判断两个对象中具体各成员的值或者构造
是否相同;
而基本数据类型的实例
就不用我们费心了,JDK中已经重载好了。相关阅读:
4. Android(0.66)
1. 能不能在子线程里面做UI更新(界面更新)?为什么?
- 系统不建议在子线程访问UI:
UI控件 非线程安全 ,在多线程中并发访问可能会导致UI控件处于不可预期的状态。
- 而不对UI控件的访问加上锁机制的原因有:
上锁会让UI控件变得复杂和低效;
上锁后会阻塞某些进程的执行;如此,既然UI不能上锁,非线程安全,
那自然是只能有一个线程有权操作它,
在整个程序中,只能有一个线程,那主线程(UI线程)自然当仁不让了。
2.有没有遇到过内存泄漏的场景?
- Android内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,
但是它们却可以直接或间接地引用到gc roots导致无法被GC回收。
无用的对象占据着内存空间,使得实际可使用内存变小,形象地说法就是内存泄漏了。
场景
- 类的静态变量持有大数据对象
静态变量长期维持到大数据对象的引用,阻止垃圾回收。
- 非静态内部类的静态实例
非静态内部类会维持一个到外部类实例的引用,
如果非静态内部类的实例是静态的,
就会间接长期维持着外部类的引用,阻止被回收掉。
- 资源对象未关闭
资源性对象如Cursor、File、Socket,应该在使用后及时关闭。
未在finally中关闭,会导致异常情况下资源对象未被释放的隐患。
- 注册对象未反注册
未反注册会导致观察者列表里维持着对象的引用,阻止垃圾回收。
- Handler临时性内存泄露
Handler通过发送Message与主线程交互,
Message发出之后是存储在MessageQueue中的,
有些Message也不是马上就被处理的。
在Message中存在一个 target,是Handler的一个引用,
如果Message在Queue中存在的时间越长,就会导致Handler无法被回收。
如果Handler是非静态的,则会导致Activity或者Service不会被回收。
由于AsyncTask内部也是Handler机制,同样存在内存泄漏的风险。
此种内存泄露,一般是临时性的。(你如果有两个地方同时引用这块内存的话,那其实只是这块内存被复用了而已,为什么会引起内存泄漏呢?)
(如果a引用了一个地方,你如果不用的话,你可以把a释放掉,那b还可以继续正常使用啊)
3.Android中对于多个Activity之间的通讯,你是怎么做的?
- 在service中执行完耗时操作后,将结果以广播的形式发送,
在所有的activity中注册广播,接收到结果后更新UI;
这种方式比较简单,也是比较推荐的方式。
因为耗时的操作结果不需要以handler的方式发送到主线程,
可以直接在子线程中发送广播,接收者始终运行在主线程中。
- 在这种方法中,
service
和broadcast
都作为多个Activity之间通讯
的媒介
;所有绑定了
媒介service
的一系列Activity
,
都可以把需要处理的数据
传给service
;
经过service
中子线程的sendBroadcast()
之手,
以广播的方式(把处理完毕的数据参数),
发送到同系列(绑定了同样的媒介service
,注册了同样的媒介Receiver
)的
各个Activity
手中(包括发送待处理数据参数的Activity
自身);
- 各个
Activity
通过绑定媒介service
,调用service
中的方法
,把
需要处理的数据
作为service中方法的参数
传给service
;
service
接收到Activity
送来的待处理数据参数,将之送进
子线程
中处理,子线程
处理完数据之后,
将处理完毕的数据
作为参数putExtra
到intent
中,intent
发送广播
,将处理完毕的数据
发送出去;
- 相关
Activity
接收到处理完毕的数据
,回调
onReceive()
,进行数据的获取、接收处理
以及UI的更新
;
- 下面这个是模型图
(**其中
name
是概念抽象出来的多个Activity之间通讯
的数据内容
,
在实际运用中我们可以用各种数据形式
来 覆盖这个参数name
的位置**):
结合以上模型图以及下面这篇博文可以进一步详细理解;
4.Activity的生命周期是什么样子的?
在Activity的生命周期涉及到七大方法,分别是:
- onCreate()表示Activity 正在创建,常做初始化工作,如setContentView界面资源、初始化数据
- onStart()表示Activity 正在启动,这时Activity 可见但不在前台,无法和用户交互
- onResume()表示Activity 获得焦点,此时Activity 可见且在前台并开始活动
- onPause()表示Activity 正在停止,可做 数据存储、停止动画等操作
- onStop()表示activity 即将停止,可做稍微重量级回收工作,如取消网络连接、注销广播接收器等
- onDestroy()表示Activity 即将销毁,常做回收工作、资源释放
- 另外,当Activity由后台切换到前台,由不可见到可见时会调用onRestart(),表示Activity 重新启动
5.具体的场景,横竖屏切换的时候,Activity的生命周期是什么样子的?
当非人为终止Activity时,
比如系统配置发生改变时导致Activity被杀死并重新创建、资源内存不足导致低优先级的Activity被杀死,
会调用 onSavaInstanceState()来保存状态。
该方法调用在onStop
之前,但和onPause
没有时序关系。Activity被重新创建时会调用onRestoreInstanceState(该方法在onStart之后),
并将onSavaInstanceState保存的Bundle对象作为参数传到onRestoreInstanceState与onCreate方法。
1.AndroidManifest没有设置configChanges属性
- 竖(横)屏启动:
onCreate -->onStart-->onResume
- 切换横(竖)屏:
onPause -->onSaveInstanceState -->onStop -->onDestroy -->onCreate-->onStart -->
onRestoreInstanceState-->onResume
(Android 6.0 Android 7.0 Android 8.0)
- 2.AndroidManifest设置了configChanges
android:configChanges="orientation"
- 竖(横)屏启动::
onCreate -->onStart-->onResume
切换横(竖)屏:
- onPause -->onSaveInstanceState -->onStop -->onDestroy -->onCreate-->onStart -->
onRestoreInstanceState-->onResume
(Android 6.0)- onConfigurationChanged-->onPause -->onSaveInstanceState -->onStop -->onDestroy -->
onCreate-->onStart -->onRestoreInstanceState-->onResume
(Android 7.0)- onConfigurationChanged
(Android 8.0)
- 总结:
设置了configChanges属性为orientation之后,Android6.0 同没有设置configChanges情况相同;
Android 7.0则会先回调onConfigurationChanged方法,剩下的流程跟Android 6.0 保持一致;
Android 8.0 则只是回调了onConfigurationChanged方法。
- 3.AndroidManifest设置了configChanges
android:configChanges="orientation|keyboardHidden|screenSize"
竖(横)屏启动:onCreate -->onStart-->onResume
切换横(竖)屏:onConfigurationChanged (Android 6.0 Android 7.0 Android 8.0)
- 总结:
设置android:configChanges="orientation|keyboardHidden|screenSize" 则都不会调用Activity的其他生命周期方法,
只会调用onConfigurationChanged方法。
5. 数据结构(0.8)
常用的排序算法有哪些,各自的时间复杂度是怎么样的?
- 参考:如下表:
6. 其他
我看你学过一点神经网络对吧?
(简书之前因为导师鞭策,写了不少关于Python和机器学习的文章)
参考:根据自身所掌握的知识回答,自然是了解多少答多少,
内容可以涉及神经网络,神经网络节点,激励函数,节点的输入输出关系等等,有条件的同学可以讲一下TensorFlow。