欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

记一次android面试

程序员文章站 2022-04-20 08:29:48
Android实习生面试,有三轮技术面第一轮:Handler的原理第二轮:RecyclerView中Recycler的原理hashMap的数据结构,是否线程安全http协议的了解SharePreferences的原理扔鸡蛋问题第三轮重明数扔石头问题......

Android实习生面试,有四轮技术面(最后一面为CTO面试,基本就是问一些情况),先记录下我记忆比较深的一些题,然后最后也是收到了offer,总体感受就是算法基础要牢固,尤其是Leetcode上面要多刷题,比如链表这些都是必考的,就算一些知识点没有答上来,只要算法过了就还有希望;

目录

目录

第一轮:

Handler的原理

Handler源码简介

Handler的正确写法:

第二轮:

ListView和RecyclerView的区别

hashMap的数据结构,是否线程安全,为什么线程不安全

http协议的了解,https为什么安全

SharePreferences的原理,apply和commit的区别

扔鸡蛋问题(LeetCode887)

第三轮

汉明重量

扔石头问题

第四面

时针秒针的夹角问题



第一轮:

Handler的原理

Handler的作用为将一个任务切换到某个指定的线程中去进行,Handler的运行需要底层的MessageQueue和Looper的支持;

MessageQueue为消息队列,在内存存储了一组消息,以队列的形式对外提供插入和删除的操作,其内部的存储结构其实不是队列,而是单链表;

Looper的作用是用来消息循环,Looper会以无限循环的形式去查询是否有新消息,如果有的话就处理新消息,否则就会一直等待着;

Looper中有一个ThreadLocal,其作用是可以在每个线程中存储数据,ThreadLocal可以在不同的线程中互不干扰地存储和提供数据,通过ThreadLocal可以轻松获取每个线程的Looper;

需要注意的是,线程是默认没有Looper的,当我们需要使用Handler的时候就必须为线程创建Looper,我们经常提到的主线程,就是ActivityThread,在创建时会初始化Looper,这就是主线程中默认可以使用Handler的原因;

下面两个是我自己顺便记录的知识点

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来终止消息循环

Handler的正确写法:

 用静态内部类以及弱引用防止内存泄漏;

private static class InnerHandler extends Handler {
        WeakReference<DetailActivity> mWeakReference;

        public InnerHandler(DetailActivity detailActivity) {
            mWeakReference = new WeakReference<DetailActivity>(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);

第二轮:

ListView和RecyclerView的区别

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)

hashMap的数据结构,是否线程安全,为什么线程不安全

是一个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协议的了解,https为什么安全

  1. 基于请求和响应模式的无连接,无状态,应用层协议
  2. 简单快速:协议简单,通信速度快;
  3. 灵活:成功传输任意类型的数据对象,由Content-Type标记;
  4. 无连接:每次处理一个请求,处理完成即断开;
  5. 无状态:对事物处理没有记忆功能;
  6. http是应用层的协议,底层基于TCP/IP协议

   为什么安全: 

     应用层http和传输层tcp中间加多了一层TLS/SSL加密套件,https就是应用层将数据给到TLS/SSL,然后将数据加密后,再给到TCP进行传输;

SharePreferences的原理,apply和commit的区别

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

扔鸡蛋问题(LeetCode887)

你将获得 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<Integer, Integer> 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;
    }
}

 

本文地址:https://blog.csdn.net/qq873044564/article/details/109323144

相关标签: Android