在本文的開頭,先分享一下本身的春招經歷吧: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,沒想到過了三年終於又給我一個機會讓我從新證實了本身,能有機會去到美團這樣的大廠工做,真的倍感榮幸.最後的最後仍是慣例啦,若是喜歡個人文章別忘了給我點個贊,拜託了這對我來講真的很重要.