面试题面试题

面试题2019年7月

2019-08-20  本文已影响0人  jtsky

线程池原理

参考:
Java 线程池原理分析

线程池工作原理:
1、线程数量小于 corePoolSize,直接创建新线程处理新的任务
2、线程数量大于等于 corePoolSize,workQueue 未满,则缓存新任务
3、线程数量大于等于 corePoolSize,但小于 maximumPoolSize,且 workQueue 已满。则创建新线程处理新任务
4、线程数量大于等于 maximumPoolSize,且 workQueue 已满,则使用拒绝策略处理新任务

如何快速定位ANR

参考:
巧妙定位ANR问题

1、拿到/data/anr/trace.txt对应的文件,然后搜索关键字ANR。
2、搜索"Dalvik Thread"关键字找到主线程

Glide缓存与解码复用

image

Glide拥有4级缓存

1、活动资源:如果当前对应的图片资源正在使用,则这个图片会被Glide放入活动缓存。
2、内存缓存:如果图片最近被加载过,并且当前没有使用这个图片,则会被放入内存中
3、解码过的资源类型: 被解码后的图片写入磁盘文件中,解码的过程可能修改了图片的参数(如:inSampleSize、inPreferredConfig)
4、原始数据: 图片原始数据在磁盘中的缓存(从网络、文件中直接获得的原始数据)

Bitmap复用
Glide中,在每次解析一张图片为Bitmap的时候(磁盘缓存、网络/文件)会从其BitmapPool中查找一个可被复用的Bitmap。
BitmapPool是Glide中的Bitmap复用池,同样适用LRU来进行管理。
当一个Bitmap从内存缓存 被动 的被移除(内存紧张、达到maxSize)的时候并不会被recycle。而是加入这个BitmapPool,只有从这个BitmapPool 被动的被移除的时候,Bitmap的内存才会真正被recycle释放。

Android Dalvik与ART的区别

Dalvik和JVM的关系
1、Dalvik是基于寄存器的,而JVM是基于栈的。
2、Dalvik运行dex文件,而JVM运行java字节码。

ART 的机制与 Dalvik 不同。在Dalvik下,应用每次运行的时候,字节码都需要通过即时编译器(just in time ,JIT)转换为机器码,这会拖慢应用的运行效率,而在ART 环境中,应用在第一次安装的时候,字节码就会预先编译成机器码,使其成为真正的本地应用。这个过程叫做预编译(AOT,Ahead-Of-Time)。这样的话,应用的启动(首次)和执行都会变得更加快速。Android 4.4引入。
优点:
1、系统性能的显著提升。
2、应用启动更快、运行更快、体验更流畅、触感反馈更及时。
3、更长的电池续航能力。
4、支持更低的硬件。
缺点:
1.机器码占用的存储空间更大,字节码变为机器码之后,可能会增加10%-20%(不过在应用包中,可执行的代码常常只是一部分。比如最新的 Google+ APK 是 28.3 MB,但是代码只有 6.9 MB。)
2.应用的安装时间会变长。

Loop为什么不会阻塞主线程,死循环为什么不会耗尽cpu资源

ActivityThread thread = new ActivityThread();
thread.attach(false);//建立Binder通道 (创建新线程)

主线程的死循环一直运行是不是特别消耗CPU资源呢? 其实不然,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

xml布局中Include、ViewStub、Merge的区别

Include:解决布局复用、如果对include设置了id有可能导致找不到layout根布局的id
ViewStub:ViewStub就是一个宽高都为0的一个View,它默认是不可见的。
只有通过调用 setVisibility() 函数或者 Inflate() 函数才会将其要装载的目标布局给加载出来,从而达到延迟加载的效果。
在ViewStub布局可显示之前,系统不会消耗资源去实例化里面的布局,可以节省系统资源消耗
Merge:merge它可以删减多余的层级,优化UI。
例如你的主布局文件是垂直的LinearLayout,这时使用include将 my_layout.xml 引入进来。
新布局也是垂直的LinearLayout,那么这个新的LinearLayout就没有任何意义了。使用的话反而增加反应时间。这时可以使用<merge/>标签优化。merge 原理就是在解析xml时候,如果是 <merge/> 标签,那么直接将其中的子元素添加到merge 标签parent中,这样就保证了不会引入额外的层级

view的绘制原理

从ViewRootImpl的performTraversals()开始被触发,遍历view的onMeasure、onLayout、onDraw。
onMeasure:MeasureSpec 三种模式。32位,前2位代表模式,后2位代表size。

final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
                            lp.width);
                    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

Android为什么会出现卡顿

