_04_java核心類庫2

黙塵發表於2020-11-26

1. 異常機制

1.1 簡介

  • Throwable是所有錯誤和異常的超類。
  • Error 是嚴重的JVM無法解決的問題,無法通過程式設計解決
  • Exception 類是指一些輕微的可以程式設計解決的問題

1.2 分類

  • Throwable 是 Error 和 Exception 的父類。

  • Error:

    目前唯一見過的Error,NoClassDefFoundError 繼承關係:Error --> LinkageError --> NoClassDefFoundError

  • Exception:

    • 執行時異常,RuntimeException 的子類

      包括:ArithmeticException、NullPointerException、ClassCastException等

      編譯時檢查不出來,編碼時不會要求必須進行處理,在執行時可能出現,

    • 檢測異常,除RuntimeException之外的其他異常。

      IOException

      指編譯時可以檢測出來的異常,編寫程式碼時,如果存在這些異常就會標紅,必須處理了才能編譯。

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-PKHQu5RC-1606327865373)(C:/Users/%E9%BB%98%E5%B0%98/Desktop/image-20201120133225305.png)]

1.3 異常捕獲,執行流程

  • 構造方法 中 throws 丟擲了異常,那麼物件是不會被建立出來的,也就是說,物件在構造完成後才能產生(或者是這時候我們才能拿到????)

  • 考點

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-d0DyD71p-1606327865376)(C:/Users/%E9%BB%98%E5%B0%98/Desktop/image-20201120140238103.png)]

1.4 繼承中的異常丟擲

子類throws的異常 不能 比父類throws的更大。

也就是為了保證按照父類丟擲的異常進行處理,可以解決子類丟擲的異常。

1.5 throws 和 try…catch 的使用場景

  • 父類沒有寫throws時,子類只能 try…catch 處理
  • 當有連續的多層函式呼叫時,推薦使用throws並在 頂層 統一處理。

1.6 自定義異常

  1. 定義要求

    1. 繼承Exception類或其子類。
    2. 定 義一個無參構造和一個有參構造。
    public class MyException extends Exception{
        public MyException(){}
        public MyException(String massage){
            super(message);
        }
    }
    
  2. 產生並丟擲異常

    ... 
    public void func() throws MyException{		// 注意
        ...
        throw new MyException("***異常發生了。"); 
        ...
    }
    

1.7 異常機制的意義

​ 將異常處理的程式程式碼集中在一起,與正常的程式碼分開,使得程式簡潔、優雅並易於維護。

​ 目前使用過的異常機制,應該都是被迫使用,它的好處當真是還沒有體會到,不過也不急,畢竟現在自己的程式碼量還是太少啊 ! 沒有經歷過真實的應用場景,就不要談意義。

2. 檔案操作

2.1 File類

  1. 作用:描述檔案及目錄的資訊

  2. 常用方法:

    File(String path);
    File(String parent, String child);
    File(File parent, String child);
    
    boolean exists();
    String getName();
    long length();     // 注意直接就是 length
    long lastModifed();
    String getAbsolutePath();
    File getAbsoluteFile();
    String getParent();
    File getParentFile();	// 若是相對路徑,無法獲取到給定路徑之前的路徑
    
    boolean delete(); // 刪除資料夾必須為空,否則返回false, 檔案不存在也是返回false,不會報異常
    boolean createNewFile();  // 檔案路徑 不存在 就會 報IOException異常。  !!!
    
    boolean mkdir();	// 父路徑不存在時返回 false
    boolean mkdirs(); 
    
    File[] listFiles([FileFilter filter]);
    public String list([FileFilter filter]);		// 注意只是子檔名,不是完整路徑
    boolean isFile();
    boolean ifDirectory();
    

    例項:

    file.listFiles(new FileFilter(){
        @Override
        public boolean accept(File file){
            return file.isFile();   // 返回 true 則保留。
        }
    });
    
  3. 注意:

    檔案與資料夾也不能重名,這是系統的限制。建立重名物件時返回false。

  4. 案例:

    1. 如何通過File獲取當前路徑?
    String path = new File("").getAbsolutePath();    // 獲取的應該是啟動啟動程式時的路徑
    A.class.getResource("/").getPath();				 // 類路徑的 根目錄
    A.class.getResource("").getPath();				 // A類的class檔案所在路徑
    

2.2 IO流簡介

  1. 基本分類

    • 位元組流、字元流

    • 輸入流、輸出流

    • 節點流、處理流

      節點流:直接與輸入輸出 源 對接的流。

      處理流:建立在節點流基礎之上的流。

  2. 結構框架

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-MRlYw7Wf-1606327865377)(C:/Users/%E9%BB%98%E5%B0%98/Desktop/image-20201120165628842.png)]

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-i6AEFALK-1606327865380)(C:/Users/%E9%BB%98%E5%B0%98/Desktop/image-20201120170048991.png)]

