一.ArrayList和LinkedList區別及使用場景
LinkedList和ArrayList的差別主要來自于Array和LinkedList數據結構的不同。ArrayList是基于數組實現的,LinkedList是基于雙鏈表實現的。另外LinkedList類不僅是List接口的實現類,可以根據索引來隨機訪問集合中的元素,除此之外,LinkedList還實現了Deque接口,Deque接口是Queue接口的子接口,它代表一個雙向隊列,因此LinkedList可以作為雙向隊列 ,棧(可以參見Deque提供的接口方法)和List集合使用,功能強大。
因為Array是基于索引(index)的數據結構,它使用索引在數組中搜索和讀取數據是很快的,可以直接返回數組中index位置的元素,因此在隨機訪問集合元素上有較好的性能。Array獲取數據的時間復雜度是O(1),但是要插入、刪除數據卻是開銷很大的,因為這需要移動數組中插入位置之后的的所有元素。
相對于ArrayList,LinkedList的隨機訪問集合元素時性能較差,因為需要在雙向列表中找到要index的位置,再返回;但在插入,刪除操作是更快的。因為LinkedList不像ArrayList一樣,不需要改變數組的大小,也不需要在數組裝滿的時候要將所有的數據重新裝入一個新的數組,這是ArrayList最壞的一種情況,時間復雜度是O(n),而LinkedList中插入或刪除的時間復雜度僅為O(1)。ArrayList在插入數據時還需要更新索引(除了插入數組的尾部)。
LinkedList需要更多的內存,因為ArrayList的每個索引的位置是實際的數據,而LinkedList中的每個節點中存儲的是實際的數據和前后節點的位置。
使用場景:
(1)如果應用程序對數據有較多的隨機訪問,ArrayList對象要優于LinkedList對象;
(2)如果應用程序有更多的插入或者刪除操作,較少的數據讀取,LinkedList對象要優于ArrayList對象;
(3)不過ArrayList的插入,刪除操作也不一定比LinkedList慢,如果在List靠近末尾的地方插入,那么ArrayList只需要移動較少的數據,而LinkedList則需要一直查找到列表尾部,反而耗費較多時間,這時ArrayList就比LinkedList要快。
二.hashmap如何解決hash沖突,為什么hashmap中的鏈表需要轉成紅黑樹?
1.hash值沖突是發生在put()時,先查詢是否存在該hash值,若不存在,則直接以Entry<V,V>的方式存放在數組中,若存在,則再對比key是否相同,若hash值和key都相同,則替換value,若hash值相同,key不相同,則形成一個單鏈表,將hash值相同,key不同的元素以Entry<V,V>的方式存放在鏈表中,這樣就解決了hash沖突
2.在jdk1.8版本后,Java對HashMap做了改進,在鏈表長度大于8的時候,將后面的數據存在紅黑樹中,以加快檢索速度。
三.JDK1.7 并發的HashMap為什么會引起死循環?
線程不安全的HashMap, HashMap在并發執行put操作時會引起死循環,是因為多線程會導致HashMap的Entry鏈表形成環形數據結構,查找時會陷入死循環。
table這個Entry數組是線程共享的,而next,e是線程私有的。一個map中存有3->7->null兩個元素,當兩個線程put第三個元素8時,如果出發了resize(),A線程走到transfer()方法時B線程進來了,
B線程會把老的table在自己線程中變成一個新的table寫入內存,修改了node的指向關系,變成了7->3->null;這時A線程再拿到B修改過的table去變成新的table,在循環讀取元素時,先拿到3放入新的table,
線程A再復制元素 7 ,當前 e = 7 ,而next值由于線程 B 修改了它的引用,所以next 為 3,由于上面取到的next = 3, 接著while循環,即當前處理的結點為3, next就為null ,退出while循環。
這時node的指向關系是,7->3->7形成循環,在get操作時會死循環
四.hashmap的數組長度為什么要保證是2的冪
關鍵代碼:tab[i = (n - 1) & hash] 這里是計算數組索引下標的位置
&為二進制中的與運算
運算規則:0&0=0; 0&1=0; 1&0=0; 1&1=1;
即:兩位同時為“1”,結果才為“1”,否則為0
因為hashMap 的數組長度都是2的n次冪 ,那么對于這個數再減去1,轉換成二進制的話,就肯定是最高位為0,其他位全是1 的數。
不為2的n次冪 的話,對應的二進制數肯定有一位為0 , 這樣不管你的hashCode 值對應的該位,是0還是1 ,
最終得到的該位上的數肯定是0,這帶來的問題就是HashMap上的數組元素分布不均勻,而數組上的某些位置,永遠也用不到。
五.如何用LinkedHashMap實現LRU
LinkedHashMap默認的元素順序是put的順序,但是如果使用帶參數的構造函數,那么LinkedHashMap會根據訪問順序來調整內部 順序。 LinkedHashMap的get()方法除了返回元素之外還可以把被訪問的元素放到鏈表的底端,這樣一來每次頂端的元素就是remove的元素。
public LinkedHashMap (int initialCapacity, float loadFactor, boolean accessOrder);
initialCapacity 初始容量
loadFactor 加載因子,一般是 0.75f
accessOrder false 基于插入順序 true 基于訪問順序(get一個元素后,這個元素被加到最后,使用了LRU 最近最少被使用的調度算法)
LinkedHashMap中有一個方法removeEldestEntry(entry) 我們只需要覆蓋這個方法,根據我們自己的需求在一定條件下返回true,這樣就實現了LruCache
六.ConcurrentHashMap是如何在保證并發安全的同時提高性能
HashTable容器在競爭激烈的并發環境下表現出效率低下的原因,是因為所有訪問HashTable的線程都必須競爭同一把鎖,那假如容器里有多把鎖,每一把鎖用于鎖容器其中一部分數據,
那么當多線程訪問容器里不同數據段的數據時,線程間就不會存在鎖競爭,從而可以有效的提高并發訪問效率,這就是ConcurrentHashMap所使用的鎖分段技術,首先將數據分成一段一段的存儲,
然后給每一段數據配一把鎖,當一個線程占用鎖訪問其中一個段數據的時候,其他段的數據也能被其他線程訪問
七.ConcurrentHashMap是如何讓多線程同時參與擴容
先判斷table的長度是否小于64,如果小于,則優先使用擴容來解決問題,擴容為原來的一倍,調整某一個桶中元素過多的問題(超出了8個)),會觸發某些桶中的元素重新分配,避免在一個桶中有太多的元素影響訪問效率
在多線的環境下,用volatile的方式讀取sizectrl屬性的值,來判斷map所處的狀態,通過cas修改操作來告訴其它線程Map的狀態類型。
未初始化
等于0,表示未指定初始化容量,則使用默認容量
大于0,為指定的初始化容量
初始化中
等于-1,表示正在初始化,并且通過cas告訴其它線程
正常狀態
等于原table長度n*0.75,擴容閥值
擴容中
小于0,表示有其他線程正在執行擴容操作
等于(resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2表示此時只有一個線程在執行擴容
并且多線程分段擴容
八.ReentrantLock如何實現公平和非公平鎖是如何實現
公平鎖的lock方法在進行cas判斷時多了一個hasQueuedPredecessors()方法,當阻塞隊列中沒有線程時才嘗試獲取鎖
九.CountDownLatch和CyclicBarrier的區別?各自適用于什么場景?
CountDownLatch和CyclicBarrier都是java.util.concurrent包下面的多線程工具類。
區別:CountDownLatch,當計數為0的時候,下一步的動作實施者是main函數;對于CyclicBarrier,下一步動作實施者是“其他線程”
對于CountDownLatch,其他線程為游戲玩家,比如英雄聯盟,主線程為控制游戲開始的線程。在所有的玩家都準備好之前,主線程是處于等待狀態的,也就是游戲不能開始。當所有的玩家準備好之后,下一步的動作實施者為主線程,即開始游戲。
公司要求所有人在翻越當前障礙物之后再開始翻越下一個障礙物,也就是所有人翻越第一個障礙物之后,才開始翻越第二個,以此類推。類比地,每一個員工都是一個“其他線程”。當所有人都翻越的所有的障礙物之后,程序才結束。而主線程可能早就結束了,這里我們不用管主線程
十.往線程池里提交一個任務會發生什么
當調用execute()方法添加一個任務時,線程池會做如下判斷:
a. 如果正在運行的線程數量小于corePoolSize,那么馬上創建線程運行這個任務
b. 如果正在運行的線程數量大于或等于corePoolSize,那么將這個任務放入隊列
c. 如果這時候隊列滿了,而且正在運行的線程數量小于maximunPoolSize,那么還是要創建非核心線程立刻運行這個任務
d. 如果隊列滿了,而且正在運行的線程數量大于或等于maximunPoolSize,那么線程池會拋出RejectedExecutionException
十一.線程池的幾個參數如何設置
corePoolSize 核心線程池大小
maximumPoolSize 線程池最大容量大小
keepAliveTime 線程池空閑時,線程存活的時間
TimeUnit 線程活動保持時間的單位
BlockingQueue<Runnable> 任務隊列,用于保存等待執行的任務的阻塞隊列
ThreadFactory 用于設置線程的工廠
RejectedExecutionHandler 飽和策略
十二.線程池的非核心線程什么時候會被釋放
阻塞隊列里沒有任務,那么非核心線程也要在等到keepAliveTime時間后才會釋放
十三.synchronized與ReentrantLock的區別
sychronized有三條原則:
當一個線程訪問“某對象”的“synchronized方法”或者“synchronized代碼塊”時,其他線程對“該對象”的該“synchronized方法”或者“synchronized代碼塊”的訪問將被阻塞。
當一個線程訪問“某對象”的“synchronized方法”或者“synchronized代碼塊”時,其他線程仍然可以訪問“該對象”的非同步代碼塊。
當一個線程訪問“某對象”的“synchronized方法”或者“synchronized代碼塊”時,其他線程對“該對象”的其他的“synchronized方法”或者“synchronized代碼塊”的訪問將被阻塞。
synchronized會在進入同步塊的前后分別形成monitorenter和monitorexit字節碼指令.在執行monitorenter指令時會嘗試獲取對象的鎖,如果此沒對象沒有被鎖,或者此對象已經被當前線程鎖住,
那么鎖的計數器加一,每當monitorexit被鎖的對象的計數器減一.直到為0就釋放該對象的鎖.由此synchronized是可重入的,不會出現自己把自己鎖死.
ReentrantLock:
除了synchronized的功能,多了三個高級功能
等待可中斷:在持有鎖的線程長時間不釋放鎖的時候,等待的線程可以選擇放棄等待,tryLock(long timeout, TimeUnit unit)
公平鎖:按照申請鎖的順序來一次獲得鎖稱為公平鎖,synchronized的是非公平鎖,ReentrantLock可以通過構造函數實現公平鎖。new RenentrantLock(boolean fair)
綁定多個Condition:通過多次newCondition可以獲得多個Condition對象,可以簡單的實現比較負責的線程同步的功能,通過await()等待,signal()喚醒;
十四.樂觀鎖和悲觀鎖的區別
樂觀鎖:相信事務之間的數據競爭(data race)的概率是比較小的,因此盡可能直接做下去,直到提交的時候才去鎖定,所以不會產生任何鎖和死鎖。樂觀鎖不是數據庫自帶的,需要我們自己去實現。
悲觀鎖:就是在操作數據時,認為此操作會出現數據沖突,所以在進行每次操作時都要通過獲取鎖才能進行對相同數據的操作。悲觀鎖是由數據庫自己實現了的。
使用select…for update會把數據給鎖住,不過我們需要注意一些鎖的級別,MySQL InnoDB默認行級鎖。行級鎖都是基于索引的,如果一條SQL語句用不到索引是不會使用行級鎖的,會使用表級鎖把整張表鎖住,這點需要注意。
十五.如何實現一個樂觀鎖
比如多線程實現a++操作。如果簡單循環創建線程執行a++,最終結果不一定是預期的樣子,因為a++操作需要三步完成:1.從主內存中讀取a的值到線程內存2.對a進行加1操作3.把值寫入主內存。所以可能一個線程
還沒寫回主內存,其他線程就拿到舊的值進行加1。可以參考AtomicInteger的實現就利用了樂觀鎖。原理是自增操作主要是使用了unsafe的getAndAddInt方法,其內部又調用了Unsafe.compareAndSwapInt方法。這個機制叫做CAS機制。
CAS 即比較并替換,實現并發算法時常用到的一種技術。CAS操作包含三個操作數——內存位置、預期原值及新值。執行CAS操作的時候,將內存位置的值與預期原值比較,如果相匹配,那么處理器會自動將該位置值更新為新值,否則,處理器不做任何操作。
十六.Java中的強引用、軟引用、弱引用、虛引用
強引用:如果一個對象具有強引用,不會被垃圾回收器回收。當內存空間不足,Java虛擬機寧愿拋出OutOfMemoryError錯誤,使程序異常終止,也不回收這種對象。
軟引用:軟引用關聯著的對象,只有在內存不足的時候JVM才會回收該對象
Obj obj = new Obj();
SoftReference<Obj> sr = new SoftReference<Obj>(obj);
obj = null;
System.out.println(sr.get());
弱引用:當JVM進行垃圾回收時,無論內存是否充足,都會回收被弱引用關聯的對象
WeakReference<String> sr = new WeakReference<String>(new String("hello"));
System.out.println(sr.get()); // 輸出hello
System.gc(); //通知JVM的gc進行垃圾回收
System.out.println(sr.get()); // 輸出null
虛引用:不影響對象的生命周期。在java中用java.lang.ref.PhantomReference類表示。如果一個對象與虛引用關聯,則跟沒有引用與之關聯一樣,在任何時候都可能被垃圾回收器回收。虛引用主要用來跟蹤對象被垃圾回收的活動。
虛引用必須和引用隊列關聯使用,當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會把這個虛引用加入到與之 關聯的引用隊列中。程序可以通過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否將要被垃圾回收
ReferenceQueue<String> queue = new ReferenceQueue<String>();
PhantomReference<String> pr = new PhantomReference<String>(new String("hello"), queue);
利用軟引用和弱引用解決OOM問題:假如有一個應用需要讀取大量的本地圖片,如果每次讀取圖片都從硬盤讀取,則會嚴重影響性能,但是如果全部加載到內存當中,又有可能造成內存溢出,此時使用軟引用可以解決這個問題。
十七.java NIO與BIO的區別
NIO:同步非阻塞io模式,核心通過selector管理多個連接,達到一個線程處理多個事件
BIO:同步阻塞IO模式,數據的讀取寫入必須阻塞在一個線程內等待其完成。
十八.同步阻塞、同步非阻塞、異步的區別
異步:當一個異步過程調用發出后,調用者不會立即得到結果。而是在“發出后”,“被調用者“通過狀態,來通知調用者,或通過回調函數處理這個調用
同步阻塞:調用者發出請求后會一直等待結果
同步非阻塞:調用者發出請求后就去執行其他任務,過一會再詢問被調用者執行結果
十九.JVM內存模型
根據java虛擬機規范,java虛擬機管理的內存將分為下面五大區域
1.程序計數器是一塊很小的內存空間,它是線程私有的,可以認作為當前線程的行號指示器。
2.線程私有棧描述的是Java方法執行的內存模型
3.本地方法棧是與虛擬機棧發揮的作用十分相似,區別是虛擬機棧執行的是Java方法,而本地方法棧則為虛擬機使用到的native方法服務,也是線程私有
4.堆是java虛擬機管理內存最大的一塊內存區域,因為堆存放的對象是線程共享的,所以多線程的時候也需要同步機制
5.方法區同堆一樣,是所有線程共享的內存區域,用于存儲已被虛擬機加載的類信息、常量、靜態變量,如static修飾的變量加載類的時候就被加載到方法區中。
二十.為什么要劃分成年輕代和老年代,年輕代為什么被劃分成eden、survivor區域
年輕代用來存放新近創建的對象,老年代中存放的對象是存活了很久的。
每次新生代的使用,會是eden區和一塊survivor區。當我們進行垃圾回收的時候,清除正在使用的區域,將其中的存貨對象,
放入到另一個survivor區域,并進行整理,保證空間的連續。如果對象長時間存活,則將對象移動到老年區
二十一.java動態代理和cglib動態代理的區別
java動態代理是利用反射機制生成一個實現代理接口的匿名類,在調用具體方法前調用InvokeHandler來處理。JDK動態代理只能對實現了接口的類生成代理,而不能針對類
cglib動態代理是利用asm開源包,對代理對象類的class文件加載進來,通過修改其字節碼生成子類來處理。CGLIB是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法
二十二.spring中bean的生命周期是怎樣的
- 實例化一個Bean,也就是我們通常說的new
- 按照Spring上下文對實例化的Bean進行配置,也就是IOC注入
- 如果這個Bean實現了BeanNameAware接口,會調用它實現的setBeanName(String beanId)方法,此處傳遞的是Spring配置文件中Bean的ID
- 如果這個Bean實現了BeanFactoryAware接口,會調用它實現的setBeanFactory(),傳遞的是Spring工廠本身(可以用這個方法獲取到其他Bean)
- 如果這個Bean實現了ApplicationContextAware接口,會調用setApplicationContext(ApplicationContext)方法,傳入Spring上下文,該方式同樣可以實現步驟4,但比4更好,以為ApplicationContext是BeanFactory的子接口,有更多的實現方法
- 如果這個Bean關聯了BeanPostProcessor接口,將會調用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor經常被用作是Bean內容的更改,并且由于這個是在Bean初始化結束時調用After方法,也可用于內存或緩存技術
- 如果這個Bean在Spring配置文件中配置了init-method屬性會自動調用其配置的初始化方法
- 如果這個Bean關聯了BeanPostProcessor接口,將會調用postAfterInitialization(Object obj, String s)方法
注意:以上工作完成以后就可以用這個Bean了,那這個Bean是一個single的,所以一般情況下我們調用同一個ID的Bean會是在內容地址相同的實例 - 當Bean不再需要時,會經過清理階段,如果Bean實現了DisposableBean接口,會調用其實現的destroy方法
- 最后,如果這個Bean的Spring配置中配置了destroy-method屬性,會自動調用其配置的銷毀方法
二十三.屬性注入和構造器注入哪種會有循環依賴的問題
Spring容器對構造函數配置Bean進行實例化有一個前提,即Bean構造函數入參引用的對象必須已經準備就緒。
由于這個機制,如果兩個Bean都相互引用,都采用構造函數注入方式,就會發生類似于線程死鎖的循環依賴問題。
可以指定一個bean設置懶加載屬性
二十四.redis性能為什么高
1.完全基于內存,絕大部分請求是純粹的內存操作
2.數據存在內存中,類似于HashMap,HashMap的優勢就是查找和操作的時間復雜度都是O(1)
3.采用單線程,避免了不必要的上下文切換和競爭條件,也不存在多進程或者多線程導致的切換而消耗 CPU
4.使用多路I/O復用模型,非阻塞IO。
二十五.單線程的redis如何利用多核cpu機器
redis的單一線程只能用到一個cpu核心,所以可以在同一個多核的服務器中,可以啟動多個實例,組成master-master或者master- slave的形式
二十六.redis的緩存淘汰策略
1.定時過期:每個設置過期時間的key都需要創建一個定時器,到過期時間就會立即清除。該策略可以立即清除過期的數據,對內存很友好;但是會占用大量的CPU資源去處理過期的數據
2.惰性過期:只有當訪問一個key時,才會判斷該key是否已過期,過期則清除。該策略可以最大化地節省CPU資源,卻對內存非常不友好。極端情況可能出現大量的過期key沒有再次被訪問,從而不會被清除,占用大量內存。
3.定期過期:每隔一定的時間,會掃描一定數量的數據庫的expires字典中一定數量的key,并清除其中已過期的key。expires字典會保存所有設置了過期時間的key的過期時間數據
二十七.有海量key和value都比較小的數據,在redis中如何存儲才更省內存
原理:通過大幅減少key的數量來降低內存的消耗
實現:hash,在客戶端通過分組將海量的key根據一定的策略映射到一組hash對象中
二十八.如何保證redis和DB中的數據一致性
1.讀的時候先訪問緩存,緩存沒有訪問數據庫,然后插入緩存
2.寫的時候先寫數據庫,再寫到緩存
二十九.如何解決緩存穿透和緩存雪崩
緩存穿透是指查詢一個一定不存在的數據,由于緩存不命中,接著查詢數據庫也無法查詢出結果,因此也不會寫入到緩存中,這將會導致每個查詢都會去請求數據庫,造成緩存穿透
解決:當存儲層不命中后,即使返回的空對象也將其緩存起來,同時會設置一個過期時間,之后再訪問這個數據將會從緩存中獲取,保護了后端數據源;
緩存雪崩是指,由于緩存層承載著大量請求,有效的保護了存儲層,但是如果緩存層由于某些原因整體不能提供服務,于是所有的請求都會達到存儲層,存儲層的調用量會暴增,造成存儲層也會掛掉的情況。
解決:使用redis集群部署;對緩存數據設置過期時間增加隨機因子,防止緩存同時集體失效
三十.redis如何實現分布式鎖
1.分布式鎖需要解決的問題
互斥性:任意時刻只能有一個客戶端擁有鎖,不能同時多個客戶端獲取
安全性:鎖只能被持有該鎖的用戶刪除,而不能被其他用戶刪除
死鎖:獲取鎖的客戶端因為某些原因而宕機,而未能釋放鎖,其他客戶端無法獲取此鎖,需要有機制來避免該類問題的發生
容錯:當部分節點宕機,客戶端仍能獲取鎖或者釋放鎖
2.通過setNX,并設置失效時間,在finally塊釋放鎖
三十一.Mysql(innondb) 有哪幾種事務隔離級別
1.read uncommitted(讀取未提交數據):即便是事務沒有commit,但是我們仍然能讀到未提交的數據,級別最低,造成臟讀
2.read committed(可以讀取其他事務提交的數據):當前會話只能讀取到其他事務提交的數據,未提交的數據讀不到。會造成不可重復讀
3.repeatable read(可重讀)---MySQL默認的隔離級別:當前會話可以重復讀,就是每次讀取的結果集都相同,而不管其他事務有沒有提交。會造成幻讀
4.serializable(串行化):其他會話對該表的寫操作將被掛起,隔離級別中最嚴格的,但是這樣做勢必對性能造成影響
三十二.mysql的行鎖、表鎖、間隙鎖、意向鎖分別是做什么的
行鎖:mysql的行鎖是通過索引加載的,即是行鎖是加在索引響應的行上的,要是對應的SQL語句沒有走索引,則會全表掃描
表鎖:就是對沒有索引字段上進行事務操作,會導致表鎖
間隙鎖:當我們用范圍條件而不是相等條件檢索數據,InnoDB會給符合條件的已有數據記錄的索引項加鎖;對于鍵值在條件范圍內并不存在的記錄,叫做間隙
意向鎖:意向鎖產生的主要目的是為了處理行鎖和表鎖之間的沖突
三十三.mysql的讀鎖和寫鎖
MySQL的表級鎖有兩種模式:讀鎖和寫鎖。讀的時候可以讀,讀的時候不能寫,寫的時候不能讀,寫的時候不能寫(讀就是共享讀,其他的可以讀,不能寫,寫是獨立寫,其他的不能讀也不能寫)
三十四.mysql的最左索引匹配原則
最左優先,以最左邊的為起點任何連續的索引都能匹配上。同時遇到范圍查詢(>、<、between、like)就會停止匹配。
三十五.mysql如何優化慢查詢
1.索引沒起作用的情況:
在使用LIKE關鍵字進行查詢的查詢語句中,如果匹配字符串的第一個字符為“%”,索引不會起作用。只有“%”不在第一個位置索引才會起作用。
滿足最左匹配原則
2.優化數據庫結構
將字段很多的表分解成多個表
對于需要經常聯合查詢的表,可以建立中間表以提高查詢效率
3.分解關聯查詢
將一個大的查詢分解為多個小查詢是很有必要的。
4.優化LIMIT分頁
select id,title from collect limit 90000,10; -> select id,title from collect where id>=(select id from collect order by id limit 90000,1) limit 10;
三十六.分庫分表如何選擇分表鍵
1.通過redis維護當前最大id返回
2.可以通過設置數據庫 sequence 或者表的自增字段步長來進行水平伸縮。每個服務的初始ID不同
3.UUID,因為其無序性,導致索引B+樹不能連續維護索引,降低效率
4.當前系統時間戳,高并發情況下不理想
5.snowflake 算法:snowflake 算法是 twitter 開源的分布式 id 生成算法,采用Scala語言實現,是把一個64位的long型的id,1個 bit是不用的+用其中的41bit作為毫秒數+用10bit作為工作機器id+12bi作為序列號。
三十七.分庫分表的情況下,查詢時一般是如何做排序的
借鑒三十六.5