安卓面试整理(持续不定时记录中。。。)

目录

总的来说,面试这个东西,基本上是面试官最快了解你能力的地方。所以说,如果想要进入自己心仪的公司。面试基本的问题还是要有一定的准备的,不然也是浪费彼此的时间。这边是给自己稍微做个总结。

主要分为以下下几个部分

  1. java面试题
  2. Android面试题
  3. 高级开发技术面试题
  4. Kotlin面试题

java模块

java面试基础

  • java中 == 和equals和hashCode的区别

答:== 是运算符,用来比较两个值、两个对象的内存地址是否相等
equals:equals是Object类的方法,默认情况下比较两个对象是否是同一个对象,内部实现是通过 “==”来实现的。
如果想比较两个对象的其他内容,则可以通过重写equals方法
hashCode(): hashCoed也是Object类里面的方法,返回值是一个对象的哈希码,同一个对象哈希码一定相等,但不 同对象哈希码也有可能相等。
如果两个对象通过equals方法比较相等,那么他的hashCode一定相等;
如果两个对象通过equals方法比较不相等,那么他的hashCode有可能相等;

  • char、short、int、long各占多少字节数

数据类型占内存的位数实际上与操作系统的位数和编译器(不同编译器支持的位数可能有所不同)都有关,具体某种数据类型占字节数得编译器根据操作系统位数两者之间进行协调好后分配内存大小。具体在使用的时候如想知道具体占内存的位数通过sizeof(int)可以得到准确的答案
一般 char 占1个字节, short 占2个字节 ,int 占4个字节 (ILp64占8个),long 占4个字节(iLp64、Lp64占8个字节)

  • int与integer的区别
  1. int 是java的基本数据类型。Integer 继承了Object类,是对象类型,是 int 的包装类
  2. 值的存储
    int 存储在栈中
    Integer 对象的引用存储在栈空间中,对象的数据存储在堆空间中。
  3. 初始化
    int 初始化值为0。
    Integer 初始化值为null。
  4. 传参
    int 是值传递,栈中的数据不可变。
    Integer 对象是引用传递,引用不可变,但是引用指向的堆空间地址中的值是可以改变的。
  5. 泛型支持
    泛型不支持int,但是支持Integer。
  6. 运算
    int 可以直接做运算,是类的特性。
    Integer 的对象可以调用该类的方法,但是在拆箱之前不能进行运算,需要转化为基本类型int。
  7. 相同值下的 int 和 Integer 的比较结果
    两个通过new生成的变量,结果为false。
    int 和 Integer 的值比较,若两者的值相等,则为true。
    (注意:在比较时,Integer会自动拆箱为int类型,然后再做比较。)
    new 生成的Integer变量 和 非new 生成的Integer变量比较,,结果为false。
    (注意:new 生成的Integer变量的值在堆空间中,非new 生成的Integer变量的值在在常量池中。)
    (注意:非new生成的Integer变量,会先判断常量池中是否有该对象,若有则共享,若无则在常量池中放入该对象;也叫享元模式,后面再说。)
    两个非new 生成的Integer对象比较,则结果为true。
    (注意:此处需要一个前提:值的范围在 -128 ~ 127 之间。
    涉及到java对 int 与 Integer 的自动装箱和拆箱的一种模式:享元模式—flyweight,为了加强对简单数字的重复利用。
    在赋值时,其实是执行了Integer的valueOf()方法。
    当值在 -128 ~ 127之间时,java会进行自动装箱,然后会对值进行缓存,如果下次再有相同的值,会直接在缓存中取出使用。缓存是通过Integer的内部类IntegerCache来完成的。
    当值超出此范围,会在堆中new出一个对象来存储。
    PS:自动装箱与拆箱是在JDK1.5中出现的。
    内部类IntegerCache
    通过此类可以缓存简单数字。
    缓存的数大小可以由 -XX:AutoBoxCacheMax = 控制。
    jvm初始化时,java.lang.Integer.IntegerCache.high属性可以设置并保存在私有系统属性中。
    规定了low属性的值:-128
  • HashMap特性?

HashMap存储键值对,实现快速存取数据;允许null键/值;线程不安全;不保证有序(比如插入的顺序)

  • 谈谈对java多态的理解

多态是指:父类引用指向子类对象,在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。(同一消息可以根据发送对象的不同而采用多种不同的行为方式。
多态的作用:消除类型之间的耦合关系
实现多态的技术称为:动态绑定(dynamicbinding),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。
实现多态的三要素:继承,重写,父类引用指向子类对象(即,声明是父类,实际指向的是子类的一个对象)

  • String、StringBuffer、StringBuilder区别

String:字符串常量 不适用于经常要改变值得情况,每次改变相当于生成一个新的对象
StringBuffer:字符串变量 (线程安全)
StringBuilder:字符串变量(线程不安全,由于StringBuffer有缓冲区) 确保单线程下可用,效率略高于StringBuffer

  • 什么是内部类?内部类的作用

内部类可直接访问外部类的属性
Java中内部类主要分为成员内部类局部内部类(嵌套在方法和作用域内)、匿名内部类(没构造方法)、静态内部类(static修饰的类,不能使用任何外围类的非static成员变量和方法,不依赖外围类)

  • 进程和线程的区别

进程是cpu资源分配的最小单位,线程是cpu调度的最小单位。
进程之间不能共享资源,而线程共享所在进程的地址空间和其它资源。
一个进程内可拥有多个线程,进程可开启进程,也可开启线程。
一个线程只能属于一个进程,线程可直接使用同进程的资源,线程依赖于进程而存在。

  • 为什么要有线程,而不是仅仅用进程?

线程可以增加并发的程度啊。其实多进程也是可以并发,但是为什么要是线程呢?因为线程是属于进程的,是个轻量级的对象。所以再切换线程时只需要做少量的工作,而切换进程消耗很大。这是从操作系统角度讲。
从用户程序角度讲,有些程序在逻辑上需要线程,比如扫雷,它需要一个线程等待用户的输入,另一个线程的来更新时间。还有一个例子就是聊天程序,一个线程是响应用户输入,一个线程是响应对方输入。如果没有多线程,那么只能你说一句我说一句,你不说我这里就不能动,我还不能连续说。所以用户程序有这种需要,操作系统就要提供响应的机制

  • final,finally,finalize的区别

final:修饰类、成员变量和成员方法,类不可被继承,成员变量不可变,成员方法不可重写(但是里面的内容可以修改)
finally:与try…catch…共同使用,确保无论是否出现异常都能被调用到
finalize:类的方法,垃圾回收之前会调用此方法,子类可以重写finalize()方法实现对资源的回收

  • Serializable 和Parcelable 的区别

Serializable Java 序列化接口 在硬盘上读写 读写过程中有大量临时变量的生成,内部执行大量的i/o操作,效率很低。
Parcelable Android 序列化接口 效率高 使用麻烦 在内存中读写(AS有相关插件 一键生成所需方法) ,对象不能保存到磁盘中

  • Java的异常体系

Throwable,Error,Exception

java面试高级

  • 哪些情况下的对象会被垃圾回收机制处理掉?

1.所有实例都没有活动线程访问。
2.没有被其他任何实例访问的循环引用实例。
3.Java 中有不同的引用类型。判断实例是否符合垃圾收集的条件都依赖于它的引用类型。 要判断怎样的对象是没用的对象。这里有2种方法:
1.采用标记计数的方法: 给内存中的对象给打上标记,对象被引用一次,计数就加1,引用被释放了,计数就减1,当这个计数为0的时候,这个对象就可以被回收了。当然,这也就引发了一个问题:循环引用的对象是无法被识别出来并且被回收的。所以就有了第二种方法:
2.采用根搜索算法: 从一个根出发,搜索所有的可达对象,这样剩下的那些对象就是需要被回收的

  • 什么时候触发GC?

GC_FOR_MALLOC: 表示是在堆上分配对象时内存不足触发的GC。 GC_CONCURRENT:
当我们应用程序的堆内存达到一定量,或者可以理解为快要满的时候,系统会自动触发GC操作来释放内存。 GC_EXPLICIT:
表示是应用程序调用System.gc、VMRuntime.gc接口或者收到SIGUSR1信号时触发的GC。 GC_BEFORE_OOM:
表示是在准备抛OOM异常之前进行的最后努力而触发的GC。

  • Eden空间和两块Survivor空间的工作流程

// 分配了一个又一个对象
放到Eden区
// 不好,Eden区满了,只能GC(新生代GC:Minor GC)了
把Eden区的存活对象copy到Survivor A区,然后清空Eden区(本来Survivor B区也需要清空的,不过本来就是空的)
// 又分配了一个又一个对象
放到Eden区
// 不好,Eden区又满了,只能GC(新生代GC:Minor GC)了
把Eden区和Survivor A区的存活对象copy到Survivor B区,然后清空Eden区和Survivor A区
// 又分配了一个又一个对象
放到Eden区
// 不好,Eden区又满了,只能GC(新生代GC:Minor GC)了
把Eden区和Survivor B区的存活对象copy到Survivor A区,然后清空Eden区和Survivor B区
// …
// 有的对象来回在Survivor A区或者B区呆了比如15次,就被分配到老年代Old区
// 有的对象太大,超过了Eden区,直接被分配在Old区
// 有的存活对象,放不下Survivor区,也被分配到Old区
// …
// 在某次Minor GC的过程中突然发现:
// 不好,老年代Old区也满了,这是一次大GC(老年代GC:Major GC)
Old区慢慢的整理一番,空间又够了
// 继续Minor GC
// …
// …
在回答上面提出的问题?
为什么要分新生代和老年代?
综合使用算法,最优采用分代算法所以分为新生代和老年代两块区域。具体为什么1:2?应该是根据实践测试得出的结果,也可以调整。

  • hashmap.put 为什么是线程不安全的

正常情况下 hashmap 在保存数据时,底层是数组+链表+红黑树 但是 你去源码中看时,发现在hashMap 底层没有加任何的多线程中的锁机制,
比如: synchronize修饰 ,所以在多线程的情况下 hashMap 的单项链表, 可能会变成一个环形的链表,所以这个链表上的Next元素的指向永远不为null, 所以在遍历的时候就是死循环啊。

HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容数组中,在多线程的环境下,存在同时其他的元素也在进行put操作,如果hash值相同,可能出现同时在同一数组下用链表表示,造成闭环,导致在get时会出现死循环,所以HashMap是线程不安全的
HashMap底层是一个Entry数组,当发生hash冲突的时候,hashmap是采用链表的方式来解决的,在对应的数组位置存放链表的头结点。对链表而言,新加入的节点会从头结点加入。在hashmap做put操作的时候会调用到以上的方法。现在假如A线程和B线程同时对同一个数组位置调用addEntry,两个线程会同时得到现在的头结点,然后A写入新的头结点之后,B也写入新的头结点,那B的写入操作就会覆盖A的写入操作造成A的写入操作丢失

  • 说说你对Java反射的理解

JAVA反射机制是在运行状态中, 对于任意一个类, 都能够知道这个类的所有属性和方法; 对于任意一个对象,都能够调用它的任意一个方法和属性。 从对象出发,通过反射(Class类)可以取得取得类的完整信息(类名 Class类型,所在包、具有的所有方法 Method[]类型、某个方法的完整信息(包括修饰符、返回值类型、异常、参数类型)、所有属性 Field[]、某个属性的完整信息、构造器 Constructors),调用类的属性或方法 概括起来就是: 在运行过程中获得类、对象、方法的所有信息。

  • ART与Dalvik的区别

说说你对Java注解的理解

元注解
元注解的作用就是负责注解其他注解。java5.0的时候,定义了4个标准的meta-annotation类型,它们用来提供对其他注解的类型作说明。
1.@Target
2.@Retention
3.@Documented
4.@Inherited

  • String为什么要设计成不可变的?

1、字符串池的需求
字符串池是方法区(Method Area)中的一块特殊的存储区域。当一个字符串已经被创建并且该字符串在 池中,该字符串的引用会立即返回给变量,而不是重新创建一个字符串再将引用返回给变量。如果字符串不是不可变的,那么改变一个引用(如: string2)的字符串将会导致另一个引用(如: string1)出现脏数据。

2、允许字符串缓存哈希码

在java中常常会用到字符串的哈希码,例如: HashMap 。String的不变性保证哈希码始终一,因此,他可以不用担心变化的出现。
这种方法意味着不必每次使用时都重新计算一次哈希码——这样,效率会高很多。

3、安全

String广泛的用于java 类中的参数,如:网络连接(Network connetion),打开文件(opening files
)等等。如果String不是不可变的,网络连接、文件将会被改变——这将会导致一系列的安全威胁。操作的方法本以为连接上了一台机器,但实际上却不是。由于反射中的参数都是字符串,同样,也会引起一系列的安全问题。

  • run()和start()方法区别

这个问题经常被问到,但还是能从此区分出面试者对Java线程模型的理解程度。start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。

Android面试题

  • http和https的区别
  • 自定义View的流程,requestLayout和invalidate的区别
  • 内存泄漏

内存泄露的根本原因:长生命周期的对象持有短生命周期的对象。短周期对象就无法及时释放。

1.静态集合类引起内存泄露

主要是hashmap,Vector等,如果是静态集合 这些集合没有及时setnull的话,就会一直持有这些对象。

2.remove 方法无法删除set集 Objects.hash(firstName, lastName);

经过测试,hashcode修改后,就没有办法remove了。

3.observer 我们在使用监听器的时候,往往是addxxxlistener,但是当我们不需要的时候,忘记removexxxlistener,就容易内存leak。

广播没有unregisterrecevier

4.各种数据链接没有关闭,数据库contentprovider,io,sokect等。cursor

5.内部类:

java中的内部类(匿名内部类),会持有宿主类的强引用this。

所以如果是new Thread这种,后台线程的操作,当线程没有执行结束时,activity不会被回收。

Context的引用,当TextView 等等都会持有上下文的引用。如果有static drawable,就会导致该内存无法释放。

6.单例

单例 是一个全局的静态对象,当持有某个复制的类A是,A无法被释放,内存leak。

  • 内存溢出

当程序需要申请一段“大”内存,但是虚拟机没有办法及时的给到,即使做了GC操作以后

这就会抛出 OutOfMemoryException 也就是OOM。避免如下:

1、减少内存对象的占用,比如

ArrayMap/SparseArray代替hashmap

避免在android里面使用Enum

减少bitmap的内存占用

inSampleSize:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。

decode format:解码格式,选择ARGB_8888/RBG_565/ARGB_4444/ALPHA_8,存在很大差异。

减少资源图片的大小,过大的图片可以考虑分段加载

2、内存对象的重复利用

大多数对象的复用,都是利用对象池的技术。

listview/gridview/recycleview contentview的复用

inBitmap 属性对于内存对象的复用ARGB_8888/RBG_565/ARGB_4444/ALPHA_8

这个方法在某些条件下非常有用,比如要加载上千张图片的时候。

避免在ondraw方法里面 new对象

StringBuilder 代替+

  • ANR 是什么?怎样避免和解决 ANR

ANR->Application Not Responding

也就是在规定的时间内,没有响应。

三种类型:

1). KeyDispatchTimeout(5 seconds) —主要类型按键或触摸事件在特定时间内无响应

2). BroadcastTimeout(10 seconds) —BroadcastReceiver在特定时间内无法处理完成