2.3 位元組流

位元組輸入流:InputStream 【抽象】

位元組輸出流:OutputStream 【抽象】

這兩個是所有位元組流的超類。直接子類:FileOutputStream 、ByteArrayOutputStream 。。。

2.3.1 FileOutputStream

  • 構造方法

    public FileOutputStream(String path);
    public FileOutputStream(File path);
    // 以上兩個方法都可以再跟一個引數 Boolean append,預設false,表示新建檔案以寫入資料,設定為true時,表示向已有檔案追加
    // 如果檔案不存在都會先建立,如果父目錄不存在,報FileNotFoundException
    
  • write方法

    void write(int b);  // 注意 int只能寫入底八位其他的會被捨棄
    void write(byte[] b);
    void write(byte[] b,int offset,int len);
    

    注意:預設換行符:window: \r\n linux: \n mac: \r

  • 其他

    void close();   
    void flush(); 
    

2.3.2 FileInputStream

  • 構造方法

    public FileInputStream(File path);
    public FileInputStream(String path);
    
  • read方法

    int read();   // 注意它的返回值也是int型別,雖然只有底八位
    int read(byte[] buff);
    // 如果讀完了 返回值 都是 -1
    int available();  // 獲取檔案的大小
    
  • 其他方法

    long skip(long n);  // 跳過 n 個位元組,據測試返回的都是n,不知道返回值有什麼用。
    

2.3.3BufferedInputStream & BufferedOutputStream

  • 位元組緩衝流是對其他IO流的封裝(包括但不限於檔案IO流),使其具備緩衝區的功能。

  • 優點

    • 在他們的內部自動維護一個緩衝陣列,預設大小8192B,以此來減少對底層的讀寫次數,可以提高效率。
    • 增加了Mark的功能【暫時還未用到】。
  • 構造方法:

    public BufferedInputStream(InputStream in,[int size]);
    public BufferedOutputStream(OutputStream out,[int size]);
    // 是對其他IO流的封裝,size 為緩衝區的大小,預設8192,也可自行指定
    
  • 讀寫

    // 讀寫同 FileInputStream FileOutputStream
    
  • 如下是複製一個視訊的用時

    // 基本位元組流 1          // 245390
    // 基本位元組流 1024       // 437   [8192  140]  [8192+1024  124]
    // 緩衝位元組流 1          // 1828
    // 緩衝位元組流 1024       // 125
    

    可見使用緩衝流確實可以提高效率【底層呼叫減少了】

    但是先要提高效率,好像直接加大緩衝陣列就好了呀,為啥要有緩衝流呢?他到底有什麼優勢。-------或許是我沒有資料操作的原因。

2.4 字元流

2.4.1 字元編碼

  • 常用字符集

    ASCII、Unicode、GB系列

    注意:Unicode是一個字元編碼解決方案,它給每一個字元編了一個號,也就是一個十進位制的數字。那怎麼將這個號存入計算機呢?為此,就有了utf-8,utf-16,utf-32三種編碼,它們通過自己的規則將字元的Unicode號轉換為二進位制存入記憶體。

    GB系列包括:GBK和GB2312,GBK完全相容GB2312。

    Unicode這個號,到底存不存在,Java中字串是不是用的這個號。如果是字元不可能用兩個位元組就能完全存下,那麼java中可不可能出現不能表示的字元。??????

    java 中的編碼問題:java檔案編碼、JDK預設編碼(編譯時可指定encoding)、JVM預設編碼(getBytes是使用的預設編碼,目前不會自行指定)

    這是一個複雜的問題,要慢慢解決,先要逐漸明確各種編碼區別聯絡,還要了解java的編解碼都在什麼時候發生。

  • java字串編解碼方法

    "".getBytes(String charset);			// 編碼
    new String(byte[] data,String charset); // 解碼
    // charset可以省略,使用平臺預設字符集,
    // 平臺的預設字符集,也就是JVM預設編碼,不同平臺不一樣,也是可以認為指定的。
    

2.4.2 Reader、Writer

  • 為什麼需要字元流:如上所述字元的編碼是很複雜的,如果讓我們在InputStream中拿到資料後,自己轉化為字串,這時自己面對各種各樣的編碼,著實不太美妙。

  • 字元流不是什麼新東西,他就像BufferedInputStream一樣,是對位元組流的封裝,只不過是提供了一些特殊功能而已。

    也就是說,java的IO流就兩種InputStream、OutputStream,其他的要麼是它們的子類,也就是提供這倆東西的實現;要麼就是對這兩個東西的封裝,即實現特殊功能,如緩衝,字元編解碼。

  • 抽象基類:Reader、Write,這倆東西也就是一個規範,即說明字元流應該具有哪些功能。

