Android面试常见问题

Posted by Don on December 12, 2018

导读:最近有点时间,所以整理了一下Android面试中经常遇到的一些问题,大部分都是互联网上收集的,很多原作者的链接没有了,非常抱歉,如有问题可直接与我联 系,thanks.

1、Service 和Thread 的区别

a)、Thread: thread是程序执行的最小单元,他是CPU的基本单位。可以用thread来执行一些异步操作。
b)、Service: service是android的一种机制,当它运行的时候如果是LocalService,那么对应的Service运行在主线程的main线程上。如果是RemoteService,那么对 应的service则运行在独立进程的main线程上。

既然这样,那我们为什么还要用Service呢?其实这跟android的系统机制有关,我们先拿Thread来说。Thread的运行是独立于Activity的,也就是说当Activity被 finish之后,如果你没有主动停止Thread或者Thread里的run方法没有执行完毕的话,Thread也会一直执行(PS:当activity被切换到后台时如果系统紧张,系统会随时 结束掉你的process).因此这里会出现一个问题,当activity被finish之后,你不在持有该Thread的引用。另外一方面,你没有办法在不同的activity中对同一个 Thread进行控制。

举个例子:如果你的Thread需要不停的隔一段时间就要连接服务器做某种同步的话,该Thread需要在Activity没有start的时候也在运行。这个时候当你start一个 activity就没办法在该activity里控制之前创建的Thread。因此,你便需要创建并启动一个Service,在Service里面创建、运行并控制该Thread,这样便解决了该问题 (因为任何Activity都可以控制同一Service,而系统也只会创建一个相应的Service实例)。

因此你可以把Service想象成一种消息服务,而你可以在任何有Context的地方调用Context.startService、Context.stopService、Context.bindService、 Context.unbindService来控制它,你也可以在Service里注册BroadcastReceiver,在其他地方通过发送broadcast来控制它,当然,这些都是Thread做不到的。 根据进程的优先级,Thread在后台运行(Activity stop)的优先级低于后台运行的Service,如果执行系统资源紧张,会优先杀死前一种,后台运行的service一般不会 被杀死,如果杀死,系统空闲时会重新启动service。
service运行在主线程中,如果在Service中做耗时任务要开启子线程。

2、Handler是什么?

Handler是android给我们提供用来更新UI的一套机制,也是一套消息处理机制,我们可以发送消息,也可以通过它来处理消息。

a)、为什么要用Handler?

android在设计的时候就封装了一套消息创建、传递、处理机制。如果不遵循这样的机制就没办法更新UI信息,就会抛出异常 (android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.)

b)、android为什么要设计通过Handler机制更新UI呢?

最根本的问题是解决多线程并发的问题。
假设如果在一个activity中,有多个线程去更新UI,并且都没有加锁机制,那么会产生什么样的问题?
更新界面错乱
如果对更新的UI操作都进行加锁处理的话又会产生什么样的问题?
1)、上锁会让UI控件变得复杂和低效;
2)、上锁后会阻塞某些进程的执行
出于对以上问题的考虑,android给我们提供了一套更新UI的机制,我们只需要遵循这样的机制就可以了,根本不用关心多线程的问题,所有更新UI操作都是在主线程的 消息队列当中去轮流处理的

c)、handler的原理

Handler
封装了消息的发送,解决多线程并发引发的问题,地址就是Messagetarget(默认情况发送给自己)

Looper
1)、内部包含一个消息队列也就是MessageQueue,所有的handler发送的消息都走向这个消息队列
2)、Looper.Looper方法,就是一个死循环,不断的从MessageQueue中取消息,如有消息就处理,没有消息就阻塞

Handler、Looper、MessgaeQueue三者的引用关系
Handler 中有MessageQueue对象、Looper对象
Looper 中有MessageQueue对象(和Handler中的是同一个对象)
MessageQueue 中有 Message(而Message中有Handler(target属性))
详情可参考:手写Handler