3). ServiceTimeout(20 seconds) —小概率类型 Service在特定的时间内无法处理完成

为什么会超时:事件没有机会处理 & 事件处理超时

怎么避免ANR

ANR的关键

是处理超时,所以应该避免在UI线程,BroadcastReceiver 还有service主线程中,处理复杂的逻辑和计算

而交给work thread操作。

1)避免在activity里面做耗时操作,oncreate & onresume

2)避免在onReceiver里面做过多操作

3)避免在Intent Receiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。

4)尽量使用handler来处理UI thread & workthread的交互。

如何解决ANR

首先定位ANR发生的log:

04-0113:12:11.572I/InputDispatcher(220):Applicationisnot
responding:Window{2b263310com.android.email/com.android.email.activity.SplitScreenActivitypaused=false}.5009.8ms
sinceevent,5009.5ms since waitstartedCPUusagefrom4361ms to699ms
ago----CPU在ANR发生前的使用情况04-0113:12:15.872E/ActivityManager(220):100%TOTAL:4.8%user+7.6%kernel+87%iowait04-0113:12:15.872E/ActivityManager(220):CPUusagefrom3697ms
to4223ms later:–ANR后CPU的使用量

从log可以看出,cpu在做大量的io操作。

所以可以查看io操作的地方。

当然,也有可能cpu占用不高,那就是 主线程被block住了。

  • websocket 和socket区别

Socket是传输控制层协议,WebSocket是应用层协议。Socket其实并不是一个协议,而是为了方便使用TCP或UDP而抽象出来的一层,是位于应用层和传输控制层之间的一组接口。当两台主机通信时,必须通过Socket连接,Socket则利用TCP/IP协议建立TCP连接。TCP连接则更依靠于底层的IP协议,IP协议的连接则依赖于链路层等更低层次。WebSocket同HTTP一样是应用层的协议,但是它是一种双向通信协议,是建立在TCP之上的。websocket通信过程如下:

  1. 浏览器、服务器建立TCP连接,三次握手。这是通信的基础,传输控制层,若失败后续都不执行。

  2. TCP连接成功后,浏览器通过HTTP协议向服务器传送WebSocket支持的版本号等信息。(开始前的HTTP握手)

  3. 服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据。

  4. 当收到了连接成功的消息后,通过TCP通道进行传输通信。

Kotlin模块

你可能感兴趣的:(安卓面试整理(持续不定时记录中。。。))