2.4.3 InputStreamReader & OutputStreamWriter

  • 構造方法

    public InputStreamReader(InputStream in, String charset);
    public OutputStreamWriter(OutputStream out, String charset);
    // 注意:charset指定編解碼字符集,可有可無預設應該是JVM的預設編碼,應該是平臺相關,但是沒試過。
    
  • 讀寫方法

    // 寫
    void write(int c);  // 給定字元的Unicode編碼,也就是\u後邊的東西,可以有字元強轉為int得到,用的應該不多。
    void write(char[] cbuff);
    void write(char[] cbuff,int offset,int len);
    void write(String s);	
    void write(String s,int offset,int len);
    
    void flush(); // 它是由緩衝的。
    
    // 讀
    int read(); // 讀一個字元,雖然返回值是int但強轉為char就行。  也就是說,輸入字元流,已經把字元轉化為Java字串預設的Unicode編碼。
    int read(char[] cbuff);
    int read(char[] cbuff,int offset,int len);  // offset 表示char[]中的便宜,len表示最多讀幾個字元
    public String(char[] date,int offset,int len); // 將字元陣列轉換為字串
    

2.4.4 FileReader & FileWriter

  • 構造方法

    public FileReader(String file);
    public FileReader(File file);
    
    public FileWriter(String file, boolean append);
    public FileWruter(File file, boolean append);
    // append 可省,預設false。
    
    // FileReader和FileWriter使用平臺預設字符集,無法指定編碼型別。
    // FileReader和FileWriter都是對 對應 InputStreamReader和OutputStreamWriter的分裝,且只不過是改了一下構造方法而已,其他的兩者完全一樣。
    
  • 作用:

    只是為了簡化建立程式碼,且命名格式上與FileInputStream和FileOutputStream對應而已。

2.4.5 BufferedReader & BufferedWriter

  • 與BufferedInputStream 對應,新增了緩衝功能。

  • 作用:當然可以提高讀寫效率,更重要的是他提供了一些針對字元IO的方便方法,如讀一行。

  • 構造方法

    public BufferedReader(Reader in,int size);
    public BufferedWriter(Writer our,int size);
    // size 可省,預設8192
    // 只有這4種構造方法。
    
  • 讀寫方法

    // 寫方法
    // 同OutputStreamWriter,5種方法,int,char[] 2種,String 2種。
    
    // 特有寫方法
    public void newLine();    // 輸出 系統項關 的換行符
    public void flush();
    
    // 讀方法
    // 繼承自Reader,可以使用read()和read(char[])
    
    // 特有讀方法
    public String readLine(); // 讀一行,不包括換行符,讀完後返回null
    void newLine();
    

2.4.6 PrintStream

PrintStream(OutputStream out);

void print(String s);
void println(String s);

void flush();
void close();

作用:System.out就是 PrintStream 的例項。將各種資料進行格式化輸出,使用上就跟向控制檯輸出一樣。

2.4.7 PrintWriter

PrintWriter(Writer out);
// 用法上與PrintStream相似。

2.4.8 DataInputStream & DataOutputStream

// 構造方法
DataInputStream(InputStream is);
DataOutputStream(OutputStream os);

void writeInt(int v);
int readInt();

void close();

作用:

​ 提供了將各種基本資料型別的資料,直接寫到流中的方法,然後可以用對應的read方法讀出。

​ 優點就在於讀寫時 不用進行資料型別和資料格式的轉化。

​ 重要的檔案不會使用這種隨意的格式進行讀寫,快捷的儲存臨時資料也有Object-io可以選擇。所以它的用處就不是很大。

注意:

​ writeInt(10) 與 write(10) 的區別,前者寫四個位元組,後者寫一個位元組。