总结:handler负责发送消息,Looper负责接收Handler发送的消息,并通过Message的target.dispatchMessage(msg)发消息回传给Handler自己,MessageQueue就是一个消息存储容器(MessageQueue的数据结构是一个单向链表,先进先出)

3、如何保证Service不被杀死

a)、提高进程的优先级,降低进程被杀死的概率

1)、利用Activity提升权限       
例:监控手机锁屏解锁事件,在屏幕锁屏时启动1个像素的Activity,在用户解锁后将Activity销毁。        
适用场景:本方案主要解决第三方应用及系统管理工具在检测到锁屏事件后一段时间(一般为5分钟以内)内会杀死后台进程,
已达到省电的目的问题   

  2)、利用Notification提升权限
Android中的Service的优先级为4,通过setForeground接口可以将后台Service设置为前台Service,是进程的优先级由4提升为2, 从而使进程的优先级仅仅低于用户当前正在交互的进程,与可见进程的优先级一致。

  3)、AndroidManifest设置
在AndroidManifest.xml文件中对于intent-filter可以通过android:priority=”1000”这个属性设置最高优先级,1000是最高值, 数字越小,优先级越低,同时也适用于广播。

b)、进程死后,进行拉活

  1)、利用系统广播拉活
例:在发生特定系统事件时(如:屏幕亮灭、锁屏解锁、网络变化,开机等),系统会发出响应的广播,通过在AndroidManifest中“静态”注册对应的广播监听器, 即可在发生响应的事件拉活。

  2)、利用系统Service机制拉活    
例:将Service设置为START_STICKY,利用系统机制在 Service 挂掉后自动拉活

  3)、利用Native进程进行拉活  
例:利用 Linux 中的 fork 机制创建 Native 进程,在 Native 进程中监控主进程的存活,当主进程挂掉后, 在Native进程中立即对主进程进行拉活。

  4)、自定义广播拉活
例:service +broadcast 方式,就是当service走ondestory的时候,发送一个自定义的广播,当收到广播的时候,重新启动service

c)、通过推送拉活

根据终端不同,在小米手机(包括 MIUI)接入小米推送、华为手机接入华为推送;其他手机可以考虑接入腾讯信鸽或极光推送与小米推送做 A/B Test。
国外版应用可考虑接入 Google 的 GCM。       

4、Activity和Fragment的生命周期

5、Activity的任务栈

在AndroidManifest.xml中使用android:launchMode=”standard|singleInstance|singleTask|SingleTop”来控制activity的任务栈。

任务栈是一种后进先出的结构。位于栈顶的Activity处于焦点状态,当按下back键时,栈内的activity会一个一个的出栈,并调用onDestory()方法。如果栈内没有 activity,那么系统就会回收这个栈,每个app默认只有一个栈,以app的包名来命名.

standard:标准模式

每次启动activity都会创建一个新的的activity实例,并且将其压入任务栈栈顶,而不管这个activity是否已经存在。

singleTop:顶单例模式(栈顶复用模式)

这种模式和standard模式基本相似,但有一点不同,当要被启动的目标activity已经位于栈顶时,系统不会重新创建目标 activity的实例,而是直接复用已有的activity实例。

singleTask:内单例模式(栈内复用模式)

采用这种加载模式的Activity在同一个任务栈内只有一个实例,当采用此模式启动activity时,可分为如下三种情况:
a)、如果要启动的activity不存在,系统则会创建activity的实例,并将它加到栈顶。
b)、如果要启动的activity已经位于栈顶,此时与singleTop相同
c)、如果要启动的activity已经存在,但是没有位于栈顶,系统将会把位于该activity上面的所有的activity移出栈,从而使得目标acitivity转入栈顶。

singleInstance:全局单例模式

这种加载模式,系统保证无论从那个任务栈中启动目标activity,只会创建一个目标activity实例,并会使用一个全新的的任务栈来装载该activity实例。
采用这种方式启动activity,分为如下2种情况:
a)、如果将要启动的目标activity不存在,系统会先创建一个全新的栈,再创建目标activity的实例,并将他加入新的栈的栈顶。 b)、如果要启动的activity已经存在,无论它位于哪个应用程序中,无论位于那个栈中,系统都会把该activity所在的栈转到前台,从而使该activity显示出 来。

