面试

Android面试题

2017-07-04  本文已影响264人  Alfred泉

最新整理的面试题

点我

需要继续学习的

  1. Android中为啥会65535的限制,解释下原因.
    http://blog.csdn.net/u011733020/article/details/71481395
  2. 解释下Android Lint的工作机制原理
  3. PathClassloader和DexClassloader的区别
  4. 算法实现下如果统计出Activity中的view树的深度.
  5. okHttp的工作原理以及缓存机制
  6. blockingqueue的实现原理,里面有几把锁.

Android面试题

  1. JVM垃圾回收机制。
    http://blog.csdn.net/xiajian2010/article/details/17376453

http://www.jianshu.com/p/8fa373ceb552?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io

http://www.blogjava.net/fancydeepin/archive/2013/09/29/jvm_heep.html

Android 中的Dalvik和ART是什么,有啥区别?
http://www.jianshu.com/p/58f817d176b7
Dalvik VM 和JVM的区别
https://www.zhihu.com/question/20207106

2 Activity的启动模式
http://droidyue.com/blog/2015/08/16/dive-into-android-activity-launchmode/index.html http://blog.csdn.net/zhangjg_blog/article/details/10923643

3 service和线程的关系 怎么让一个service不死掉,一直运行,service生命周期 service和Intentservice的区别
http://blog.csdn.net/guolin_blog/article/details/11952435

Service两种启动方式的区别
http://www.jianshu.com/p/2fb6eb14fdec

IntentService有以下特点:
(1) 它创建了一个独立的工作线程来处理所有的通过onStartCommand()传递给服务的intents。
(2) 创建了一个工作队列,来逐个发送intent给onHandleIntent()。
(3) 不需要主动调用stopSelft()来结束服务。因为,在所有的intent被处理完后,系统会自动关闭服务。
(4) 默认实现的onBind()返回null
(5) 默认实现的onStartCommand()的目的是将intent插入到工作队列中

4 Activity生命周期
http://peiquan.blog.51cto.com/7518552/1277373
http://blog.csdn.net/johnsonblog/article/details/7838108

5 handler AsyncTask可能导致内存泄漏 context可能导致的内存泄露
http://droidyue.com/blog/2015/04/12/avoid-memory-leaks-on-context-in-android/index.html

http://droidyue.com/blog/2014/11/08/bad-smell-of-asynctask-in-android/

http://droidyue.com/blog/2014/12/28/in-android-handler-classes-should-be-static-or-leaks-might-occur/

http://gold.xitu.io/entry/56d64b9e816dfa005943a55c

6 自定义view
http://blog.csdn.net/guolin_blog/article/details/12921889
自定义view四个构造函数详解
http://blog.csdn.net/zhao123h/article/details/52210732

7 设计模式

8 工作中遇到一次最大困难时什么 你最后是怎么解决的 如果让你再来一次你是否能够解决的更好

9 listview里面的item怎么优化,如果item的layout不同你要怎么优化 listview 多type 复用 convertview 的解决方法
http://www.eoeandroid.com/thread-246995-1-1.html?_dsign=8fb6add2

10 职业规划

11 JAVA 中堆和栈的区别
http://droidyue.com/blog/2014/12/07/differences-between-stack-and-heap-in-java/index.html

12 HandlerThread
http://blog.csdn.net/feiduclear_up/article/details/46840523

13 缓存Cache
http://blog.csdn.net/g uolin_blog/article/details/28863651

14 Touch事件传递机制
http://www.open-open.com/lib/view/open1422428386548.html

15 解决屏幕适配
http://blog.csdn.net/lmj623565791/article/details/49990941

16 handler Asynctask内部原理分析
http://blog.csdn.net/lmj623565791/article/details/38377229
http://blog.csdn.net/lmj623565791/article/details/38614699

17 动画
http://blog.csdn.net/guolin_blog/article/details/43536355

18.recyclerView 和 listview 的区别