2.4.9 ObjectInputStream & ObjectOutputStream

  1. 注意:

    ​ 讀寫物件的型別必須實現 java.io.Serializable 介面來開啟類的序列化功能。所謂序列化就是將一個物件 需要儲存的相關資訊 有效組織成 位元組序列 的過程。逆向過程就稱之為反序列化。

  2. 常用方法:

    ObjectOutputStream(OutputStream out);
    ObjectInputStream(InputStream in);
    
    void writeObject(Object obj);
    Object readObject();
    
    void close();
    
  3. 序列化版本號

    序列化版本號的作用是什麼?

    ​ 標識這個類是否發生改變,序列化時 版本號會一起寫入檔案,反序列化時,會判斷檔案中的版本號與類中的版本號是否一致,如果不一致就認為類的結構發生了變化,這時候就會報 InvalidClassException 。所以serialVersionUID應該與類結構對應,當對類進行了修改後(如加了一個欄位),就應該換一個新的版本號。

    ​ 版本號一致就可以轉換,類中如果有新增的欄位會以預設方式初始化,檔案比類中多字元段也不會報錯。一般寫版本號的目的就是保證類修改後依然可以反序列化。

    如何生成序列化版本號?

    ​ 因為版本號只是當前類結構的一種標識,所以隨便寫一個就好,只要保證類修改後換一個新的版本號行。但是每次修改類都要自己編一個版本號也比較麻煩,所以方式二就是通過IDE生成,IDE會自動根據當前類的內容生成一個版本號,每次修改類後只要重新生成一次就好,不用擔心會不會和以前的版本號重複。

    為什麼不寫也可以?

    ​ 可以發現實現serializable介面後,不顯示書寫serialVersionUID也能行啊?如果不寫,編譯器在編譯時自動根據類的內容計算並新增一個版本號。這時只要修改類的結構,增刪欄位或方法就會導致版本號不同(方法的內容修改不會導致版本號不同)。但是多數情況下我們並不希望它如此敏感。所以一般都會寫serialVersionUID,來自行控制版本問題。

  4. transient關鍵字

    用於標識不需要進行序列化的欄位。

    private transient String name;
    

    這時name欄位序列化時就不會寫入檔案,反序列化時會以預設方式初始化,也就是 null 。

  5. 經驗

    同一個檔案中可以寫入多個物件,但讀取時無法判斷是否讀到檔案末尾,如果讀到了末尾還進行讀取,會報EOFException。所以一個檔案中儲存多個物件時,可以將多個物件放入一個ArrayList物件中,一起儲存。

2.4.10 RandomAccessFile

  1. 簡介

    實現檔案的隨機讀寫。

  2. 常用方法

    RandomAccessFile(String path, String mode);
    // mode 取值:r 只讀,rw:讀寫,rwd:讀寫,同步檔案內容更新,rws:讀寫,同步檔案內容和原資料的更新
    
    int read();
    void seek(long pos);  // 從開頭向後偏移pos個位元組  
    void write(int b);   // 輸出一個字元,並覆蓋原有字元
    
    close();
    

    此類由於在檔案的指定位置進行操作,目前還用不明白,等需要了再說吧。

2.4.11 問題

  1. 為什麼使用字元流複製圖片會失敗?

    • 問題描述:字元流就是在位元組流的基礎上新增了字元的編解碼功能,那麼 解碼、編碼 兩相對應不應該內容不變嗎?

    • 實驗:

    public static void main(String[] args) throws IOException {
            // 自己輸出一個二進位制檔案,
            FileOutputStream out = new FileOutputStream("C:\\Users\\默塵\\Desktop\\test01.png");
            byte[] data = {-128,-128,-128,-128,-128,-128,-128,-128,-128,-128,-128};
            out.write(data);
            out.close();
            // 使用字元流拷貝這個 位元組二進位制檔案
            FileReader fr = new FileReader("C:\\Users\\默塵\\Desktop\\test01.png");
            FileWriter fw = new FileWriter("C:\\Users\\默塵\\Desktop\\test02.bmp");
            int buff = 0;
            while(-1!=(buff=fr.read())){
                fw.write(buff);
            }
            fw.close();
            fr.close();
            // 使用InputStream讀取拷貝的檔案,看看是否還是原資料。
            FileInputStream in = new FileInputStream("C:\\Users\\默塵\\Desktop\\test02.bmp");
            byte[] b2 = new byte[data.length];
            in.read(b2);
            in.close();
            // 列印
            System.out.println("原資料:"+Arrays.toString(data));
            System.out.println("拷貝後:"+Arrays.toString(b2));
        }
    

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-0b7NnwZy-1606327865383)(C:/Users/%E9%BB%98%E5%B0%98/Desktop/image-20201120230222831.png)]

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-wgMDV83y-1606327865383)(C:/Users/%E9%BB%98%E5%B0%98/Desktop/image-20201120230309329.png)]

    • 分析

      複製二進位制檔案有成功的,也有失敗的。故猜測,是因為不是所有的二進位制資料都有與之對應的字元,像上例中 -128 二進位制位全1組合,就沒有與之對應的字元,解碼時肯定有它的處理機制,但這樣編碼回去後就肯定不是原來的值了。這也就是問題的根源。

    • 結論

    因為我們無法確定圖片等二進位制檔案中是否存在這樣 沒有字元對應的二進位制組合,所以也就無法保證複製成功,所以不能用字元流複製二進位制檔案。

