HashMap的底層使用數組+鏈表/紅黑樹實現。html
transient Node<K,V>[] table;這表示HashMap是Node數組構成,其中Node類的實現以下,能夠看出這其實就是個鏈表,鏈表的每一個結點是一個<K,V>映射。 static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; }
HashMap的每一個下標都存放了一條鏈表。java
常量/變量定義node
1 /* 常量定義 */ 2 3 // 初始容量爲16 4 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 5 // 最大容量 6 static final int MAXIMUM_CAPACITY = 1 << 30; 7 // 負載因子,當鍵值對個數達到DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR會觸發resize擴容 8 static final float DEFAULT_LOAD_FACTOR = 0.75f; 9 // 當鏈表長度大於8,且數組長度大於MIN_TREEIFY_CAPACITY,就會轉爲紅黑樹 10 static final int TREEIFY_THRESHOLD = 8; 11 // 當resize時候發現鏈表長度小於6時,從紅黑樹退化爲鏈表 12 static final int UNTREEIFY_THRESHOLD = 6; 13 // 在要將鏈表轉爲紅黑樹以前,再進行一次判斷,若數組容量小於該值,則用resize擴容,放棄轉爲紅黑樹 14 // 主要是爲了在創建Map的初期,放置過多鍵值對進入同一個數組下標中,而致使沒必要要的鏈表->紅黑樹的轉化,此時擴容便可,可有效減小衝突 15 static final int MIN_TREEIFY_CAPACITY = 64; 16 17 /* 變量定義 */ 18 19 // 鍵值對的個數 20 transient int size; 21 // 鍵值對的個數大於該值時候,會觸發擴容 22 int threshold; 23 // 非線程安全的集合類中幾乎都有這個變量的影子,每次結構被修改都會更新該值,表示被修改的次數 24 transient int modCount;
關於modCount的做用見這篇blogmysql
在一個迭代器初始的時候會賦予它調用這個迭代器的對象的modCount,若是在迭代器遍歷的過程當中,一旦發現這個對象的modCount和迭代器中存儲的modCount不同那就拋異常。
Fail-Fast機制:java.util.HashMap不是線程安全的,所以若是在使用迭代器的過程當中有其餘線程修改了map,那麼將拋出ConcurrentModificationException,這就是所謂fail-fast策略。這一策略在源碼中的實現是經過modCount域,modCount顧名思義就是修改次數,對HashMap內容的修改都將增長這個值,那麼在迭代器初始化過程當中會將這個值賦給迭代器的expectedModCount。在迭代過程當中,判斷modCount跟expectedModCount是否相等,若是不相等就表示已經有其餘線程修改了Map。程序員
注意初始容量和擴容後的容量都必須是2的次冪,爲何呢?算法
hash方法spring
先看散列方法sql
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
HashMap的散列方法如上,其實就是將hash值的高16位和低16位異或,咱們將立刻看到hash在與n - 1相與的時候,高位的信息也被考慮了,能使碰撞的機率減少,散列得更均勻。數據庫
在JDK 8中,HashMap的putVal方法中有這麼一句編程
if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null);
關鍵就是這句(n - 1) & hash
,這行代碼是把待插入的結點散列到數組中某個下標中,其中hash就是經過上面的方法的獲得的,爲待插入Node的key的hash值,n是table的容量即table.length
,2的次冪用二進制表示的話,只有最高位爲1,其他爲都是0。減去1,恰好就反了過來。好比16的二進制表示爲10000,減去1後的二進制表示爲01111,除了最高位其他各位都是1,保證了在相與時,能夠使得散列值分佈得更均勻(由於若是某位爲0好比1011,那麼結點永遠不會被散列到1111這個位置),且當n爲2的次冪時候有(n - 1) & hash == hash % n
, 舉個例子,好比hash等於6時候,01111和00110相與就是00110,hash等於16時,相與就等於0了,多舉幾個例子即可以驗證這一結論。最後來回答爲何HashMap的容量要始終保持2的次冪
注意table.length是數組的容量,而transient int size
表示存入Map中的鍵值對數。
int threshold
表示臨界值,當鍵值對的個數大於臨界值,就會擴容。threshold的更新是由下面的方法完成的。
static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }
該方法返回大於等於cap的最小的二次冪數值。好比cap爲16,就返回16,cap爲17就返回32。
put方法
put方法主要由putVal方法實現:
tab[i = (n - 1) & hash]
處新建一個結點;get方法
get方法由getNode方法實現:
remove方法的流程大體和get方法相似。
HashMap的擴容,resize()過程?
newCap = oldCap << 1
resize方法中有這麼一句,說明是擴容後數組大小是原數組的兩倍。
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; // 若是數組中只有一個元素,即只有一個頭結點,從新哈希到新數組的某個下標 if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order // 數組下標處的鏈表長度大於1,非紅黑樹的狀況 Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; // oldCap是2的次冪,最高位是1,其他爲是0,哈希值和其相與,根據哈希值的最高位是1仍是0,鏈表被拆分紅兩條,哈希值最高位是0分到loHead。 if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } // 哈希值最高位是1分到hiHead else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; // loHead掛到新數組[原下標]處; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; // hiHead掛到新數組中[原下標+oldCap]處 newTab[j + oldCap] = hiHead; } } } } } return newTab;
舉個例子,好比oldCap是16,二進制表示是10000,hash值的後五位和oldCap相與,由於oldCap的最高位(從右往左數的第5位)是1其他位是0,所以hash值的該位是0的全部元素被分到一條鏈表,掛到新數組中原下標處,hash值該位爲1的被分到另一條鏈表,掛到新數組中原下標+oldCap處。舉個例子:桶0中的元素其hash值後五位是0XXXX的就被分到桶0種,其hash值後五位是1XXXX就被分到桶4中。
Java中的全部異常都是Throwable的子類對象,Error類和Exception類是Throwable類的兩個直接子類。
Error:包括一些嚴重的、程序不能處理的系統錯誤類。這些錯誤通常不是程序形成的,好比StackOverflowError和OutOfMemoryError。
Exception:異常分爲運行時異常和檢查型異常。
首先接口Collection和Map是平級的,Map沒有實現Collection。
Map的實現類常見有HashMap、TreeMap、LinkedHashMap和HashTable等。其中HashMap使用散列法實現,低層是數組,採用鏈地址法解決哈希衝突,每一個數組的下標都是一條鏈表,當長度超過8時,轉換成紅黑樹。TreeMap使用紅黑樹實現,能夠按照鍵進行排序。LinkedHashMap的實現綜合了HashMap和雙向鏈表,可保證以插入時的順序(或訪問順序,LRU的實現)進行迭代。HashTable和HashMap比,前者是線程安全的,後者不是線程安全的。HashTable的鍵或者值不容許null,HashMap容許。
Collection的實現類常見的有List、Set和Queue。List的實現類有ArrayList和LinkedList以及Vector等,ArrayList就是一個可擴容的對象數組,LinkedList是一個雙向鏈表。Vector是線程安全的(ArrayList不是線程安全的)。Set的裏的元素不可重複,實現類常見的有HashSet、TreeSet、LinkedHashSet等,HashSet的實現基於HashMap,實際上就是HashMap中的Key,一樣TreeSet低層由TreeMap實現,LinkedHashSet低層由LinkedHashMap實現。Queue的實現類有LinkedList,能夠用做棧、隊列和雙向隊列,另外還有PriorityQueue是基於堆的優先隊列。
反射:容許任意一個類在運行時獲取自身的類信息,而且能夠操做這個類的方法和屬性。這種動態獲取類信息和動態調用對象方法的功能稱爲Java的反射機制。
反射的核心是JVM在運行時才動態加載類或調用方法/訪問屬性。它不須要事先(寫代碼的時候或編譯期)知道運行對象是誰,如Class.ForName()
根本就沒有指定某個特定的類,徹底由你傳入的類全限定名決定,而經過new的方式你是知道運行時對象是哪一個類的。 反射避免了將程序「寫死」。
反射能夠下降程序耦合性,提升程序的靈活性。new是形成緊耦合的一大緣由。好比下面的工廠方法中,根據水果類型決定返回哪個類。
public class FruitFactory { public Fruit getFruit(String type) { Fruit fruit = null; if ("Apple".equals(type)) { fruit = new Apple(); } else if ("Banana".equals(type)) { fruit = new Banana(); } else if ("Orange".equals(type)) { fruit = new Orange(); } return fruit; } } class Fruit {} class Banana extends Fruit {} class Orange extends Fruit {} class Apple extends Fruit {}
可是咱們事先並不知道以後會有哪些類,好比新增了Mango,就須要在if-else中新增;若是之後不須要Banana了就須要從if-else中刪除。這就是說只要子類變更了,咱們必須在工廠類進行修改,而後再編譯。若是用反射呢?
public class FruitFactory { public Fruit getFruit(String type) { Fruit fruit = null; try { fruit = (Fruit) Class.forName(type).newInstance(); } catch (Exception e) { e.printStackTrace(); } return fruit; } } class Fruit {} class Banana extends Fruit {} class Orange extends Fruit {} class Apple extends Fruit {}
若是再將子類的全限定名存放在配置文件中。
class-type=com.fruit.Apple
那麼無論新增多少子類,根據不一樣的場景只需修改文件就行了,上面的代碼無需修改代碼、從新編譯,就能正確運行。
哪些地方用到了反射?舉幾個例子
Java實現多態有三個必要條件:繼承、重寫、向上轉型。
public class OOP { public static void main(String[] args) { /* * 1. Cat繼承了Animal * 2. Cat重寫了Animal的eat方法 * 3. 父類Animal的引用指向了子類Cat。 * 在編譯期間其靜態類型爲Animal;在運行期間其實際類型爲Cat,所以animal.eat()將選擇Cat的eat方法而不是其餘子類的eat方法 */ Animal animal = new Cat(); printEating(animal); } public static void printEating(Animal animal) { animal.eat(); } } abstract class Animal { abstract void eat(); } class Cat extends Animal { @Override void eat() { System.out.println("Cat eating..."); } } class Dog extends Animal { @Override void eat() { System.out.println("Dog eating..."); } }
對於不想進行序列化的變量,使用transient關鍵字修飾。功能是:阻止實例中那些用此關鍵字修飾的的變量序列化;當對象被反序列化時,被transient修飾的變量值不會被持久化和恢復。transient只能修飾變量,不能修飾類和方法。
== 對於基本類型,比較值是否相等,對於對象,比較的是兩個對象的地址是否相同,便是否是指相同一個對象。
equals的默認實現實際上使用了==來比較兩個對象是否相等,可是像Integer、String這些類對equals方法進行了重寫,比較的是兩個對象的內容是否相等。
對於Integer,若是依然堅持使用==來比較,有一些要注意的地方。對於[-128,127]區間裏的數,有一個緩存。所以
Integer a = 127; Integer b = 127; System.out.println(a == b); // true Integer a = 128; Integer b = 128; System.out.println(a == b); // false // 不過採用new的方式,a在堆中,這裏打印false Integer a = new Integer(127); Integer b = 127; System.out.println(a == b);
對於String,由於它有一個常量池。因此
String a = "gg" + "rr"; String b = "ggrr"; System.out.println(a == b); // true // 固然牽涉到new的話,該對象就在堆上建立了,因此這裏打印false String a = "gg" + "rr"; String b = new String("ggrr"); System.out.println(a == b);
本質是考察Java反射,由於要實現一個通用的程序。實現可能根本不知道該類有哪些字段,因此不能經過get和set等方法來獲取鍵-值。使用反射的getDeclaredFields()能夠得到其聲明的字段。若是字段是private的,須要調用該字段的f.setAccessible(true);
,才能讀取和修改該字段。
import java.lang.reflect.Field; import java.util.HashMap; public class Object2Json { public static class Person { private int age; private String name; public Person(int age, String name) { this.age = age; this.name = name; } } public static void main(String[] args) throws IllegalAccessException { Person p = new Person(18, "Bob"); Class<?> classPerson = p.getClass(); Field[] fields = classPerson.getDeclaredFields(); HashMap<String, String> map = new HashMap<>(); for (Field f: fields) { // 對於private字段要先設置accessible爲true f.setAccessible(true); map.put(String.valueOf(f.getName()), String.valueOf(f.get(p))); } System.out.println(map); } }
獲得了map,再弄成JSON標準格式就行了。
@Test public void fun2() throws SQLException, ClassNotFoundException { // 1. 註冊驅動 Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/xxx?useUnicode=true&characterEncoding=utf-8"; // 2.創建鏈接 Connection connection = DriverManager.getConnection(url, "root", "admin"); // 3. 執行sql語句使用的Statement或者PreparedStatment Statement statement = connection.createStatement(); String sql = "select * from stu;"; ResultSet resultSet = statement.executeQuery(sql); while (resultSet.next()) { // 第一列是id,因此從第二行開始 String name = resultSet.getString(2); // 能夠傳入列的索引,1表明第一行,索引不是從0開始 int age = resultSet.getInt(3); String gender = resultSet.getString(4); System.out.println("學生姓名:" + name + " | 年齡:" + age + " | 性別:" + gender); } // 關閉結果集 resultSet.close(); // 關閉statemenet statement.close(); // 關閉鏈接 connection.close(); }
ResultSet維持一個指向當前行記錄的cursor(遊標)指針
因爲JDBC默認自動提交事務,每執行一個update ,delete或者insert的時候都會自動提交到數據庫,沒法回滾事務。因此若須要實現事務的回滾,要指定setAutoCommit(false)
。
true
:sql命令的提交(commit)由驅動程序負責false
:sql命令的提交由應用程序負責,程序必須調用commit或者rollback方法JDBC操做事務的格式以下,在捕獲異常中進行事務的回滾。
就普通的實現方法來看。
public class SingletonImp { // 餓漢模式 private static SingletonImp singletonImp = new SingletonImp(); // 私有化(private)該類的構造函數 private SingletonImp() { } public static SingletonImp getInstance() { return singletonImp; } }
餓漢模式:線程安全,不能延遲加載
public class SingletonImp4 { private static volatile SingletonImp4 singletonImp4; private SingletonImp4() {} public static SingletonImp4 getInstance() { if (singletonImp4 == null) { synchronized (SingletonImp4.class) { if (singletonImp4 == null) { singletonImp4 = new SingletonImp4(); } } } return singletonImp4; } }
雙重檢測鎖+volatile禁止語義重排。由於singletonImp4 = new SingletonImp4();
不是原子操做。
public class SingletonImp6 { private SingletonImp6() {} // 專門用於建立Singleton的靜態類 private static class Nested { private static SingletonImp6 singletonImp6 = new SingletonImp6(); } public static SingletonImp6 getInstance() { return Nested.singletonImp6; } }
靜態內部類,能夠實現延遲加載。
最推薦的是單一元素枚舉實現單例。
public enum Singleton { INSTANCE; public void anyOtherMethod() {} }
其中,先序、中序、後序遍歷屬於深度優先搜索(DFS),層序遍歷屬於廣度優先搜索(BFS)
平衡二叉樹首先是一棵二叉查找樹,其次它須要知足其左右兩棵子樹的高度之差不超過1,且子樹也必須是平衡二叉樹,換句話說對於平衡二叉樹的每一個結點,要求其左右子樹高度之差都不超過1。
二叉查找樹在最壞狀況下,退化成鏈表,查找時間從平均O(lg n)降到O(n),平衡二叉樹使樹的結構更加平衡,提升了查找的效率;可是因爲插入和刪除後須要從新恢復樹的平衡,因此插入和刪除會慢一些。
應用場景好比在HashMap中用到了紅黑樹(平衡二叉樹的特例),數據庫索引中的B+樹等。
應用場景:數組適合讀多寫少、事先知道元素大概個數的狀況;鏈表適合寫多讀少的狀況。
O(N^2)
最差O(N^2)
,空間複雜度O(1)
O(N lgN)
最差O(N^2)
,基於遞歸的實現因爲用到了系統棧,因此平均狀況下空間複雜度爲O(lgN)
排序中所說的穩定是指,對於兩個相同的元素,在排序後其相對位置沒有發生變化。
常見的穩定排序有,冒泡、插入、歸併、基數排序。選擇、希爾、快排、堆排序都不是穩定的。
hash(key) = a * key + b
解決哈希衝突的方法:
堆排序使用了最大堆/最小堆,拿數組升序排序來講,須要創建一個最大堆,基於數組實現的二叉堆能夠看做一棵徹底二叉樹,其知足堆中每一個父結點它左右兩個結點值都大,且堆頂的元素最大。
每次調整堆的平均時間爲O(lg N),所以對大小爲N的數組排序,時間複雜度最差和平均都 O(N lg N).
快排序在平均狀況下,比絕大多數排序算法都快些。不少編程語言的sort默認使用快排,像Java的Array.sort()就採用了雙軸快速排序 。堆排序使用堆實現,空間複雜度只有O(1)。堆排序使用堆的結構,能以O(1)的時間得到最大/最小值,在處理TOP K問題時很方便,另外堆還能夠實現優先隊列。
時間複雜度:
空間複雜度:
放一張神圖
c.next = a.next; a.next = c; c.prev = a; // 若是a不是最後一個結點,就有下面一句 c.next.prev = c;
GET和POST本質都是TCP鏈接。不過GET產生一個TCP數據包;POST產生兩個TCP數據包。
對於GET方式的請求,瀏覽器會把http header和data一併發送出去,服務器響應200 OK(返回數據);
而對於POST,瀏覽器先發送header,服務器響應100 continue,瀏覽器再發送data,服務器響應200 OK(返回數據)。
三次握手
四次揮手
兩次握手的話,只要服務端發出確認就創建鏈接了。有一種狀況是客戶端發出了兩次鏈接請求,但因爲某種緣由,使得第一次請求被滯留了。第二次請求先到達後創建鏈接成功,此後第一次請求終於到達,這是一個失效的請求了,服務端覺得這是一個新的請求因而贊成創建鏈接,可是此時客戶端不搭理服務端,服務端一直處於等待狀態,這樣就浪費了資源。假設採用三次握手,因爲服務端還須要等待客戶端的確認,若客戶端沒有確認,服務端就能夠認爲客戶端沒有想要創建鏈接的意思,因而此次鏈接不會生效。
由於第四次握手客戶端發送ACK確認後,有可能丟包了,致使服務端沒有收到,服務端就會再次發送FIN = 1,若是客戶端不等待當即CLOSED,客戶端就不能對服務端的FIN = 1進行確認。等待的目的就是爲了能在服務端再次發送FIN = 1時候能進行確認。若是在2MSL內客戶端都沒有收到服務端的任何消息,便認爲服務端收到了確認。此時能夠結束TCP鏈接。
好比網上購物,每一個用戶有本身的購物車,當點擊下單時,因爲HTTP協議無狀態,並不知道是哪一個用戶操做的,因此服務端要爲特定的用戶建立特定的Session,用於標識這個用戶,而且跟蹤用戶。
Session原理:瀏覽器第一次訪問服務器時,服務器會響應一個cookie給瀏覽器。這個cookie記錄的就是sessionId,以後每次訪問攜帶着這個sessionId,服務器裏查詢該sessionId,即可以識別並跟蹤特定的用戶了。
Cookie原理:第一次訪問服務器,服務器響應時,要求瀏覽器記住一個信息。以後瀏覽器每次訪問服務器時候,攜帶第一次記住的信息訪問。至關於服務器識別客戶端的一個通行證。Cookie不可跨域,瀏覽覽器判斷一個網站是否能操做另外一個網站Cookie的依據是域名。Google與Baidu的域名不同,所以Google不能操做Baidu的Cookie,換句話說Google只能操做Google的Cookie。
即OSI參考模型。
還有一種TCP/IP五層模型,就是把應用層、表示層、會話層統一歸到應用層。借用一張圖。
網絡層是針對主機與主機之間的服務。而傳輸層針對的是不一樣主機進程之間的通訊。傳輸層協議將應用進程的消息傳送到網絡層,可是它並不涉及消息是怎麼在網絡層之間傳送(這部分是由網絡層的路由選擇完成的)。網絡層真正負責將數據包從源IP地址轉發到目標IP地址,而傳輸層負責將數據包再遞交給主機中對應端口的進程。
打個比方。房子A中的人要向房子B中的人寫信。房子中都有專門負責將主人寫好的信投遞到郵箱,以及從郵箱接收信件後交到主人手中的管家。那麼:
以上只是我的理解,若有誤請聯繫更正。
可靠傳輸是指
TCP如何實現可靠傳輸:
當TCP鏈接創建以後,應用程序就可以使用該鏈接進行數據收發。應用程序將數據提交給TCP,TCP將數據放入本身的緩存,數據會被當作字節流並進行分段,而後加上TCP頭部並提交給網絡層。再加上IP頭後被網絡層提交給到目的主機,目的主機的IP層會將分組提交給TCP,TCP根據報文段的頭部信息找到相應的socket,並將報文段提交給該socket,socket是和應用關聯的,因而數據就提交給了應用。
對於UDP會簡單些,UDP面向報文段。傳輸層加上UDP頭部遞交給網絡層,再加上IP頭部經路由轉發到目的主機,目的主機將分組提交給UDP,UDP根據頭部信息找到相應的socket,並將報文段提交給該socket,socket是和應用關聯的,因而數據就提交給了應用。
序號和確認號是實現可靠傳輸的關鍵。
通訊雙方經過序號和確認號,來判斷數據是否丟失、是否按順序到達、是否冗餘等,以此決定要不要進行重傳丟失的分組或丟棄冗餘的分組。換句話說,由於有了序號、確認號和重傳機制,保證了數據不丟失、不重複、有序到達。
當在瀏覽器輸入網址www.baidu.com並敲下回車後:
HTTP協議是基於TCP協議的,客戶端向服務端發送一個HTTP請求時,須要先與服務端創建TCP鏈接(三次握手),握手成功之後才能進行數據交互。
HTTP請求的報文格式:
HTTP響應的報文格式:
常見的狀態碼有:
synchronized對內置鎖引入了偏向鎖、輕量級鎖、自旋鎖、鎖消除等優化。使得性能和重入鎖差很少了。
首先要搞明白在I/O中的同步、異步、阻塞、非阻塞是什麼意思。
同步I/O。由用戶進程本身處理I/O的讀寫,處理過程當中不能作其餘事。須要主動去詢問I/O狀態。
異步I/O。由系統內核完成I/O操做,完成後系統會通知用戶進程。
阻塞。I/O請求操做須要的條件不知足,請求操做一直等待,直到條件知足。
非阻塞。 I/O請求操做須要的條件不知足,會當即返回一個標誌,而不會一直等待。
如今來看BIO、NIO、AIO的區別。
BIO:同步並阻塞。用戶進程在發起一個I/O請求後,必須等待I/O準備就緒,I/O操做也由本身來處理,在IO操做未完成以前,用戶進程必須等待。
NIO:同步非阻塞。用戶進程發起一個I/O請求後可當即返回去作其餘任務,當I/O準備就緒時它會收到通知。接着由這個線程自行進行I/O操做,I/O操做自己仍是同步的。
AIO:異步非阻塞。用戶進程發起一個I/O操做之後可當即返回去作其餘任務,真正的I/O操做由內核完成後通知用戶進程。
NIO和AIO的不一樣:NIO是操做系統通知用戶進程I/O已經準備就緒,由用戶進程自行完成I/O操做;AIO是操做系統完成I/O後通知用戶進程。
BIO是爲每個客戶端鏈接開啓一個線程,簡單說就是一個鏈接一個線程。
NIO主要組件有Seletor、Channel、Buffer,數據須要經過BUffer包裝後才能使用Channel進行讀取和寫入。一個Selector能夠由一個線程管理,每個Channel可看做一個客戶端鏈接。一個Selector能夠監聽多個Channel,即便用一個或極少數的線程來管理大量的客戶端鏈接。當與客戶端鏈接的數據沒有準備好時,Selector處於等待狀態,一旦某個Channel的準備好了數據,Selector就能當即獲得通知。
先使用synchronized實現。PrintOdd用於打印奇數;PrintEven用於打印偶數。核心就是判斷當前count若是是奇數,就讓PrintEven阻塞,PrintOdd打印後喚醒在lock對象上等待的PrintEven而且釋放鎖。此時PrintEven得到鎖打印偶數再喚醒PrintOdd,兩個線程如此交替喚醒對方就實現了交替打印奇偶數。
public class PrintOddEven { private static final Object lock = new Object(); private static int count = 1; static class PrintOdd implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { synchronized (lock) { try { while ((count & 1) != 1) { lock.wait(); } System.out.println(Thread.currentThread().getName() + " " +count); count++; lock.notify(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } static class PrintEven implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { synchronized (lock) { try { while ((count & 1) != 0) { lock.wait(); } System.out.println(Thread.currentThread().getName() + " " +count); count++; lock.notify(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } public static void main(String[] args) { new Thread(new PrintOdd()).start(); new Thread(new PrintEven()).start(); } }
進程是資源分配的最小單位,線程是程序執行的最小單位。 進程是線程的容器,即進程裏面能夠容納多個線程,多個線程之間能夠共享數據。
是指兩個或兩個以上的線程在執行過程當中,互相佔用着對方想要的資源但都不釋放,形成了互相等待,結果線程都沒法向前推動。
死鎖的檢測:能夠採用等待圖(wait-for gragh)。採用深度優先搜索的算法實現,若是圖中有環路就說明存在死鎖。
解決死鎖:
協同式線程調度:線程的執行時間以及線程的切換都是由線程自己來控制,線程把本身的任務執行完後,主動通知系統切換到另外一個線程。優勢是沒有線程安全的問題,缺點是線程執行的時間不可控,可能由於某一個線程不讓出CPU,而致使整個程序被阻塞。
搶佔式調度模式:線程的執行時間和切換都是由系統來分配和控制的。不過能夠經過設置線程優先級,讓優先級高的線程優先佔用CPU。
Java虛擬機默認採用搶佔式調度模型。
JDK 7中使用的是分段鎖,內部分紅了16個Segment即分段,每一個分段能夠看做是一個小型的HashMap,每次put只會鎖定一個分段,下降了鎖的粒度:
多線程put的時候,只要被加入的鍵值不屬於 同一個分段,就能夠作到真正的並行put。對不一樣的Segment則無需考慮線程同步,對於同一個Segment的操做才需考慮。
JDK 8中使用了CAS+synchronized保證線程安全,也採起了數組+鏈表/紅黑樹的結構。
put時使用synchronized鎖住了桶中鏈表的頭結點。
數組的擴容,被問到了我在看吧.....我只知道多個線程能夠協助數據的遷移。
有這麼一個問題,ConcurrentHashMap,有三個線程,A先put觸發了擴容,擴容時間很長,此時B也put會怎麼樣?此時C調用get方法會怎麼樣?C讀取到的元素是舊桶中的元素仍是新桶中的
A先觸發擴容,ConcurrentHashMap遷移是在鎖定舊桶的前提下進行遷移的,並無去鎖定新桶。
對於共享變量,通常採起同步的方式保證線程安全。而ThreadLocal是爲每個線程都提供了一個線程內的局部變量,每一個線程只能訪問到屬於它的副本。
實現原理,下面是set和get的實現
// set方法 public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } // 上面的getMap方法 ThreadLocalMap getMap(Thread t) { return t.threadLocals; } // get方法 public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
sleep() 容許指定以毫秒爲單位的一段時間做爲參數,它使得線程在指定的時間內進入阻塞狀態,不能獲得CPU 時間,指定的時間一過,線程從新進入可執行狀態。調用sleep後不會釋放鎖。
yield() 使得線程放棄CPU執行時間,可是不使線程阻塞,線程從運行狀態進入就緒狀態,隨時可能再次分得 CPU 時間。有可能當某個線程調用了yield()方法暫停以後進入就緒狀態,它又立刻搶佔了CPU的執行權,繼續執行。
wait()是Object的方法,會使線程進入阻塞狀態,和sleep不一樣,wait會同時釋放鎖。wait/notify在調用以前必須先得到對象的鎖。
run方法只是一個普通方法調用,仍是在調用它的線程裏執行。
start纔是開啓線程的方法,run方法裏面的邏輯會在新開的線程中執行。
前三個是線程私有的,後兩個是線程共享的。
字節碼解釋器經過改變程序計數器的值來決定下一條要執行的指令,爲了在線程切換後每條線程都能正確回到上次執行的位置,由於每條線程都有本身的程序計數器。
虛擬機棧是存放Java方法內存模型,每一個方法在執行時都會建立一個棧幀,用於存儲局部變量表、操做數棧、動態連接、方法返回地址等信息。方法的開始調用對應着棧幀的進棧,方法執行完成對應這棧幀的出棧。位於棧頂被稱爲「當前方法」。
本地方法棧和虛擬機棧相似,不過虛擬機棧針對Java方法,而本地方法棧針對Native方法。
Java堆。對象實例被分配內存的地方,也是垃圾回收的主要區域。
方法區。存放被虛擬機加載的類信息、常量(final)、靜態變量(static)、即時編譯期編譯後的代碼。方法區是用永久代實現的,這個區域的內存回收目標主要是針對常量池的回收和類型的卸載。運行時常量池是方法區的一部分,運行時常量池是Class文件中的一項信息,存放編譯期生成的各類字面量和符號引用。
Java堆分爲新生代和老年代。在新生代又被劃分爲Eden區,From Sruvivor和To Survivor區,比例是8:1:1,因此新生代可用空間其實只有其容量的90%。對象優先被分配在Eden區。
發生在新生代的GC稱爲Minor GC,當Eden區被佔滿了而又須要分配內存時,會發生一次Minor GC,通常使用複製算法,將Eden和From Survivor區中還存活的對象一塊兒複製到To Survivor區中,而後一次性清理掉Eden和From Survivor中的內存,使用複製算法不會產生碎片。
老年代的GC稱爲Full GC或者Major GC:
在通過可達性分析後,到GC Roots不可達的對象能夠被回收(但並非必定會被回收,至少要通過兩次標記),此時對象被第一次標記,並進行一次判斷:
所以finalize方法被調用後,對象不必定會被回收。
先說類加載器。
在Java中,系統提供了三種類加載器。
固然用戶也能夠自定義類加載器。
再說類加載的過程。
主要是如下幾個過程:
加載 -> 驗證 -> 準備 -> 解析 -> 初始化 -> 使用 -> 卸載
加載
驗證
準備。
爲類變量(static)分配內存並設置默認值。好比static int a = 123在準備階段的默認值是0,可是若是有final修飾,在準備階段就會被賦值爲123了。
解析。
將常量池中的符號引用替換成直接引用的過程。包括類或接口、字段、類方法、接口方法的解析。
初始化。
按照程序員的計劃初始化類變量。如static int a = 123,在準備階段a的值被設置爲默認的0,而到了初始化階段其值被設置爲123。
類加載器之間知足雙親委派模型,即:除了頂層的啓動類加載器外,其餘全部類加載器都必需要本身的父類加載器。當一個類加載器收到類加載請求時,本身首先不會去加載這個類,而是不斷把這個請求委派給父類加載器完成,所以全部的加載請求最終都傳遞給了頂層的啓動類加載器。只有當父類沒法完成這個加載請求時,子類加載器纔會嘗試本身去加載。
雙親委派模型的好處?使得Java的類隨着它的類加載器一塊兒具有了一種帶有優先級的層次關係。Java的Object類是全部類的父類,所以不管哪一個類加載器都會加載這個類,由於雙親委派模型,全部的加載請求都委派給了頂層的啓動類加載器進行加載。因此Object類在任何類加載器環境中都是同一個類。
如何打破雙親委派模型?使用OSGi能夠打破。OSGI(Open Services Gateway Initiative),或者通俗點說JAVA動態模塊系統。能夠實現代碼熱替換、模塊熱部署。在OSGi環境下,類加載器再也不是雙親委派模型中的樹狀結構,而是進一步發展爲更加複雜的網狀結構。
CMS(Concurrent Mark Sweep) 從名字能夠看出是能夠進行併發標記-清除的垃圾收集器。針對老年代的垃圾收集器,目的是儘量地減小用戶線程的停頓時間。
收集過程有以下幾個步驟:
CMS的缺點:
CMS比較相似適合用戶交互的場景,能夠得到較小的響應時間。
G1(Garbage First),有以下特色:
在使用G1收集器時,Java堆的內存劃分爲多個大小相等的獨立區域,新生代和老年代再也不是物理隔離。G1跟蹤各個區域的垃圾堆積的價值大小,在後臺維護一個優先列表,每次根據容許的收集時間,優先回收價值最大的區域。
G1的收集過程和CMS有些相似:
G1的優點:可預測的停頓;實時性較強,大幅減小了長時間的gc;必定程度的高吞吐量。
由上一個問題可總結出CMS和G1的區別:
GC進行時必須暫停全部Java執行線程,這被稱爲Stop The World。爲何要停頓呢?由於可達性分析過程當中不容許對象的引用關係還在變化,不然可達性分析的準確性就沒法獲得保證。因此須要STW以保證可達性分析的正確性。
程序執行時並不是在全部地方都能停頓下來開始GC,只有在「安全點」才能暫停。安全點指的是:HotSpot沒有爲每一條指令都生成OopMap(Ordinary Object Pointer),而是在一些特定的位置記錄了這些信息。這些位置就叫安全點。
在具體解釋上面的四個隔離級別前。有必要了解事務的四大特性(ACID)
事務併發可能產生的問題:
髒數據:事務對緩衝池中的行記錄進行修改,可是尚未被提交。
髒讀是讀取到事務未提交的數據,不可重複度讀讀取到的是提交提交後的數據,只不過在一次事務中讀取結果不同。
不可重複讀側重於修改,幻讀側重於新增或刪除。解決不可重複讀的問題只需鎖住知足條件的行,解決幻讀須要鎖表。
通常來講,數據庫隔離級別不同,可能出現的併發問題也不一樣。級別最高的是串行化,全部問題都不會出現。可是在併發下性能極低,可重複讀會只會致使幻讀。
因此通常使用MySQL默認的可重複讀便可。MVCC(多版本併發控制)使用undo_log使得事務能夠讀取到數據的快照(某個歷史版本),從而實現了可重複讀。MySQL採用Next-Key Lock算法,對於索引的掃描不只是鎖住掃描到的索引,還鎖住了這些索引覆蓋的範圍,避免了不可重複讀和幻讀的產生。
死鎖是指兩個或兩個以上的事務在執行過程當中,因爭奪鎖資源而形成的一種互相等待的現象,若無外力做用兩個事務都沒法推動,這樣就產生了死鎖。下去 死鎖的四個必要條件:
避免死鎖能夠經過破環四個必要條件之一。
解決死鎖的方法:
開啓慢查詢,查找哪些sql語句執行得慢。使用explain查看語句的執行計劃,好比有沒有使用到索引,是否啓用了全表掃描等。查詢慢,很大多是由於沒有使用索引或者索引沒有被命中。還有其餘的緣由,好比發生了死鎖,硬件、網速等緣由。
優化手段:爲相關列添加索引,而且確保索引能夠被命中。優化sql語句的編寫。
索引是對數據庫表中一個或多個列的值進行排序的結構。MySql中索引是B+樹,在查找時能夠利用二分查找等高效率的查找方式,以O(lg n)的時間找到。所以索引能夠加快查詢速度。
哪些狀況不適合創建索引?
建了一個(a,b,c)的聯合索引,那麼實際等於建了(a),(a,b),(a,b,c)三個索引,可是有時在條件查詢時只會匹配到a或者(a, b)而不會匹配到(a, b, c)。下面的例子
SELECT * FROM table WHERE a = 1 AND c = 3; // 使用了索引a,c不走索引 SELECT * FROM table WHERE a = 1 AND b < 2 AND c = 3; // 使用到了索引(a,b),c不走索引
創建聯合索引(a, b ,c),因此索引是按照a -> b -> c的順序進行排序的。a-b-c這樣的索引是先找a,而後在範圍裏面找b,再在範圍內找c。 因此上面的語句裏的c 會分散在不少個b裏面且不是排序的,因此沒辦法走索引。
舉個例子好比(a, b)聯合索引,先按a排序再按b排序,獲得
(1,1)->(1, 2)->(2, 1) (2, 4)->(3, 1)->(3, 2)
若是執行select a from table where b=2
,就沒有使用到(a, b)這個聯合索引,由於b的值1,2,1,4,1,2顯然不是排序的。
具體來講:MySQL會從左開始一直向右匹配直到遇到範圍查詢(>,<,BETWEEN,LIKE)就中止匹配,好比: a = 1 AND b = 2 AND c > 3 AND d = 4,若是創建 (a,b,c,d)順序的索引,使用了索引(a, b, c),可是d是沒有走索引的,若是創建(a,b,d,c)的索引,則能夠命中索引(a, b, c, d),其中a,b,d的順序能夠任意調整。
等於(=)和in 能夠亂序。好比,a = 1 AND b = 2 AND c = 3 創建(a,b,c)索引能夠任意順序。
a = 1 AND b = 2 AND c > 3 AND d = 4
,創建(a, b, d, c)就是不錯的選擇;SELECT * FROM t WHERE c = 100 and d = 'xyz' ORDER BY b
創建(c, d, b)聯合索引就是不錯的選擇LIKE '%abc'
這樣的不能命中索引;不過LIKE 'abc%'
能夠命中索引。not in, <>,!=
則不會命中索引。注:<>
是不等號LIKE '%abc'
這樣不能命中索引MySQL5.0以前,一個表一次只能使用一個索引,沒法同時使用多個索引分別進行條件掃描。可是從5.1開始,引入了 index merge 優化技術,對同一個表能夠使用多個索引分別進行條件掃描。
大多數狀況下索引能大幅度提升查詢效率,但數據的變動(增刪改)都須要維護索引,所以更多的索引意味着更多的維護成本和更多的空間 (一本100頁的書,卻有50頁目錄?)並且太小的表,創建索引可能會更慢(讀個2頁的宣傳手冊,你還先去找目錄?)
B-樹是一種平衡的多路查找樹。2-3樹和2-3-4樹都是B-樹的特例。一棵M階的B-樹,除了根結點外的其餘非葉子結點,最多含有M-1對鍵和連接,最少含有M/2對鍵和連接。根結點能夠少於M/2,可是也不能少於2對。
B+樹是B-樹的變體,也是一種多路查找樹。
B+ 樹更適合用於數據庫和操做系統的文件系統中。
假設一個結點就是一個頁面,B樹遍歷全部記錄,經過中序遍歷的方式,要屢次返回到父結點,同一個結點屢次訪問了,增長了磁盤I/O操做的次數。B+由於在葉子結點存放了全部的記錄,並且是雙向鏈表的結構,只需在葉子節點這一層就能遍歷全部記錄,大大減小了磁盤I/O操做,因此數據庫索引用B+樹結構更好。
COUNT(*)
和COUNT(1)
的區別?COUNT(列名)
和COUNT(*)
的區別?COUNT(*)
和COUNT(1)
沒區別。COUNT(列名)
和COUNT(*)
區別在於前者不會統計列爲NULL的數據,後者會統計。
悲觀鎖:老是假設在併發下會出現問題,即假設多個事務對同一個數據的訪問會產生衝突。當其餘事務想要訪問數據時,會在臨界區提早加鎖,須要將其阻塞掛起。好比MySQL中的排他鎖(X鎖)、和共享鎖(S鎖)
樂觀鎖: 老是假設任務在併發下是安全的,即假設多個事務對同一個數據的訪問不會發生衝突,所以不會加鎖,就對數據進行修改。當遇到衝突時,採用CAS或者版本號、時間戳的方式來解決衝突。數據庫中使用的樂觀鎖是版本號或時間戳。樂觀併發控制(OCC)是一種用來解決寫-寫衝突的無鎖併發控制,不用加鎖就嘗試對數據進行修改,在修改以前先檢查一下版本號,真正提交事務時,再檢查版本號有,若是不相同說明已經被其餘事務修改了,能夠選擇回滾當前事務或者重試;若是版本號相同,則能夠修改。
提一下樂觀鎖和MVCC的區別,其實MVCC也利用了版本號,和樂觀鎖仍是能扯上些關係。
MVCC主要解決了讀-寫的阻塞,由於讀只能讀到數據的歷史版本(快照);OCC主要解決了寫-寫的阻塞,多個事務對數據進行修改而不加鎖,更新失敗的事務能夠選擇回滾或者重試。
當多個用戶/進程/線程同時對數據庫進行操做時,會出現3種衝突情形:讀-讀,不存在任何問題;讀-寫,有隔離性問題,可能遇到髒讀、不可重複讀 、幻讀等。寫-寫,可能丟失更新。多版本併發控制(MVCC)是一種用來解決讀-寫衝突的無鎖併發控制,讀操做只讀該事務開始前的數據庫的快照,實現了一致性非鎖定讀。 這樣在讀操做不用阻塞寫操做,寫操做不用阻塞讀操做的同時,避免了髒讀和不可重複讀。樂觀併發控制(OCC)是一種用來解決寫-寫衝突的無鎖併發控制,不用加鎖就嘗試對數據進行修改,在修改以前先檢查一下版本號,真正提交事務時,再檢查版本號有,若是不相同說明已經被其餘事務修改了,能夠選擇回滾當前事務或者重試;若是版本號相同,則能夠修改。
MVCC(多版本併發控制)使用undo_log使得事務能夠讀取到數據的快照(某個歷史版本),從而實現了可重複讀。MySQL採用Next-Key Lock算法,對於索引的掃描不只是鎖住掃描到的索引,還鎖住了這些索引覆蓋的範圍,避免了不可重複讀和幻讀的產生。
具體來講:
在可重複讀下: select....from會採用MVCC實現的一致性非鎖定讀,讀取的是事務開始的快照,避免了不可重複讀。select .....from .... for update會採用 Next-Key Locking來保證可重複讀和幻讀。
在讀已提交下: select....from 會採用快照,讀取的是最新一份的快照數據,不可以保證不可重複讀和幻讀;select .....from .... for update會採用Record Lock,不可以保證不可重複讀/幻讀。
若是一個索引包含(或覆蓋)全部須要查詢的字段的值,即只需掃描索引而無須回表,這稱爲「覆蓋索引」。InnoDB的輔助索引在葉子節點中保存了部分鍵值信息以及指向彙集索引鍵的指針,若是輔助索引葉子結點中的鍵值信息已經覆蓋了要查詢的字段,就沒有必要利用指向主鍵索引的主鍵,而後再經過主鍵索引來找到一個完整的行記錄了。
UNION 操做符用於合併兩個或多個 SELECT 語句的結果集。UNION 內部的 SELECT 語句必須擁有相同數量的列。列也必須擁有相同的數據類型。同時,每條 SELECT 語句中的列的順序必須相同。默認狀況下,UNION會過濾掉重複的值。使用 UNION ALL則會包含重複的值。
JOIN用於鏈接兩個有關聯的表,篩選兩個表中知足條件(ON後的條件)的行記錄獲得一個結果集。從結果集中SELECT的字段能夠是表A或者表B中的任意列。
JOIN經常使用的有LEFT JOIN、RIGHT JOIN、INNER JOIN。
INNER JOIN
FULL OUTER JOIN
LEFT JOIN
RIGHT JOIN和LEFT JOIN相似。
所謂SQL注入式攻擊,就是攻擊者把SQL命令插入到Web表單的輸入域或頁面請求的查詢字符串,欺騙服務器執行惡意的SQL命令。
好比在登陸界面,若是用戶名填入'xxx' OR 1=1 --
就能構造下面的SQL語句,由於OR 1=1,password被註釋掉,所以不管name和password填入什麼都能登陸成功。
SELECT * FROM USER WHERE NAME='xxx' OR 1=1 -- and password='xxx';
使用PrepareStatement,能夠防止sql注入攻擊,sql的執行須要編譯,注入問題之因此出現,是由於用戶填寫 sql語句參與了編譯。使用PrepareStatement對象在執行sql語句時,會分爲兩步,第一步將sql語句 "運送" 到mysql上預編譯,再回到java端拿到參數運送到mysql端。預先編譯好,也就是SQL引擎會預先進行語法分析,產生語法樹,生成執行計劃,也就是說,後面你輸入的參數,不管你輸入的是什麼,都不會影響該sql語句的語法結構了。用戶填寫的 sql語句,就不會參與編譯,只會當作參數來看。從而避免了sql注入問題。
AOP:面向切面編程。能夠將應用各處的功能分離出來造成可重用的組件。核心業務邏輯與安全、事務、日誌等這些非核心業務邏輯分離,使得業務邏輯更簡潔清晰。
提供了對像關係映射(ORM)、事務管理、遠程調用和Web應用的支持。
Spring使用IOC容器建立和管理對象,好比在XML中配置了類的全限定名,而後Spring使用反射+工廠來建立Bean。BeanFactory是最簡單的容器,只提供了基本的DI支持,ApplicationContext基於BeanFactory建立,提供了完整的框架級的服務,所以通常使用應用上下文。
IOC(Inverse of Control)即控制反轉。能夠理解爲控制權的轉移。傳統的實現中,對象的建立和依賴關係都是在程序進行控制的。而如今由Spring容器來統一管理、對象的建立和依賴關係,控制權轉移到了Spring容器,這就是控制反轉。
DI(Dependency Injection)依賴注入。對象的依賴關係由負責協調各個對象的第三方組件在建立對象的時候進行設定,對象無需自行建立或管理它們的依賴關係。通俗點說就是Spring容器爲對象注入外部資源,設置屬性值。DI的好處是使得各個組件之間鬆耦合,一個對象若是隻用接口來代表依賴關係,這種依賴能夠在對象絕不知情的狀況下,用不一樣的具體類進行替換。
IOC和DI實際上是對同一種的不一樣表述。
AOP(Aspect-Orientid Programming)面向切面編程,能夠將遍及在應用程序各個地方的功能分離出來,造成可重用的功能組件。系統的各個功能會重複出如今多個組件中,各個組件存在於核心業務中會使得代碼變得混亂。使用AOP能夠將這些多處出現的功能分離出來,不只能夠在任何須要的地方實現重用,還能夠使得核心業務變得簡單,實現了將核心業務與日誌、安全、事務等功能的分離。
具體來講,散佈於應用中多處的功能被稱爲橫切關注點,這些橫切關注點從概念上與應用的業務邏輯是相分離的,可是又經常會直接嵌入到應用的業務邏輯中,AOP把這些橫切關注點從業務邏輯中分離出來。安全、事務、日誌這些功能均可以被認爲是應用中的橫切關注點。
一般要重用功能,能夠使用繼承或者委託的方式。可是繼承每每致使一個脆弱的對像體系;委託帶來了複雜的調用。面向切面編程仍然能夠在一個地方定義通用的功能,可是能夠用聲明的方法定義這個功能要在何處出現,而無需修改受到影響的類。橫切關注點能夠被模塊化爲特殊的類,這些類被稱爲切面(Aspect)。好處在於:
AOP術語介紹
通知:切面所作的工做稱爲通知。通知定義了切面是什麼,以及在什麼時候使用。Spring切面能夠應用5種類型的通知
鏈接點:能夠被通知的方法
切點:實際被通知的方法
切面:即通知和切點的結合,它是什麼,在什麼時候何處完成其功能。
引入:容許向現有的類添加新方法或屬性,從而能夠在無需修改這些現有的類狀況下,讓它們具備新的行爲和狀態。
織入:把切面應用到目標對象並建立新的代理對象的過程。切面在指定的鏈接點被織入到目標對象中。在目標對象的生命週期裏有多個點能夠進行織入:
Spring AOP構建在動態代理基礎之上,因此Spring對AOP的支持僅限於方法攔截。
Spring的切面是由包裹了目標對象的代理類實現的。代理類封裝了目標類,並攔截被通知方法的調用,當代理攔截到方法調用時,在調用目標bean方法以前,會執行切面邏輯。其實切面只是實現了它們所包裝bean相同接口的代理。
Spring AOP中的動態代理主要有兩種方式,JDK動態代理和CGLIB動態代理。JDK動態代理經過反射來接收被代理的類,而且要求被代理的類必須實現一個接口。JDK動態代理的核心是InvocationHandler接口和Proxy類。
若是目標類沒有實現接口,那麼Spring AOP會選擇使用CGLIB來動態代理目標類。CGLIB(Code Generation Library),是一個代碼生成的類庫,能夠在運行時動態的生成某個類的子類,注意,CGLIB是經過繼承的方式作的動態代理,所以若是某個類被標記爲final,那麼它是沒法使用CGLIB作動態代理的。
Spring使用動態代理,代理類封裝了目標類,當代理攔截到方法調用時,在調用目標bean的方法以前,會執行切面邏輯。
Spring建立、管理對象。Spring容器負責建立對象,裝配它們,配置它們並管理它們的整個生命週期。
<bean id="">
@Bean
註解@ComponentScan
和@AutoWired
註解bean的注入方式有:
推薦對於強依賴使用構造器注入,對於弱依賴使用屬性注入。
默認狀況下Spring中的bean都是單例的。
Hibernate :是一個標準的ORM(對象關係映射) 框架; SQL語句是本身生成的,程序員不用本身寫SQL語句。所以要對SQL語句進行優化和修改比較困難。適用於中小型項目。
MyBatis: 程序員本身編寫SQL, SQL修改和優化比較自由。 MyBatis更容易掌握,上手更容易。主要應用於需求變化較多的項目,如互聯網項目等。
首先要了解幾種數據結構和算法:
對上億個無重複數字的排序,或者找到沒有出現過數字,注意由於無重複數字,而BitMap的0和1正好能夠表示該數字有沒有出現過。若是要求更小的內存,能夠先分出區間,對落入區間的進行計數。必然有的區間數量未滿,再遍歷一次數組,只看該區間上的數字,使用BitMap,遍歷完成後該區間中必然有沒被設置成0的的地方,這些地方就是沒出現的數。
數據在小範圍內波動,好比人類年齡,並且數據容許重複,可用計數排序處理數值排序或查找沒有出現過的值,計數的桶中頻次爲0的就是沒有出現過的數。
數據是數字,要找最大的Top K,直接用大小爲K的小根堆,不斷淘汰最小元素便可。
數據是數字或非數字,要找頻次最高的Top K。可以使用HashMap統計頻次,統計出頻次最大的前K個便可。統計頻次選出前K的過程能夠用小根堆。還能夠用Hash分流的方法,即用一個合適的hash函數將數據分到不一樣的機器或者文件中,由於對於一樣的數據,因爲hash函數的性質,必然被分配到同一個文件中,所以不存在相同的數據分佈在不一樣的文件這種狀況。對每一個文件採用HashMap統計頻次,用小根堆選出Top K,而後彙總所有文件,從全部部分結果的Top K中再利用小根堆獲得最終的Top K。
查找數值的排名,好比找到中位數。好比將數劃分區間,對落入每一個區間的數進行計數。而後能夠得知中位數落在哪一個區間,再遍歷全部數,此次只關心落在該區間的數,不劃分區間的對其進行計數,就能夠找出中位數。