1、渲染性能,布局层级过深。60fps即一秒钟需要渲染60帧,每个16ms发出VSYNC信号,掉帧。
2、垃圾回收会阻塞线程。所以避免频繁的创建对象和回收对象。不要在生命周期短的方法中创建对象,比如onDraw中创建Paint对象。
3、动画过于复杂,超过16ms。

JVM内存结构

1、线程私有:程序计数器、jvm虚拟机栈、本地方法栈
2、线程共有:堆、方法区(包含常量池)(没有明确的规范)
垃圾回收的判断标准:
1、计数法:循环依赖的对象不能被回收
2、根查找法(目前采用)
垃圾回收算法:
1、标记清理:会造成内存碎片
2、复制收集法:分成2个大小相等的区,将在使用的对象复制到应一块内存中,一次性清理未使用的对象 会造成内存浪费
3、标记 整理 清理
4、分代算法:分成新生代、老年代、持久代。
新生代按8:1:1分成edge和survival区

Flutter的skia渲染引擎和Android原生渲染引擎的差别

目前,Skia已然是Android官方的图像渲染引擎了,因此Flutter Android SDK无需内嵌Skia引擎就可以获得天然的Skia支持;
Dart因为同时支持JIT和AOT,所以既开发效率高,又运行速度好、执行性能高.
FlutterView集成FrameLayout,内部持有FlutterSurfaceView继承SurfaceView。

View和SurfaceView的区别:
1、View适用于主动更新的情况,而SurfaceView则适用于被动更新的情况,比如频繁刷新界面。
2、View在主线程中对页面进行刷新,而SurfaceView则开启一个子线程来对页面进行刷新。
3、View在绘图时没有实现双缓冲机制,SurfaceView在底层机制中就实现了双缓冲机制。

双缓冲技术是游戏开发中的一个重要的技术。当一个动画争先显示时,程序又在改变它,前面还没有显示完,程序又请求重新绘制,这样屏幕就会不停地闪烁。而双缓冲技术是把要处理的图片在内存中处理好之后,再将其显示在屏幕上。双缓冲主要是为了解决 反复局部刷屏带来的闪烁。把要画的东西先画到一个内存区域里,然后整体的一次性画出来。

sync和lock的区别和原理。

image.png

synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中。

存储优化

SharedPreferences:
1、跨进程不安全
2、加载缓慢
3、全量写入
4、卡顿 apply异步写入在崩溃火其他异常情况下会导致数据丢失。
Protocol Buffers:
1、性能。使用了二进制编码压缩,相比 JSON 体积更小,编解码速度也更快,感兴趣的同学可以参考protocol-buffers 编码规则
2、兼容性。跨语言和前后兼容性都不错,也支持基本类型的自动转换,但是不支持继承与引用类型
3、使用成本。Protocol Buffers 的开发成本很高,需要定义.proto 文件,并用工具生成对应的辅助类。辅助类特有一些序列化的辅助方法,所有要序列化的对象,都需要先转化为辅助类的对象,这让序列化代码跟业务代码大量耦合,是侵入性较强的一种方式。

sqlite:
便于管理,除非保存的数据比较大否则不建议使用。

Binder

Binder IPC 机制中涉及到的内存映射通过 mmap() 来实现,mmap() 是操作系统中一种内存映射的方法。内存映射简单的讲就是将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。
内存映射能减少数据拷贝次数,实现用户空间和内核空间的高效互动。两个空间各自的修改能直接反映在映射的内存区域,从而被对方空间及时感知。也正因为如此,内存映射能够提供对进程间通信的支持。
一次完整的 Binder IPC 通信过程通常是这样:
1.首先 Binder 驱动在内核空间创建一个数据接收缓存区;
2.接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;
3.发送方进程通过系统调用 copyfromuser() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。

AIDL:

IBinder : IBinder 是一个接口,代表了一种跨进程通信的能力。只要实现了这个借口,这个对象就能跨进程传输。
IInterface : IInterface 代表的就是 Server 进程对象具备什么样的能力(能提供哪些方法,其实对应的就是 AIDL 文件中定义的接口)
Binder : Java 层的 Binder 类,代表的其实就是 Binder 本地对象。BinderProxy 类是 Binder 类的一个内部类,它代表远程进程的 Binder 对象的本地代理;这两个类都继承自 IBinder, 因而都具有跨进程传输的能力;实际上,在跨越进程的时候,Binder 驱动会自动完成这两个对象的转换。
Stub : AIDL 的时候,编译工具会给我们生成一个名为 Stub 的静态内部类;这个类继承了 Binder, 说明它是一个 Binder 本地对象,它实现了 IInterface 接口,表明它具有 Server 承诺给 Client 的能力;Stub 是一个抽象类,具体的 IInterface 的相关实现需要开发者自己实现。
1、创建实体类Book实现Parcelable接口
2、创建接口BookManager.aidl并提供相应的方法
3、编写服务端代码AIDLService extends Service。内部创建BookManager.Stub对象。并重写服务端的方法。客户绑定service并与服务端进行通信。

