Please enable Javascript to view the contents

面试

 ·  ☕ 15 分钟  ·  🎅 qqnv
    🏷️

Java

  1. String、StringBuilder、StringBuffer区别
    String 值是不可变的,每次对String的操作都会生成新的String对象,浪费内存空间,StringBuilder可变类,速度快,StringBuffer可变类线程安全

  2. String为什么被设计成不可变的

    1. 防止被篡改,保证信息数据的安全性
    2. 不变的对象和值是线程安全的
    3. 哈希值唯一性带来更好的性能
    4. 提高常量池的可用性
  3. String a = “A"与String a = new String(“A”)二者区别
    一个在常量池中一个在堆内存中

  4. LinkedHashMap与HashMap区别
    LinkedHashMap底层是基于HashMap和双向链表实现的,按照插入顺序进行排序,所以有序
    HashMap基于哈希表乱序

  5. hashcode()和equal()的作用、区别、联系

    重写equal一般比较复杂,效率较低但准确,利用hashcode只需生成hash就可以

    hashCode()并不是完全可靠,有时候不同的对象他们生成的hashcode也会一样(生成hash值得公式可能存在的问题)

  6. Hashmap实现原理和如何解决散列碰撞(必问),Hashmap底层为什么是线程不安全的?
    采用hash算法来决定每个元素的存储位置

  7. 1、HashMap线程不安全原因:
    原因:
    JDK1.7 中,由于多线程对HashMap进行扩容,调用了HashMap#transfer(),具体原因:某个线程执行过程中,被挂起,其他线程已经完成数据迁移,等CPU资源释放后被挂起的线程重新执行之前的逻辑,数据已经被改变,造成死循环、数据丢失。
    JDK1.8 中,由于多线程对HashMap进行put操作,调用了HashMap#putVal(),具体原因:假设两个线程A、B都在进行put操作,并且hash函数计算出的插入下标是相同的,当线程A执行完第六行代码后由于时间片耗尽导致被挂起,而线程B得到时间片后在该下标处插入了元素,完成了正常的插入,然后线程A获得时间片,由于之前已经进行了hash碰撞的判断,所有此时不会再进行判断,而是直接进行插入,这就导致了线程B插入的数据被线程A覆盖了,从而线程不安全。

    改善:
    数据丢失、死循环已经在在JDK1.8中已经得到了很好的解决,如果你去阅读1.8的源码会发现找不到HashMap#transfer(),因为JDK1.8直接在HashMap#resize()中完成了数据迁移。

    2、HashMap线程不安全的体现:
    JDK1.7 HashMap线程不安全体现在:死循环、数据丢失
    JDK1.8 HashMap线程不安全体现在:数据覆盖

  8. HashSet底层采取哈希表存储数据,哈希表是一种对于增删改查都比较好的结构。哈希表组成:jdk1.8以前:数组+链表。jdk8以后:数组+链表+红黑树
    当链表长度超过8,且数组长度大于64时,自动转为红黑树且存储自定义对象时,需要重写hashCode和equel方法(无序,不重复,无索引)
    LinkedHashSet底层数据结构仍然是哈希表,只是每个元素有额外多了个双链表的机制记录存储的顺序(有序,不重复,无索引)
    TreeSet底层时基于红黑树存储数据(可排序,不重复,无索引)
    发生Hash碰撞时:链表法(桶和槽)和开放寻址法

  9. 内存泄漏是发生在堆内存还是栈内存?为什么?
    堆内存,不再需要的对象的引用一直没有被移除,占用内存,造成内存泄漏

  10. Java几种引用类型

    强引用
    这种引用是平时开发中最常用的,例如 String strong = new String(“Strong Reference”),当一个实例对象具有强引用时,垃圾回收器不会回收该对象,当内存不足时,宁愿抛出OutOfMemeryError异常也不会通过回收强引用的对象,因为JVM认为强引用的对象是用户正在使用的对象,它无法分辨出到底该回收哪个,强行回收有可能导致系统严重错误

    软引用
    如果一个对象只有软引用,那么只有当内存不足时,JVM才会去回收该对象,其他情况不会回收。软引用可以结合ReferenceQueue来使用,当由于系统内存不足,导致软引用的对象被回收了,JVM会把这个软引用加入到与之相关联的ReferenceQueue中。

    弱引用
    只有弱引用的对象,当JVM触发gc时,就会回收该对象。与软引用不同的是,不管是否内存不足,弱引用都会被回收。弱引用可以结合ReferenceQueue来使用,当由于系统触发gc,导致软引用的对象被回收了,JVM会把这个弱引用加入到与之相关联的ReferenceQueue中,不过由于垃圾收集器线程的优先级很低,所以弱引用不一定会被很快回收。

    虚引用

    如果一个对象只有虚引用在引用它,垃圾回收器是可以在任意时候对其进行回收的,虚引用主要用来跟踪对象被垃圾回收器回收的活动,当被回收时,JVM会把这个虚引用加入到与之相关联的ReferenceQueue中。与软引用和弱引用不同的是,虚引用必须有一个与之关联的ReferenceQueue,通过phantomReference.get()得到的值为null

  11. GC回收算法
    标记-清除算法
    该算法先标记,后清除,将所有需要回收的算法进行标记,然后清除;这种算法的缺点是:效率比较低;标记清除后会出现大量不连续的内存碎片,这些碎片太多可能会使存储大对象会触发GC回收,造成内存浪费以及时间的消耗。

    复制算法
    复制算法将可用的内存分成两份,每次使用其中一块,当这块回收之后把未回收的复制到另一块内存中,然后把使用的清除。这种算法运行简单,解决了标记-清除算法的碎片问题,但是这种算法代价过高,需要将可用内存缩小一半,对象存活率较高时,需要持续的复制工作,效率比较低。

    标记-整理算法
    标记整理算法是针对复制算法在对象存活率较高时持续复制导致效率较低的缺点进行改进的,该算法是在标记-清除算法基础上,不直接清理,而是使存活对象往一端游走,然后清除一端边界以外的内存,这样既可以避免不连续空间出现,还可以避免对象存活率较高时的持续复制。这种算法适合老生代。

    分代收集算法
    分代收集算法就是目前虚拟机使用的回收算法,它解决了标记整理不适用于老年代的问题,将内存分为各个年代,在不同年代使用不同的算法,从而使用最合适的算法,新生代存活率低,可以使用复制算法。而老年代对象存活率高,没有额外空间对它进行分配担保,所以使用标记整理算法

  12. Java内存模型

    程序计数器

    程序计数器是一块很小的内存空间,它是线程私有的,可以认作为当前线程的行号指示器。
    复制代码

    Java栈(虚拟机栈)

    同计数器也为线程私有,生命周期与相同,就是我们平时说的栈,栈描述的是Java方法执行的内存模型。
    每个方法被执行的时候都会创建一个栈帧用于存储局部变量表,操作栈,动态链接,
    方法出口等信息。每一个方法被调用的过程就对应一个栈帧在虚拟机栈中从入栈到出栈的过程
    复制代码

    本地方法栈

    本地方法栈是与虚拟机栈发挥的作用十分相似,区别是虚拟机栈执行的是Java方法(也就是字节码)
    服务,而本地方法栈则为虚拟机使用到的native方法服务,可能底层调用的c或者c++,
    我们打开jdk安装目录可以看到也有很多用c编写的文件,可能就是native方法所调用的c代码
    复制代码

    堆是java虚拟机管理内存最大的一块内存区域,因为堆存放的对象是线程共享的,
    所以多线程的时候也需要同步机制
    复制代码

    方法区

    方法区同堆一样,是所有线程共享的内存区域,为了区分堆,又被称为非堆。
    用于存储已被虚拟机加载的类信息、常量、静态变量,如static修饰的变量加载类的时候
    就被加载到方法区中

  13. Java及Android类加载机制
    Java:BootClassLoader-ExtClassLoader-系统类加载器
    Android:
    BootClassLoader:加载framework层字节码文件
    URLClassLoader:通过URI路径从jar文件和文件夹中加载类和资源
    BaseDexClassLoader:下面的两者父类
    PathClassLoader:加载系统类和应用程序的类
    DexClassLoader:可以加载dex文件以及包含dex的apk文件或jar文件,也支持从SD卡进行加载,这也意味着DexClassLoader可以在应用未安装的情况下加载dex相关文件。这是热修复插件化技术的基础

  14. 单层for循环时间复杂度
    单层for循环时间复杂度为O(N),两层的嵌套for循环时间复杂度应该为O(N2)

  15. 算法
    重点掌握二分查找和冒泡排序
    见此链接

  16. 网络编程(http、udp、tcp、socket)
    udp:InetAdress、DatagramPacket、DatagramSocket
    tcp:socket、serverSocket
    http:三次握手四次挥手
    https:加密,包括对称加密和非对称加密,常见加密算法:md5、sha1、sha256

  17. https握手过程,如何实现数据加密?客户端如何保证安全实现双重证书校验?请你设计一个登录功能,需要注意哪些安全问题?

