在本文的开头,先分享一下本身的春招经历吧:html
各位掘友你们好,我是练习时长快一年的Android小蔡鸡,喜欢看源码,逛掘金,写技术文章......java
好了好,不开玩笑了OWO,本人大三,今年春招投了许多简历的,都是实习岗,可是被捞的只有阿里,头条和美团,一路下来挺不容易的.node
我的认为在春招中运气>性格>三观>技术.面试
此次春招给我最大的感触就是,当你以为本身像复读机能把面试题给复读出来而且对面试官所提的问题能像HashMap同样在常数时间内找到答案的时候,你就离成功很近了.算法
下面是我在准备面试的时候收集的一些知识点:编程
volatile
理解,JMM中主存和工做内存究竟是啥?和JVM各个部分怎么个对应关系?参考link数组
Serializable
在序列化时使用了反射,从而致使GC的频繁调用,参考link缓存
volatile
,一个线程的修改对另一个线程是立刻可见的,CAS
操做,要么都作要么都不作synchronized
经过进入和退出Monitor
(观察器)实现,CPU
可能会乱序执行指令,若是在本线程内观察,全部操做都是有序的,若是在一个线程中观察另外一个线程,全部操做都是无序的.参考linkjava锁机制实际上是锁总线,同步关键字和Lock接口的优劣.安全
字面值是常量,在字节码中使用id索引,equals相等引用不必定相等,Android上String的构造函数会被虚拟机拦截,重定向到StringFactorybash
数组加链表加红黑树,默认负载因子0.75
,树化阈值8
,这部分比较常考,建议专门准备.(打个小广告OWO,你也能够关注个人专栏,里面有一篇文章分析了HashMap和ArrayMap)
CAS的实现如AtomicInteger
等等
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
复制代码
信号量要与一个锁结合使用,当前线程要先得到这个锁,而后等待与这个锁相关联的信号量,此时该锁会被解锁,其余线程能够抢到这个锁,若是其余线程抢到了这个锁,那他能够通知这个信号量,而后释放该锁,若是此时第一个线程抢到了该锁,那么它将从等待处继续执行(应用场景,将异步回调操做封装为变为同步操做,避免回调地狱)
信号量与锁相比的应用场景不一样,锁是服务于共享资源的,而信号量是服务于多个线程间的执行的逻辑顺序的,锁的效率更高一些.
线程上保存着ThreadLocalMap,每一个ThreadLocal使用弱引用包装做为Key存入这个Map里,当线程被回收或者没有其余地方引用ThreadLocal时,ThreadLocal也会被回收进而回收其保存的值
ClassLoader
双亲委派机制简单来讲就是先把加载请求转发到父加载器,父加载器失败了,再本身试着加载
经过System Class Loader或者Boot Class Loader加载的class对象,经过自定义类加载器加载的class不必定是GC Root:
名称 | 描述 | 优势 | 缺点 |
---|---|---|---|
标记-清除算法 | 暂停除了GC线程之外的全部线程,算法分为“标记”和“清除”两个阶段,首先从GC Root开始标记出全部须要回收的对象,在标记完成以后统一回收掉全部被标记的对象。 | 标记-清除算法的缺点有两个:首先,效率问题,标记和清除效率都不高。其次,标记清除以后会产生大量的不连续的内存碎片,空间碎片太多会致使当程序须要为较大对象分配内存时没法找到足够的连续内存而不得不提早触发另外一次垃圾收集动做 | |
复制算法 | 将可用内存按容量分红大小相等的两块,每次只使用其中一块,当这块内存使用完了,就将还存活的对象复制到另外一块内存上去,而后把使用过的内存空间一次清理掉 | 这样使得每次都是对其中一块内存进行回收,内存分配时不用考虑内存碎片等复杂状况,只须要移动堆顶指针,按顺序分配内存便可,实现简单,运行高效 | 复制算法的缺点显而易见,可以使用的内存降为原来一半 |
标记-整理算法 | 标记-整理算法在标记-清除算法基础上作了改进,标记阶段是相同的,标记出全部须要回收的对象,在标记完成以后不是直接对可回收对象进行清理,而是让全部存活的对象都向一端移动,在移动过程当中清理掉可回收的对象,这个过程叫作整理。 | 标记-整理算法相比标记-清除算法的优势是内存被整理之后不会产生大量不连续内存碎片问题。复制算法在对象存活率高的状况下就要执行较多的复制操做,效率将会变低,而在对象存活率高的状况下使用标记-整理算法效率会大大提升 | |
分代收集算法 | 是java的虚拟机的垃圾回收算法.基于编程中的一个事实,越新的对象的生存期越短,根据内存中对象的存活周期不一样,将内存划分为几块,java的虚拟机中通常把内存划分为新生代和年老代,当新建立对象时通常在新生代中分配内存空间,当新生代垃圾收集器回收几回以后仍然存活的对象会被移动到年老代内存中,当大对象在新生代中没法找到足够的连续内存时也直接在年老代中建立 |
建议看《Android开发艺术探索》,这玩意三言两语讲不清楚
建议看一下,这个可能会被问,不过我运气好没被问到.
网上有不少相关的文章,能够本身结合源码去看一下,若是能讲个大概的话也是很加分的.
数据报,流模式,TCP可靠,包序不对会要求重传,UDP无论,甚至不能保证送到
这个被问的概率很是的大,几乎等于必问,建议专门花时间去看.
CA证书,中间机构,公钥加密对称秘钥传回服务端,一个明文一个加密,SSL层,中间人攻击,参考link
对于ACM,比较常考链表的题,不常刷算法的同窗必定不要对其有抵触心理.
你可能会问为何要ACM?网上答案说的什么提升代码质量,可以更好地阅读别人的代码这些理由有必定道理,但对于咱们去面试的人而言最重要的是ACM是面试官考察你编码能力的最直接的手段,因此不用说这么多废话刷题就够了.
刷题的话,建议去刷leetcode,题号在200之内的,简单和中等难度,不建议刷困难,由于面试的时候基本就不会出,没人愿意在那里等你想一个半个小时的.
在面试官面前现场白板编程时,能够先把思路告诉面试官,写不写得出来是另一回事,时间复杂度和空间复杂度是怎么来的必定要搞清楚.在编码时也不必定要写出最佳的时间和空间的算法,但推荐你写出代码量最少,思路最清晰的,这样面试官看得舒服,你讲得也舒服.
下面是我在网上收集或者是在实际中遇到过的ACM题,基本上在leetcode上也都有相似的.
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;
}
复制代码
public void deleteNode(ListNode node) {
ListNode next = node.next;
node.val = next.val;
node.next = next.next;
}
复制代码
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;
}
复制代码
public static class Stack<T>
{
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;
}
}
复制代码
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;
}
复制代码
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;
}
复制代码
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<Integer> list) {
if (list == null || list.isEmpty()) {
return;
}
quickSort(list, 0, list.size() - 1);
}
private static void quickSort(List<Integer> 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<Integer> list, int i, int j) {
int baseVal = list.get(i);
while (i < j) {
while (i < j && baseVal <= list.get(j)) {
j--;
}
list.set(i, list.get(j));
while (i < j && baseVal >= list.get(i)) {
i++;
}
list.set(j, list.get(i));
}
list.set(i, baseVal);
return i;
}
复制代码
不要答TCP,答RTMP实时传输协议,RTMP在Github也有不少开源实现,建议面这方面的同窗能够去了解一下.
这一块比较抽象,根据你本身的项目来,着重讲你比较熟悉,有把握的模块,通常面试官都会从中抽取一些问题来向你提问.
不要问诸如:
这种问题一点价值都没有,由于你即便问了也不能从他那里得到额外的信息,也不可以影响他对你的判断,要问就要问面试官对你的感觉与评价,还要体现出你想要加入的心情以及你问题的深度.
每每有不少同窗明明以为本身已经准备得很好了已经很复读了,可最后仍是不没拿到offer,那么你可能须要考虑如下的问题了.
就像我一开始说的,春招是运气>性格>三观>技术的,项目与基础当然很重要,可是想在短短的数小时能彻底掌握一我的全部的基本状况实在是太难了,面试官看到的每每只是你的冰山一角,因此在面试的时候,你可能还要在乎如下的问题:
透露一下,本人是双非二本,自从高考失利之后还觉得本身要一直这么平凡下去QAQ,没想到过了三年终于又给我一个机会让我从新证实了本身,能有机会去到美团这样的大厂工做,真的倍感荣幸.最后的最后仍是惯例啦,若是喜欢个人文章别忘了给我点个赞,拜托了这对我来讲真的很重要.