RecyclerView的ViewHolder规范化
RecyclerView可以实现线性布局效果,网格布局效果,瀑布流布局效果
ListView具有setEmptyView() addHeaderView() addFooterView()
RecyclerView支持局部刷新
RecyclerView轻松实现item动画效果
RecyclerView没有setOnItemClickListener() setOnItemLongClickListener() 而是实现了RecyclerView.OnItemTouchListener()
RecyclerView自定义分割线

19.View的绘制流程
从ViewRoot的performTraversals()方法开始依次调用perfromMeasure、performLayout和performDraw这三个方法。这三个方法分别完成顶级View的measure、layout和draw三大流程,其中perfromMeasure会调用measure,measure又会调用onMeasure,在onMeasure方法中则会对所有子元素进行measure,这个时候measure流程就从父容器传递到子元素中了,这样就完成了一次measure过程,接着子元素会重复父容器的measure,如此反复就完成了整个View树的遍历.
同理,performLayout和performDraw也分别完成perfromMeasure类似的流程。通过这三大流程,分别遍历整棵View树,就实现了Measure,Layout,Draw这一过程,View就绘制出来了。

HTTP和HTTPS的区别:
1、HTTP协议使用默认80端口,HTTPS协议使用443端口
2、HTTPS协议需要到CA申请证书,一般免费的证书较少,需要交费
3、HTTP信息是明文传输,HTTPS使用具有安全性的SSL加密传输信息

http1和http2的区别:
1.http2可以同时发多个请求
2.http2会压缩,体积小
3.http2服务器会推送

AsyncTask
1、设置当前AsyncTask的状态为RUNNING,上面的switch也可以看出,每个异步任务在完成前只能执行一次。
2、执行了onPreExecute(),当前依然在UI线程,所以我们可以在其中做一些准备工作。
3、将我们传入的参数赋值给了mWorker.mParams ,mWorker为一个Callable的子类,且在内部的call()方法中,调用了doInBackground(mParams),然后得到的返回值作为postResult的参数进行执行;postResult中通过sHandler发送消息,最终sHandler的handleMessage中完成onPostExecute的调用。
4、exec.execute(mFuture),mFuture为真正的执行任务的单元,将mWorker进行封装,然后由sDefaultExecutor交给线程池进行执行。

如果现在大家去面试,被问到AsyncTask的缺陷,可以分为两个部分说,在3.0以前,最大支持128个线程的并发,10个任务的等待。在3.0以后,无论有多少任务,都会在其内部单线程执行;

Handler源码分析总结
1、首先Looper.prepare()在本线程中保存一个Looper实例,然后该实例中保存一个MessageQueue对象;因为Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中只会存在一个。
2、Looper.loop()会让当前线程进入一个无限循环,不端从MessageQueue的实例中读取消息,然后回msg.target.dispatchMessage(msg)方法。
3、Handler的构造方法,会首先得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue想关联。
4、Handler的sendMessage方法,会给msg的target赋值为handler自身,然后加入MessageQueue中。
5、在构造Handler实例时,我们会重写handleMessage方法,也就是msg.target.dispatchMessage(msg)最终调用的方法。

好了,总结完成,大家可能还会问,那么在Activity中,我们并没有显示的调用Looper.prepare()和Looper.loop()方法,为啥Handler可以成功创建呢,这是因为在Activity的启动代码中,已经在当前UI线程调用了Looper.prepare()和Looper.loop()方法。

webView

Android webView优化
https://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=2651746383&idx=2&sn=9b8f8ec2adf7c13934bfb9891eae4d81&chksm=bd12a9028a652014ab8b89ff996cf7b53e8d40bfbcd64725c7c82df72515669fcf5267272ccf&scene=0&key=8652b956ca1971a40ad6b41493d96b1dec8f3b81a9dc906f9c847f4096f53793bf38273a695ff893f30d3fc5eae8409e0ed72b4fb4de3469ba0f52f101d17460a7f899a398cd880b515f2fea36a1ae19&ascene=0&uin=ODE4NjgzNjgw&devicetype=iMac+MacBookPro12%2C1+OSX+OSX+10.12.1+build(16B2555)&version=12020510&nettype=WIFI&fontScale=100&pass_ticket=UYjezO5sOIUhdToTt7sxrHDotjZd3okvLWNljbwnQR7zFq3dGtrbuuXIzLM5LKTr

