往事只能回味!春招 Android 开发岗:我居然三天就拿到了offer?(1)

  • 处于激活状态的线程

  • 栈中的对象

  • JNI栈中的对象

  • JNI中的全局对象

  • 正在被用于同步的各种锁对象

  • JVM自身持有的对象,比如系统类加载器等。

2.1.14 GC算法

| 名称 | 描述 | 优点 | 缺点 |

| — | — | — | — |

| 标记-清除算法 | 暂停除了GC线程以外的所有线程,算法分为“标记”和“清除”两个阶段,首先从GC Root开始标记出所有需要回收的对象,在标记完成之后统一回收掉所有被标记的对象。 | | 标记-清除算法的缺点有两个:首先,效率问题,标记和清除效率都不高。其次,标记清除之后会产生大量的不连续的内存碎片,空间碎片太多会导致当程序需要为较大对象分配内存时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作 |

| 复制算法 | 将可用内存按容量分成大小相等的两块,每次只使用其中一块,当这块内存使用完了,就将还存活的对象复制到另一块内存上去,然后把使用过的内存空间一次清理掉 | 这样使得每次都是对其中一块内存进行回收,内存分配时不用考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效 | 复制算法的缺点显而易见,可使用的内存降为原来一半 |

| 标记-整理算法 | 标记-整理算法在标记-清除算法基础上做了改进,标记阶段是相同的,标记出所有需要回收的对象,在标记完成之后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的对象,这个过程叫做整理。 | 标记-整理算法相比标记-清除算法的优点是内存被整理以后不会产生大量不连续内存碎片问题。复制算法在对象存活率高的情况下就要执行较多的复制操作,效率将会变低,而在对象存活率高的情况下使用标记-整理算法效率会大大提高 | |

| 分代收集算法 | 是java的虚拟机的垃圾回收算法.基于编程中的一个事实,越新的对象的生存期越短,根据内存中对象的存活周期不同,将内存划分为几块,java的虚拟机中一般把内存划分为新生代和年老代,当新创建对象时一般在新生代中分配内存空间,当新生代垃圾收集器回收几次之后仍然存活的对象会被移动到年老代内存中,当大对象在新生代中无法找到足够的连续内存时也直接在年老代中创建 | | |

2.2 Android


2.2.1 Handler、MessageQueue等一套东西讲一下,详细说了下源码。为什么主线程loop不会ANR?

  • Android线程模型就是消息循环,Looper关联MessageQueue,不断尝试从MessageQueue取出Message来消费,这个过程可能会被它自己阻塞.

  • 而Handler最终都调用enqueueMessage(Message,when)入队的,延迟的实现时当前是时间加上延迟时间给消息指定一个执行的时间点,然后在MessageQueue找到插入位置,此时会判断是否需要唤醒线程来消费消息以及更新下次需要暂停的时间.

  • Message知道要发到哪个Handler是因为Message把Handler保存到了target.

  • Message内部使用链表进行回收复用

2.2.2 View事件以及View体系相关知识

建议看《Android开发艺术探索》,这玩意三言两语讲不清楚

2.2.3 Android中使用多线程的方法

  • 裸new一个Thread(失控线程,不推荐)

  • RxJava的调度器(io(优先级比低),密集计算线程(优先级比高,用于执行密集计算任务),安卓主线程, Looper创建(实际上内部也是创建了Handler))

  • Java Executor框架的Executors#newCachedThreadPool(),不会造成资源浪费,60秒没有被使用的线程会被释放

  • AsyncTask,内部使用FutureTask实现,通过Handler将结果转发到主线程,默认的Executor是共用的,如果同时执行多个AsyncTask,就可能需要排队,但是可以手动指定Executor解决这个问题,直接new匿名内部类会保存外部类的引用,可能会导致内存泄漏

  • Android线程模型提供的Handler和HandlerThread

  • 使用IntentService

  • IntentService和Service的区别——没什么区别,其实就是开了个HandlerThread,让它不要在主线程跑耗时任务

2.2.4 RecyclerView复用缓存

建议看一下,这个可能会被问,不过我运气好没被问到.

2.2.5 Activity启动流程

网上有很多相关的文章,可以自己结合源码去看一下,如果能讲个大概的话也是很加分的.

