Java 的一些進階知識,以及一些經常使用示例。java
Jvm 是個虛擬機,主要由:類裝載系統、執行引擎、垃圾回收器組成。c++
執行引擎,是Java跨平臺的核心,負責虛擬機與OS的指令交互工做。算法
類加載的過程主要包括:加載、驗證、準備、解析、初始化等階段。sql
1~4 都是 c++ 程序在執行,5爲java執行程序。編程
Java 除頂層加載器以外,類加載器是一種子->父的單向循環過程。
主要是爲了保證類不會被加載第二次,相似於一種層級的環境變量。數組
類加載器的主要過程就是重複以下操做:
先到當前class查詢緩存是否存在此類,存在則返回,不存在將加載請求拋給父類。緩存
當頂層類依然不存在此類的時候:安全
// 自定義類加載器--> 遵循雙親委派原則 通常都用這個 public class ExpLoder extends ClassLoader{ // 頂層找不到類的時候,會來這裏 @Override protected Class<?> findClass(String name){ return null; } // 將字節流轉化爲,jvm可識別的文件 @Override protected final Class<?> defineClass(byte[] b,int off,int len){ return defineClass(null, b, off, len, null); } } // 自定義類加載器 --> 不遵循雙親委派原則 public class ExpLoder extends ClassLoder{ // 重寫這個方法會直接破壞雙親原則 @Override protected Class<?> loadClass(String name,boolean resolve){ //... } }
自己是併發帶來的問題,多個線程各自走雙親委派原則,致使類加載不一致。
因此線程上下文類加載,並不遵循雙親委派原則,而是直接給主線程去加載了。數據結構
Java 對象執行完畢以後,清除無用空間的操做,這裏指堆空間。多線程
新生代:每一個週期都有大量空間失效,因此用複製算法。
Eden 區:存儲 new 的對象,不少對象會當場去世。
from 區:存儲從 Eden 區存活下的對象。
to 區:存儲上 from 區活蝦的對象。
- 當 Eden區滿了,則開啓 GC,執行一次週期。
- 再將 to 區存活對象,移動至老年代。
- 再將 from 區存活對象,移動至 to 區域。
- 先將 Eden 區存對象,移動至 from 區,再清除 Eden 區。
老年代:相對而言,每一個週期少許空間失效,通常用 標記-整理,或標記-清楚。
空間滿了,則開啓GC,根據算法不一樣選擇不一樣的策略。
元空間,之前叫永久代,受限於本地內存。
string 常量區就在這裏,相似於方法區這個東西。
串行收集器 serial
串行收集器會中止全部線程,簡單且高效,適用於單線程、C/S的client端。
新生代-複製,老年代-標記整理。
ParNew
自己就是串行收集器的多線程版本,沒啥差異,中止全部線程,簡單高效。
CMS
CMS 是一種基本的以空間換時間的收集器,追求最短響應時間。
- 初始標記:中止全部線程,標記失效空間。
- 併發標記:不會中止任何線程,而是開新線程去獲取失效空間地址。
- 從新標記:中止全部線程,將併發標記結果寫入標記集合。
- 併發清除:不會中止任何線程,開新線程清除失效的空間。
CMS 會致使吞吐量降低,部分浮動垃圾沒法清除,且碎片空間太多了。
G1
G1 是對 CMS 的一種改進版本。
- 初始標記:短期中止全部線程,標記失效空間。
- 併發標記:不會中止任何線程,開新線程獲取失效空間地址。
- 最終標記:停不停線程都行,將併發標記結果寫入標記集合。
- 篩選回收:停不停線程均可以,開新線程標記-總體失效空間。
G1 相對於 CMS主要是,線程停頓時間可控,且採用標記-整理。
G1 垃圾回收的性能由,線程停頓時間控制。
Jvm 內存結構,內存模型。
Jvm 內存結構主要分爲兩大部分。
進程區,亦稱爲線程共享區,全部線程均可訪問。
線程區,亦稱線程私有區,僅線程自身能夠訪問。
自己就是一個緩存模型,由主內存,工做內存,棧內存構成。
JMM通訊模型:files/jmm.png。
被 volatile 的變量的全部讀寫都是在主存內執行的,且會全部操做會加鎖。
問題所在
當下 cpu 通常都存在多級緩存,其查找變量的時候,先在一級緩存中尋找,繼續在二級緩存之中尋找,如此類推。雖然緩存有不少,但內存終究就只有一個。多級緩存會帶來一個數據不一致的問題。
在不一樣 cpu 執行的不一樣線程對同一個變量的緩存值不一樣,爲了解決這個問題。
硬件協議
通常爲解決,多級緩存,以及多線程緩存不一致的問題,從 cpu 自己出發的 mesi 協議,可解決緩存不一致的問題。
讀屏障:在指令前插入讀屏障,可讓高速緩存中的數據失效,強制從主內存取。
Jvm內存屏障
Jvm是在代碼指令中添加彙編指令,使用cpu的內存屏障協議保證數據的一致性。
1.阻止屏障兩側指令重排序,(Jvm有內存優化的)。
2.強制把寫緩衝區/高速緩存中的髒數據等寫回主內存,讓緩存中相應的數據失效。
併發的主要依靠多線程來解決,在這個過程當中須要鎖來維持數據一致性。
鎖主要的做用是保證線程安全,但會影響性能。
獨佔鎖:即爲寫鎖,亦稱互斥鎖,最多隻能有一個線程獲取此類鎖。
共享鎖:即爲讀鎖,存在寫操做時,只能有一個線程可獲取鎖;僅存在讀操做時,可存在多個線程同時獲取鎖。
悲觀鎖:默認會發生衝突,因此任何操做都加鎖。
樂觀鎖:默認發生錯誤較少,因此僅在寫操做時加鎖。
公平鎖:內部順序獲取鎖,不會產生飢餓,但效率較低。
非公平鎖:內部會根據規則搶鎖,會產生飢餓,但效率高。
亦稱遞歸鎖,鎖是以現稱爲單位獲取獲釋放的。
當線程中某一部分獲取鎖後,至關於整個線程獲取到了鎖。
主要是保證了同一個鎖,不會由於相同線程的爭搶而致使發生死鎖。
當線程獲取鎖被阻塞時,輪詢獲取鎖。
優勢是避免程序切換上下文,缺點是消耗cpu。
全部被 synchronized 鎖住的都是引用類型。
全部被 lock 鎖住的都是代碼塊。
悲觀鎖,非公平鎖,重入鎖。
只有類鎖,對象鎖,成員鎖這三種引用鎖,且是虛擬機級別的。任何一個類,對象,成員都有一個內置鎖,亦稱監聽器 monitor。
是一個互斥鎖,即同時刻只能有一個線程得到某一級別的鎖,其餘線程阻塞。
但不一樣線程或相同線程可得到不一樣級別的鎖,即爲重入鎖。被synchronized修飾的代碼都會根據條件獲取指定鎖,其餘線程只能等鎖釋放
public class Example{ static int k = 10; public int m = 10; public void show(){ //... } synchronized static public void sh(){ //... } synchronized public void show(){ //... } } // 測試對象 Example exp = new Example(); // 同步 類,靜態變量 或調用靜態同步方法時,會加類鎖。 synchronized(Example.class){}; synchronized(exp.k){}; sh(); // 同步對象,則加對象鎖。 synchronized(exp){} // 同步成員變量,或調用同步成員函數時,加成員鎖。 synchronized(exp.m){} show();
悲觀鎖,默認非公平鎖,可設定爲公平鎖,可重入鎖,。
核心爲 AbstractQueueSynchronzer,AQS基於FIFO雙向隊列實現。
// 此時爲,非公平鎖 ReentrantLock lock = new ReentrantLock(); // 此時爲,公平鎖 ReentrantLock lock = new ReentrantLock(true);
相同之處:都是悲觀鎖,且都是可重入鎖,都是阻塞式鎖。
類型 | 加鎖範圍 | 公平鎖 | 級別 | 鎖問題 |
---|---|---|---|---|
synchronized | 類,對象,成員 | 非公平鎖 | 虛擬機級別 | 寫鎖 |
ReentrantLock | 代碼區 | 公平鎖,非公平所均可以 | 線程級別 | 可設定讀寫鎖 |
volatile 亦是一種線程同步的方式,主要是線程通訊。
synchronized 由一對 monitorenter/moniterexit 字節碼指令實現。
java 鎖競爭的基本原則,會根據金證程度來使用權限。
偏向鎖->輕量級鎖->重量級鎖。
多線程是提升併發量、吞吐量的主要手段。
線程同步的方式,可根據消耗按照以下三種方式排序。
// 利用 volatile 同步線程 public class Example implements Runnable{ // 被 volatile 修飾的變量,寫操做後必須刷新變量的值 static volatile int k = 20; @Override public void run(){ while(true){ System.out.println(Thread.currentThread().getName()+":"+k--); try{ Thread.currentThread().sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } } } } // 利用 synchronized 同步數據 public class Example implements Runnable{ static int k = 20; @Override public void run(){ while(true){ // 成員鎖,僅限定這個成員的訪問 show(); try{ Thread.currentThread().sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } } } // 同步方法 synchronized private void show(){ System.out.println(Thread.currentThread().getName()+":"+k--); } } import java.util.concurrent.locks.ReentrantLock; // 併發量很大的時候,用 ReentrantLock 鎖同步代碼區 public class Example implements Runnable{ // 同步測試 static int k = 20; // 同一時間,被 lock 的代碼區,只能由一個線程來執行 Lock lock = new ReentrantLock(); @Override public void run(){ while(true){ try{ // 同步鎖開始 lock.lock(); // 輸當前線程名稱,k的值,以後再 k-- System.out.println(Thread.currentThread().getName()+":"+k--); }catch(Exception e){ e.printStackTrace(); }finally{ // 同步鎖結尾 // 放在這裏是爲了防止發生異常後直接鎖死整個線程組 lock.unlock(); try{ // try catch 塊 真他媽是個讓人頭疼的東西。 Thread.currentThread().sleep(1000); }catch(InterruptedException d){ d.printStackTrace(); } } } } } Example exp = new Example(); // 併發執行多線程,同步數據。 new Thread(exp,"Thread-A").start(); new Thread(exp,"Thread-B").start(); new Thread(exp,"Thread-C").start(); new Thread(exp,"Thread-D").start();
多線程併發除了可能會影響數據一致性以外,亦會因線程自己而引起異常。
線程池就是首先建立一些線程,它們的集合稱爲線程池。
線程池能夠很好地提升性能,線程池在系統啓動時即建立大量空閒的線程。
程序將任務傳給線程池,線程池就會啓動一條線程來執行這個任務。
執行結束之後,該線程並不會死亡,而是再次返回線程池中成爲空閒狀態,等待執行下一個任務。
在線程池的編程模式下,任務是提交給整個線程池,而不是直接提交給某個線程。
線程池在拿到任務後,就在內部尋找是否有空閒的線程,若是有,則將任務交給某個空閒的線程。
一個線程同時只能執行一個任務,但能夠同時向一個線程池提交多個任務。
線程池管理接口。
一般用於執行生存週期較短的異步任務。
當任務來的時候,會檢測是否有被緩存的線程,有則用緩存的線程,無則新增線程。
全部正在執行的線程都是核心線程,全部被緩存的線程都是非核心線程。
全部被緩存的線程都有超時機制,超過 60秒沒有被引用,線程會被回收。
// 初始化線程池,此時線程池內無線程,以後新增的線程都會被緩存,緩存後可複用 ExecutorService cache = Executors.newCachedThreadPool(); for(int i=0; i<10; i++){ Thread.sleep(1000); // 新增線程-->至線程池中 cache.execute(new Runnable(){ @Override public void run(){ System.out.println(Thread.currentThread.getName()+"-running"); try{ Thread.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } } }); } // 雖然循環 10 次,但實際只有 2 個線程。
內部擁有固定數量的可重用線程。
全部線程都是核心線程,不會被回收,任務超過線程上限則進入隊列等待。
// 獲取系統核數,利用這個建立固定大小的線程池 int corenum = Runtime.getRuntime().availableProcessors(); // 建立 固定數量線程的線程池 ExecutorService fix = Executors.newFixedThreadPool(corenum); for(int i=0; i<100; i++){ fix.execute(new Runnable(){ @Override public void run(){ System.out.println(Thread.currentThread.getName()+"-running"); try{ Thread.sleep(1000); }catch(TnterruptedException e){ e.printStackTrace(); } } }); }
通常爲固定數量的可重用線程,外加額外線程,且線程都是週期性執行任務。
其核心線程數量是固定的,空閒亦不會被回收,而非核心線程執行完畢則當即回收。
// 建立固定數量的線程池 ScheduledExecutorService sched = Executors.newScheduledThreadPool(5); for(int i = 0; i<5; i++) { sched.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println("延遲執行" + Thread.currentThread().getName()); } }, 5, 1, TimeUnit.SECONDS); } // 此處含義爲,建立任務後 5 秒以後開始週期性執行,每一個週期間隔 1 秒
內部只有一個線程,且全部任務會按照 FIFO 順序執行。
內部只有一個核心線程,空閒亦不會被回收。
// 內部只有一個線程 ExecutorService single = Executors.newSingleThreadExecutor(); for(int i = 0;i < 10;i++){ // 任務執行順序不會改變 single.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"-running"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); }
其上所展現的四種線程池都來自這個類,只不過參數被肯定了。
類型 | 建立 | 核心線程 |
---|---|---|
緩存線程池 | newCachedThreadPool() | 核心線程與非核心線程動態變化 |
固定線程池 | newFixedThreadPool(int n) | 全部線程都是核心線程 |
單線程線程池 | newSingleThreadExecutor() | 只有一個核心線程 |
週期性線程池 | newScheduledThreadPool(int n) | 固定的核心線程,動態變化的非核心線程 |
// 線程池原始定義方法 public ThreadPoolExecutor( // 核心線程數量 int corePoolSize, // 最大線程數量,當核心線程滿了以後,會查詢這個值來肯定是否新增線程 int maximumPoolSize, // 線程總數大於核心線程數量時,多餘空閒線程最多空閒時間,超時則釋放資源 long keepAliveTime, // keepAliveTime 時間單位 TimeUnit unit, // 超出核心線程時,多餘任務的存儲位置 BlockingQueue<Runnable> workQueue, // 建立新線程時使用的工廠 ThreadFactory threadFactory, // 當線程池達到上限之時,所要執行的策略 // 當任務隊列達到最大值被阻塞,且總線程數量達到最大值時,即爲上限 RejectedExecutionHandler handler ) { //... }
線程總數 < 核心線程:新開核心線程,任務進入線程。
線程總數 = 核心線程:若存在空閒的核心線程,任務進入線程,不然進入隊列。
隊列到達上限,線程總數 < 線程最大值:新增非核心線程,任務進入隊列。
隊列到達上限,線程總數 = 線程最大值:執行飽和策略。
// 此處主要是來存儲,超出執行的任務 BlockingQueue<Runnable> task = null; // 內部是數組,數組必有界 task = new ArrayBlockingQueue<>(100); // 內部是鏈表,鏈表內存是動態化的,但也可設置上限,即有界、無界皆可 task = new LinkedBlockingQueue<>(); // 無緩衝的隊列,無界 task = new SynchronousQueue<>(); // 任務到達上限時所要執行的 飽和策略 RejectedExecutionHandler rejection = null; // 忽略新增任務,新增則拋異常,亦爲默認值 rejection = new ThreadPoolExecutor.AbortPolicy(); // 忽略新增任務,新增不會拋異常 rejection = new ThreadPoolExecutor.DiscardPolicy(); // FIFO,刪除隊列頭部任務,以後繼續新增 rejection = new ThreadPoolExecutor.DiscardOldestPolicy(); // 新增任務失敗後,主線程本身去執行,不要用這玩意兒 // 由於新增任務都須要主線程,纔可進入隊列或直接進入線程 // 且隊列內的任務,都須要主線程分配給各個線程纔可執行 rejection = new ThreadPoolExecutor.CallerRunsPolicy();
BlockingQueue<Runnable> task = new SynchronousQueue<Runnable>(); RejectedExecutionHandler rej = new ThreadPoolExecutor.AbortPolicy(); ThreadFactory th = Executors.defaultThreadFactory(); // 核心線程池 ExecutorService core = new ThreadPoolExecutor(6,18,60,TimeUnit.SECONDS,task,th,rej); /* * 線程池 * execute 實現 Runnable接口建立任務 */ core.execute(new Runnable(){ @Override public void run(){ System.out.println("Runnable--execute"); } }); Future<?> ex = core.submit(new Runnable(){ @Override public void run(){ System.out.println("running"); System.out.println(0/0); } }); /* * 線程池 * submit 實現 Callable<Object> 接口建立任務,存在返回值 * Future 類能夠獲取,submit 任務的返回值,但會阻塞至任務結束。 * Future submit Ruuuable 能獲取到的只有異常,或null * Future submit Callable 能夠獲取到異常以及程序返回值 */ Future<?> res = core.submit(new Callable<Integer>(){ @Override public Integer call(){ System.out.println("Runnable-submit"); return 1; } }); // 此處會阻塞至,res執行完畢,纔會有結果 System.out.println(res.get());
Java 全部線程池中的任務發生異常後,整個線程會被回收。
// 新建線程池 ExecutorService core = Executors.newFixedThreadPool(3); // execute Runnable 系列沒法捕獲異常,線程會被回收 core.execute(new Runnable{ @Override public void run(){ System.out.println("runnable -- Exception"); // 此處會發生異常 System.out.println(10/0); } }); // submit Runnnable 可捕獲異常,線程依然會被回收 Future exp = core.submit(new Runnable(){ @Override public void run(){ System.out.println("runnable -- Exception"); // 此處會發生異常 System.out.println(10/0); } }); // 此處能夠捕獲線程執行發生的異常 try{ System.out.println("exception"+exp.get()); }catch(Exception e){ e.printStackTrace(); }
Future 是面向任務的一種類,可控制當前任務。
submit 通常配合 Future 使用。
Callable<?>存在返回值,可拋出異常,僅在線程池中使用。
// 新建線程池 ExecutorService core = Executors.newFixedThreadPool(3); /* * Future submit callable 系列可控制一個完成的線程生命 */ Future res = core.submit(new Callable<Integer>{ // callable 通常僅在線程池中使用 @Override public Integer call(){ System.out.println("submit future"); return 1; } }) // Future 可獲取當前任務的全部信息 System.out.println(res); /* * Future 類亦能夠中途取消執行任務 * true 可強行中斷,false 不可強行中斷 * 返回值,取消結果 */ Boolean ok = res.cancel(true); /* * Future 可獲取線程執行結果 * Future 可捕獲,任務線程執行時發生的異常 */ try{ // res.get() 便可獲取線程執行結果,阻塞式獲取 System.out.println(res.get()); }catch(Exception e){ e.printStackTrace(); }
ThreadLocal,線程級別的HashMap,存儲線程局部變量。
主要是一些線程安全的數據結構。
Synchronized:全部被修飾的變量都是線程安全的。
StringBuffer:線程安全的字符串結構。
BlockQueue:線程安全的隊列。
Vector:線程安全的數據結構。
Collections.synchronizedList:將列表轉化爲線程安全的,但內部鎖粒度過大。
ConcurrentHashMap:內部是分段鎖,不會把整個表鎖起來。
SynchronizedMap/Hashtable:線程安全,但鎖粒度太大。
主要介紹常見的IO知識點。
主要分爲字節流、字符流。
字節流:即爲二進制數據流。
字符流:即爲char[]流,通常須要字符編碼格式,默認utf-8。
同步:一種可靠的時序操做,執行操做後,後續任務須要等待。
異步:執行操做後,後續任務無需等待,通常經過事件、回調等方式實現。阻塞:這是線程的狀態,僅當條件容許時,線程會進入就緒狀態等待執行。
非阻塞:無論 IO是否結束,該返回直接返回。
IO,亦稱BIO,是一種同步且阻塞的IO方式,默認IO都是這個模型。
NIO 是一種同步非阻塞的IO方式。
AIO 是一種異步非組賽的IO方式。
BIO:讀寫時,會阻塞線程,只有操做執行完畢時纔會返回結果。
NIO:讀寫時,不會阻塞線程,只有操做執行完畢時纔會返回結果。
AIO:讀寫時,不會阻塞線程,執行結果會分段返回。
主要是一些類的經常使用示例。
主要介紹下 Java-8的一些特色。
1.語法糖
// 語法糖,本質上源代碼沒變,只是減小了代碼量 // lambda 系列 new Thread(()->{System.out.println("lambda");}).start(); // lambda 若一行能夠寫完,則無需; new Thread(()->System.out.println("lambda")).start();
2.接口默認方法
// 接口內部能夠存在默認方法,且能夠被重寫 public interface Exp{ default void show(){ System.out.println("interface-default-function"); } }
3.函數式接口
函數式接口,就是一個接口默認一個方法,能夠即時定義使用。
調用形式有一種,面向切面編程的思想。
// 函數式接口是一種特殊的接口 // 必須被 FunctionalInterface 註解標註,且內部只能存在一個方法 @FunctionalInterface public interface Exp{ public void show(); } // 定義 接口內的 show 方法 Exp exp = () -> System.out.println("Function interface"); exp.show(); @FunctionalInterface public interface Example{ public int sh(int k); } // 不一樣的函數接口,就是不一樣的切面,且此處 return 可省略 Example example = (x) -> x++ ; Example exp = (x) -> x*10; System.out.println(example.sh(10)+"-"+exp.sh(10)); // java8 自帶一些函數式接口 Consumer<String> ex = (x) -> System.out.println(x); ex.accept("hello"); Suppiler<String> ex = () -> "hello"; System.out.println(ex.get()); // 這裏就是動態代理 public void proxyFun(Consumer<String> consumer,String msg){ consumer.accept(msg); } // 函數式編程模式 proxyFun((x)->System.out.println(x),"123465");
4.時間API
// 新增一組時間API Clock clock = Clock.systemDefaultZone(); System.out.println(clock.millis()); //... Date date = Date.from(clock.instance());
5.Stream
增長了部分相似,sql語法的方法。
6.多重註解
容許對註解單位加多重註解,面向多切面編程。
此下的方法適用於Number的全部子類。
// 將 string 解析爲 Number Integer.parseInt("123"); // 將 Number 轉化爲 String Integer.toString(123); // 比較兩個 Number 的大小,相等 0 ,小於 -1,大於 1 Integer.compare(1,2);
此下主要包括一些經常使用的計算方法。
// Number 求絕對值 Math.abs(-1000); // Number 四捨五入 Math.round(); // Number 四捨五入 保留一位小數 Math.ceil(); // Math.max(); Math.min(); // ...
String 是最爲通用的類型。
char 類型能夠看做是 int 類型。
// char 類型能夠看做特殊的類型 char a = 100; // 將 char 轉化爲長度爲一的string Character.toString(a);
String 是一個特殊的類型,實際是個常量,只要改變至關於一次新建。
// 將 Number 類型轉化爲 String String p = String.valueOf(123); String p = "abc"; // p 轉大寫 p = p.toUpperCase(); String p = "ABC"; // p 轉小寫 p = p.toLowerCase(); // 相等爲 0,adc-abd -1,abc-abb 1 p.compareTo("ABD"); // 比較 String 是否相同 "sd".equals("sd"); // 比較 String 是否相同,忽略大小寫 "SD".equalsIgnoreCase("sd"); // ...
常量池中的變量都是 final,即不可更改的變量,自己是一個char數組。
String p = "hellow";
此種方式建立string的時候,先到常量池中搜索,存在相同的string則直接返回引用,不然在常量池之中建立相應的 string,並返回引用。
String p = new String("hellow");
此種方式建立string的時候,先在常量池中搜索,若存在則直接返回引用,不然在堆區建立相應的對象。
// 此種方式建立的string即是在常量池中間的變量 String p = "hellow"; // p q 都指向常量池中的一個變量 String q = "hellow";
不一樣於 String 類型,其自己是對象,任何操做都是針對對象自己,不會出現瘋狂召喚GC的狀況。
時間類型是最爲基本的經常使用類型。
// 獲取時間對象 Date date = new Date(); // 這裏獲取的是一個完整的時間對象,通常用不到 Calendar calendar = Calendar.getInstance(); // 這裏獲取的就只是時間對象 Date cal = Calendar.getInstance().getTime(); // 時間戳 13 位 date.getTime(); Calendar.getInstance().getTimeInMillis(); System.currentTimeMillis(); // 時間格式化類型 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 格式化時間,Date對象便可 sdf.format(date); sdf.format(cal);
Java 自己分爲字符流、字節流,以下爲基本的抽象類。
流類型 | in | out |
---|---|---|
字節流 | InputStream | OutputStream |
字符流 | Reader | Writer |
// 常見流 FileInputStream input; FileOutputStream output; FileReader reader; FileWriter writer; //序列化的流 FileOutputStream out = new FileOutputStream("./exp.txt"); ObjectOutputStream obj = new ObjectOutputStream(out); obj.write(new Object()); obj.close(); out.close(); // 反序列化的流 FileInputStream in = new FileInputStream("./exp.txt"); ObjectInputStream obj = new ObjectInputStream(in); Object exp = obj.readObject(); obj.close(); in.close(); // IOException
集合類型是一種經常使用數據結構類型,動態存儲空間,且面向對象設計。
Collection接口採用線性列表的方式存儲數據。
List接口用於存儲有序,可重複的數據。
/* * ArrayList<?> 實現類 * 優勢:實際結構是動態數組,查詢快,增刪慢。 * 缺點:線程不安全。 */ ArrayList<String> list = new ArrayList<String>(); /* * Vector<?> 實現類 * 優勢:實際結構是動態數組,查詢快,增刪慢。 * 缺點:線程安全。 */ Vector<String> vector = new Vector<String>(); /* * LinkedList<?> 實現類 * 優勢:實際結構是鏈表,查詢慢,增刪快。 * 缺點:線程不安全。 */ LinkedList<String> linklist = new LinkedList<String>(); /* * Collections.synchronizedList(),靜態化方法 * 此方法的做用是將,任何基於List接口實現的類轉化爲線程安全的類。 */ List safelist = Collections.synchronizedList(list); // 新增 移除 索引 ...
Set接口用於不可重複的數據。
/* * HashSet<?> 實體類 * 內部就是個 hash表,依賴 hashCode(),equals()方法保證惟一性。 * 主要存儲 無序,不重複的數據。 */ HashSet<String> set = new HashSet<String>(); /* * LinkedHashSet<?> 實體類 * 內部是一個由 hash表,和鏈表結構組成。 * 主要存儲 有序,不可重複的數據。 */ LinkedHashSet<String> linkset = new LinkedHashSet<String>(); /* * TreeSet<?> 實體類 * 內部就是大名鼎鼎的紅黑樹。 * 主要存儲 有序,不可重複的數據。 */ TreeSet<String> tree = new TreeSet<String>();
Queue接口提供阻塞式,以及非阻塞式隊列。
/* * BlockingQueue<?> 接口 * 這個接口下的隊列都是阻塞式的。 */ BlockingQueue blockqueue; /* * ConcurrentLinkedQueue<?> 實體類 * 這個類提供非阻塞式的隊列。 */ ConcurrentLinkedQueue<String> q = new ConcurrentLinkedQueue<String>();
Map 接口採用鍵值對映射的方式存儲數據。
/* * HashMap<?,?> 實體類 * 內部就是 hash表。 */ HashMap<String,String> map = HashMap<String,String>(); /* * LinkedHashMap<?,?> 實體類 * 內部鍵值映射爲 hash表,外部鍵排序去重爲鏈表。 * 是一個鍵有序的,hash表。 */ LinkedHashMap<String,String> m = new LinkedHashMap<String,String>();
java 線程,其優勢在於併發,缺點在於相較於協程其更復雜且佔用資源較大。
Runnable 接口核心在於,執行線程資源申請,並在新資源中間執行線程。
Runnable 接口內僅定義 run方法,線程管理須要手動定義。
// Runnable 接口實現過程 public class Example implements Runnable{ // 線程核心方法 @Override public void run(){ while(true){ //... } } } // 實例化對象,內部沒有定義線程管理 Example exp = new Example(); // 沒有線程管理策略,僅能直接打開線程,其餘操做作不了 exp.run(); // Runnable 接口,管理 public class Demo implements Runnable{ private boolean flag = false; public Demo(){ //... } // 線程核心方法 @Override public void run(){ while(true){ //... if(this.flag){ break; } } } // 此時打開線程 public void start(){ this.run(); } // 此時會直接退出線程而並不是掛起線程 public void stop(){ this.flag = true; } } // 實例化對象,Demo內部實現了部分線程管理 Demo demo = new Demo(); // 開線程 demo.start(); // 關閉線程,線程處於死亡狀態 demo.stop();
Thread 類通常須要重寫 run()方法來使用。
public class Example extends Thread{ @Override public void run(){ //... } } // Thread 類自帶線程管理方法 new Example().start(); public class Demo extends Runnable{ @Override public void run(){ //... try{ // Thread.currentThread(),靜態方法獲取當前線程引用 Thread.currentThread().sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } } } Demo demo = new Demo(); // 利用 Thread類以及實現Runnable接口的類的對象建立線程 // 線程能夠命名,也不命名 Thread th = new Thread(demo,"thread-1"); // 建立線程,命名線程,開啓線程 new Thread(demo).start(); // 簡寫開線程,name字段能夠不加 new Thread( new Runnable(){ @Override public void run(){ //... } } ).start(); // lambda 簡寫建立線程 new Thread(() -> { while(true){System.out.println(123);} }).start();
一些特殊的類。
Hash 亦稱哈希,是一種將任意字符串轉化爲定長字符串,本質是一種壓縮映射。
Hash 亦屬於數字簽名,或稱信息摘要算法中的一個。
Object obj = new Object(); // java 中任何一個對象都有其相應的 hash 值。 obj.hashCode();
Map 是一種 key:value 鍵值對形式的對象。
1.底層原理
默認是一個數組,且數組內部可嵌套其餘數據結構。
// 1. 獲取 hashCode() int hash = obj.hashCode(); // 2. 用數組長度對 hash 取模運算,主要是獲取數組下標 int i = hash%len; // 或執行與運算,數組長度要減 1,現版是這個,但效果和取模同樣 int i = hash&(len-1); // 3. 根據 i 存入數組內部,此處是鏈表 // 當後續新的對象已被存到這裏時,加入鏈表便可 // 當鏈表長度超 8,則變爲紅黑樹,否則查詢太慢 // 當紅黑樹實際元素少於 6,則退化爲鏈表 // 4. 獲取對象到這裏時,利用 equals 方法便可
2.哈希碰撞與擴容
哈希碰撞即爲兩個對象哈希值相同而產生的異常。 HashMap 數組默認 16,鍵值對 64。 超過最大閾值 75%,則擴容,空間大一倍。