Android

  1. activity启动模式

    standard:默认
    singletop:顶层复用
    singletask:栈内复用
    singleInstance:单独开栈

  2. App启动过程

  3. service几种类型及应用长技

  4. 在Service里实现下载如何刷新UI

  5. Service 和 Activity如何交互, 如何在后台下载任务, 并在Activity显示进度

  6. 广播注册模式及区别

  7. Android handler消息机制(Handler如何在handleMessage方法拦截之前发出的message)

  8. Handler、Looper、MessageQueue、Thread关系?
    一个线程可以有多个Handler实例,一个线程对应一个Looper,一个Looper也只对应一个MessageQueue,一个MessageQueue对应多个Message和Runnable。所以就形成了一对多的对应关系,一方:线程、Looper、MessageQueue;多方:Handler、Message。同时可以看出另一个一对一关系:一个Message实例对应一个Handler实例

  9. 线程进程间通信

  10. Android事件分发机制

    public boolean dispatchTouchEvent(event):用于进行点击事件的分发
    public boolean onInterceptTouchEvent(event):用于进行点击事件的拦截
    public boolean onTouchEvent(event):用于处理点击事件

  11. 自定义View

  12. RecyclerView缓存机制

    四级缓存,分别是mAttachedScrap(屏幕内),mCacheViews(屏幕外),mViewCacheExtension(自定义缓存),mRecyclerPool(缓存池)

    • mAttachedScrap(屏幕内),用于屏幕内itemview快速重用,不需要重新createView和bindView
    • mCacheViews(屏幕外),保存最近移出屏幕的ViewHolder,包含数据和position信息,复用时必须是相同位置的ViewHolder才能复用,应用场景在那些需要来回滑动的列表中,当往回滑动时,能直接复用ViewHolder数据,不需要重新bindView。
    • mViewCacheExtension(自定义缓存),不直接使用,需要用户自定义实现,默认不实现。
    • mRecyclerPool(缓存池),当cacheView满了后或者adapter被更换,将cacheView中移出的ViewHolder放到Pool中,放之前会把ViewHolder数据清除掉,所以复用时需要重新bindView
  13. Android中为什么主线程不会因为Looper.loop()里的死循环卡死? Android应用程序的主线程在进入消息循环过程前,会在内部创建一个Linux管道(Pipe),这个管道的作用是使得Android应用程序主线程在消息队列为空时可以进入空闲等待状态,并且使得当应用程序的消息队列有消息需要处理时唤醒应用程序的主线程。

  14. 解决Android多线程访问SQLite数据库死锁问题? 单例+同步锁,并且在每次数据库操作后都关闭数据库

  15. Android如何实现大图加载

  16. 防止搜索框过度频繁发送请求

  17. 弹幕的实现方式

  18. ThreadPool实现原理 每个Thread的对象都有一个ThreadLocalMap,当创建一个ThreadLocal的时候,就会将该ThreadLocal对象添加到该Map中,其中键就是ThreadLocal,值可以是任意类型,在该类中,最重要的方法就是两个:set()和get()方法。当调用ThreadLocal的get()方法的时候,会先找到当前线程的ThreadLocalMap,然后再找到对应的值。set()方法也是一样

  19. HandlerThread原理以及对比单个New Thread的好处,优点以及试用场景?需要注意的是HandlerThread 是单个线程的不太适合执行网络的IO操作

  20. 系统是如何监听ANR的 AndroidFramework层有一个单独的进程

  21. 设计图片异步加载组件

    1. 单例实现
    2. 图片缓存管理
    3. 图片压缩
    4. 后台轮询线程:每次加载图片请求都会创建一个新任务放到任务队列,后台线程使用线程池去TaskQueue去取一个任务执行
  22. EventBus实现原理
    EventBus是一款针对Android优化的发布/订阅事件总线。主要功能是替代Intent,Handler,BroadCast在Fragment,Activity,Service,线程之间传递消息.优点是开销小,代码更优雅。以及将发送者和接收者解耦。

  23. RxJava
    观察者模式、装饰器模式
    操作符

  24. Retrofit
    CreateApi实现原理
    retrofit上传文件接口如何定义

  25. okhttp 双队列设计责任链模式与拦截器

  26. glide

  27. 使用GreenDao如何实现创建表、关联表(一对一,一对多,多对多)

  28. hilt依赖注入框架

  29. Retrofit+OkHttp+RxJava在华为的好多手机会OOM是由线程数溢出引起如何解决

  30. 当使用帧动画时如何避免oom
    帧动画是通过AnimationDrawable来实现的,是将所有帧的图片都解析出来,而且会占用内存,一旦动画较复杂帧数较多,在低配置手机上容易发生OOM。 优化的思路采用分次加载,SurfaceView就可以做到

  31. SurfaceView: 优点:可以在一个独立的线程中进行绘制,不会影响主线程,使用双缓冲机制,播放视频时画面更流畅 缺点:Surface不在View hierachy中,它的显示也不受View的属性控制,所以不能进行平移,缩放等变换。SurfaceView 不能嵌套使用
    TextureView 优点:支持移动、旋转、缩放等动画,支持截图 缺点:必须在硬件加速的窗口中使用,占用内存比SurfaceView高,在5.0以前在主线程渲染,5.0以后有单独的渲染线程。

  32. 如何防止搜索框过度频繁地发起请求?

    1. 当搜索框内容发生改变时等待1000毫秒,message携带内容通知handler进行处理判断handler携带的数据和搜索框的内容是否相等。
    2. 当handler携带的数据不为空并且和搜索框的内容相当时进行网络请求。
  33. handler postDelay这个延迟是怎么实现的 Handler postDelay如何实现不阻塞UI线程? 1.postDelay()一个1秒钟的MyTask任务、消息进队,MessageQueue开始阻塞,Looper阻塞,mBlocked为true,在enqueueMessage的if中将needWake = mBlocked。 2.然后post一个新的任务、消息进队,判断现在A时间还没到、正在阻塞,把新的任务插入消息队列的头部(MyTask任务的前面),然后此时needWake为true调用nativeWake()方法唤醒线程。 3.MessageQueue.next()方法被唤醒后,重新开始读取消息链表,第一个消息B无延时,直接返回给Looper; 4.Looper处理完这个消息再次调用next()方法,MessageQueue继续读取消息链表,第二个消息A还没到时间,计算一下剩余时间(假如还剩9秒)继续阻塞; 5.直到阻塞时间到或者下一次有Message进队

  34. 断点续传实现

  35. Dalvik与ART区别

  36. 进程保活

  37. 设计模式(代理模式和装饰器模式的区别)

  38. oom 内存泄漏原因 1.单例类引用Context造成内存泄漏。 2.非静态内部类引用外部类造成内存泄漏。 3.handler引用activity造成内存泄漏。 4.属性动画没有取消,导致view一直被引用造成内存泄漏。 5.监听器没有取消、回调没有反注册。 内存优化措施 1.使用线程池复用线程,因为线程本身会占用相对比较大的内存,复用就可以省下部分内存。 2.在onDraw方法内避免创建对象。因为onDraw会被频繁调用,导致其内部的对象也会被频繁创建,占用过多内存。 3.尽量使用StringBuilder或StringBuffer拼接字符串,减少String的使用。(因为拼接字符串时,String会创建新的对象,而StringBuilder、StringBuffer是在原字符串基础上拼接) 4.视图资源不可见时进行清除,避免占用内存。如Bitmap执行.recycle方法进行清除、对图片和lottie资源进行销毁。 避免内存泄漏 ①单例类应引用Application的Context,因为Application的Context的生命周期是和APP一致的,不会造成单例类引用某个activity的context以致该activity无法被回收的问题。 ②将非静态内部类改为静态内部类,这样就不会引用外部类。 ③handler:a.handler使用结束时调用removeCallbacksAndMessages(null)清除队列;b.静态内部类+弱引用方式可避免内存泄漏。 ④属性动画、监听器使用结束应及时取消,广播或其他一些外部库的回调应该及时反注册。

  39. ANR

  40. 实现轮询的两种方式
    handler发送延迟消息
    Rxjava和Retrofit observable的interval方法

  41. RxJava缓存策略
    LruCache中的LinkedHashMap

  42. 注解
    元注解:target(权限)、retention(注解保留周期)

Kotlin

  1. Kotlin中静态方法:单例类、companion object都不算是真正的单例需要加JvmStatic、顶层方法的调用
  2. kotlin协程
  3. kotlin标准函数、扩展函数、高阶函数、内联函数
分享

qqnv
作者
qqnv
Android Developer