回顾下一次面试经历,去的是一家文学网站的Android开发应聘,面试过程中回答的一些面试题有些片面或者比较不全面,现在抽时间进行整体总结下:
一、Java方面:
1、二进制和异或的算法?
这个当时估计也是想考察下最基础的Java知识点,简单问了1和2的二进制以及其异或后的结果。首先考察了二进制和十进制的转换,其次考察了异或的知识点,涉及知识点或者延申知识点:
1)十进制转换为二进制
关于进制间转换,我专门写过一篇博客,需要学习可以参考下二进制转换
2)位运算符
&:按位与 —— 只有两个操作数对应位同为1时,结果为1,其余全为0.
| :按位或 —— 只有两个操作数对应位同为0时,结果为0,其余全为1.
~:按位非 —— 原码 --取反-> 反码 --加1-> 补码 --取反-> 按位非值(负数);原码(补码 ) --取反-> 按位非值(正数).
^:按位异或 —— 异:1 ,同:0
<<:左移位运算符 —— 丢弃最高位,0补最低位;如果移动的位数超过了该类型的最大位数,那么编译器会对移动的位数取 模。如对int型移动33位,实际上只移动了332=1位。
>>:右移位运算符 —— 符号位不变,左边补上符号位 。 按二进制形式把所有的数字向右移动对应的位数,低位移出(舍 弃),高位的空位补符号位,即正数补零,负数补1,当右移的运算数是byte 和short类型时,将自动把 这些类型扩大为 int 型
>>>:无符号右移运算符
位运 算 符 中 ,除 ~ 以 外 ,其余 均 为 二 元 运 算 符 。 操 作 数 只 能 为 整 型 和字 符 型 数 据 。
在Java语言中,二进制数使用补码表示,最高位为符号位,正数的符号位为0,负数为1。补码的表示需要满足如下要求。
(a)正数的最高位为0,其余各位代表数值本身(二进制数)。
(b)对于负数,通过对该数绝对值的补码按位取反,再对整个数加1。
具体分析,后续博客会进行详尽的总结(未完待续。。。)。
2、HashMap中元素可以有null么,原因是什么?
HashMap的key 和 value 都可以为null,但是HashTable的key 和 value 都不可以为null;并且两者的key都不能重复,若是添加的key值相同,后面的value会自动覆盖前面的value,但是不会报错。
hashMap是根据key 的 hashCode来寻找存放位置的,当key 为null的时候在put 方法中,处理了key== null的情况:
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
*/
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);
int i = indexFor(hash, table.length);
for (HashMapEntry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
从put方法可以看出key == null 的时候直接执行putForNullKey这个方法:
/**
* Offloaded version of put for null keys
*/
private V putForNullKey(V value) {
for (HashMapEntry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
在for循环中,是在table[0]链表中查找key == null 的元素,如果找到,则将这个值重新赋值给这个元素的value,并返回原来的value 。具体HashMap的底层存储分析可以参考博客:https://www.cnblogs.com/peizhe123/p/5790252.html
3、内存?
内存与PC的内存是一样的,是用来运行程序,不能用来永久存储数据,手机一旦关机,在内存中的所有数据都将会丢失,内存也是现在人类制造的所有电子设备所必需拥有的。
内部存储,一般可以存储在以下的 目录文件中:
1.data/data/包名/shared_prefs
2.data/data/包名/databases
3.data/data/包名/files
4.data/data/包名/cache
外部存储,一般在storage 或者 mnt 的目录文件下,这个具体由各大手机的厂商来决定。
具体的存储分析可以参考这篇博客:https://blog.csdn.net/wulex/article/details/72899363,关于Android的数据存储的分析后期也会自己做一些相关的分析总结(未完待续。。。)
4、多线程并发,多线程可以同时访问数据库数据么,会出现什么情况?
/**
* 多线程
*/
private void threadTest() {
//方法一:线程启动
new Thread() {
@Override
public void run() {
super.run();
//耗时操作
}
}.start();
//方法二:线程启动
new Thread(new Runnable() {
@Override
public void run() {
//耗时操作
}
}).start();
}
这是两种启动线程的方法,看起来好像是一样的,其实不是,方法一是覆写了Thread类中run 方法,方法二通过Thread的构造函数传递了Runnable对象,而在其中执行耗时操作。但是其实分析两个方法的源码,发现最终执线程执行的任务是Runnable。
方法一中覆写了run()方法,其实
@Override
public void run() {
if (target != null) {
target.run();
}
}
在这里target其实就是Runnable对象,Thread其实就是对Runnable的包装。当启动一个线程的时候,如果Thread的target不为空,则会在子线程中执行这个run 方法中的耗时操作,否则虚拟机会执行该线程自身run函数。具体线程基本问题后期会具体分析(未完待续。。。)
多线程访问数据库,一般是通过加锁synchronize来解决并发问题的,给增删改查方法加锁,但是这种方式的效率比较低。可以用队列来解决这个问题:
思路就是:创建一个单线程的线程池,所有的数据库操作都用这个线程池来操作
https://blog.csdn.net/lizhengwei1989/article/details/69264554
5、ArrayList 和 LinkedList 的区别?
ArrayList : 采用数组的形式来保存数据,采用顺序存储方式,对象存储在连续的位置中,最大缺点是插入和删除非常麻烦。
LinkedList :采用双向链表的存储方式,但是不是首尾相连接的循环链表,对象存放在独立的空间中,且每个空间中还存储着下一个链接的索引,最大的缺点就是查找非常麻烦,需要从第一个索引开始。
两者都实现List接口,并同时拥有Collection接口,Android开发中使用区别:
1)、ArrayList实现了基于动态数组的数据结构,LinkedList基于链表的数据结构;
2)、对于随机访问get() set(),ArrayList优于LinkedList,因为LinkedList要移动指针;
3)、对于新增add()和删除remove操作,LinkedList比较占优势,因为ArrayList要移动数据;
4)、ArrayList线程是不同步的。
6、内部类?
内部类分为成员内部类、方法内部类、匿名内部类,静态内部类。
内部类隐藏你不想让别人知道的操作,即封装性;
一个内部类对象可以访问创建他的外部类对象的内容,甚至包括私有变量;
静态内部类没有了指向外部类的引用;
多继承更简单易懂;
可以使用来写测试用例。
7、进程及线程,Service属于进程还是线程,进程的优先级?
在Android的底层,进程构造了底部的一个运行池,不仅仅是Task中的各个Activity组件,其他三大组件Service、Content Provider、Broadcast Receiver,都是寄宿在底层某个进程中,进行运转;service 和activity 一样是Android一个组件, 依赖进程的主线程上,处理跟交互UI无关工作。
具体分析区别曾经转载过一片博客进程与线程分析
进程优先级:前台进程 > 服务进程 > 后台进程
- 后台服务会随着app的退出而退出
- 前台服务在app退出后仍然在运行
- 服务进程比后台进程优先级高
- 服务比线程更方便控制
- Service的onCreate、onBind、onStartCommand运行在主线程中,可以在Service中开启子线程
8、Java中== 与 equals 的区别?
== : 基本数据类型,比较的是他们的值;
其他类型比较的是操作符两端是否是同一个对象;
两边的操作数必须是同一个类型才能编译通过;
比较的是地址,如果是具体的阿拉伯数字的比较,值相等则为true,如:
int a=10 与 long b=10L 与 double c=10.0都是相同的(为true),因为他们都指向地址为10的堆。
equals: 用来比较的是两个对象的内容是否相等;
在比较两个字符串是否相等的时候,也可以说是在比较他俩的值是否相等。
9、switch参数可以是long , int ,String 么?
switch表达式后面的数据类型只能是byte,short,char,int四种数据类型,枚举类型和java.lang.String类型(从java 7才允 许),switch能够支持他们的包装类,是因为自动拆箱(从JDK1.5开始支持自动拆箱和自动装箱,自动拆箱就是自动将引用 数据类型转化为基本数据类型,自动装箱就是自动将基本数据类型转化为引用数据类型)的原因。
二、Android方面
1、app销毁后内存是否释放?
程序退出后,还有一些内存没有释放。销毁进程杀死,可以释放内存。
2、LinearLayout和RelativeLayout哪个效率更高?
(1)RelativeLayout慢于LinearLayout是因为它会让子View调用2次measure过程,而后者只需一次,但是有weight属性存在时,后者同样会进行两次measure。
(2)RelativeLayout的子View如果高度和RelativeLayout不同,会引发效率问题,可以使用padding代替margin以优化此问题。
(3)在不响应层级深度的情况下,使用Linearlayout而不是RelativeLayout。
具体分析:https://www.cnblogs.com/qitian1/p/6461473.html
3、Glide与Picasso 的比较?
1)、Glide的库大小和方法的数量远远大于Picasso;
2)、Glide的使用方法与Picasso类似,但是Glide与Activity以及Fragment的生命周期有关系;
3)、Glide可以加载gif图片,Picasso 不行;
4)、两个库都支持缓存图片,都通过下载网络图片缓存到本地。但是缓存机制两个库有不同的做法:
Picasso 是将网络图片缓存到本地,如果需要改变图片尺寸,一般先从网络缓存完整图片尺寸,然后再进行裁剪得到需要 的尺寸。
Glide 不同的尺寸需求从网络获取图片后先进行裁剪获得合适尺寸图片然后再缓存到本地,同一网络图片可以缓存不同尺 寸的本地图片。
5)、Glide默认使用RGB_555 的设定,Picasso默认使用ARGB _8888的设定。Picasso是缓存full size 的图片,而Glide是加载 已经改变后的图片,所以内存相对于Picasso会小一点,这样可以减少 OutOfMemoryError 的可能性;
6)、加载图片Picasso相对于Glide会快一点,因为Picasso是直接把图加载到内存中,而Glide需要先改变图片尺寸再加载到内 存中;从缓存中加载图片Glide相对于Picasso要快一点,因为Glide是直接从内存中加载图片,而Picasso从内存得到图片 还需要进行裁剪再加载。
4、未完待续。。。