APT

HashMap 和 HashTable 以及 CurrentHashMap 的区别。

一般来说,这三个东西基本在面试中 70% 会被问到,而问的方向也不太一样。比如初级的问法是讲讲它们之前的区别,这个我想没什么难度,大多数人还是知道主要核心区别是并发上的处理。此外,内部数据结构的实现、扩容、存取操作这些问题应该是很老生常谈了,这并没有什么好说的,大多数人也都知道。稍微问的深一点的可能会在下面这些点上出问题。哈希碰撞,哈希计算,哈希映射,为什么是头插法,扩容为什么是 2 的幂次等这样的问题。

synchronized 和 volatile 、ReentrantLock 、CAS 的区别。

这个问题被问频率不在 HashMap 之下,因为并发编程,真的很重要。能问到这几个点的方式真的是太多了,我们能发挥的空间也同样很大。CAS 的 ABA 问题?上面几个东西的特性?使用场景?大概我不用再例举了吧?对了,我多次被问到的一个问题是:synchronized 修饰实例方法和修饰静态方法有啥不一样。

JVM 类加载机制、垃圾回收算法对比、Java 虚拟机结构等。

这三个问题大概出现概率 40%,基本只需要看我每日一问系列的推文就差不多了吧,希望更清楚明白的可以直接看《深入理解 Java 虚拟机》。当你讲到分代回收算法的时候,不免会被追问到新生对象是怎么从年轻代到老年代的,以及可以作为 root 结点的对象有哪些两个问题。

Java 的四大引用

四大引用面试出现概率比我想象中要高,我原本以为就强引用、软引用、弱引用、虚引用这四个玩意儿没啥可讲的。实际上也确实没啥好讲的,稍微问的深一些的面试官会和内存泄漏检测原理以及垃圾回收糅杂在一起。

Java 的泛型,<? super T> 和 <? extends T> 的区别。

<? extends T>:是指 上界通配符(Upper Bounds Wildcards) 只有读取权限 没有写入权限
<? super T>:是指 下界通配符(Lower Bounds Wildcards)只有写入权限 没有读取权限
频繁往外读取内容的,适合用上界Extends。
经常往里插入的,适合用下界Super。

Java 线程有哪些状态,有哪些锁,各种锁的区别。

线程状态:新建、就绪、运行、阻塞、死亡
对应的方法:
进入就绪状态:star
运行状态:由CPU进行调控
阻塞状态:sleep()、wait()、join()、suspend()(已废弃)
死亡状态:run()方法的正常退出

线程锁:
1、synchronized必须有对应的锁对象,ReentrantLock没有对应的锁对象,这两个锁之间无论如何都不会互斥。
2、ReentrantLock提供了更多功能,比如tryLock()、设置锁的公平性、是否可以接收中断信号等。3、synchronized没有对应的功能。synchronized关键字是从虚拟机层次上保证互斥的,而ReentrantLock是从代码层次上保证互斥的。

sleep 、wait、yield 的区别,wait 的线程如何唤醒它?

wait和sleep的区别 面试 wait和notify yield
很基本的区别:wait是object类的方法,sleep、yield是thread类的方法。
wait释放cpu资源,同时释放锁
sleep也释放cpu资源,但不释放锁
wait需要搭配notify

final 、finally、finalize 区别。
final 用于申明属性,方法和类,表示属性不可变,方法不可以被覆盖,类不可以被继承。
finally 是异常处理语句结构中,表示总是执行的部分。  
finallize 表示是object类一个方法,在垃圾回收机制中执行的时候会被调用被回收对象的方法。允许回收此前未回收的内存垃圾。所有object都继承了finalize()方法

计算机网络部分。

计算机网络部分还是挺容易考察的,不过考察的点不会那么深入。通常来说也就是这些问题:

TCP 有哪些状态。
三次握手、四次挥手。为啥是三次不是两次?
参考:
通俗大白话来理解TCP协议的三次握手和四次分手

image.png

三次握手:因为客户端和服务端两边都需要确认收到对方的消息,这样才能表示网络通道是连通的。客户端发起占了一次,然后双方各自确认又占了一次,所以需要三次握手。
四次挥手:
那四次分手又是为何呢?TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议。TCP是全双工模式,这就意味着,当主机1发出FIN报文段时,只是表示主机1已经没有数据要发送了,主机1告诉主机2,它的数据已经全部发送完毕了;但是,这个时候主机1还是可以接受来自主机2的数据;当主机2返回ACK报文段时,表示它已经知道主机1没有数据发送了,但是主机2还是可以发送数据到主机1的;当主机2也发送了FIN报文段时,这个时候就表示主机2也没有数据要发送了,就会告诉主机1,我也没有数据要发送了,之后彼此就会愉快的中断这次TCP连接。如果要正确的理解四次分手的原理,就需要了解四次分手过程中的状态变化。
还是用打电话举例:
1、A跟B说 我要挂断电话了
2、B回复A 我收到你要挂断电话的消息了
3、B跟A说 那我也要挂断了
4、A回复B说 我确认你要挂断了