6、android中有哪几种解析xml的类,官方推荐哪种?以及他们的原理和区别

  • DOM解析
    优点:
    a)、XML树在内存中完整存储,因此可以直接修改其数据和结构.
    b)、可以通过该解析器随时访问XML树中的任何一个节点.
    c)、DOM解析器的API在使用上也现对比较简单  
    缺点:  
    如果XML文档体积比较大时,将文档读入内存是非常消耗系统资源的。    
  • SAX解析
    优点:
    SAX对内存的要求比较低,因为它让开发人员自己来决定所要处理的标签。特别是当开发人员只需要处理文档中所包含的部分数据时,SAX这种扩展能力得到了更好的体现。  
    缺点:
    用SAX进行XML解析时,需要顺序执行,所以很难访问到同一文档中的不同数据。此外,在基于该方式的解析编码过程也相对复杂。  
               
  • Xmlpull解析  
    android SDK提供了xmlpull api,xmlpull和sax类似,是基于流(stream)操作文件,然后根据节点事件回调开发者编写的处理 程序,因为是基于流的处理,因此xmlpull和sax都比较节约内存资源,不会像dom那样要把所有节点以橡树的形式展开在内存中,xmlpull 比sax更简明,而且不需要扫描完整个流。 

7、Jar和Aar的区别

jar里只有代码,aar里面除了代码还包括资源文件,比如drawable文件,xml资源文件。对于一些不常变动的Android Library,我们可以直接引用aar, 加快编译速度。  

8、Fragment特点

Fragment可以作为Activity界面的一部分出现;
可以在一个Activity中同时出现多个Fragment,并且一个Fragment也可以在多个Activity中使用;    
在Activity运行过程中,可以添加、移除或者替换Fragment;    
Fragment可以响应自己的输入事件,并且有自己的生命周期,它的生命周期会受宿主Activity的生命周期影响。

9、怎么考虑数据传输的安全性

如果如果应用对传输的数据没有任何安全措施,攻击者设置的钓鱼网络中更改DNS服务器。这台服务器可以获取用户信息, 或者充当中间人与源服务器交换数据。在SSL/TLS通信中,客户端可以通过数字证书判断服务器是否可信,并采用证书的公钥与服务器进行加密通讯。

10、移动端获取网络数据优化的几个点

连接复用:节省连接建立时间,如开启keep-alive.
对于android来说默认情况下HttpURLConnetctionh和HttpClient都开启了keep-alive。只是2.2之前HttpURLConnection存在影响连接池的bug.              

请求合并:即将多个请求合并为一个请求,比较常见的就是网页中的css image sprites。

减少请求数据的大小: 对于post请求,body可以做gzip压缩的,header也可以做数据压缩(不过只支持Http2.0).                             返回数据的body也可以做gzip压缩,body数据体积可以缩小到原来的30%左右。(也可以考虑压缩返回的json数据的key数据体积,尤其是针对 返回数据格式变化不大的情况,支付宝聊天返回的数据用到了)
根据用户的当前的网络质量来判断下载什么质量的图片(电商用的较多)                            

11、导致内存泄漏的原因有哪些?如何避免内存溢出?

请查阅笔者内存专题文章 Android 面试之内存优化篇

12、Retrofit + okHttp + RxJava

Retrofit: Retrofit是简化HTTP请求的库,retrofit 2背后的Http client只支持okHttp。

原理:
通过java接口以及注解来描述网络请求,并用动态代理的方式,在调用接口方法前后(before/after)注入自己的方法,before通过接口方法和注解生成网络请求的request,after通过client调 用相应的网络框架(默认okhttp)去发起网络请求,并将返回的response通过converterFactorty转换成相应的数据model,最后通过calladapter转换成其他数据方式(如rxjavaObservable)
详情请参考:https://academy.realm.io/cn/posts/droidcon-jake-wharton-simple-http-retrofit-2/

