🌊 IO 流#
輸入輸出
四大抽象類#
- 按照處理數據類型
- 字節流
- 字符流
- 按數據流向
- 輸入流 向內存中輸入
- 輸出流 從內存中輸出
- 按功能不同
- 節點流
直接操作數據流 - 處理流
對其他流進行處理
- 節點流
📥 InputStream#
FileInputStream#
public FileInputStream(String)
打開輸入流連接 需要傳入文件路徑並拋出異常public int reader()
讀取下一個字節並返回對應的 ASCII 值,如果到達文件未尾返回-1
📤 OutputStream#
FileOutputStream#
public FileOutputStream(String)
打開流連接 需要傳入文件路徑並拋出異常 覆蓋寫入public FileOutputStream(String,Boolean)
打開流連接 需要傳入文件路徑並拋出異常 為true
時是追加寫入public void write()
public void write(byte[])
public void write(byte[],int,int)
public void flush()
public void append()
📖 Reader#
FileReader#
字符輸入流 每次讀取一個字符,可以避免漢字亂碼問題主要適用於純文本文件
✍️ Writer#
FileWriter#
public void write()
public void write(char[])
public void write(char[],int,int)
public void write(String)
public void write(String,int,int)
public void flush()
public void append()
🔌 節點處理流#
需要傳入節點流(FileInputStream、FileOutputStream、FileReader、FileWriter)進行處理
🧺 緩衝流#
BufferedReader#
BufferedWriter#
BufferedInputStream#
public String readLine()
返回此次讀到的數據,如果到達文件未尾返回null
,不讀取換行符public int available()
該文件可獲取的字節數
BufferedOutputStream#
💫 轉換流#
將字節流轉換為字符流
🖨️ PrintStream#
打印流 為了方便操作提供了很多打印方法,用戶可以把數據傳遞進去直接打印
System
類中的 標準輸入輸出
in
:標準輸入,默認在控制台輸入
out
:標準輸出,默認打印在控制台
err
在 System
類中提供了三個重定向標準輸入 / 輸出的方法
static void setErr (PrintStream err) 重定向 “標準” 錯誤輸出流
static void setIn (PrintStream in) 重定向 “標準” 輸入流
static void setOut (PrintStream out) 重定向 “標準” 輸出流
FileOutputStream fos = new FileOutputStream("./src/test.txt");
// 包裝輸出流時輸出操作更便捷
// 字節打印流
PrintStream ps = new PrintStream(fos);
// System 中 默認的 out 是打印在控制台,但是可以修改打印路徑
System.setOut(ps);
System.out.println("===============");
特點#
- 打印流是輸出最方便的類
- 包含字節打印流
PrintStream
,字符打印流PrintWriter
PrintStream
是 OutputStream 的子類,把一個輸出流的實例傳遞到打印流之後,可以更加方便地輸出內容,相當於把輸出流重新包裝一下PrintStream
類的print()
方法被重載很多次print(int i)
、print(boolean b)
、print(char c)
PrintWriter#
字符打印流
public void println()
✍️ DataInputStream#
Linux Windows 等操作系統對數據進行存儲的方式不同
為了解決不同平台之間數據讀取的統一性
數據流 保證數據的一致性
📁 File#
public boolean isFile()
判斷是否是文件public boolean isDirectory()
判斷是否是目錄public boolean exists()
判斷是否存在public String getAbsolutePath()
獲取全路徑(包含文件名和後綴)public String getName()
獲取文件名 + 後綴 a.txtpublic String getParent()
獲取父目錄public File getParentFile()
獲取父目錄文件對象public File[] listFiles()
獲取子文件對象
📬 序列化、反序列化#
序列化:把對象保存在硬盤中,進行持久化存儲
反序列化:把持久化存儲的對象,載入內存中
目的:為了長期存儲某個對象,便於在網絡中進行傳遞
需要該類實現 Serializable
接口才能被序列化
指定版本號 可以使用serialVersionUID
屬性指定Serializable
的版本 以驗證加載的類和序列化的對象是否兼容
transient#
關鍵字
限制當前屬性不能被序列化,屬性值不會再被保存
🧵 多線程#
程序:一組命令的集合,為了完成指定的功能,程序是靜態概念,一般保存在硬盤當中
進程:正在運行的程序,是一個動態概念,需要保存在內存當中,操作系統會分配對應的 PID,當我們直接關閉某個進程的時候 該進行會在
線程:一個程序中,不同的執行分支,如果同一個時間節點允許多個線程同時執行的時候,我們稱為支持多線程
在 Java 中,main()
方法開始執行,就是一個線程,稱為主線程
並發 一個 CPU,同時執行多個任務
並行 多個 CPU,同時執行多個任務
繼承方式#
繼承 Thread
類 重寫 run()
方法 就等於是新線程的 main()
方法
實現方式#
實現 Runnable
類 重寫 run()
方法
繼承和實現的區別#
- 區別
- 繼承 Thread:線程代碼存放 Thread 子類 run 方法中。
- 實現 Runnable:線程代碼存在接口的子類的 run 方法。
- 實現方式的好處
- 避免了單繼承的局限性
- 多個線程可以共享同一個接口實現類的對象,非常適合多個相同線程來處理同一份資源。
注意
不能直接調用 run()
方法,否則只是方法調用而已並不會開啟新線程
優先級#
優先級默認為 5
public final int getPriority()
獲得線程的優先級數值public final void setPriority(int newPriority)
設置線程的優先級數值
方法#
public final synchronized void setName(String name)
設置線程名稱public final String getName()
獲取線程名稱 名字為 Thread-0 Thread-1 ...public final native boolean isAlive()
判斷線程是否還 “活” 著,即線程是否還未終止。public static native void sleep(long millis) throws InterruptedException
將當前線程睡眠指定毫秒數public final void stop()
停止該線程(不推薦),可能導致死鎖狀態,所以採用標識符終止public final void join() throws InterruptedException
使兩個線程合併public static native void yield();
讓出當前 CPU 時間片,讓其他線程去執行
如果拿到了 CPU 時間片,並且存在相同優先級的線程時,可以選擇讓位public static native Thread currentThread()
返回當前正在執行的線程對象的引用
⌛ 生命週期#
JDK 中用 Thread.state
類定義了線程的幾種狀態
五種狀態
- 新建
當一個 Thread 類或其子類的對象被聲明並創建時,新生的線程對象處於新建狀態 - 就緒
處於新建狀態的線程被 start () 後,將進入線程隊列等待 CPU 時間片,此時它已具備了運行的條件,只是沒分配到 CPU 資源 - 運行
當就緒的線程被調度並獲得 CPU 資源時,便進入運行狀態,run () 方法定義了線程的操作和功能 - 阻塞
- 在某種特殊情況下,被人為掛起或執行輸入輸出操作時,讓出 CPU 並臨時中止自己的執行,進入阻塞狀態
- 死亡
線程完成了它的全部工作或線程被提前強制性地中止或出現異常導致結束
🤝 線程同步#
多個線程執行的不確定性引起執行結果的不穩定
線程同步:當多個線程有可能同時操作同一個數據的時候,為了保證數據一致性,需要進行同步執行
本質是同步數據,是一種安全機制
異步編程:線程之間是完全獨立的,相互沒有影響
同步編程:線程之間不是完全獨立的,相互可能有影響
🔒 線程鎖#
方法鎖#
synchronized:方法加鎖
當某線程訪問某個對象中加 synchronized 修飾的成員方法時,則該對象中所有加 synchronized 修飾的成員方法全部鎖定
此時任何線程均不能訪問 synchronized 修飾的成員方法,需排隊當上一個線程執行完方法後交出鎖,此時其他線程才可以去訪問
缺點:效率低。需要排隊執行,並且整個對象中所有加鎖的成員方法全部鎖定
優點:數據的安全性和一致性有所保障,避免出現數據錯誤
🍰 語句塊鎖#
類鎖#
靜態也可以加鎖 稱為類鎖
所有加鎖的靜態方法和靜態語句塊鎖全部鎖定 Synchronized(類名.class){}
對象鎖#
成員 也可以加鎖,稱為對象鎖
該對象中,所有加鎖的成員方法和成員語句塊鎖全部鎖定 Synchronized(對象){}
(不同對象直接不會影響)
🔐 Lock 鎖#
- 從 JDK 5.0 開始,Java 提供了更強大的線程同步機制 —— 通過顯式定義同步鎖對象來實現同步。同步鎖使用 Lock 對象充當。
java.util.concurrent.locks.Lock
接口是控制多個線程對共享資源進行訪問的工具。鎖提供了對共享資源的獨占訪問,每次只能有一個線程對 Lock 對象 加鎖,線程開始訪問共享資源之前應先獲得 Lock 對象。- ReentrantLock 類實現了 Lock ,它擁有與 synchronized 相同的並發性和內存語義,在實現線程安全的控制中,比較常用的是 ReentrantLock,可以顯式加鎖、釋放鎖。
lock 是顯式鎖,需要手動開啟和關閉
synchronized 是隱式鎖,自動開啟,執行完自動關閉
lock 只有代碼塊鎖,而 synchronized 支持方法和代碼塊鎖
lock 鎖,需要 JVM 花費較少的時間來進行資源調度。性能相對較好,而有很好的擴展性
使用順序 : Lock 鎖 --> 同步代碼塊鎖 --> 方法鎖
⏱️ 定時器任務#
定時器:計劃任務
只要有一個計劃任務就會開啟一個線程,進行計時,到達指定時間後由該線程來完成這個任務
方法#
public void schedule(TimerTask task, long delay, long period)
public void schedule(TimerTask task, long delay)
public class MyTimerTest {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new MyTimer(), 1000, 500);
}
}
class MyTimer extends TimerTask {
@Override
public void run() {
System.out.println("run() 方法執行");
}
}
🦸 守護線程#
每個程序運行時,都会有一個守護線程同步啟動,用於監聽我們的正常程序
當主線程執行完之後,守護線程也就沒有存在的價值了,因為沒有工作可做,此時 JVM 就會關機,守護線程終止
我們可以通過 線程對象.setDaemon(true)
來把某個線程設置為守護線程(必須在啟動之前設置)
🧟♀️ 死鎖#
在程序執行過程中,都遇到了對方進入加鎖的方法中導致方法都無法訪問
原理 :
- 某個線程執行完成,需要先後嵌套鎖定執行兩個對象,同時在這個過程中先鎖定第一個對象,再鎖定第二個對象
- 另外一個線程執行完成,需要 先後 嵌套 鎖定 執行兩個對象,同時在這個過程中,先鎖定第二個對象,再鎖定第一個對象
- 在第一個線程,鎖定第一個對象後,要去鎖定第二個對象時,發現第二個對象已經被鎖定,只能等待
- 第二個線程,鎖定第二個對象後要去鎖定第一個對象時,發現第一個對象已經被鎖定,只能等待
✉️ 線程通信#
Object 中的方法
wait()
: 讓該線程進入等待狀態(掛起狀態), 當被喚醒後進入就緒狀態,然後繼續執行之前掛起的地方
無參 或 傳入參數 0
都表示不會自動喚醒,只能被叫醒 (notify(),notifyAll()
)
notify()
: 隨機喚醒一個在該對象上等待的條線程讓其他線程去執行notifyAll()
: 喚醒所有在該對象上等待的線程
也可以傳入 long 類型的毫秒數,到指定毫秒數之後自動喚醒
wait 和 sleep 的區別:sleep 不會交出鎖,依然占用鎖,其他線程無法進入,wait 會交出鎖,其他線程可以進去
以上方法必須用在成員方法中且該方法必須加鎖(synchronized)