HTTPS 和 HTTP 的区别。HTTPS 2.0,3.0?
浏览器输入一个 URL,按下回车网络传输的流程?
喜欢深问一点的还会问到网络架构,每层有些什么协议,FTP 这些相关原理,印象比较深刻的还有一个问题是:TCP 建立连接后,发包频率是怎样的?


image.png

Kotlin

优点:
1、完全兼容Java
2、NULL safe
3、支持lambda表达式
4、支持扩展方法和属性
5、优秀的IDE支持

缺点:
1、编译速度变慢
2、增大代码量
3、额外的学习成本

Android 部分

Android 很广,所以这里只是简单说下有些什么问题。这个的话其实真的 70% 问题出自你的简历。

Activity 的生命周期;
Android 的 4 大启动模式,注意 onNewIntent() 的调用;
组件化架构思路,如何从一个老项目一步一步实现组件化,主要问实现思路,考察应试者的架构能力和思考能力。
这一块内容真的很多,你需要考虑的问题很多,哪一步做什么,顺序很重要。
MVC、MCP、MVVP 的区别和各种使用场景,如何选择适合自己的开发架构?
Router 原理,如何实现组件间通信,组件化平级调用数据方式。
系统打包流程;
APP 启动流程;
如何做启动优化?
冷启动什么的肯定是基础,后续应该还有的是懒加载,丢线程池同步处理,需要注意这里可能会有的坑是,丢线程池如何知道全部完成。
事件分发机制。
事件分发已经不是直接让你讲了,会给你具体的场景,比如 A 嵌套 B ,B 嵌套 C,从 C 中心按下,一下滑出到 A,事件分发的过程,这里面肯定会有 ACTION_CANCEL 的相关调用时机。
如何检测卡顿,卡顿原理是什么,怎么判断是页面响应卡顿还是逻辑处理造成的卡顿?
生产者模式和消费者模式的区别?
单例模式双重加锁,为什么要这样做。
Handler 机制原理,IdleHandler 什么时候调用。
LeakCanary 原理,为什么检测内存泄漏需要两次?
BlockCanary 原理。
ViewGroup 绘制顺序;
Android 有哪些存储数据的方式。
SharedPrefrence 源码和问题点;
讲讲 Android 的四大组件;
属性动画、补间动画、帧动画的区别和使用场景;
自定义 ViewGroup 如何实现 FlowLayout?如何实现 FlowLayout 调换顺序?
自定义 View 如何实现打桌球效果;
自定义 View 如何实现拉弓效果,贝瑟尔曲线原理实现?
APK 瘦身是怎么做的,只用 armabi-v7a 没有什么问题么?
APK 瘦身这个基本是 100% 被面试问到,可能是我简历上提到的原因。
ListView 和 RecyclerView 区别?RecyclerView 有几层缓存,如何让两个 RecyclerView 共用一个缓存?
如何判断一个 APP 在前台还是后台?
如何做应用保活?全家桶原理?
讲讲你所做过的性能优化。
Retrofit 在 OkHttp 上做了哪些封装?动态代理和静态代理的区别,是怎么实现的。
讲讲轨迹视频的音视频合成原理;
AIDL 相关;
Binder 机制,讲讲 Linux 上的 IPC 通信,Binder 有什么优势,Android 上有哪些多进程通信机制?
RxJava 的线程切换原理。
OkHttp 和 Volloy 区别;
Glide 缓存原理,如何设计一个大图加载框架。
LRUCache 原理;
讲讲咕咚项目开发中遇到的最大的一个难题和挑战;
这个问题基本是 95% 必问的一个问题;
说说你开发最大的优势点。
出现率同上。

算法

洗牌算法:随机打乱一个数组的顺序

java.util.Collections.shuffle(List<?> list);

如何层次遍历树结构

String 转 int。
核心算法就三行代码,不过临界条件很多,除了判空,还需要注意负数、Integer 的最大最小值边界等;
如何判断一个单链表有环?
链表翻转;
快排;
100 亿个单词,找出出现频率最高的单词。要求几种方案;
链表每 k 位逆序;
镜像二叉树;
找出一个无序数组中出现超过一半次数的数字;
计算二叉树的最大深度,要求非递归算法。
String 方式计算加法。

上一篇下一篇

猜你喜欢

热点阅读