Android webView与js的交互
http://droidyue.com/blog/2014/09/20/interaction-between-java-and-javascript-in-android/index.html

Android性能优化

布局优化、绘制优化、内存泄露优化、响应速度优化、ListView优化、线程优化以及一些性能优化的建议。

布局优化:尽量减少布局文件的层级,删除布局中无用的控件和层级。<merge> <incude> <ViewStub>提供按需加载的功能
绘制优化:onDraw()方法中不要创建新的局部对象,不要做耗时的任务 GPU过度绘制 HierarchyView来检测 开发者选项 显示开发者过度绘制选项
内存泄露优化:context、handler、bitmap、单例模式、内部类、静态变量、资源对象没有关闭
响应速度优化和ANR日志分析:避免在主线程中做耗时操作,系统会在/data/anr目录下创建一个文件traces.txt

避免创建过多的对象
不要过多使用枚举,枚举占用的内存空间要比整型大
常量请使用static final 来修饰
使用一些Android特有的数据结构,比如SparseArray和Pair等,他们都具有更好的性能
适当使用软引用和弱引用
采用内存缓存和磁盘缓存
尽量采用静态内部类,这样可以避免潜在的由于内部类而导致的内存泄露

http://www.jianshu.com/p/be05874965d4

简单参考:主要是怎么来通过tools来进行性能优化
http://blog.csdn.net/yanbober/article/details/48394201

ViewStub 和 merge 的区别
在动态加载布局时,使用 ViewStub 的性能要比使用设置 View 的可见性高。因为虽然把 View 的初始可见 View.GONE,使其不可见,但是在 Inflate 布局的时候 View 仍然会被 Inflate,也就是说仍然会创建对象,会被实例化,会被设置属性。也就是说,会耗费内存等资源。

内存泄露检测框架-leakcanary原理分析
https://juejin.im/entry/5928f6360ce463006b120c3c

自定义BaseAdapter
http://blog.csdn.net/lmj623565791/article/details/38902805

RelativeLayout和LinearLayout性能分析
http://www.jianshu.com/p/8a7d059da746
1.RelativeLayout会让子View调用2次onMeasure,LinearLayout 在有weight时,也会调用子View2次onMeasure
2.RelativeLayout的子View如果高度和RelativeLayout不同,则会引发效率问题,当子View很复杂时,这个问题会更加严重。如果可以,尽量使用padding代替margin。
3.在不影响层级深度的情况下,使用LinearLayout和FrameLayout而不是RelativeLayout。
为什么Google给开发者默认新建了个RelativeLayout,而自己却在DecorView中用了个LinearLayout。因为DecorView的层级深度是已知而且固定的,上面一个标题栏,下面一个内容栏。采用RelativeLayout并不会降低层级深度,所以此时在根节点上用LinearLayout是效率最高的。而之所以给开发者默认新建了个RelativeLayout是希望开发者能采用尽量少的View层级来表达布局以实现性能最优,因为复杂的View嵌套对性能的影响会更大一些。

19.MVP模式讲解
http://blog.csdn.net/lmj623565791/article/details/46596109

BaseActivity
友盟统计的一些方法
bindKnife
initView initData
addFragment removeFragment
Toolbar的定制
入场出场动画

BaseFragment
抽象getlayoutId() initView() initData()
在onAttach()中获取activity的实例

android onSaveInstanceState方法
http://blog.sina.com.cn/s/blog_618199e60101g1k5.html

hashcode() 和 equals()
http://www.cnblogs.com/skywang12345/p/3324958.html
国内一线互联网公司内部面试题库
https://github.com/JackyAndroid/AndroidInterview-Q-A/blob/master/README-CN.md#%E6%8E%A5%E5%8F%A3%E7%9A%84%E6%84%8F%E4%B9%89-%E7%99%BE%E5%BA%A6