2.2.6 JNI(除非你自己说你会,否则不是很常考)

  • 可避免的内存拷贝,直接传递对象,到C层是一个jobject的指针,可以使用jmethodID和jfiledID访问方法和字段,无需进行内存拷贝,使用直接缓冲区也可以避免内存拷贝.

  • 无法避免的内存拷贝,基本类型数组,无法避免拷贝,因为JVM不信任C层的任何内存操作,特别是字符串操作,因为Java的字符串与C/C++的字符串所使用的数据类型是不一样的C/C++使用char一个字节(1字节=8位)或wchar_t是四字节.而jstring和jchar使用的是UTF-16编码使用双字节.(Unicode是兼容ASCII,但不兼容GBK,需要自己转换)

  • 自己创建的局部引用一定要释放,否则一直持有内存泄漏

  • 非局部引用方法返回后就会失效,除非创建全局引用,jclass是一个jobject,方法外围使用时需要创建全局引用,jmethodID和jfiledID不需要.

  • JNI是通过Java方法映射到C函数实现的,如果使用这种方法,函数必须以C式接口导出(因为C++会对名字做修饰处理),当然也可以在JNI_OnLoad方法中注册.

  • JNIEnv是线程独立的,JNI中使用pthread创建的线程没有JNIEnv,需要AttachCurrentThread来获取JNIEnv,不用时要DetachCurrentThread

2.3专业课


2.3.1 TCP和UDP的根本区别?

数据报,流模式,TCP可靠,包序不对会要求重传,UDP不管,甚至不能保证送到

2.3.2 TCP三次握手

这个被问的几率非常的大,几乎等于必问,建议专门花时间去看.

2.3.3 Http和Https

CA证书,中间机构,公钥加密对称秘钥传回服务端,一个明文一个加密,SSL层,中间人攻击,参考link

2.4 ACM


对于ACM,比较常考链表的题,不常刷算法的同学一定不要对其有抵触心理.

你可能会问为什么要ACM?网上答案说的什么提高代码质量,能够更好地阅读别人的代码这些理由有一定道理,但对于我们去面试的人而言最重要的是ACM是面试官考察你编码能力的最直接的手段,所以不用说这么多废话刷题就够了.

刷题的话,建议去刷leetcode,题号在200以内的,简单和中等难度,不建议刷困难,因为面试的时候基本就不会出,没人愿意在那里等你想一个半个小时的.

在面试官面前现场白板编程时,可以先把思路告诉面试官,写不写得出来是另外一回事,时间复杂度和空间复杂度是怎么来的一定要搞清楚.在编码时也不一定要写出最佳的时间和空间的算法,但推荐你写出代码量最少,思路最清晰的,这样面试官看得舒服,你讲得也舒服.

下面是我在网上收集或者是在实际中遇到过的ACM题,基本上在leetcode上也都有类似的.

2.4.1 数组、链表

  • 链表逆序(头条几乎是必考的)

public ListNode reverseList(ListNode head)

{

if (head == null)

{

return null;

}

if (head.next == null)

{

return head;

}

ListNode prev = null;

ListNode current = head;

while (current != null)

{

ListNode next = current.next;

current.next = prev;

prev = current;

current = next;

}

return prev;

}

  • 删除排序数组中的重复项

public int removeDuplicates(int[] nums)

{

int length = nums.length;

if (length == 0 || length == 1)

{

return length;

}

int size = 1;

int pre = nums[0];

for (int i = 1; i < length; )

{

if (nums[i] == pre)

{

i++;

} else

{

pre = nums[size++] = nums[i++];

}

}

return size;

}

  • 数组中找到重复元素

  • n个长为n的有序数组,求最大的n个数

  • 用O(1)的时间复杂度删除单链表中的某个节点 把后一个元素赋值给待删除节点,这样也就相当于是删除了当前元素,只有删除最后一个元素的时间为o(N)平均时间复杂度仍然为O(1)

public void deleteNode(ListNode node) {

ListNode next = node.next;

node.val = next.val;

node.next = next.next;

}

  • 删除单链表的倒数第N个元素 两个指针,第一个先走N步第二个再走,时间复杂度为O(N),参考link

public ListNode removeNthFromEnd(ListNode head, int n) {

if (head == null)

{

return null;

}

if (head.next == null)

{

return n == 1 ? null : head;

}

int size = 0;

ListNode point = head;

ListNode node = head;

do

{

if (size >= n + 1)

{

point = point.next;

}

node = node.next;

size++;

} while (node != null);

if (size == n)

{

return head.next;

}

node = point.next;

point.next = node == null ? null : node.next;

return head;

}

  • 从长序列中找出前K大的数字

  • 用数组实现双头栈

public static class Stack

{

public Stack(int cap)

{

if (cap <= 0)

{

throw new IllegalArgumentException();

}

array = new Object[cap];

left = 0;

right = cap - 1;

}

private Object[] array;

private int left;

private int right;

public void push1(T val)

{

int index = left + 1;

if (index < right)

{

array[index] = val;

}

left = index;

}

@SuppressWarnings(“unchecked”)

public T pop1()

{

if (left > 0)

{

return (T)array[left–];

}

return null;

}

public void push2(T val)

{

int index = right - 1;

if (index > left)

{

array[index] = val;

}

right = index;

}

@SuppressWarnings(“unchecked”)

public T pop2()

{

if (right < array.length)

{

return (T)array[right++];

}

return null;

}

}

  • 两个链表求和,返回结果也用链表表示 1 -> 2 -> 3 + 2 -> 3 -> 4 = 3 -> 5 -> 7