2.5 總結

  • 檔案項關的IO流只有兩個FileInputStream & FileOutputStream

  • 對IO流的讀寫【包括但不限於檔案IO流】,又分為兩類,即位元組和字元讀寫。

    對於位元組讀寫:多使用Buffered… 只是宣告時多一個殼兒,用法一樣,效率還高。

    對於字元讀寫:也多使用Buffered… 加 FileReader宣告,效率高。還好用寫,readLine和newLine更便於書寫。

    所以,就是將Buffered用成習慣。

  • 對於效能問題:

    一次測試:

    我用位元組流複製五個視訊檔案到一個檔案,總大小100M

    使用5M的陣列空間,不論使用者用buffered用時都是200ms的樣子。

    但使用1024B的陣列空間,不用Buffered耗時約700ms,但用了Buffered時間還是200ms。

    而且後來我給出了10M的陣列空間,速度依然只有200ms,可能達到了物理上限。

    按我想的Buffered的空間只有8K,按道理我的陣列空間只要比8K大,那不用Buffered就會的到更優的效果,但並非如此,至於原因,等你把這些用成條件反射的時候再看原始碼分析吧。原因可能是多方面的,可能不止java的實現方法。

3. 多執行緒

3.1 程式和執行緒的概念

通俗來講就是程式就是程式的一次執行過程,是系統進行資源分配和排程的一個單位。執行緒就是輕量級的程式,它的建立、排程、銷燬 所花費的開銷要比程式更小,是系統資源排程的更小的單位。一個程式可以包含多個執行緒。

從程式設計角度講,執行緒就是一個可以獨立進行的任務流程。我們可以通過執行緒實現多個操作的併發執行。

3.2 執行緒的兩種建立方式

  1. 繼承 java.lang.Thread 類,並重寫run方法。
  2. 以Runnable的例項為實參構建Thread物件。

3.3 Thread類

// 構造方法
Thread([Runnable trget][, String threadName]);

void start();   // 啟動執行緒,,是由JVM去掉run方法,而非在start中呼叫run方法

long getId();  // 獲取執行緒的唯一標識,
String getName();  // 獲取執行緒名稱
void setName(String name);  // 設定執行緒的名稱

static Thread currentThread();  // 獲取當前正在執行的執行緒的引用,也就是獲取程式碼所在的執行緒。

static void yield();    // 讓步,執行狀態的執行緒退出到就緒狀態排隊。
static void sleep(long ms);  

int getPriority();
void setPriority(int p);
// 優先順序 [1,10],預設為5;優先順序越高也不一定先執行,只是獲取時間片的機會更大而已。
// 常量:Thread.MAX_PRIORITY, Thread.MIN_PRIORITY

void join([long ms]);  // 當前執行緒等待,呼叫者執行緒結束後,再繼續執行,若有ms,表示最多等待的時間。

boolean isDaemon();   // 守護執行緒在主執行緒結束後停止(但也不是立即停止),非守護執行緒繼續執行,預設為非守護執行緒。
void setDaemon(true);  // 將執行緒設定為守護執行緒,也就是設定它在main結束後停止。
// 注意:setDaemon必須在start之前。

3.4 執行緒的生命週期

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-rgLKwcw6-1606327865384)(img/image-20201122114102128.png)]

  • 新建狀態:使用new關鍵字建立之後的狀態,並未開始執行。
  • 就緒狀態:呼叫start()之後進入的狀態,依然沒有開始執行。
  • 執行狀態:執行緒排程器呼叫該程後進入的狀態,執行緒開始執行,當時間片耗盡且人物未執行完時,就返回就緒狀態。
  • 消亡狀態:執行緒任務完成後進入的狀態,此時執行緒已終止。
  • 阻塞狀態:執行過程中發生阻塞事件進入的狀態。阻塞解除後進入的是就緒狀態,而非執行狀態。

3.5 執行緒同步機制

3.5.1 相關概念

  1. 同步機制:

    ​ 保證多個執行緒有條不紊的同時(巨集觀)進行的機制,它的作用在於保證多個執行緒同時進行不出錯,所以它的主要任務是對多個執行緒在執行次序上進行協調,使併發執行的多個執行緒之間能按照一定的規則共享臨界資源。

  2. **臨界資源:**一次僅允許一個執行緒使用的資源。

  3. **臨界區:**訪問臨界資源的程式碼區域。

3.5.2 實現方式:

​ 對臨界區加鎖,不允許兩個執行緒同時進入臨界區。synchronized加鎖或lock加鎖。