RxJava: rxJava为android 提供了一种全新的异步操作机制,详情请参考:https://academy.realm.io/cn/posts/360andev-christina-lee-intro-rxjava-java-android/

okHttp:

12、面向对象

笔者曾专门介绍过面向对象思想,有兴趣的可以点此查看

13、Android 两种序列化的区别与作用

Serializable: 使用简单,但是需要做大量的I/O操作,开销很大,效率低,适用于序列化到本地或网络传输.
Parcelable: 直接在内存上读写,开销小,效率高,适用于运行时数据传递,比如使用Intent,Bundle 等传递数据; 但是Parcelable无法将数据进行持久化,因此在将数据保存在磁盘的时候,仍然需要使用Serializable

14、Android几种数据存储方式的应用场景

  • SharedPreferences:
    适用于保存少量数据,且这些数据的格式非常简单:字符串型、基本类型的值
  • File 存储
    适用于存储多媒体文件, 文件缓存
  • Sqlite数据库
    适用于对保存的数据进行一些复杂的操作,或者数据量很大,超出了文本文件和Preference的性能的范围的数据
  • ContentProvider
    适用于进程(应用程序)间数据共享的数据
  • 网络存储
    由于手机内存的限制,和数据实时性的要求,手机APP大部分的数据来源还是来自于服务器,通过调动接口获取,本地只是作为缓存,辅助存储用。比如天气数据等

15、Merge、ViewStub的作用

Merge:

merge用于消除视图层次结构中的冗余视图,主要用于辅助include标签,例如根布局是Linearlayout,那么我们又include一个LinerLayout布局就没意义了,反而会减慢UI加载速度

使用场景
  1. 根布局是FrameLayout且不需要设置background或padding等属性,可以用merge代替,因为Activity的ContentView父元素就是FrameLayout,所以可以用merge消除只剩一个.
  2. 某布局作为子布局被其他布局include时,使用merge当作该布局的顶节点,这样在被引入时顶结点会自动被忽略,而将其子节点全部合并到主布局中.
  3. 自定义View如果继承LinearLayout(ViewGroup),建议让自定义View的布局文件根布局设置成merge,这样能少一层结点.
    注意事项
  4. 因为merge标签并不是View,所以在通过LayoutInflate.inflate()方法渲染的时候,第二个参数必须指定一个父容器,且第三个参数必须为true,也就是必须为merge下的视图指定一个父亲节点.
  5. 因为merge不是View,所以对merge标签设置的所有属性都是无效的.
  6. merge标签必须使用在根布局
  7. ViewStub标签中的layout布局不能使用merge标签

    ViewStub

    ViewStub 标签最大的优点是当你需要时才会加载,使用它并不会影响UI初始化时的性能.各种不常用的布局像进度条、显示错误消息等可以使用ViewStub标签,以减少内存使用量,加快渲染速度.ViewStub是一个不可见的,实际上是把宽高设置为0的View.效果有点类似普通的view.setVisible(),但性能体验提高不少

第一次初始化时,初始化的是ViewStub View,当我们调用inflate()或setVisibility()后会被remove掉,然后在将其中的layout加到当前view hierarchy中

注意事项
  1. ViewStub标签不支持merge标签
  2. 使用ViewStub时应先判断ViewStub(做单例)是否已经加载过,因为ViewStub的inflate只能被调用一次,第二次调用会抛出异常,setVisibility可以被调用多次,但不建议这么做(ViewStub 调用过后,可能被GC掉,再调用setVisibility()会报异常)
  3. 为ViewStub赋值的android:layout_XX属性会替换待加载布局文件的根节点对应的属性

