详析一次鹅厂面经 | 移动端开发岗题解

  • 4.2 20:12 电话方式,确认时间;
  • 4.3 14:03 开始电话面试 时长32min

1. 综合

1.1.可以先自我介绍一下吗?

  • 参考:根据自己的情况提前准备好即可。

1.2.学了这么多东西,你觉得你学的最好的是哪一块?

  • 参考:这里只能说平时注意自己核心竞争力和技术特色的培养;
  • 打个比方:嗯我觉得我在Android这一方面相对比较有把握,然后在***(某个方向)的知识和运用上比较有心得;

1.3.有没有什么实战项目做出来呢?

要点:平时注意多上GitHub、各种网课上或者找导师多扒些项目去做

  • 参考思路:
  1. 最好提前准备好表述,注意表达的效率!当被前辈提问时,简单扼要地把重点说明白!
    切忌拖泥带水含糊不清,没有效率地说了一堆话,很扣分
  2. 项目最难的地方最有价值的问题解决方案,最好自己总结好,
    交代好项目重点之后顺带提出来,不然面试官前辈会问下面 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 URL传给一个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去取的,另外:

  • GETPOST本质上是没有区别的,它们都是TCP链接,

    • GET和POST能做(能做——具备实现的能力)的事情一样一样
      技术上要给GET加上request body,给POST带上url参数,也是行的通的。
    • 只不过HTTPTCP链接披上了GETPOST的外衣,
      打上了服务类型的标签,大多数的语言框架也对此作了一个约定俗成,
      使得不同服务类型的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计,有极限但是一般用不满的;

相关阅读:

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,实现了MapCloneablejava.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,实现了MapCloneablejava.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)

补充:

  • List接口的实现类
    List接口的常用实现类有ArrayListLinkedList.

  • 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接口中同样提供了集合的常用方法,除此之外还包括如下表所示的常用方法:

        注意:Map集合中允许值对象是null,而且没有个数限制,例如,可通过“map.put("05",null)”语句向集合中添加对象。
    • Map接口的实现类

      • Map接口常用的实现类有HashMapTreeMap
      • 建议使用HashMap类实现Map集合;
      • HashMap类实现的Map集合添加和删除映射关系效率更高
      • HashMap是基于哈希表Map接口的实现;
      • HashMap通过哈希码对其内部的映射关系进行快速查找
      • TreeMap中的映射关系存在一定的顺序
      • 如果希望Map集合中的对象也存在一定的顺序,应该使用TreeMap类实现Map集合。


相关阅读:

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);

相关阅读:

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中的地址
      cequals()是根类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机制,同样存在内存泄漏的风险。
      此种内存泄露,一般是临时性的。

  • BAT Android常见面试题详解
    (你如果有两个地方同时引用这块内存的话,那其实只是这块内存被复用了而已,为什么会引起内存泄漏呢?)
    (如果a引用了一个地方,你如果不用的话,你可以把a释放掉,那b还可以继续正常使用啊)

3.Android中对于多个Activity之间的通讯,你是怎么做的?

  • 在service中执行完耗时操作后,将结果以广播的形式发送,
    在所有的activity中注册广播,接收到结果后更新UI;
    这种方式比较简单,也是比较推荐的方式。
    因为耗时的操作结果不需要以handler的方式发送到主线程,
    可以直接在子线程中发送广播,接收者始终运行在主线程中。
  • 在这种方法中,servicebroadcast都作为多个Activity之间通讯媒介
    所有绑定了媒介service的一系列Activity
    都可以把需要处理的数据传给service
    经过service中子线程的sendBroadcast()之手,
    以广播的方式(把处理完毕的数据参数),
    发送到同系列(绑定了同样的媒介service,注册了同样的媒介Receiver)的
    各个Activity手中(包括发送待处理数据参数的Activity自身);

    • 各个Activity通过绑定媒介service,调用service中的方法
      需要处理的数据作为service中方法的参数传给service

    • service接收到Activity送来的待处理数据参数,
      将之送进子线程中处理,子线程处理完数据之后,
      处理完毕的数据作为参数putExtraintent中,
      intent发送广播,将处理完毕的数据发送出去;

    • 相关Activity接收到处理完毕的数据
      回调onReceive(),进行数据的获取、接收处理以及UI的更新


  • 下面这个是模型图
    其中name是概念抽象出来的 多个Activity之间通讯数据内容
    在实际运用中我们可以用各种数据形式来 覆盖这个参数name的位置
    ):
    结合以上模型图以及下面这篇博文可以进一步详细理解;
  • Android中Service与多个Activity通信的三种方式

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。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269