3.5.3 synchronized

  1. 部分程式碼鎖定

    synchronized(var){   // 注意這裡的var可以是任意引用型別物件的引用,一個物件就是一把鎖。
        // 臨界區程式碼
    }
    
  2. 方法鎖定

    public synchronized void func(){
        
    } 
    public static synchronized void func(){
        
    }
    

    相當於:

    synchronized(this){ 整個方法體 }
    synchronized(A.class){ 整個方法體 } // A 表示方法所在的類
    

    也就是說能不能鎖住要看,呼叫方法時兩個this是不是同一個物件。

  3. 注意事項

    • synchronized括號中的量必須是同一個物件才能鎖的住。
    • 儘量減少同步程式碼塊的範圍以提高效率。

3.5.4 Lock介面

常用方法:

ReentrantLock();  // Lock的實現類
void lock();      // 獲取鎖
void unlock();    // 釋放鎖

使用方法:

ReentrantLock lock = new ReentrantLock();

lock.lock();
// 同步程式碼塊
lock.unlock();

3.6 執行緒協作

3.6.1 相關方法

Object中的三個方法用於執行緒間的協作:

void wait([long timeout]);    // 釋放物件的鎖,進入等待,timeout表示最多等待的毫秒數,沒有給出就一直等   
void notify();		
void notifyAll();

注意:

  1. wait()、notify()和notifyAll()方法是本地方法,並且為final方法,無法被重寫。
  2. 呼叫某個物件的wait()方法能讓當前執行緒阻塞,並且當前執行緒必須擁有此物件的monitor(即鎖)
  3. 呼叫某個物件的notify()方法能夠喚醒一個正在等待這個物件的monitor的執行緒,如果有多個執行緒都在等待這個物件的monitor,則只能喚醒其中一個執行緒;
  4. 呼叫notifyAll()方法能夠喚醒所有正在等待這個物件的monitor的執行緒;

3.6.2 生產者消費者問題

  1. 問題描述

    有兩個程式:一組生產者程式和一組消費者程式共享一個初始為空、固定大小為n的快取(緩衝區)。生產者的工作是製造一段資料,只有緩衝區沒滿時,生產者才能把訊息放入到緩衝區,否則必須等待,如此反覆; 同時,只有緩衝區不空時,消費者才能從中取出訊息,一次消費一段資料(即將其從快取中移出),否則必須等待。由於緩衝區是臨界資源,它只允許一個生產者放入訊息,或者一個消費者從中取出訊息。

3.8 Callable介面

  1. 定義如下
public interface Callable<V> {
    V call() throws Exception;
}
  1. 特點

    作用同Runnable差不多,特點是它支援泛型,可以有返回值。

    預設丟擲異常

3.9 Future 介面

  1. Future介面
public interface Future<V> {
    boolean cancel(boolean var1);
    boolean isCancelled();
    boolean isDone();
    V get();
    V get(long var1, TimeUnit var3);
}

說明:

  • cancel方法用來取消任務,如果取消任務成功則返回true,如果取消任務失敗則返回false。引數mayInterruptIfRunning表示是否允許取消正在執行卻沒有執行完畢的任務,如果設定true,則表示可以取消正在執行過程中的任務。如果任務已經完成,則無論mayInterruptIfRunning為true還是false,此方法肯定返回false,即如果取消已經完成的任務會返回false;如果任務正在執行,若mayInterruptIfRunning設定為true,則返回true,若mayInterruptIfRunning設定為false,則返回false;如果任務還沒有執行,則無論mayInterruptIfRunning為true還是false,肯定返回true。
  • isCancelled方法表示任務是否被取消成功,如果在任務正常完成前被取消成功,則返回 true。
  • isDone方法表示任務是否已經完成,若任務完成,則返回true;
  • get()方法用來獲取執行結果,這個方法會產生阻塞,會一直等到任務執行完畢才返回;
  • get(long timeout, TimeUnit unit)用來獲取執行結果,如果在指定時間內,還沒獲取到結果,就直接返回null。

作用:定義了獲取返回值,取消任務的等功能。

  1. FutureTask類

    FutureTask類繼承自RunnableFuture介面,RunnableFuture又繼承自Runnable介面和Future介面。

    故FutureTask介面可作為Runnable的實現類有執行緒執行,又可以使用Future宣告的功能獲取執行結果。

    使用方法:

    public static void main(String[] args) {
        FutureTask<String> ft = new FutureTask<>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                System.out.println("我是子執行緒的執行過程。等3秒後才能看到我的返回值。");
                Thread.sleep(3000);
                return "我是子執行緒的返回值";
            }
        });
        new Thread(ft).start();
    
        try {
            System.out.println("----"+ft.get());    // 主執行緒會被阻塞在這裡知道子執行緒執行完
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    
        System.out.println("----主執行緒的輸出");
    }
    

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-mV356lCY-1606327865385)(img/image-20201123152302250.png)]

    大致原理:

    ​ Thread呼叫FutureTask的run方法,run方法中又呼叫了Callable中的call方法,並將其返回值儲存outcome欄位中,當呼叫get方法時,執行緒執行完了就將outcome返回。run方法中還將當前執行緒的引用存放了成員變數runner中,這樣就可以cancel方法中中斷執行緒了。