横竖屏切换 Activity生命周期的变化

1、不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次
2、设置Activity的android:configChanges="orientation"时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次
3、设置Activity的android:configChanges="orientation|keyboardHidden"时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法

Retrofit、Glide、okHttp源码分析
Retrofit :http://www.jianshu.com/p/45cb536be2f4

Retrofit.build()
new OkHttpClient()
create callbackExecutor(include main thread handler)
create CallAdapterFactory List
创建默认的defaultCallAdapterFactory(callbackExecutor)
create ConverterFactory List
new Retrofit

Retrofit.create() 使用动态代理
create ServiceMethod使用缓存来获取ServiceMethod
create CallAdapter ExecutorCallAdapterFactory
create responseConverter
parseMethodAnnotation
new OkHttpCall()
adapter(okHttpCall)

ExecutorCallbackCall(Call<>) callAdapter.adapter(OkHttpCall<>)
ExecutorCallbackCall.equeue(new Callback<>)
okHttpCall.equeue(new Callback){
handler.post(new Runnable(){
callback.onResponse
})
}

requestConvertor
okhttp3.Request = okhttpCall.toRequest()

responseConvertor
Retrofit.Response = okhttpcall.toResponse()

Observable<> callAdapter.adapter(OkHttpCall<>)

Glide :http://blog.csdn.net/guolin_blog/article/details/53759439

线程池 Java内存管理 多态
java内存管理 :http://www.cnblogs.com/vamei/archive/2013/04/28/3048353.html

线程池 :https://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650239804&idx=1&sn=3df2deb8eb577d577bcfb24588719365&chksm=88638253bf140b45b2d81e8e57611eb0d3284a0dbf9cbc28abbf2c8868497ca38a8f84efc3b1&scene=0&key=7dad7409be596df6fa2a201bc7e34b32f851b40d533f3eb14c224b425841aef31d08971594bb3bd3440663321063e94be8858097b81afdcd1de959476e65b4b13d5c16c04321d289036f8b90183d8f47&ascene=0&uin=ODE4NjgzNjgw&devicetype=iMac+MacBookPro12%2C1+OSX+OSX+10.12.1+build(16B2555)&version=12020510&nettype=WIFI&fontScale=100&pass_ticket=EIpIAyC4xo4xBanlqdA8hUO4ImkurY7%2BQjp3%2FnGyc81SzH7J9hBHcvYjzcfD%2Bfkq

进程和线程的关系

Thread和Runnable的区别
http://www.cnblogs.com/yangdy/p/5274455.html

线程死锁
http://blog.csdn.net/abc006250/article/details/8007233
http://blog.csdn.net/ns_code/article/details/17200937

设计模式

单例
http://blog.csdn.net/jason0539/article/details/23297037

强引用 软引用 弱引用 虚引用
http://blog.csdn.net/mazhimazh/article/details/19752475

线程安全

HTTP
http://52android.blog.51cto.com/2554429/496621/
http://blog.csdn.net/coder_pig/article/details/46312153
http://www.cnblogs.com/hanyonglu/archive/2012/02/19/2357842.html

HTTP header
http://www.cnblogs.com/nylcy/p/5474613.html

java线程面试题
http://www.cnblogs.com/dolphin0520/p/3958019.html
http://blog.csdn.net/jackfrued/article/details/44921941

图片处理
http://blog.csdn.net/guolin_blog/article/details/9316683

缓存
http://blog.csdn.net/guolin_blog/article/details/28863651
http://blog.csdn.net/guolin_blog/article/details/9316683

webview
http://www.jianshu.com/p/3c94ae673e2a

Android多渠道打包
http://tech.meituan.com/android-apk-v2-signature-scheme.html

从头到尾总结一下:

1.Touch事件分发中只有两个主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三个相关事件。View包含dispatchTouchEvent、onTouchEvent两个相关事件。其中ViewGroup又继承于View。
2.ViewGroup和View组成了一个树状结构,根节点为Activity内部包含的一个ViwGroup。
3.触摸事件由Action_Down、Action_Move、Aciton_UP组成,其中一次完整的触摸事件中,Down和Up都只有一个,Move有若干个,可以为0个。
4.当Acitivty接收到Touch事件时,将遍历子View进行Down事件的分发。ViewGroup的遍历可以看成是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的View,这个View会在onTouchEvent结果返回true。
5.当某个子View返回true时,会中止Down事件的分发,同时在ViewGroup中记录该子View。接下去的Move和Up事件将由该子View直接进行处理。由于子View是保存在ViewGroup中的,多层ViewGroup的节点结构时,上级ViewGroup保存的会是真实处理事件的View所在的ViewGroup对象:如ViewGroup0-ViewGroup1-TextView的结构中,TextView返回了true,它将被保存在ViewGroup1中,而ViewGroup1也会返回true,被保存在ViewGroup0中。当Move和UP事件来时,会先从ViewGroup0传递至ViewGroup1,再由ViewGroup1传递至TextView。
6.当ViewGroup中所有子View都不捕获Down事件时,将触发ViewGroup自身的onTouch事件。触发的方式是调用super.dispatchTouchEvent函数,即父类View的dispatchTouchEvent方法。在所有子View都不处理的情况下,触发Acitivity的onTouchEvent方法。
7.onInterceptTouchEvent有两个作用:1.拦截Down事件的分发。2.中止Up和Move事件向目标View传递,使得目标View所在的ViewGroup捕获Up和Move事件。

问:Android中为什么主线程不会因为Looper.loop()里的死循环卡死?

答:线程是一段可执行的代码,当可执行代码执行完成后,线程生命周期便终止,线程退出。对于主线程,我们是绝不希望运行一段时间后自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,例如,Binder线程也是采用死循环的方法,通过循环方式不断与Binder驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。真正会卡死主线程的操作是在回调方法中onCreate()、onStart()、onResume等操作时间过长,导致掉帧,甚至发生ANR,Looper.loop()本身不会导致应用卡死。

dispatchTouchEvent源码分析总结
触摸控件(View)首先执行dispatchTouchEvent方法。
在dispatchTouchEvent方法中先执行onTouch方法,后执行onClick方法(onClick方法在onTouchEvent中执行,下面会分析)。
如果控件(View)的onTouch返回false或者mOnTouchListener为null(控件没有设置setOnTouchListener方法)或者控件不是enable的情况下会调运onTouchEvent,dispatchTouchEvent返回值与onTouchEvent返回一样。
如果控件不是enable的设置了onTouch方法也不会执行,只能通过重写控件的onTouchEvent方法处理(上面已经处理分析了),dispatchTouchEvent返回值与onTouchEvent返回一样。
如果控件(View)是enable且onTouch返回true情况下,dispatchTouchEvent直接返回true,不会调用onTouchEvent方法。

Volley源码分析总结
1. 当一个RequestQueue被成功申请后会开启一个CacheDispatcher和4个默认的NetworkDispatcher。
2. CacheDispatcher缓存调度器最为第一层缓冲,开始工作后阻塞的从缓存序列mCacheQueue中取得请求;对于已经取消的请求,标记为跳过并结束这个请求;新的或者过期的请求,直接放入mNetworkQueue中由N个NetworkDispatcher进行处理;已获得缓存信息(网络应答)却没有过期的请求,由Request的parseNetworkResponse进行解析,从而确定此应答是否成功。然后将请求和应答交由Delivery分发者进行处理,如果需要更新缓存那么该请求还会被放入mNetworkQueue中。
3. 将请求Request add到RequestQueue后对于不需要缓存的请求(需要额外设置,默认是需要缓存)直接丢入mNetworkQueue交给N个NetworkDispatcher处理;对于需要缓存的,新的请求加到mCacheQueue中给CacheDispatcher处理;需要缓存,但是缓存列表中已经存在了相同URL的请求,放在mWaitingQueue中做暂时处理,等待之前请求完毕后,再重新添加到mCacheQueue中。
4. 网络请求调度器NetworkDispatcher作为网络请求真实发生的地方,对消息交给BasicNetwork进行处理,同样的,请求和结果都交由Delivery分发者进行处理。
5. Delivery分发者实际上已经是对网络请求处理的最后一层了,在Delivery对请求处理之前,Request已经对网络应答进行过解析,此时应答成功与否已经设定;而后Delivery根据请求所获得的应答情况做不同处理;若应答成功,则触发deliverResponse方法,最终会触发开发者为Request设定的Listener;若应答失败,则触发deliverError方法,最终会触发开发者为Request设定的ErrorListener;处理完后,一个Request的生命周期就结束了,Delivery会调用Request的finish操作,将其从mRequestQueue中移除,与此同时,如果等待列表中存在相同URL的请求,则会将剩余的层级请求全部丢入mCacheQueue交由CacheDispatcher进行处理。