16、Android App的启动过程?怎么加速启动App?

  • app启动方式
    启动方式分为:冷启动、热启动、温启动。
    1. 冷启动
      启动app时,后台没有app的进程,或者进程被killed,这叫冷启动。冷启动因为系统会重新创建一个新的进程分配给它,所以会先创建和初始化Application类,再创建和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上。
    2. 热启动
      启动app时,后台已有app的进程(例:按back键、home键,应用虽然会退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,这种启动会从已有的进程中来启动应用,这个方式叫热启动。
      热启动因为会从已有的进程中来启动,所以热启动就不会走Application这步了,而是直接走MainActivity(包括一系列的测量、布局、绘制),所以热启动的过程只需要创建和初始化一个MainActivity就行了,而不必创建和初始化Application,因为一个应用从新进程的创建到进程的销毁,Application只会初始化一次。
    3. 温启动
      介于冷启动和热启动之间, 一般来说在以下两种情况下发生: 用户back退出了App, 然后又启动. App进程可能还在运行, 但是activity需要重建。用户退出App后, 系统可能由于内存原因将App杀死, 进程和activity都需要重启, 但是可以在onCreate中将被动杀死锁保存的状态(saved instance state)恢复。 通过三种启动状态的相关描述, 可以看出我们要做的启动优化其实就是针对冷启动. 热启动和温启动都相对较快.
  • App启动过程
    App启动进程大致可以分为3步:
    1. 创建进程
      当用户点击app的启动图标后,AMS(ActivityManagerService)会创建一个新的进程分配给应用
    2. 绑定Application
      接下来要做的就是将进程和指定的Application绑定起来,这个是通过ActivityThread对象中调用bindApplication()方法完成的
    3. 启动Activity 经过前两个步骤之后, 系统已经拥有了该application的进程. 后面的调用顺序就是普通的从一个已经存在的进程中启动一个新进程的activity了.

更多详情可参考[译]Android Application启动流程分析

  • 扩展
    1. AMS(ActivityManagerService) Activity的管理者。其实除了Activity,AMS也管Service等组件信息,另外AMS还管理Process信息。
    2. ActivityThread
      每一个App进程有一个主线程,它由ActivityThread描述。它负责这个App进程中各个Activity的调度和执行,以及响应AMS的操作请求等。
    3. ApplicationThread
      AMS和Activity通过它进行通信。对于AMS而言,ApplicationThread代表了App的主线程。简而言之,它是AMS与ActivityThread进行交互的接口。注意ActivityThread和ApplicationThread之间的关系并不像Activity与Application。后者的关系是Application中包含了多个Activity,而前者ActivityThread和ApplicationThread是同一个东西的两种”View”,ApplicationThread是在AMS眼中的ActivityThread。
  • App加速启动优化方案
    1. 利用提前展示出来的Window,快速展示出来一个界面,给用户快速反馈的体验(闪屏页面);
    2. 避免在启动时做密集沉重的初始化(Heavy app initialization);
    3. 定位问题:避免I/O操作、反序列化、网络操作、布局嵌套等。

更多详情可参考Android性能优化(一)之启动加速35%

17、理解Activity,View,Window三者关系

这个问题真的很不好回答。所以这里先来个算是比较恰当的比喻来形容下它们的关系吧。Activity像一个工匠(控制单元),Window像窗户(承载模型),View像窗花(显示视图)LayoutInflater像剪刀,Xml配置像窗花图纸。

1:Activity构造的时候会初始化一个Window,准确的说是PhoneWindow。

2:这个PhoneWindow有一个“ViewRoot”,这个“ViewRoot”是一个View或者说ViewGroup,是最初始的根视图。

3:“ViewRoot”通过addView方法来一个个的添加View。比如TextView,Button等

4:这些View的事件监听,是由WindowManagerService来接受消息,并且回调Activity函数。比如onClickListener,onKeyDown等。

17、静态属性和静态方法是否可以被继承?是否可以被重写?以及原因?

可继承 不可重写 被隐藏

如果子类里面定义了静态方法和属性,那么这时候父类的静态方法或属性称之为”隐藏”。如果你想要调用父类的静态方法和属性,直接通过父类名.方法或变量名完成。

18、成员内部类、静态内部类、局部内部类和匿名内部类的理解,以及项目中的应用

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

最重要的作用:可以实现多重继承
大家都知道Java只能继承一个类,它的多重继承在我们没有学习内部类之前是用接口来实现的。但使用接口有时候有很多不方便的地方。比如我们实现一个接口就必须实现它里面的所有方法。而有了内部类就不一样了。它可以使我们的类继承多个具体类或抽象类。而内部类使得多重继承的解决方案变得更加完整。(例如:n个内部类继承n个类,那么外部类就拥有了这n个类的属性和方法,也就间接地实现了多继承)

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

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

20、Java中实现多态的机制是什么?

答:方法的重写Overriding和重载Overloading是Java多态性的不同表现
重写Overriding是父类与子类之间多态性的一种表现
重载Overloading是一个类中多态性的一种表现.

21、说说你对Java反射的理解

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

自己的总结: 在运行过程中获得类、对象、方法的所有信息。

22、List,Set,Map的区别

Set是最简单的一种集合。集合中的对象不按特定的方式排序,并且没有重复对象。
Set接口主要实现了两个实现类:HashSet: HashSet类按照哈希算法来存取集合中的对象,存取速度比较快
TreeSet :TreeSet类实现了SortedSet接口,能够对集合中的对象进行排序。

List的特征是其元素以线性方式存储,集合中可以存放重复对象。
ArrayList() : 代表长度可以改变得数组。可以对元素进行随机的访问,向ArrayList()中插入与删除元素的速度慢。
LinkedList(): 在实现中采用链表数据结构。插入和删除速度快,访问速度慢。

Map 是一种把键对象和值对象映射的集合,它的每一个元素都包含一对键对象和值对象。 Map没有继承于Collection接口 从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。
HashMap:Map基于散列表的实现。插入和查询“键值对”的开销是固定的。可以通过构造器设置容量capacity和负载因子load factor,以调整容器的性能。
LinkedHashMap: 类似于HashMap,但是迭代遍历它时,取得“键值对”的顺序是其插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一点。而在迭代访问时发而更快,因为它使用链表维护内部次序。
TreeMap : 基于红黑树数据结构的实现。查看“键”或“键值对”时,它们会被排序(次序由Comparabel或Comparator决定)。TreeMap的特点在 于,你得到的结果是经过排序的。TreeMap是唯一的带有subMap()方法的Map,它可以返回一个子树。
WeakHashMao :弱键(weak key)Map,Map中使用的对象也被允许释放: 这是为解决特殊问题设计的。如果没有map之外的引用指向某个“键”,则此“键”可以被垃圾收集器回收。

22、ArrayMap和HashMap的对比

1、存储方式不同
HashMap内部有一个HashMapEntry<K, V>[]对象,每一个键值对都存储在这个对象里,当使用put方法添加键值对时,就会new一个HashMapEntry对象
2、添加数据时扩容时的处理不一样,HashMap进行了new操作,重新创建对象,开销很大。ArrayMap用的是copy数据,所以效率相对要高。
3、ArrayMap提供了数组收缩的功能,在clear或remove后,会重新收缩数组,释放空间
4、ArrayMap采用二分法查找;

23、HashMap和HashTable的区别

HashMap不是线程安全的,效率高一点
hashtable是线程安全,不允许有null的键和值,效率稍低

24、HashMap与HashSet的区别

hashMap:HashMap实现了Map接口,HashMap储存键值对,使用put()方法将元素放入map中,HashMap中使用键对象来计算hashcode值,HashMap比较快,因为是使用唯一的键来获取对象。
HashSet实现了Set接口,HashSet仅仅存储对象,使用add()方法将元素放入set中,HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,如果两个对象不同的话,那么返回false。HashSet较HashMap来说比较慢。

24、HashSet与HashMap怎么判断集合元素重复?

HashSet不能添加重复的元素,当调用add(Object)方法时候, 首先会调用Object的hashCode方法判hashCode是否已经存在,如不存在则直接插入元素;如果已存在则调用Object对象的equals方法判断是否返回true,如果为true则说明元素已经存在,如为false则插入元素。

25、ArrayList和LinkedList的区别,以及应用场景

ArrayList是基于数组实现的,ArrayList线程不安全。
LinkedList是基于双链表实现的
应用场景:
简单而言,当应用场景中有很多的add/remove操作,只有少量的随机访问操作时, 应该选择LinkedList;其他场景下,考虑使用ArrayList.

26、数组和链表的区别

数组:是将元素在内存中连续存储的;它的优点:因为数据是连续存储的,内存地址连续,所以在查找数据的时候效率比较高;它的缺点:在存储之前,我们需要申请一块连续的内存空间,并且在编译的时候就必须确定好它的空间的大小。在运行的时候空间的大小是无法随着你的需要进行增加和减少而改变的,当数据两比较大的时候,有可能会出现越界的情况,数据比较小的时候,又有可能会浪费掉内存空间。在改变数据个数时,增加、插入、删除数据效率比较低。

链表:是动态申请内存空间,不需要像数组需要提前申请好内存的大小,链表只需在用的时候申请就可以,根据需要来动态申请或者删除内存空间,对于数据增加和删除以及插入比数组灵活。还有就是链表中数据在内存中可以在任意的位置,通过应用来关联数据(就是通过存在元素的指针来联系)

27、开启线程的三种方式?

Java有三种创建线程的方式,分别是继承Thread类、实现Runable接口和使用线程池

28、run()和start()方法区别

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

29、在Java中wait和seelp方法的不同

Java程序中wait 和 sleep都会造成某种形式的暂停,它们可以满足不同的需要。wait()方法用于线程间通信,如果等待条件为真且其它线程被唤醒时它会释放锁,而sleep()方法仅仅释放CPU资源或者让当前线程停止执行一段时间,但不会释放锁。

30、谈谈wait/notify关键字的理解

等待对象的同步锁,需要获得该对象的同步锁才可以调用这个方法,否则编译可以通过,但运行时会收到一个异常:IllegalMonitorStateException。

调用任意对象的 wait() 方法导致该线程阻塞,该线程不可继续执行,并且该对象上的锁被释放。

唤醒在等待该对象同步锁的线程(只唤醒一个,如果有多个在等待),注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。

调用任意对象的notify()方法则导致因调用该对象的 wait()方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。

31、如何保证线程安全?

1.synchronized; 2.Object方法中的wait,notify; 3.ThreadLocal机制 来实现的。

32、synchronized 和volatile 关键字的区别

1.volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
2.volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
3.volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
4.volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
5.volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化

33、Java中堆和栈有什么不同? 为什么把这个问题归类在多线程和并发面试题里?因为栈是一块和线程紧密相关的内存区域。每个线程都有自己的栈内存,用于存储本地变量,方法参数和栈调用,一个线程中存储的变量对其它线程是不可见的。而堆是所有线程共享的一片公用内存区域。对象都在堆里创建,为了提升效率线程会从堆中弄一个缓存到自己的栈,如果多个线程使用该变量就可能引发问题,这时volatile 变量就可以发挥作用了,它要求线程从主存中读取变量的值。

34、onNewIntent()的调用时机

ActivityA跳转到ActivityB,再跳转到ActivityC,此时需要从ActivityC跳转到ActivityA,并给ActivityA传递数据,那么系统会根据ActivityA的加载方式(launchMode)调用不同的方法:
当ActivityA的LaunchMode为SingleTop时,如果ActivityA在栈顶,且现在要再启动ActivityA,这时会调用onNewIntent()方法

当ActivityA的LaunchMode为SingleInstance,SingleTask时,如果已经ActivityA已经在堆栈中,那么此时会调用onNewIntent()方法

当ActivityA的LaunchMode为Standard时,由于每次启动ActivityA都是启动新的实例,和原来启动的没关系,所以不会调用原来ActivityA的onNewIntent方法,仍然调用的是onCreate方法