3.10 執行緒池

  1. 相關概念

    ​ 執行緒池:首先建立一些執行緒,把它們的集合稱為執行緒池,當需要時從執行緒池中取出一個空閒的執行緒為之服務,用完後不關閉該執行緒,而是將該執行緒換回到執行緒池中。

    ​ 線上程池程式設計模式下,任務是提交給執行緒池的,而不是直接交給某個Thread。任務提交後再由執行緒池分配執行緒處理。一個執行緒只能執行一個任務,但可以同時向一個執行緒池提交多個任務。

    ​ 執行緒池從Java1.5開始支援。

  2. 相關類和方法

    ExecutorService介面 定義了執行緒池的功能,定義了和後臺任務執行相關的方法

    ThreadPoolExecutor類 是ExecutorService的主要實現類。然而他的例項建立起來很是麻煩,有多個引數需要制定。

    Executor 是執行緒池的工廠類,一般用它來幫我們建立執行緒池,它建立的例項也一般為ThreadPoolExecutor類的例項。

  3. Executors的常用方法

    static ExecutorService newCachedThreadPool();   // 可以根據需要新建立執行緒
    static ExecutorService newFixedThreadPool(int n);  // 固定執行緒數的
    static ExecutorService newSingleThreadExecutor();  // 只有一個執行緒的執行緒池
    
  4. ExecutorService介面的常用方法

    void execute(Runnable runner);
    <T> Future<T> submit(Callable<T> | Runnable task);  
    // 它返回的就是 FutureTask 物件;它是一個泛型方法
    // 傳入Runnable的例項時,返回值為Future<?> ,故get只能得到Object。
    void shutdown();
    void shutdownNow();
    
  5. 舉例

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        Future<String> task = executorService.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                return "123456";
            }
        });
        System.out.println(task.get());
        executorService.shutdown();   // 否則main不會退出
    }
    

多個執行緒使用相同的所鎖定不同的程式碼塊,還可以執行嗎?

只要是用同一個物件為鎖,不論是否在同一個run函式中,不論加鎖的內容是否相同,都能鎖住。

4. 網路程式設計

4.1 相關協議

  1. TCP協議

    TCP協議的三次握手

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-UglxS9aK-1606327865386)(img/image-20201126010515332.png)]

    四次揮手

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-ZbcdTFEI-1606327865387)(img/image-20201126010606878.png)]

  2. UDP協議

    1、UDP是無連線的,即傳送資料之前不需要建立連線;

    2、UDP使用盡最大努力交付,即不保證可靠交付;

    3、UDP是面向報文的;

    4、UDP支援一對一、一對多、多對一和多對多的互動通訊等。

4.2 TCP程式設計模板

public static void main(String[] args) throws IOException {		
    //建立服務端物件,繫結4574埠。
    ServerSocket serv =new ServerSocket(4574);		 
    System.out.println("伺服器啟動成功,等待使用者接入");		  
    //accept()等待使用者接入,如果沒有使用者連線,會一直等待。
    //有客戶連線後,accept()方法會返回一個Socket物件,代表客戶端
    Socket sc=serv.accept();
    System.out.println("有客戶端接入,客戶端ip:"+sc.getInetAddress());
    //從Socket中得到網路輸入流,接收來自網路的資料
    InputStream in=sc.getInputStream();
    //從Socket中得到網路輸出流,將資料傳送到網路上
    OutputStream out=sc.getOutputStream();
    //接收客戶端發來的資料
    byte[] bs=new byte[1024];
    //將資料存入bs陣列中,返回值為陣列的長度
    int len=in.read(bs);
    String str=new String(bs,0,len);
    System.out.println("來自客戶端的訊息: "+str);
    //向客戶端寫資料,注意客戶端程式碼中別忘記寫read()方法,否則會拋異常
    out.write("歡迎訪問,你好,我是服務端".getBytes());	 
    System.out.println("服務端正常結束");
    //關閉流!
    sc.close();		 
}
public static void main(String[] args) throws UnknownHostException, IOException {
    //建立Socket連線,new Socket(服務端ip,埠port);
    Socket so=new Socket("192.168.0.104",4574);
    System.out.println("連線伺服器成功");
    //從Socket中得到網路輸入流,接收來自網路的資料
    InputStream in=so.getInputStream();
    //從Socket中得到網路輸出流,將資料傳送到網路上
    OutputStream out=so.getOutputStream();
    //write方法中只能為byte[],或者int。
    //若服務端沒有read(),則客戶端會一直等。即停留在write方法中。
    out.write("你好,我是客戶端".getBytes());
    //接收服務端發來的資料
    byte[] bs=new byte[1024];
    //將資料存入bs陣列中,返回值為陣列的長度
    int len=in.read(bs);
    String str=new String(bs,0,len);
    System.out.println("來自服務端的訊息: "+str);
    //關閉流
    so.close();
}