public ListNode addTwoNumbers(ListNode node1, ListNode node2)

{

ListNode head = null;

ListNode tail = null;

boolean upAdd = false;

while (!(node1 == null && node2 == null))

{

ListNode midResult = null;

if (node1 != null)

{

midResult = node1;

node1 = node1.next;

}

if (node2 != null)

{

if (midResult == null)

{

midResult = node2;

} else

{

midResult.val += node2.val;

}

node2 = node2.next;

}

if (upAdd)

{

midResult.val += 1;

}

if (midResult.val >= 10)

{

upAdd = true;

midResult.val %= 10;

}

else

{

upAdd = false;

}

if (head == null)

{

head = midResult;

tail = midResult;

} else

{

tail.next = midResult;

tail = midResult;

}

}

if (upAdd)

{

tail.next = new ListNode(1);

}

return head;

}

  • 交换链表两两节点

public ListNode swapPairs(ListNode head)

{

if (head == null)

{

return null;

}

if (head.next == null)

{

return head;

}

ListNode current = head;

ListNode after = current.next;

ListNode nextCurrent;

head = after;

do

{

nextCurrent = after.next;

after.next = current;

if (nextCurrent == null)

{

current.next = null;

break;

}

current.next = nextCurrent.next;

after = nextCurrent.next;

if (after == null)

{

current.next = nextCurrent;

break;

}

current = nextCurrent;

} while (true);

return head;

}

  • 找出数组中和为给定值的两个元素,如:[1, 2, 3, 4, 5]中找出和为6的两个元素。

public int[] twoSum(int[]mun,int target)

{

Map<Integer, Integer> table = new HashMap<>();

for (int i = 0; i < mun.length; ++i)

{

Integer value = table.get(target - mun[i]);

if (value != null)

{

return new int[]{i, value};

}

table.put(mun[i], i);

}

return null;

}

2.4.2 树

  • 二叉树某一层有多少个节点

2.4.3 排序

  • 双向链表排序(这个就比较过分了,遇到了就自求多福吧)

public static void quickSort(Node head, Node tail) {

if (head == null || tail == null || head == tail || head.next == tail) {

return;

}

if (head != tail) {

Node mid = getMid(head, tail);

quickSort(head, mid);

quickSort(mid.next, tail);

}

}

public static Node getMid(Node start, Node end) {

int base = start.value;

while (start != end) {

while(start != end && base <= end.value) {

end = end.pre;

}

start.value = end.value;

while(start != end && base >= start.value) {

start = start.next;

}

end.value = start.value;

}

start.value = base;

return start;

}

/**

  • 使用如内部实现使用双向链表的LinkedList容器实现的快排

*/

public static void quickSort(List list) {

if (list == null || list.isEmpty()) {

return;

}

quickSort(list, 0, list.size() - 1);

}

private static void quickSort(List list, int i, int j) {

if (i < j) {

int mid = partition(list, i, j);

partition(list, i, mid);

partition(list,mid + 1, j);

}

}

private static int partition(List list, int i, int j) {

int baseVal = list.get(i);

while (i < j) {

while (i < j && baseVal <= list.get(j)) {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

题外话

我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。

我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在IT学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多程序员朋友无法获得正确的资料得到学习提升,故此将并将重要的Android进阶资料包括自定义view、性能优化、MVC与MVP与MVVM三大框架的区别、NDK技术、阿里面试题精编汇总、常见源码分析等学习资料。

【Android思维脑图(技能树)】

知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。

希望我能够用我的力量帮助更多迷茫、困惑的朋友们,帮助大家在IT道路上学习和发展~

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

g-VQYLQQle-1712409909467)]

[外链图片转存中…(img-n5S4ONQX-1712409909467)]

[外链图片转存中…(img-QM62WTyq-1712409909468)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

题外话

我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。

我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在IT学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多程序员朋友无法获得正确的资料得到学习提升,故此将并将重要的Android进阶资料包括自定义view、性能优化、MVC与MVP与MVVM三大框架的区别、NDK技术、阿里面试题精编汇总、常见源码分析等学习资料。

【Android思维脑图(技能树)】

知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。

[外链图片转存中…(img-KolJIVRC-1712409909468)]

希望我能够用我的力量帮助更多迷茫、困惑的朋友们,帮助大家在IT道路上学习和发展~

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 13
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值