简历
程序员找不到工作怎么办?总是面试不通过怎么办呢?咱们就说说面试中常见的问题。
天天投简历就是得不到面试机会,其实,这个问题很简单,就是因为你的简历写的不行,你想想,HR也没见过你的人,对你也不了解,只能看到你的简历内容,如果得不到面试机会那就想办法优化简历。
比如你擅长的技术点、项目经验、拿到的成果等等,如果实在拿不到面试机会也可以适当的夸张一点,就是所谓的美化简历吧,目的就是能拿到面试机会。只有拿到面试机会,你才有机会展示自己,让面试官了解你,对吧。
自我介绍
当你拿到面试机会以后,在面试时面试官往往第一个问题都是,你先做一个自我介绍吧。
你千万别随便说,这个问题特别重要,这个自我介绍就是你给面试官的第一印象。你想想,你去相亲的时候,第一印象重不重要?面试跟相亲差不多,第一印象特别重要。
去面试以前,一定要提前准备好自我介绍的内容,而且要多练习几次,千万别让你说的时候,你再现想,那给人的感觉就完全不一样了,你能保证每句话说的都特别流畅吗?你能保证把你的亮点都说出来吗?所以说,要提前准备准备,真正说的时候就非常自然了。
那自我介绍怎么说呢?
一般不要超过1分钟,面试官没那么多时间听你的个人详细经历,一定要在一分钟内说重点。比如我叫什么,我要应聘的岗位是什么,有几年的相关工作经验,大致做过哪些项目,项目的成果是什么,根据自己过往的项目经验和咱们公司对这个岗位的要求来看,感觉自己比较适合这个岗位等等。
离职原因
再一个比较敏感的话题,但是基本上大部分面试官都会问,就是你为什么从上家公司离职?
你千万别像有些直性子的程序员说,我被裁了,或者说我们部门进行了大批量裁员,我被裁了。你这样回答的话,给面试官的感觉就是你的技术不行被淘汰了。
也别说是因为上家公司给的工资太低了,也别说加班太多了,更别说自己的领导怎么怎么样,总之呢,不要说上家公司的任何不好。只要你说上家公司的不好,给人的感觉就是,你事特别多,不安稳,就算让你入职的话,你在公司也干不长就走了。
你可以说,我想在这个专业里持续深耕,但是上家公司在调整业务方向,跟我的职业发展规划不是很相符,所以我就离开了。这么说的话,给人的感觉就完全不一样了,你体会体会。
面试的技术性问题
给大家一些建议,提前在网上搜一些面试题,多看看面试题库。
你想想,面试官也是普通人,你别把他想的特别牛,在技术上他也不一定比你厉害,所以说,面试官一般很难创造问题,大部分问题也是常见的问题。如果说有新问题,也是他根据你简历中的项目来说的,比如他会问,你在这个项目中做了哪些功能,具体这个功能是怎么实现的,用到了哪些技术点。就这样,面试官会在这个技术点上深挖,一方面是为了判断这个项目是不是真是你做的,再一方面是考察一下你的技术水平。如果你把我刚才说的几个问题都能流畅的说出来,基本上在项目经验这块是没啥问题的。
当然了,也会有一些奇葩面试官,他会脱离简历去问一些他想问的技术点,或者是他最近遇到的技术难点,问问你的解题思路等等。这种话就没有太好的办法了,主要看你的技术积累和背的面试题库怎么样了。这种问题也不一定所有问题都能答上来,大部分都能说点就行了,当然了,说的越全面越好,最好在回答以后,还反问一下面试官,我的回答可能不全面,你有没有更好的解决方案。或者说,我一般都用这种技术来解决这个问题,你们一般是怎么解决的。怎么说呢,在面试的过程中,跟面试官多交流,让面试官了解你的同时,也可以了解一下面试官。千万不要坐在那一动不动,面试官问你什么就答什么,不问你你就不说话,面试过程尽量轻松一点。
最后
对于那些没有面试资料或者是不知道在哪找面试资料比较全面的朋友可以参考一下我整理的这份《Android面试题汇总》,其中包含了Java基础、Java多线程、虚拟机、Android方面、算法、Kotlin、Flutter、Android Framework、音视频、企业常见面试题,每一个板块每一道面试题都有详细的答案。
面试题展示
*一、抽象类与接口的区别?
1、抽象类可以提供成员方法的实现细节,而接口中只能存在 public 抽象方法;
2、抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的;
3、接口中不能含有构造器、静态代码块以及静态方法,而抽象类可以有构造器、静态代码块和静态方法;
4、一个类只能继承一个抽象类,而一个类却可以实现多个接口;
5、抽象类访问速度比接口速度要快,因为接口需要时间去寻找在类中具体实现的方法;
6、如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。
7、如果你往接口中添加方法,那么你必须改变实现该接口的类。
8、接口更多的为了约束类的行为,可用于解耦,而抽象类更加侧重于代码复用。
二、调用者如何获取绑定后的Service的方法
onBind回调方法将返回给客户端一个IBinder接口实例,IBinder允许客户端回调服务的方法,比如得到Service运行的状态或其他操作。我们需要IBinder对象返回具体的Service对象才能操作,所以说具体的Service对象必须首先实现Binder对象。
三、既使用startService又使用bindService的情况
如果一个Service又被启动又被绑定,则该Service会一直在后台运行。首先不管如何调用,onCreate始终只会调用一次。对应startService调用多少次,Service的onStart
方法便会调用多少次。Service的终止,需要unbindService和stopService同时调用才行。不管startService与bindService的调用顺序,如果先调用unbindService,此时服务不会自动终止,再调用stopService之后,服务才会终止;如果先调用stopService,此时服务也不会终止,而再调用
unbindService或者之前调用bindService的Context不存在了(如Activity被finish的时候)之后,服务才会自动停止。那么,什么情况下既使用startService,又使用bindService呢?
如果你只是想要启动一个后台服务长期进行某项任务,那么使用startService便可以了。如果你还想要与正在运行的Service取得联系,那么有两种方法:一种是使用broadcast,另一种是使用bindService。前者的缺点是如果交流较为频繁,容易造成性能上的问题,而后者则没有这些问题。因此,这种情况就需要startService和bindService一起使用了。
另外,如果你的服务只是公开一个远程接口,供连接上的客户端(Android的Service是C/S架构)远程调用执行方法,这个时候你可以不让服务一开始就运行,而只是bindService,这样在第一次bindService的时候才会创建服务的实例运行它,这会节约很多系统资源,特别是如果你的服务是远程服务,那么效果会越明显(当然在Servcie创建的是偶会花去一定时间,这点需要注意)。
四、什么是反射机制?反射机制的应用场景有哪些?
Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。
应用场景:1. 逆向代码,例如反编译
- 与注解相结合的框架,如 Retrofit
- 单纯的反射机制应用框架,例如 EventBus(事件总线)
- 动态生成类框架 例如Gson
五、谈谈你对Java泛型中类型擦除的理解,并说说其局限性?
Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。局限性:如在代码中定义的List和List等类型,在编译后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM 来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。类型擦除也是Java的泛型实现方法与C++模版机制实现方式之间的重要区别。
六、能具体说说是怎么导致死锁的吗?
在 POSIX 标准中,fork 的行为是这样的:复制整个用户空间的数据 (通常使用 copy-on-write 的策略,所以可以实现的速度很快) 以及所有系统对象,然后仅复制当前线程到子进程。这里:所有父进程中别的线程,到了子进程中都是突然蒸发掉的对于锁来说,从 OS 看,每个锁有一个所有者,即最后一次 lock 它的线程。假设这么一个环境,在 fork 之前,有一个子线程 lock 了某个锁,获得了对锁的所有权。fork 以后,在子进程中,所有的额外线程都人间蒸发了。而锁却被正常复制了,在子进程看来,这个锁没有主人,所以没有任何人可以对它解锁。 当子进程想 lock 这个锁时,不再有任何手段可以解开 了。程序发生死锁
七、MMAP的内存映射原理了解吗?
MMAP内存映射的实现过程,总的来说可以分为三个阶段:
(一)进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域
- 进程在用户空间调用库函数mmap,原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
- 在当前进程的虚拟地址空间中,寻找一段空闲的满足要求的连续的虚拟地址
- 为此虚拟区分配一个vm_area_struct结构,接着对这个结构的各个域进行了初始化
- 将新建的虚拟区结构 (vm_area_struct) 插入进程的虚拟地址区域链表或树中
(二)调用内核空间的系统调用函数mmap(不同于用户空间函数),实现文件物理地址和进程虚拟地址的一一映射关系
- 为映射分配了新的虚拟地址区域后,通过待映射的文件指针,在文件描述符表中找到对应的文件描 述 符,通过文件描述符,链接到内核“已打开文件集”中该文件的文件结构体 (struct file) , 每个
文件结构体维护着和这个已打开文件相关各项信息。- 通过该文件的文件结构体,链接到file_operations模块,调用内核函数mmap,其原型为: int mmap(struct file *filp, struct vm_area_struct *vma),不同于用户空间库 函数。
- 内核mmap函数通过虚拟文件系统inode模块定位到文件磁盘物理地址。
- 通过remap_pfn_range函数建立页表,即实现了文件地址和虚拟地址区域的映射关系。此时,这 片 虚拟地址并没有任何数据关联到主存中。 注:前两个阶段仅在于创建虚拟区间并完成地址映射,但是并没有将任何文件数据的拷贝至主存。
真正的文件读取是当进程发起读或写操作时。进程的读或写操作访问虚拟地址空间这一段映射地址,通过查询页表,发现这一段地址并不在物
理页面上。因为目前只建立了地址映射,真正的硬盘数据还没有拷贝到内存中,因此引发缺页异常。(三)进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝
注:前两个阶段仅在于创建虚拟区间并完成地址映射,但是并没有将任何文件数据的拷贝至主存。真正的文件读取是当进程发起读或写操作时。进程的读或写操作访问虚拟地址空间这一段映射地址,通过查询页表,发现这一段地址并不物理页面上。因为目前只建立了地址映射,真正的硬盘数据还没有拷贝到内存中,因此引发缺页异常。
- 缺页异常进行一系列判断,确定无非法操作后,内核发起请求调页过程。
- 调页过程先在交换缓存空间 (swap cache) 中寻找需要访问的内存页,如果没有则调用nopage 函 数把所缺的页从磁盘装入到主存中。
- 之后进程即可对这片主存进行读或者写的操作,如果写操作改变了其内容,一定时间后系统会自动回写脏页面到对应磁盘地址,也即完成了写入到文件的过程。
注 :修改过的脏页面并不会立即更新回文件中,而是有一段时间的延迟,可以调用msync()来强制同步,这样所写的内容就能立即保存到文件里了
八、简述下Handler机制的总体原理
- Looper 准备和开启轮循: Looper#prepare()初始化线程独有的 Looper 以及 MessageQueue Looper#loop()开启死循环读取 MessageQueue 中下一个满足执行时间的 Message 尚无 Message 的话,调用
Native 侧的 pollOnce()进入无限等待存在 Message,但执行时间 when 尚未满足的话,调用
pollOnce()时传入剩余时长参数进入有限等待- Message 发送、入队和出队: Native 侧如果处于无限等待的话:任意线程向 Handler 发送 Message 或 Runnable 后,Message 将按照 when 条件的先后,被插 入 Handler 持有的 Looper 实例所对应的
MessageQueue 中适当的位置。 MessageQueue 发现有合适的 Message 插入后将调用 Native 侧的
wake() 唤醒无限等待的线程。这将促使 MessageQueue 的读取继续进入下一次循环,此刻 Queue 中已有满足条件的
Message 则 出队返回给 Looper Native 侧如果处于有限等待的话:在等待指定时长后 epoll_wait
将返回。线程继续读取 MessageQueue, 此 刻因为时长条件将满足将其出队 Looper 处理Message 的实现:- Looper 得到 Message 后回调 Message 的 callback 属性即 Runnable,或依据 target 属性即 Handler,去执行 Handler 的回调。存在 mCallback属性的话回调 Handler$Callback 反之,回调
handleMessage()
九、system. server为什么要在Zygote 中启动,而不是由init 直接启动呢?
Zygote 作为一个孵化器,可以提前加载一些资源,这样fork) 时基于Copy-On-Write 机制创建的其他进程就能直接使用这些资源,而不用重新加载。比如 system. server就可以直接使用Zygote 中的JNI 函数.共享库、常用的类、以及主题资源。
十、Binder机制是如何跨进程的
1.Binder驱动
1.1 在内核空间创建- 块接收缓存区,
1.2 实现地址映射:将内核缓存区、接收进程用户空间映射到同一接收缓存区
2.发送进程通过系统调用(copy. from_ user) 将数据发送到内核缓存区。由于内核缓存区和接收进程用户空间存在映射关系,故相当于也发送了接收进程的用户空间,实现了跨进程通信。