注意事項:

檔案的IO流結束一定要關閉

Tcp的IO流不能關閉,但結束一定要flush

4.3 UDP程式設計模板

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-7J3f2QQa-1606327865387)(img/image-20201126011006621.png)]

5. 反射

反射是指:在執行階段決定建立什麼物件,呼叫什麼方法的程式設計機制

5.1 Class類

  1. 基本概念

    • java.lang.Class類的例項可以描述Java的類和介面,也就是一種資料型別。
    • 它沒有公共構造方法,其例項由 JVM 和 ClassLoader 自動建立。
  2. 獲取方式

    // 1. 資料型別 * 
    String.class();    // class java.lang.String
    int.class();	   // int
    void.class();	   // void
    
    // 2. 物件.getClass()
    obj.getClass();
    
    // 3. 包裝類.type()  // 獲取其基本型別的 Class 物件
    Integer.type;    // int
    Integer.class;   // java.lang.Integer
    
    // 4. forName方法 *
    Class.forName("完全限定名");
    
    // 5. 類載入器
    ClassLoader cl = A.class.getClassLoader();
    cl.loadClass("完全限定名")
    
  3. 常用方法

    newInstance();   // 呼叫無參構造,建立物件,已過時
    
    Constructor<T> getConstructor(Class<?>... parameterTypes); // 獲取Class物件代表的型別中對應引數的公共構造方法。  // 注意這裡引數 必須是Class的物件
    Constructor<T>[] getConstructors();  // 獲取 所代表的 型別的所有公 共構造器
    
    Field getDeclaredField(String name);  // 獲取 欄位物件,Declared表示獲取所有的,不加只能獲取public的。
    Field[] getDeclareFields();			  // 獲取所有欄位
    
    Method getMethod(String name,Class<?>... parameterTypes);
    // 用於獲取該Class物件表示類中名字為name引數為parameterTypes的指定公共成員方法
    Method[] getMethods(); // 用於獲取該Class物件表示類中所有公共成員方法
    

5.2 Constructor類

  1. 獲取例項

    通過Class物件的getConstructor方法獲取

  2. 常用方法

    T newInstance(Object... initargs);  // 建立例項 **
    
    int getModifiers();                 // 獲取方法的訪問修飾符,【因為不只可以獲取到共有構造方法】
    String getName();					// 獲取方法名,不知有何用?
    Class<?>[] getParameterTypes();     // 獲取方法的所有引數型別
    
  3. 示例

    public static void main(String[] args) throws Exception{
        Class pclass = Class.forName("mc.PC.Person");
        Constructor constructor = pclass.getConstructor(String.class,int.class);
        Object o = constructor.newInstance("Jsumy",15);
        System.out.println(o);
    }
    

5.3 Feild類

常用方法:

Object get(Object obj);
void set(Object obj,);

void setAccessible(boolean flag); //當實參傳遞true時,則反射物件在使用時應該取消 Java 語言訪問檢查

int getModifiers();  // 獲取成員變數的訪問修飾符
Class<?> getType();  // 獲取成員變數的資料型別
String getName(); 	 // 獲取成員變數的名稱

5.4 Method類

Object invoke(Object obj,Object... args); // 使用物件obj來呼叫此Method物件所表示的成員方法,實參傳遞args

int getModifiers(); // 獲取方法的訪問修飾符
Class<?> getReturnType(); // 獲取方法的返回值型別
String getName(); // 獲取方法的名稱
Class<?>[] getParameterTypes(); // 獲取方法所有引數的型別
Class<?>[] getExceptionTypes(); // 獲取方法的異常資訊

5.5 其他資訊

Package getPackage(); // 獲取所在的包資訊
Class<? super T> getSuperclass(); // 獲取繼承的父類資訊
Class<?>[] getInterfaces(); // 獲取實現的所有介面
Annotation[] getAnnotations(); // 獲取註解資訊
Type[] getGenericInterfaces(); // 獲取泛型資訊

相關文章