Android实习生面试,有四轮技术面(最后一面为CTO面试,基本就是问一些情况),先记录下我记忆比较深的一些题,然后最后也是收到了offer,总体感受就是算法基础要牢固,尤其是Leetcode上面要多刷题,比如链表这些都是必考的,就算一些知识点没有答上来,只要算法过了就还有希望;
目录
目录
第一轮:
Handler的原理
Handler源码简介
Handler的正确写法:
第二轮:
ListView和RecyclerView的区别
hashMap的数据结构,是否线程安全,为什么线程不安全
http协议的了解,https为什么安全
扔鸡蛋问题(LeetCode887)
第三轮
汉明重量
扔石头问题
第四面
时针秒针的夹角问题
Handler的作用为将一个任务切换到某个指定的线程中去进行,Handler的运行需要底层的MessageQueue和Looper的支持;
MessageQueue为消息队列,在内存存储了一组消息,以队列的形式对外提供插入和删除的操作,其内部的存储结构其实不是队列,而是单链表;
Looper的作用是用来消息循环,Looper会以无限循环的形式去查询是否有新消息,如果有的话就处理新消息,否则就会一直等待着;
Looper中有一个ThreadLocal,其作用是可以在每个线程中存储数据,ThreadLocal可以在不同的线程中互不干扰地存储和提供数据,通过ThreadLocal可以轻松获取每个线程的Looper;
需要注意的是,线程是默认没有Looper的,当我们需要使用Handler的时候就必须为线程创建Looper,我们经常提到的主线程,就是ActivityThread,在创建时会初始化Looper,这就是主线程中默认可以使用Handler的原因;
下面两个是我自己顺便记录的知识点
Handler可以通过post方法将一个Runnable投递到Handler内部的Looper中去处理,也可以通过Handler的send方法发送一个消息,这个消息同样会在Looper中去处理,其实post方法也是通过send处理的;
当Handler的send被调用后,会调用MessageQueue的enqueueMessage将这个消息放入消息队列中,读取的方法为next,如果有西消息就会通知Looper,Looper收到消息开始处理,最后Looper交由Handler吹,即Handler的dispatchMessage方法会被调用;在dispatchMessage中最后会调用handlerMessage来处理消息
Looper.prepare()可为当前线程创建一个Looper,通过Looper.loop()来开启消息循环;
Looper的退出:quit会直接退出,quitSafely只是设定一个退出表示,然后把消息队列中的已有消息处理完毕后才完全地退出;在子线程中,如有手动创建了Looper,应该在最后调用quit来终止消息循环
用静态内部类以及弱引用防止内存泄漏;
private static class InnerHandler extends Handler {
WeakReference mWeakReference;
public InnerHandler(DetailActivity detailActivity) {
mWeakReference = new WeakReference(detailActivity);
}
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
if (mWeakReference.get() != null) {
if (msg.what == 1) {
//todo
}
}
}
}
mInnerHandler = new InnerHandler(this);
mInnerHandler.sendEmptyMessage(1);
最后在onDestroy中
mInnerHandler.removeCallbacksAndMessages(null);
1.在使用效果上说:RecyclerView可以提供线性布局,网格布局,瀑布流布局三种,还可以控制横向和纵向滚动
2.使用方法:
ListView需要继承重写BaseAdapter;自定义ViewHolder和convertView一起完成复用优化工作;
RecyclerView继承重写RecyclerView.Adapter和RecyclerView.ViewHolder;设置布局管理器,控制布局效果;
RecyclerView提供了notifyItemChanged用于单个item的刷新;
RecyclerView提供了item的动画效果;
3.缓存机制:
RecyclerView比ListView多两级缓存,支持开发者自定义缓存处理逻辑,支持所有RecyclerView共用同一个RecyclerViewPool(缓存池);
1). RecyclerView缓存RecyclerView.ViewHolder,抽象可理解为:
View + ViewHolder(避免每次createView时调用findViewById) + flag(标识状态);
RecyclerView中mCacheViews(屏幕外)获取缓存时,是通过匹配pos获取目标位置的缓存,这样做的好处是,当数据源数据不变的情况下,无须重新bindView:
2). ListView缓存View。而同样是离屏缓存,ListView从mScrapViews根据pos获取相应的缓存,但是并没有直接使用,而是重新getView(即必定会重新bindView)
是一个Entry数组,每一个Entry包含一个key-value键值对;
Entry就是HashMap中的一个静态内部类;
简单来说,HashMap是由数组+链表组成的,数组时HashMap的主体,链表是为了哈希冲突而存在的,如果定位到的数组位置不含链表,那么查找,添加等操作很快,否则对于添加操作,时间复杂度为O(n)
扩容时,容量默认16,扩容2倍
java8比java7的基础上添加了红黑树这种数据结构
为什么HashMap不安全
1.put的时候导致的多线程数据不一致,比如有两个线程A和B,首先A希望插入一个key_value到HashMap中,首先计算要落到的桶的索引坐标,然后获取到该桶的链表头结点,此时线程A的时间片用完了,线程B执行,B将记录插到桶里面,假设两个索引一样,这时线程A在调用,就覆盖掉了线程B的记录,造成数据不一致;
为什么安全:
应用层http和传输层tcp中间加多了一层TLS/SSL加密套件,https就是应用层将数据给到TLS/SSL,然后将数据加密后,再给到TCP进行传输;
SharedPreferences的使用非常简单,能够轻松的存放数据和读取数据。SharedPreferences只能保存简单类型的数据,例如,String、int等。一般会将复杂类型的数据转换成Base64编码,然后将转换后的数据以字符串的形式保存在 XML文件中,再用SharedPreferences保存。
使用SharedPreferences保存key-value对的步骤如下:
(1)使用Activity类的getSharedPreferences方法获得SharedPreferences对象,其中存储key-value的文件的名称由getSharedPreferences方法的第一个参数指定。
(2)使用SharedPreferences接口的edit获得SharedPreferences.Editor对象。
(3)通过SharedPreferences.Editor接口的putXxx方法保存key-value对。其中Xxx表示不同的数据类型。例如:字符串类型的value需要用putString方法。
(4)通过SharedPreferences.Editor接口的commit方法保存key-value对。commit方法相当于数据库事务中的提交(commit)操作。
apply没有返回值,commit有返回值
在数据并发时commit效率低于apply,推荐使用apply
你将获得 K 个鸡蛋,并可以使用一栋从 1 到 N 共有 N 层楼的建筑。
每个蛋的功能都是一样的,如果一个蛋碎了,你就不能再把它掉下去。
你知道存在楼层 F ,满足 0 <= F <= N 任何从高于 F 的楼层落下的鸡蛋都会碎,从 F 楼层或比它低的楼层落下的鸡蛋都不会破。
每次扔,你可以取一个鸡蛋(如果你有完整的鸡蛋)并把它从任一楼层 X 扔下(满足 1 <= X <= N)。
你的目标是确切地知道 F 的值是多少。
无论 F 的初始值如何,你确定 F 的值的最小扔的次数是多少?
题意:就是要找到你鸡蛋在哪一个楼层扔下去正好碎掉,要考虑最坏情况下的次数
题解:因为这个题限定了鸡蛋的个数,所以不能简单地用二分法来求解,这个题用到了动态规划,就是说比如你有两个鸡蛋,在x层扔的时候,这个时候的函数为dp(2,x),然后扔下去碎了,就意味着你剩下一个鸡蛋,且只需要验证x下面的所有楼层,即dp(1,x-1); 而如果没有碎,则意味着只需要验证x之上的楼层即可,即dp(2,N-x);
又因为这两个函数的单调性,要找到所求的值,需要找到他们的交点,即可用二分法来找交点两侧的亮点,并最后求最小值
class Solution {
public int superEggDrop(int K, int N) {
return dp(K, N);
}
Map memo = new HashMap();
public int dp(int K, int N) {
if (!memo.containsKey(N * 100 + K)) {//判断之前是否计算过
int res ;
if (N == 0) { //在第0层楼
res = 0;
} else if (K == 1) { //只剩一个鸡蛋
res = N;
} else {//使用二分法找交点
int low = 1, high = N;
while (low + 1 < high) {
int x = (low + high) / 2;
int t1 = dp(K - 1, x - 1);//碎了,单调递增
int t2 = dp(K, N - x);//没碎,单调递减
//求两函数交点
if (t1 < t2) {
low = x;
} else if (t1 > t2) {
high = x;
} else {
low = high = x;
}
}
//退出循环说明在low和high在交点两侧
res = 1 + Math.min(
Math.max(dp(K - 1, low - 1), dp(K, N - low)),
Math.max(dp(K - 1, high - 1), dp(K, N - high))
);
}
memo.put(N * 100 + K, res);//防止重复计算
}
return memo.get(N * 100 + K);
}
}
编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int res = 0;
while (n != 0) {
res++;
n &= n-1; //把最后一个1变为0
}
return res;
}
}
你和你的朋友,两个人一起玩 Nim 游戏:
桌子上有一堆石头。
你们轮流进行自己的回合,你作为先手。
每一回合,轮到的人拿掉 1 - 3 块石头。
拿掉最后一块石头的人就是获胜者。
j假设你们每一步都是最优解。请编写一个函数,来判断你是否可以在给定石头数量为 n 的情况下赢得游戏。如果可以赢,返回 true;否则,返回 false 。
题解:这个题就是只要留下了4个,那么对方怎么拿,你都能赢,因此4的倍数就是是否能赢的判断条件;
class Solution {
public boolean canWinNim(int n) {
return n % 4 != 0;
}
}
题意:比如00:00时时针秒针夹角0°,00:01时夹角为6°-0.5° = 5.5°,问什么时候夹角为6°
class Solution {
public static void main(String[] args) {
for (int i = 0; i <= 23; i++) {
for (int j = 0; j <= 59; j++) {
if (getDegree(i, j) == 6*10 || getDegree(i, j) == -6*10) {
System.out.println(i + " " + j);
}
}
}
}
private static int getDegree(int hour, int minute) {
int hourDegree = 0;
int minDegree = 0;
int res = 0;
if (hour >= 12) hour -= 12;
hourDegree += hour * 30 * 10;
minDegree += minute * 6 * 10;
hourDegree += minute * 0.5 * 10;
res = hourDegree - minDegree;
return res;
}
}