接口抽象类的区别
接口中所有的方法隐含的都是抽象的。而抽象类则可以同时包含抽象和非抽象的方法。
类可以实现很多个接口,但是只能继承一个抽象类
类如果要实现一个接口,它必须要实现接口声明的所有方法。但是,类可以不实现抽象类声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。
抽象类可以在不提供接口方法实现的情况下实现接口。
Java接口中声明的变量默认都是final的。抽象类可以包含非final的变量。
Java接口中的成员函数默认是public的。抽象类的成员函数可以是private,protected或者是public。
接口是绝对抽象的,不可以被实例化。抽象类也不可以被实例化,但是,如果它包含main方法的话是可以被调用的。

16.什么是死锁(deadlock)?

两个进程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是两个进程都陷入了无限的等待中。

17.如何确保N个线程可以访问N个资源同时又不导致死锁?

使用多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。

Array和ArrayList的不同点:

Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。
Array大小是固定的,ArrayList的大小是动态变化的。
ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。
对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。

20.什么是迭代器(Iterator)?

Iterator接口提供了很多对集合元素进行迭代的方法。每一个集合类都包含了可以返回迭代器实例的

迭代方法。迭代器可以在迭代的过程中删除底层集合的元素。
克隆(cloning)或者是序列化(serialization)的语义和含义是跟具体的实现相关的。因此,应该由集合类的具体实现来决定如何被克隆或者是序列化。

21.Iterator和ListIterator的区别是什么?

下面列出了他们的区别:

Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。
Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。
ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。

Comparable和Comparator接口是干什么的?列出它们的区别。

Java提供了只包含一个compareTo()方法的Comparable接口。这个方法可以个给两个对象排序。具体来说,它返回负数,0,正数来表明输入对象小于,等于,大于已经存在的对象。

Java提供了包含compare()和equals()两个方法的Comparator接口。compare()方法用来给两个输入参数排序,返回负数,0,正数表明第一个参数是小于,等于,大于第二个参数。equals()方法需要一个对象作为参数,它用来决定输入参数是否和comparator相等。只有当输入参数也是一个comparator并且输入参数和当前comparator的排序结果是相同的时候,这个方法才返回true。

thread runnable

数据结构与算法

list map set 以及实现类的原理
HashMap : http://www.cnblogs.com/ITtangtang/p/3948406.html
LinkedList : http://www.cnblogs.com/ITtangtang/p/3948610.html
ArrayList : http://www.cnblogs.com/ITtangtang/p/3948555.html
ConcurrentHashMap : http://www.cnblogs.com/dolphin0520/p/3932905.html
List Map Set 的区别
http://developer.51cto.com/art/201309/410205_all.htm

快速排序 :
http://www.cnblogs.com/MOBIN/p/4681369.html
二分查找:
http://blog.csdn.net/lovesummerforever/article/details/24588989
数组去重:
http://www.gjnote.com/archives/459.html

面经

http://www.toutiao.com/a6436541491175620865/?tt_from=weixin&utm_campaign=client_share&app=news_article&utm_source=weixin&iid=11427333975&utm_medium=toutiao_ios&wxshare_count=1
美团面试
http://www.jianshu.com/p/ab4d0c6e9481

上一篇下一篇

猜你喜欢

热点阅读