Android面试中Java题目一般会问道几点在平时coding常用到的,如Thread,JVM和HashMap,但是有的时候也不局限于这些问题,概括起来那么五大知识点
1、基础题——关键字和字符串相关
2、JVM和GC
3、多线程
4、HashMap等数据结构
关键字和字符串相关
1、Synchronized + Volatile
可以参照这边文章:Synchronized和Volatile学习
2、Serializable + Parcelable
因为暂时没有写,先参照:序列化和反序列化
- Serializable 是Java序列化,Serializable的设计初衷是为了序列化对象到本地文件、数据库、网络流、RMI以便数据传输,当然这种传输可以是程序内的也可以是两个程序间的
- Android的Parcelable的设计初衷是由于Serializable效率过低,消耗大,而android中数据传递主要是在内存环境中(内存属于android中的稀有资源),因此Parcelable的出现为了满足数据在内存中低开销而且高效地传递问题,类似Intent,Bundle和Bitmap都是继承Parcelable接口
- Serializable序列化之后的结果你可以放到任意的位置,内存、外存、网络,因为在Serializable之后的字节流中已经带上了足够的信息去进行验证一个需要反序列化的类是否满足定义。而Parcel虽然也是把数据转换成字节流,但是它的应用空间很窄,原因是在流中写入的类描述信息仅仅是一个类名,所以尽量不要用Parcel做持久化存储
- Serializable在序列化操作的时候会产生大量的临时变量,(原因是使用了反射机制)从而导致GC的频繁调用,Parcelable是以Ibinder作为信息载体的,在内存开销上小,Parcelable是在内存中直接进行读写,而Serializable是通过使用IO流的形式将数据读写入在硬盘上
3、String、StringBuffer和StringBuilder
- 可变性
- String类用字符数组保存字符串,即private final char value[],所以String对象不可变
- StringBufferr类与StringBuilder类都是继承AbstractStringBuilder类,AbstractStringBuilder是用字符数组保存字符串,即char value[],所以StringBuffer与StringBuilder对象是可变的
- 线程安全性
- String类对象不可变,所以理解为常量,线程安全
- StringBuffer类对方法加了同步锁,线程安全
- StringBuilder类对方法未加同步锁,线程不安全
- 性能
- String类进行改变的时候,都会产生新的String对象,然后将指针指向新的String对象
- StringBuffer进行改变的时候,都会复用自身对象,性能比String高
- StringBuilder行改变的时候,都会复用自身对象,相比StringBuffer能获得10%~15%左右的性能提升,但是得承担多线程的不安全的风险
4、isEmpty( ), "" ,null
内存空间 | value | |
isEmpty() | 分配 | 值 = 空,源码显示count = 0 |
"" | 分配 | 值 = 空字串 |
null | 不分配 | 值不存在 |
5、不管是否有异常产生,finally块中代码都会执行;5、try-catch-finally-return执行顺序
- 2、当try和catch中有return语句时,finally块仍然会执行;
- 3、finally是在return后面的表达式运算后执行的,所以函数返回值是在finally执行前确定的。无论finally中的代码怎么样,返回的值都不会改变,仍然是之前return语句中保存的值;
- 4、finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
6、Error和Exception
- 相同点,都是Throwable的子类,Java中的异常
- 不同点,Exception包括RuntimeException和IOException,Error和RuntimeException都是未检查异常,其他都是检查异常
Error :系统级别的错误,表示由 JVM 所侦测到的无法预期的错误,由于这是属于 JVM 层次的严重错误 ,导致 JVM 无法继续执行,因此,这是不可捕捉到的,无法采取任何恢复的操作,顶多只能显示错误信息。
Exception :表示可恢复的例外,这是可捕捉到的。
7、 抽象和接口
is-a 和 like-a 的区别
集合相关
1、集合的框架基础接口有哪些?
集合主要分为Collection和Map2个接口
Collection又分别被List和Set继承
List被AbstractList实现,然后分为3个子类,ArrayList,LinkList和Vector
Set被AbstractSet实现,又分为HashSet和TreeSet
Map衍生出的集合分为HashMap,HashTable和TreeMap
Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set
├HashSet
└TreesSet
Map
├Hashtable
├HashMap
└WeakHashMap
2、equals和hashCode
该遵循以下规则:
(1)如果o1.equals(o2),那么o1.hashCode() == o2.hashCode()总是为true的。
(2)如果o1.hashCode() == o2.hashCode(),并不意味着o1.equals(o2)会为true。
具体可以参考 http://blog.csdn.net/javazejian/article/details/51348320
Jvm相关
1、JVM的定义 ---Java Virtual Machine
2、内存划分
3、执行过程
4、JMM模型
Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model,JMM)来屏蔽掉各种硬件和操作系统的访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果,注意一下,此处的变量并不包括局部变量与方法参数,因为它们是线程私有的,不会被共享,自然也不会存在竞争,此处的变量应该是实例字段、静态字段和构成数组对象的元素。
5、happen-before指令重排序优化
指令重排序的原因:对主存的一次访问一般花费硬件的数百次时钟周期。处理器通过缓存(寄存器、cpu缓存等)能够从数量级上降低内存延迟的成本这些缓存为了性能重新排列待定内存操作的顺序。也就是说,程序的读写操作不一定会按照它要求处理器的顺序执行,
简单的说:就是为了尽可能减少内存操作速度远慢于CPU运行速度所带来的CPU空置的影响,虚拟机会按照自己的一些规则将程序编写顺序打乱。
这就涉及到JMM中并发编程的三大保障
- 原子性,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行;
-
可见性,当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性
- 有序性,Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 原则。如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序。
6、GC
线程相关
1、创建线程的方式
- extends 继承Thread类
- implements 实现Runnable接口
- 使用ExecutorService、Callable、Future实现有返回结果的线程
优势 | 劣势 | |
Thread | 1、编写简单,访问该线程直接调用this,不用Thread.currentThread() | 1、extends只能继承这个,不能继承其他类 2、耦合性强 |
Runnable/Callable | 1、implements可以多个,不受限制 2、直接new Thread(new Runnable)创建线程,降低代码耦合度,面向接口编程 | 必须使用Thread.currentThread()方法 |
2、40个线程问题
sleep和wait方法区别?
- 这两个方法来自不同的类分别是Thread和Object
- 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
- wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在
任何地方使用
synchronized(x){
x.notify()
//或者wait()
} - sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
进程相关
1、进程的优先级
优先级从低到高排列,优先级最高的进程,最先获取资源,最后释放
1、空进程
这是Android系统优先杀死的,因为此时该进程已经没有任何用途
2、后台进程
包含不可见的Activity,即跳转到其他Activity后,由于资源不足,系统会将原来的Activity杀死(即跳转的来源)
3、服务进程
即Service,当系统资源不足时,系统可能会杀掉正在执行任务的Service,因此在Service执行比较耗时的操作,并不能保证一定能执行完毕
4、可见进程
当前屏幕上可以看到的Activity,例如显示一个对话框的Activity,那么对话框变成了前台进程,而调用他的Activity是可见进程,但并不是前台的
5、前台进程
当前处于最前端的Activity,也就是Android最后考虑杀死的对象,一般来说,前台进程Android系统是不会杀死的,只有当前4个都杀掉资源依旧不够才可能会发生
2、IPC机制
类加载相关
1、什么是类加载器
ClassLoader是用来动态加载class文件到内存中
2、类加载器类型
3、双亲委托模型(父委托加载机制)
双亲委派模型(Parernts Delegation Model),先委托父类加载器寻找目标类进行加载,如果父类不能加载,则去寻找Bootstrap加载器进行加载,Bootstrap不能加载,则会通过findClass( )去加载对应的路劲
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
return c;
}
4、双亲委派模型在热修复领域的应用
一个ClassLoader可以有多个DEX文件,每个Dex文件是一个Element,多个Dex文件排成一个有序数组dexElements,当找类的时候,会按照排序遍历DEX文件,然后从当前遍历的DEX文件中找类,由于双亲委托模型机制,值要找到就会停止查找并返回,如果找不到就从下一个DEX文件继续查找。只要我们先加在修复好的DEX文件,那么就不会加载有bug的DEX文件了。
另外,假设app中有个了叫做A,再其内部引用了B,发布过程中发现B有编写错误,那么想要发布一个新的B类,那么你就要阻止A这个类上CLASS_ISPREVERIFIED的标志。
参照:https://blog.csdn.net/qq_30379689/article/details/72550701