目錄css
HashMap是經常使用的Java集合之一,是基於哈希表的Map接口的實現。不支持同步和容許null做爲key和value。html
HashMap非線程安全,即任一時刻能夠有多個線程同時寫HashMap,可能會致使數據的不一致。java
在JDK1.6中,HashMap採用數組+鏈表實現,即便用鏈表處理衝突,同一hash值的鏈表都存儲在一個鏈表裏。
可是當位於一個數組中的元素較多,即hash值相等的元素較多時,經過key值依次查找的效率較低。
而JDK1.8中,HashMap採用數組+鏈表+紅黑樹實現,當鏈表長度超過閾值8時,將鏈表轉換爲紅黑樹,這樣大大減小了查找時間。node
/** * 根據key的哈希值和key獲取對應的節點 * getNode可分爲如下幾個步驟: * 1.若是哈希表爲空,或key對應的桶爲空,返回null * 2.若是桶中的第一個節點就和指定參數hash和key匹配上了,返回這個節點。 * 3.若是桶中的第一個節點沒有匹配上,並且有後續節點 * 3.1若是當前的桶採用紅黑樹,則調用紅黑樹的get方法去獲取節點 * 3.2若是當前的桶不採用紅黑樹,即桶中節點結構爲鏈式結構,遍歷鏈表,直到key匹配 * 4.找到節點返回null,不然返回null。 */ /** * putVal方法能夠分爲下面的幾個步驟: * 1.若是哈希表爲空,調用resize()建立一個哈希表。 * 2.若是指定參數hash在表中沒有對應的桶,即爲沒有碰撞,直接將鍵值對插入到哈希表中便可。 * 3.若是有碰撞,遍歷桶,找到key映射的節點 * 3.1桶中的第一個節點就匹配了,將桶中的第一個節點記錄起來。 * 3.2若是桶中的第一個節點沒有匹配,且桶中結構爲紅黑樹,則調用紅黑樹對應的方法插入鍵值對。 * 3.3若是不是紅黑樹,那麼就確定是鏈表。遍歷鏈表,若是找到了key映射的節點,就記錄這個節點,退出循環。若是沒有找到,在鏈表尾部插入節點。插入後,若是鏈的長度大於TREEIFY_THRESHOLD這個臨界值,則使用treeifyBin方法把鏈表轉爲紅黑樹。 * 4.若是找到了key映射的節點,且節點不爲null * 4.1記錄節點的vlaue。 * 4.2若是參數onlyIfAbsent爲false,或者oldValue爲null,替換value,不然不替換。 * 4.3返回記錄下來的節點的value。 * 5.若是沒有找到key映射的節點(二、3步中講了,這種狀況會插入到hashMap中),插入節點後size會加1,這時要檢查size是否大於臨界值threshold,若是大於會使用resize方法進行擴容。 */ /** * 對table進行初始化或者擴容。 * 若是table爲null,則對table進行初始化 * 若是對table擴容,由於每次擴容都是翻倍,與原來計算(n-1)&hash的結果相比,節點要麼就在原來的位置,要麼就被分配到「原位置+舊容量」這個位置 * resize的步驟總結爲: * 1.計算擴容後的容量,臨界值。 * 2.將hashMap的臨界值修改成擴容後的臨界值 * 3.根據擴容後的容量新建數組,而後將hashMap的table的引用指向新數組。 * 4.將舊數組的元素複製到table中。 */
同步代碼塊是使用monitorenter和monitorexit指令實現的,同步方法依靠的是方法修飾符上的ACC_SYNCHRONIZED實現。mysql
同步代碼塊:monitorenter指令插入到同步代碼塊的開始位置,monitorexit指令插入到同步代碼塊的結束位置,JVM須要保證每個monitorenter都有一個monitorexit與之相對應。任何對象都有一個monitor與之相關聯,當且一個monitor被持有以後,他將處於鎖定狀態。線程執行到monitorenter指令時,將會嘗試獲取對象所對應的monitor全部權,即嘗試獲取對象的鎖;linux
同步方法:synchronized方法則會被翻譯成普通的方法調用和返回指令如:invokevirtual、areturn指令,在JVM字節碼層面並無任何特別的指令來實現被synchronized修飾的方法,而是在Class文件的方法表中將該方法的access_flags字段中的synchronized標誌位置1,表示該方法是同步方法並使用調用該方法的對象或該方法所屬的Class在JVM的內部對象表示Class作爲鎖對象。web
volatile是變量修飾符,其修飾的變量具備可見性,Java的作法是將該變量的操做放在寄存器或者CPU緩存上進行,以後纔會同步到主存,使用volatile修飾符的變量是直接讀寫主存,volatile不保證原子性,同時volatile禁止指令重排。redis
封裝、繼承、多態算法
在運行狀態中,對於任意一個類,都可以知道這個類的全部屬性和方法;對於任意一個對象,都可以調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱爲Java語言的反射機制。spring
反射的核心是JVM在運行時才動態加載類或調用方法/訪問屬性,它不須要事先知道運行對象是誰。
程序計數器:記錄正在執行的虛擬機字節碼指令的地址(若是正在執行的是本地方法則爲空)。
Java虛擬機棧:每一個 Java 方法在執行的同時會建立一個棧幀用於存儲局部變量表、操做數棧、常量池引用等信息。每個方法從調用直至執行完成的過程,就對應着一個棧幀在 Java 虛擬機棧中入棧和出棧的過程。
本地方法棧:與 Java 虛擬機棧相似,它們之間的區別只不過是本地方法棧爲本地方法服務。
Java堆:幾乎全部對象實例都在這裏分配內存。是垃圾收集的主要區域("GC 堆"),虛擬機把 Java 堆分紅如下三塊:
新生代又可細分爲Eden空間、From Survivor空間、To Survivor空間,默認比例爲8:1:1。
方法區:方法區(Method Area)與Java堆同樣,是各個線程共享的內存區域。Object Class Data(類定義數據)是存儲在方法區的,此外,常量、靜態變量、JIT編譯後的代碼也存儲在方法區。
運行時常量池:運行時常量池是方法區的一部分。Class 文件中的常量池(編譯器生成的各類字面量和符號引用)會在類加載後被放入這個區域。除了在編譯期生成的常量,還容許動態生成,例如 String 類的 intern()。這部分常量也會被放入運行時常量池。
直接內存:直接內存(Direct Memory)並非虛擬機運行時數據區的一部分,也不是Java虛擬機規範中定義的內存區域,可是這部份內存也被頻繁地使用,並且也可能致使OutOfMemoryError 異常出現。避免在Java堆和Native堆中來回複製數據。
強引用就是指在程序代碼之中廣泛存在的,相似"Object obj = new Object()"這類的引用,只要強引用還存在,垃圾收集器永遠不會回收掉被引用的對象。
Object obj = new Object();
軟引用是用來描述一些還有用但並不是必需的對象,對於軟引用關聯着的對象,在系統將要發生內存溢出異常以前,將會把這些對象列進回收範圍進行第二次回收。若是此次回收尚未足夠的內存,纔會拋出內存溢出異常。在JDK1.2以後,提供了SoftReference類來實現軟引用。
Object obj = new Object(); SoftReference<Object> sf = new SoftReference<Object>(obj);
弱引用也是用來描述非必需對象的,可是它的強度比軟引用更弱一些,被弱引用關聯的對象,只能生存到下一次垃圾收集發生以前。當垃圾收集器工做時,不管當前內存是否足夠,都會回收掉只被弱引用關聯的對象。在JDK1.2以後,提供了WeakReference類來實現弱引用。
Object obj = new Object(); WeakReference<Object> wf = new WeakReference<Object>(obj);
虛引用也成爲幽靈引用或者幻影引用,它是最弱的一中引用關係。一個對象是否有虛引用的存在,徹底不會對其生存時間構成影響,也沒法經過虛引用來取得一個對象實例。爲一個對象設置虛引用關聯的惟一目的就是能在這個對象被收集器回收時收到一個系統通知。在JDK1.2以後,提供給了PhantomReference類來實現虛引用。
Object obj = new Object(); PhantomReference<Object> pf = new PhantomReference<Object>(obj);
垃圾回收算法包括:標記-清除算法,複製算法,標記-整理算法,分代收集算法。
標記—清除算法:
標記/清除算法,分爲「標記」和「清除」兩個階段:首先標記出全部須要回收的對象,在標記完成後統一回收全部被標記的對象。
標記階段:標記的過程其實就是前面介紹的可達性分析算法的過程,遍歷全部的GC Roots對象,對從GC Roots對象可達的對象都打上一個標識,通常是在對象的header中,將其記錄爲可達對象;
清除階段:清除的過程是對堆內存進行遍歷,若是發現某個對象沒有被標記爲可達對象,則將其回收。
複製算法:
將內存劃分爲大小相等的兩塊,每次只使用其中一塊,當這一塊內存用完了就將還存活的對象複製到另外一塊上面,而後再把使用過的內存空間進行一次清理。
將內存分爲一塊較大的 Eden 空間和兩塊較小的 Survior 空間,每次使用 Eden 空間和其中一塊 Survivor。在回收時,將 Eden 和 Survivor 中還存活着的對象一次性複製到另外一塊 Survivor 空間上,最後清理 Eden 和 使用過的那一塊 Survivor。HotSpot 虛擬機的 Eden 和 Survivor 的大小比例默認爲 8:1,保證了內存的利用率達到 90 %。若是每次回收有多於 10% 的對象存活,那麼一塊 Survivor 空間就不夠用了,此時須要依賴於老年代進行分配擔保,也就是借用老年代的空間。
標記—整理算法:
標記—整理算法和標記—清除算法同樣,可是標記—整理算法不是把存活對象複製到另外一塊內存,而是把存活對象往內存的一端移動,而後直接回收邊界之外的內存,所以其不會產生內存碎片。標記—整理算法提升了內存的利用率,而且它適合在收集對象存活時間較長的老年代。
分代收集算法:
分代回收算法其實是把複製算法和標記整理法的結合,並非真正一個新的算法,通常分爲:老年代和新生代,老年代就是不多垃圾須要進行回收的,新生代就是有不少的內存空間須要回收,因此不一樣代就採用不一樣的回收算法,以此來達到高效的回收算法。
新生代:因爲新生代產生不少臨時對象,大量對象須要進行回收,因此採用複製算法是最高效的。
老年代:回收的對象不多,都是通過幾回標記後都不是可回收的狀態轉移到老年代的,因此僅有少許對象須要回收,故採用標記清除或者標記整理算法。
順序、二分、hash
public class BinarySearch { public static int binarySearch(int[] array, int key) { int start = 0; int end = array.length - 1; while (start <= end) { int mid = (end - start) / 2 + start; if (key < array[mid]) { end = mid - 1; } else if (key > array[mid]) { start = mid + 1; } else { return mid; } } return -1; } public static int binarySearch_2(int[] array, int start, int end, int key) { int mid = (end - start) / 2 + start; if (array[mid] == key) { return mid; } if (start >= end) { return -1; } else if (key > array[mid]) { return binarySearch_2(array, mid + 1, end, key); } else if (key < array[mid]) { return binarySearch_2(array, start, mid - 1, key); } return -1; } }
將當前節點和下一節點保存起來,而後將當前節點反轉。
public ListNode ReverseList(ListNode head) { //head爲當前節點,若是當前節點爲空的話,那就什麼也不作,直接返回null ListNode pre = null;//pre爲當前節點的前一節點 ListNode next = null;//next爲當前節點的下一節點 //須要pre和next的目的是讓當前節點從pre.head.next1.next2變成pre<-head next1.next2 //即pre讓節點能夠反轉所指方向,但反轉以後若是不用next節點保存next1節點的話,此單鏈表就此斷開了 //因此須要用到pre和next兩個節點 //1.2.3.4.5 //1<-2<-3 4.5 //作循環,若是當前節點不爲空的話,始終執行此循環,此循環的目的就是讓當前節點從指向next到指向pre while (head != null) { //先用next保存head的下一個節點的信息,保證單鏈表不會由於失去head節點的原next節點而就此斷裂 next = head.next; //保存完next,就可讓head從指向next變成指向pre了 head.next = pre; //head指向pre後,就繼續依次反轉下一個節點 //讓pre,head,next依次向後移動一個節點,繼續下一次的指針反轉 pre = head; head = next; } //若是head爲null的時候,pre就爲最後一個節點了,可是鏈表已經反轉完畢,pre就是反轉後鏈表的第一個節點 //直接輸出pre就是咱們想要獲得的反轉後的鏈表 return pre; }
利用遞歸走到鏈表的末端,而後再更新每個節點的next值 ,實現鏈表的反轉。
public ListNode ReverseList(ListNode head) { //若是鏈表爲空或者鏈表中只有一個元素 if (head == null || head.next == null) return head; //先遞歸找到到鏈表的末端結點,從後依次反轉整個鏈表 ListNode reverseHead = ReverseList(head.next); //再將當前節點設置爲後面節點的後續節點 head.next.next = head; head.next = null; return reverseHead; }
public static void preOrder(TreeNode biTree) { System.out.println(biTree.value); TreeNode leftTree = biTree.left; if(leftTree != null) { preOrder(leftTree); } TreeNode rightTree = biTree.right; if(rightTree != null) { preOrder(rightTree); } } public static void preOrder_2(TreeNode biTree) { Stack<TreeNode> stack = new Stack<TreeNode>(); while(biTree != null || !stack.isEmpty()) { while(biTree != null) { System.out.println(biTree.value); stack.push(biTree); biTree = biTree.left; } if(!stack.isEmpty()) { biTree = stack.pop(); biTree = biTree.right; } } }
import java.util.Stack; /** * 思路: * 棧A用來做入隊列,棧B用來出隊列 * 當棧B爲空時,棧A所有出棧到棧B,棧B再出棧(即出隊列) */ public class Solution18 { Stack<Integer> stack1 = new Stack<Integer>(); Stack<Integer> stack2 = new Stack<Integer>(); public void push(int node) { stack1.push(node);//stack1負責入隊 } public int pop() { if (stack1.empty() && stack2.empty()) { throw new RuntimeException("隊列爲空"); } if (stack2.empty()) { while (!stack1.empty()) { stack2.push(stack1.pop()); } } return stack2.pop();//stcak2負責出隊 } }
/** * 直接利用hashmap記錄出現次數 * * @param nums * @return */ public List<Integer> findDuplicates(int[] nums) { List<Integer> res = new ArrayList<>(); if (nums == null || nums.length == 0) { return res; } HashMap<Integer, Integer> map = new HashMap<>(); int count; for (int val : nums) { if (!map.containsKey(val)) { map.put(val, 1); } else { count = map.get(val); map.put(val, ++count); } if (map.get(val) == 2) { res.add(val); } } return res; } /** * 由於數組輸入的特色 1<=a[i]<=n,則能夠把原數組當hash表用 ,由於原數組是正數,標爲負數表示出現過,若是遇到負數就表示第二次出現,就能夠找出全部出現過兩次的元素 * * @param nums * @return */ public List<Integer> findDuplicates_2(int[] nums) { List<Integer> res = new ArrayList<>(); for (int i = 0; i < nums.length; ++i) { int index = Math.abs(nums[i]) - 1; if (nums[index] < 0) { res.add(Math.abs(index + 1)); } nums[index] = -nums[index]; } return res; }
public static void quickSort(int[] array, int _left, int _right) { int left = _left;// int right = _right; int pivot;//基準線 if (left < right) { pivot = array[left]; while (left != right) { //從右往左找到比基準線小的數 while (left < right && pivot <= array[right]) { right--; } //將右邊比基準線小的數換到左邊 array[left] = array[right]; //從左往右找到比基準線大的數 while (left < right && pivot >= array[left]) { left++; } //將左邊比基準線大的數換到右邊 array[right] = array[left]; } //此時left和right指向同一位置 array[left] = pivot; quickSort(array, _left, left - 1); quickSort(array, left + 1, _right); } }
public int reverseInt(int x) { int ans = 0; while (x != 0) { ans = ans * 10 + (x % 10); x /= 10; } if (ans < Integer.MIN_VALUE || ans > Integer.MAX_VALUE) { ans = 0; } return ans; }
import java.util.Scanner; public class Main { /** * 進行分解質因數 * * @param number */ public static void factor(int number) { for (int i = 2; i < number; i++) { if (number % i == 0) { System.out.print(i + " "); //判斷number/i是否是素數,若是是素數就直接輸出 if (isPrime(number / i)) { System.out.print(number / i + " "); } else { factor(number / i); } return; } } } /** * 判斷是否是素數 * * @param number * @return */ public static boolean isPrime(int number) { for (int i = 2; i < number; i++) { if (number % i == 0) { return false; } } return true; } }
import java.util.HashSet; public class Solution24 { /** * 假設x爲環前面的路程,a爲環入口到相遇點的路程, c爲環的長度 * 當快慢指針相遇的時候: 此時慢指針走的路程爲Sslow = x + m * c + a * 快指針走的路程爲Sfast = x + n * c + a * 2 Sslow = Sfast * 2 * ( x + m*c + a ) = (x + n *c + a) * 從而能夠推導出: x = (n - 2 * m )*c - a = (n - 2 *m -1 )*c + c - a * 即環前面的路程 = 數個環的長度(爲可能爲0) + c - a * 什麼是c - a?這是相遇點後,環後面部分的路程。 * 因此,咱們可讓一個指針從起點A開始走,讓一個指針從相遇點B開始繼續日後走, 2個指針速度同樣, * 那麼,當從原點的指針走到環入口點的時候(此時恰好走了x) 從相遇點開始走的那個指針也必定恰好到達環入口點。 * 因此二者會相遇,且剛好相遇在環的入口點。 * * @param pHead * @return */ public ListNode EntryNodeOfLoop_2(ListNode pHead) { if (pHead == null || pHead.next == null || pHead.next.next == null) return null; ListNode fast = pHead.next.next; ListNode slow = pHead.next; //先判斷有沒有環 while (fast != slow) { if (fast.next != null && fast.next.next != null) { fast = fast.next.next; slow = slow.next; } else { //沒有環 return null; } } fast = pHead;//把fast指向頭節點 //有環 while (fast != slow) { fast = fast.next; slow = slow.next; } return fast; } /** * 利用HashSet元素不能重複 * * @param pHead * @return */ public ListNode EntryNodeOfLoop(ListNode pHead) { HashSet<ListNode> hashSet = new HashSet<>(); while (pHead != null) { if (!hashSet.add(pHead)) { return pHead; } pHead = pHead.next; } return null; } public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } } }
import java.util.HashMap; public class Solution42 { /** * 利用 Boyer-Moore Majority Vote Algorithm(摩爾投票算法)來解決這個問題 * * @param array * @return */ public int MoreThanHalfNum_Solution_2(int[] array) { int majority = array[0]; for (int i = 1, count = 1; i < array.length; i++) { count = array[i] == majority ? count + 1 : count - 1; if (count == 0) { majority = array[i]; count = 1; } } int count = 0; for (int val : array) if (val == majority) count++; return count > array.length / 2 ? majority : 0; } /** * 利用HashMap記錄每一個數字以及數字出現的次數 * * @param array * @return */ public int MoreThanHalfNum_Solution(int[] array) { if (array == null || array.length == 0) return 0; HashMap<Integer, Integer> map = new HashMap<>(); int count; for (int val : array) { if (!map.containsKey(val)) { map.put(val, 1); } else { count = map.get(val); map.put(val, ++count); } if (map.get(val) > array.length / 2) { return val; } } return 0; } }
import java.util.ArrayList; import java.util.HashMap; public class Solution34 { /** * 兩頭夾逼 * * @param array * @param sum * @return */ public ArrayList<Integer> FindNumbersWithSum(int[] array, int sum) { ArrayList<Integer> result = new ArrayList<>(); int left = 0; int right = array.length - 1; while (left < right) { if (array[left] + array[right] == sum) { result.add(array[left]); result.add(array[right]); break; } else if (array[left] + array[right] > sum) { right--; } else { left++; } } return result; } /** * 用HashMap存放元素和下標,而後開始遍歷數組找到和爲sum的兩個元素,從左到右找到的第一對和爲sum的就是最小的一對。 * * @param array * @param sum * @return */ public ArrayList<Integer> FindNumbersWithSum_2(int[] array, int sum) { HashMap<Integer, Integer> map = new HashMap<>(); ArrayList<Integer> result = new ArrayList<>(); int len = array.length; for (int i = 0; i < len; i++) { map.put(array[i], i); } for (int i = 0; i < len; i++) { int sub = sum - array[i]; if (map.containsKey(sub)) { result.add(array[i]); result.add(sub); break; } } return result; } }
進程是資源分配的基本單位。
進程控制塊 (Process Control Block, PCB) 描述進程的基本信息和運行狀態,所謂的建立進程和撤銷進程,都是指對 PCB 的操做。
線程是獨立調度的基本單位。
一個進程中能夠有多個線程,它們共享進程資源。
區別
選擇:
進程控制、進程同步、進程通訊、死鎖處理、處理機調度等。
內存分配、地址映射、內存保護與共享、虛擬內存等。
文件存儲空間的管理、目錄管理、文件讀寫管理和保護等。
完成用戶的 I/O 請求,方便用戶使用各類設備,並提升設備的利用率。
主要包括緩衝管理、設備分配、設備處理、虛擬設備等。
域名解析 --> 發起TCP的3次握手 --> 創建TCP鏈接後發起http請求 --> 服務器響應http請求,瀏覽器獲得html代碼 --> 瀏覽器解析html代碼,並請求html代碼中的資源(如js、css、圖片等) --> 瀏覽器對頁面進行渲染呈現給用戶
HTTPS(Secure Hypertext Transfer Protocol) 安全超文本傳輸協議是一個安全的通訊通道,它基於HTTP開發,用於在客戶計算機和服務器之間交換信息。HTTPS使用安全套接字層(SSL)進行信息交換,簡單來講HTTPS是HTTP的安全版,是使用TLS/SSL加密的HTTP協議。
HTTPS 通訊過程
TCP就是單純創建鏈接,不涉及任何咱們須要請求的實際數據,簡單的傳輸。
關閉鏈接時,當收到對方的FIN報文時,僅僅表示對方再也不發送數據了可是還能接收數據,己方也未必所有數據都發送給對方了,因此己方能夠當即close,也能夠發送一些數據給對方後,再發送FIN報文給對方來表示贊成如今關閉鏈接,所以,己方ACK和FIN通常都會分開發送。
Redis 是一個高性能的key-value數據庫。
Redis 與其餘 key - value 緩存產品有如下三個特色:
在MySQL數據庫中,支持上面四種隔離級別,默認的爲REPEATABLE READ(可重複讀)。
Redis支持五種數據類型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。
惰性刪除+按期刪除
惰性刪除
按期刪除
聯繫:
Cookie與Session都是用來跟蹤瀏覽器用戶身份的會話方式。
區別:
Servlet有三個生命週期函數,初始化方法init(),處理客戶請求的方法service(),終止方法destroy()。
public class Singleton { private volatile static Singleton instance = null; private Singleton() { } /** * 當第一次調用getInstance()方法時,instance爲空,同步操做,保證多線程實例惟一 * 當第一次後調用getInstance()方法時,instance不爲空,不進入同步代碼塊,減小了沒必要要的同步 */ public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
IOC:控制反轉也叫依賴注入,IOC利用java反射機制。所謂控制反轉是指,原本被調用者的實例是有調用者來建立的,這樣的缺點是耦合性太強,IOC則是統一交給spring來管理建立,將對象交給容器管理,你只須要在spring配置文件總配置相應的bean,以及設置相關的屬性,讓spring容器來生成類的實例對象以及管理對象。在spring容器啓動的時候,spring會把你在配置文件中配置的bean都初始化好,而後在你須要調用的時候,就把它已經初始化好的那些bean分配給你須要調用這些bean的類。
AOP是對OOP的補充和完善。AOP利用的是代理,分爲CGLIB動態代理和JDK動態代理。OOP引入封裝、繼承和多態性等概念來創建一種對象層次結構。OOP編程中,會有大量的重複代碼。而AOP則是將這些與業務無關的重複代碼抽取出來,而後再嵌入到業務代碼當中。實現AOP的技術,主要分爲兩大類:一是採用動態代理技術,利用截取消息的方式,對該消息進行裝飾,以取代原有對象行爲的執行;二是採用靜態織入的方式,引入特定的語法建立「方面」,從而使得編譯器能夠在編譯期間織入有關「方面」的代碼,屬於靜態代理。
項目管理工具
hashmap,把這兩個放到map中