尚學堂JAVA基礎學習筆記

尚學堂JAVA基礎學習筆記

寫在前面

學習連接:Java 視頻教程全集java

課件連接:Java課件git

第1章 JAVA入門

  • 計算機語言發展史以及將來方向程序員

    • 第一代語言:機器語言
  • 第二代語言:彙編語言github

  • 第三代語言:高級語言web

  • Java的核心優點redis

    • JAVA虛擬機是JAVA實現跨平臺的核心。
  • Java的各個版本算法

    2017-6-6 10-12-07.jpg

    • JavaSE(Java Standard Edition):標準版,定位在我的計算機上的應用。shell

    • JavaEE(Java Enterprise Edition):企業版,定位在服務器端的應用。數據庫

    • JavaME(Java Micro Edition):微型版,定位在消費性電子產品的應用上。

    • 雷區:不少人開始會誤解爲安卓開發就是JavaME,這兩個是徹底不一樣的內容。

  • Java的特徵和優點

    • 跨平臺/能夠執行
    • 安全性
    • 面相對象
    • 簡單性
    • 高性能
    • 分佈式
    • 多線程
    • 健壯性
  • Java應用程序的運行機制

    图片6.png

    • Java首先利用文本編輯器編寫 Java源程序,源文件的後綴名爲.java;再利用編譯器(javac)將源程序編譯成字節碼文件,字節碼文件的後綴名爲.class; 最後利用虛擬機(解釋器,java)解釋執行。
  • JVM、JRE和JDK

    • JVM(Java Virtual Machine)就是一個虛擬的用於執行bytecode字節碼的」虛擬計算機」。他也定義了指令集、寄存器集、結構棧、垃圾收集堆、內存區域。JVM負責將Java字節碼解釋運行,邊解釋邊運行,這樣,速度就會受到必定的影響。

      圖片8.png

    • Java Runtime Environment (JRE) 包含:Java虛擬機、庫函數、運行Java應用程序所必須的文件。

    • Java Development Kit (JDK)包含:包含JRE,以及增長編譯器和調試器等用於程序開發的文件

      圖片7.png

  • 第一個Java程序的總結和提高

    • 一個源文件能夠包含多個類class。
    • 正確編譯後的源文件,會獲得相應的字節碼文件,編譯器爲每一個類生成獨立的字節碼文件,且將字節碼文件自動命名爲類的名字且以「.class」爲擴展名。
  • 最經常使用DOS命令

    • cd 目錄路徑:進入一個目錄
    • cd ..:進入父目錄
    • dir:查看本目錄下的文件和子目錄列表
    • cls:清楚屏幕命令
    • 上下鍵:查找敲過的命令
    • Tab鍵:自動補齊命令

第2章 數據類型和運算符

  • 註釋

    • 單行註釋: 使用「//」開頭,「//」後面的單行內容均爲註釋。
    • 多行註釋: 以「/*」開頭以「*/」結尾,在「/*」和「*/」之間的內容爲註釋,咱們也能夠使用多行註釋做爲行內註釋。可是在使用時要注意,多行註釋不能嵌套使用。
    • 文檔註釋: 以「/**」開頭以「*/」結尾,註釋中包含一些說明性的文字及一些JavaDoc標籤(後期寫項目時,能夠生成項目的API)
  • 標識符

    • 標識符必須以字母、下劃線_、美圓符號$開頭。
    • 標識符其它部分能夠是字母、下劃線「_」、美圓符「$」和數字的任意組合。
    • Java 標識符大小寫敏感,且長度無限制。
    • 標識符不能夠是Java的關鍵字。
    • 標識符的使用規範
      • 表示類名的標識符:每一個單詞的首字母大寫,如Man, GoodMan
      • 表示方法和變量的標識符:第一個單詞小寫,從第二個單詞開始首字母大寫,咱們稱之爲「駝峯原則」,如eat(), eatFood()
  • Java中的關鍵字/保留字

    img

  • 變量的分類

    • 局部變量(local variable):方法或語句塊內部定義的變量。生命週期是從聲明位置開始到到方法或語句塊執行完畢爲止。局部變量在使用前必須先聲明、初始化(賦初值)再使用。
    • 成員變量(也叫實例變量 member variable):方法外部、類的內部定義的變量。從屬於對象,生命週期伴隨對象始終。若是不自行初始化,它會自動初始化成該類型的默認初始值。
    • 靜態變量(類變量 static variable):使用static定義。 從屬於類,生命週期伴隨類始終,從類加載到卸載。 (注:講完內存分析後咱們再深刻!先放一放這個概念!)若是不自行初始化,與成員變量相同會自動初始化成該類型的默認初始值。

    img

  • 變量和常量命名規範(規範是程序員的基本準則,不規範會直接損害你的我的形象):

    1. 全部變量、方法、類名:見名知意
    2. 類成員變量:首字母小寫和駝峯原則: monthSalary
    3. 局部變量:首字母小寫和駝峯原則
    4. 常量:大寫字母和下劃線:MAX_VALUE
    5. 類名:首字母大寫和駝峯原則: Man, GoodMan
    6. 方法名:首字母小寫和駝峯原則: run(), runRun()
  • 基本數據類型(primitive data type)

    7.png

    • 引用數據類型的大小統一爲4個字節,記錄的是其引用對象的地址!
  • 整型變量/常量

    img

    • Java 語言整型常量的四種表示形式
      • 十進制整數,如:99, -500, 0
      • 八進制整數,要求以 0 開頭,如:015
      • 十六進制數,要求 0x 或 0X 開頭,如:0x15
      • 二進制數,要求0b或0B開頭,如:0b01110011
    • Java語言的整型常數默認爲int型,聲明long型常量能夠後加‘ l ’或‘ L ’ 。
  • 浮點型變量/常量

    img

    • Java浮點類型常量有兩種表示形式
      • 十進制數形式,例如:3.14 314.0 0.314
      • 科學記數法形式,如314e2 314E2 314E-2
    • 浮點類型float,double的數據不適合在不允許舍入偏差的金融計算領域。若是須要進行不產生舍入偏差的精確數字計算,須要使用BigDecimal類。
    • java.math包下面的兩個有用的類:BigInteger和BigDecimal,這兩個類能夠處理任意長度的數值。BigInteger實現了任意精度的整數運算。BigDecimal實現了任意精度的浮點運算。
    • 不要使用浮點數進行比較!不少新人甚至不少理論不紮實的有工做經驗的程序員也會犯這個錯誤!須要比較請使用BigDecimal類。
  • 字符型變量/常量

    • char 類型用來表示在Unicode編碼表中的字符。Unicode編碼被設計用來處理各類語言的文字,它佔2個字節,可容許有65536個字符。

    • 轉義字符

      img

    • String類,實際上是字符序列(char sequence)。

  • 運算符(operator)

    img

  • 算術運算符

    • 取模運算:
      • 其操做數能夠爲浮點數,通常使用整數,結果是「餘數」,「餘數」符號和左邊操做數相同,如:7%3=1,-7%3=-1,7%-3=1。
  • 賦值及其擴展賦值運算符

    img

  • 關係運算符

    img

  • 邏輯運算符

    img

    • 短路與和短路或採用短路的方式。從左到右計算,若是隻經過運算符左邊的操做數就可以肯定該邏輯表達式的值,則不會繼續計算運算符右邊的操做數,提升效率。
  • 位運算符

    img

  • 運算符的優先級

    img

    • 邏輯與、邏輯或、邏輯非的優先級必定要熟悉!(邏輯非>邏輯與>邏輯或)。
  • 自動類型轉換

    • 自動類型轉換指的是容量小的數據類型能夠自動轉換爲容量大的數據類型。實線表示無數據丟失的自動類型轉換,而虛線表示在轉換時可能會有精度的損失。

      1.png

    • 能夠將整型常量直接賦值給byte、 short、 char等類型變量,而不須要進行強制類型轉換,只要不超出其表數範圍便可。

第3章 控制語句

  • 帶標籤的break和continue

    • 「標籤」是指後面跟一個冒號的標識符,例如:「label:」。對Java來講惟一用到標籤的地方是在循環語句以前。而在循環以前設置標籤的惟一理由是:咱們但願在其中嵌套另外一個循環,因爲break和continue關鍵字一般只中斷當前循環,但若隨同標籤使用,它們就會中斷到存在標籤的地方。

    • 在 「goto有害」論中,最有問題的就是標籤,而非goto, 隨着標籤在一個程序裏數量的增多,產生錯誤的機會也愈來愈多。 但Java標籤不會形成這方面的問題,由於它們的活動場所已被限死,不可經過特別的方式處處傳遞程序的控制權。由此也引出了一個有趣的問題:經過限制語句的能力,反而能使一項語言特性更加有用。

    • 帶標籤break和continue:控制嵌套循環跳轉(打印101-150之間全部的質數)

      public class Test18 {
          public static void main(String args[]) {
              outer: for (int i = 101; i < 150; i++) {
                  for (int j = 2; j < i / 2; j++) {
                      if (i % j == 0){
                          continue outer;
                      }
                  }
                  System.out.print(i + "  ");
              }
          }
      }
  • 方法

    • Java中進行方法調用中傳遞參數時,遵循值傳遞的原則(傳遞的都是數據的副本):
      • 基本類型傳遞的是該數據值的copy值。
      • 引用類型傳遞的是該對象引用的copy值,但指向的是同一個對象。
  • 方法的重載(overload)

    • 方法的重載是指一個類中能夠定義多個方法名相同,但參數不一樣的方法。 調用時,會根據不一樣的參數自動匹配對應的方法。

    • 重載的方法,實際是徹底不一樣的方法,只是名稱相同而已!

    • 構成方法重載的條件:

      1. 不一樣的含義:形參類型形參個數形參順序不一樣

      2. 只有返回值不一樣不構成方法的重載
      3. 只有形參的名稱不一樣,不構成方法的重載

  • 遞歸結構

    • 遞歸是一種常見的解決問題的方法,即把問題逐漸簡單化。遞歸的基本思想就是「本身調用本身」,一個使用遞歸技術的方法將會直接或者間接的調用本身。

    • 遞歸結構包括兩個部分:

      1. 定義遞歸頭。解答:何時不調用自身方法。若是沒有頭,將陷入死循環,也就是遞歸的結束條件。

      2. 遞歸體。解答:何時須要調用自身方法。

    • 遞歸的缺陷

      • 簡單的程序是遞歸的優勢之一。可是遞歸調用會佔用大量的系統堆棧,內存耗用多,在遞歸調用層次多時速度要比循環慢的多,因此在使用遞歸時要慎重。
      • 任何能用遞歸解決的問題也能使用迭代解決。當遞歸方法能夠更加天然地反映問題,而且易於理解和調試,而且不強調效率問題時,能夠採用遞歸;
      • 在要求高性能的狀況下儘可能避免使用遞歸,遞歸調用既花時間又耗內存。

第4章 Java面向對象基礎

1. 面向對象基礎

  • 類:咱們叫作class。
  • 對象:咱們叫作Object,instance(實例)。之後咱們說某個類的對象,某個類的實例。是同樣的意思。

2. 面向對象的內存分析

  • Java虛擬機的內存能夠分爲三個區域:棧stack、堆heap、方法區method area。

  • 棧的特色以下:

    1. 棧描述的是方法執行的內存模型。每一個方法被調用都會建立一個棧幀(==存儲局部變量、操做數、方法出口==等)

    2. JVM爲每一個線程建立一個棧,用於存放該線程執行方法的信息(實際參數、局部變量等)

    3. 棧屬於線程私有,不能實現線程間的共享!

    4. 棧的存儲特性是「先進後出,後進先出」

      5. 棧是由系統自動分配,速度快!棧是一個連續的內存空間!

  • 堆的特色以下:

    1. 堆用於存儲建立好的==對象和數組==(數組也是對象)

    2. JVM只有一個堆,被全部線程共享

    3. 堆是一個不連續的內存空間,分配靈活,速度慢!

  • 方法區(又叫靜態區)特色以下:

    1. JVM只有一個方法區,被全部線程共享!

    2. 方法區實際也是堆,只是用於存儲類、常量相關的信息!

    3. 用來存放程序中永遠是不變或惟一的內容。(==類信息==【Class對象】、==靜態變量、字符串常量==等)

    1.png

    neicunfenxi.png

3. 構造方法

  • 構造器也叫構造方法(constructor),用於對象的初始化。構造器是一個建立對象時被自動調用的特殊方法,目的是對象的初始化。構造器的名稱應與類的名稱一致。Java經過new關鍵字來調用構造器,從而返回該類的實例,是一種特殊的方法。

  • 要點:

    1. 經過new關鍵字調用!!

    2. 構造器雖然有返回值,可是不能定義返回值類型(返回值的類型確定是本類),不能在構造器裏使用return返回某個值。

    3. 若是咱們沒有定義構造器,則編譯器會自動定義一個無參的構造函數。若是已定義則編譯器不會自動添加!

    4. 構造器的方法名必須和類名一致!
  • 構造方法的重載

    • 構造方法也是方法,只不過有特殊的做用而已。與普通方法同樣,構造方法也能夠重載。
    • 構造方法的第一句老是super()
    • this表示建立好的對象

4. 垃圾回收機制(Garbage Collection)

  • Java引入了垃圾回收機制,令C++程序員最頭疼的內存管理問題迎刃而解。Java程序員能夠將更多的精力放到業務邏輯上而不是內存管理工做上,大大的提升了開發效率。

  • 內存管理

    • Java的內存管理很大程度指的就是對象的管理,其中包括對象空間的分配和釋放。

    • 對象空間的分配:使用new關鍵字建立對象便可
    • 對象空間的釋放:將對象賦值null便可。垃圾回收器將負責回收全部」不可達」對象的內存空間。

  • 垃圾回收過程

    任何一種垃圾回收算法通常要作兩件基本事情:

    1. 發現無用的對象

    2. 回收無用對象佔用的內存空間。

    垃圾回收機制保證能夠將「無用的對象」進行回收。無用的對象指的就是沒有任何變量引用該對象。Java的垃圾回收器經過相關算法發現無用對象,並進行清除和整理。

  • 垃圾回收相關算法

    1. 引用計數法

      堆中每一個對象都有一個引用計數。被引用一次,計數加1. 被引用變量值變爲null,則計數減1,直到計數爲0,則表示變成無用對象。優勢是算法簡單,缺點是「循環引用的無用對象」沒法別識別。

    2. 引用可達法(根搜索算法)

      程序把全部的引用關係看做一張圖,從一個節點GC ROOT開始,尋找對應的引用節點,找到這個節點之後,繼續尋找這個節點的引用節點,當全部的引用節點尋找完畢以後,剩餘的節點則被認爲是沒有被引用到的節點,即無用的節點。

  • 通用的分代垃圾回收機制

    • 分代垃圾回收機制,是基於這樣一個事實:不一樣的對象的生命週期是不同的。所以,不一樣生命週期的對象能夠採起不一樣的回收算法,以便提升回收效率。咱們將對象分爲三種狀態:年輕代、年老代、持久代。JVM將堆內存劃分爲 Eden、Survivor 和 Tenured/Old 空間。

      1. 年輕代

        全部新生成的對象首先都是放在Eden區。 年輕代的目標就是儘量快速的收集掉那些生命週期短的對象,對應的是Minor GC,每次 Minor GC 會清理年輕代的內存,算法採用效率較高的複製算法,頻繁的操做,可是會浪費內存空間。當「年輕代」區域存放滿對象後,就將對象存放到年老代區域。

      2. 年老代

        在年輕代中經歷了N(默認15)次垃圾回收後仍然存活的對象,就會被放到年老代中。所以,能夠認爲年老代中存放的都是一些生命週期較長的對象。年老代對象愈來愈多,咱們就須要啓動Major GC和Full GC(全量回收),來一次大掃除,全面清理年輕代區域和年老代區域。

      3. 持久代

        用於存放靜態文件,如Java類、方法等。持久代對垃圾回收沒有顯著影響。

    • 堆內存的劃分細節

      1.png

      • Minor GC:

        用於清理年輕代區域。Eden區滿了就會觸發一次Minor GC。清理無用對象,將有用對象複製到「Survivor1」、「Survivor2」區中(這兩個區,大小空間也相同,同一時刻Survivor1和Survivor2只有一個在用,一個爲空)

      • Major GC:

        用於清理老年代區域。

      • Full GC:

        用於清理年輕代、年老代區域。 成本較高,會對系統性能產生影響。

    • 垃圾回收過程:

      一、新建立的對象,絕大多數都會存儲在Eden中,

      二、當Eden滿了(達到必定比例)不能建立新對象,則觸發垃圾回收(GC),將無用對象清理掉,而後剩餘對象複製到某個Survivor中,如S1,同時清空Eden區

      三、當Eden區再次滿了,會將S1中的不能清空的對象存到另一個Survivor中,如S2,同時將Eden區中的不能清空的對象,也複製到S1中,保證Eden和S1,均被清空。

      四、重複屢次(默認15次)Survivor中沒有被清理的對象,則會複製到老年代Old(Tenured)區中,

      五、當Old區滿了,則會觸發一個一次完整地垃圾回收(FullGC),以前新生代的垃圾回收稱爲(minorGC)

  • JVM調優和Full GC

    • 在對JVM調優的過程當中,很大一部分工做就是對於Full GC的調節。有以下緣由可能致使Full GC:

        1.年老代(Tenured)被寫滿

        2.持久代(Perm)被寫滿

        3.System.gc()被顯式調用(程序建議GC啓動,不是調用GC)

        4.上一次GC以後Heap的各域分配策略動態變化

  • 開發中容易形成內存泄露的操做

    • 建立大量無用對象:

      好比,咱們在須要大量拼接字符串時,使用了String而不是StringBuilder。

    • 靜態集合類的使用:

      像HashMap、Vector、List等的使用最容易出現內存泄露,這些靜態變量的生命週期和應用程序一致,全部的對象Object也不能被釋放。

    • 各類鏈接對象(IO流對象、數據庫鏈接對象、網絡鏈接對象)未關閉:

      IO流對象、數據庫鏈接對象、網絡鏈接對象等鏈接對象屬於物理鏈接,和硬盤或者網絡鏈接,不使用的時候必定要關閉。

    • 監聽器的使用:

      釋放對象時,沒有刪除相應的監聽器。

  • 要點:

      1. 程序員無權調用垃圾回收器。

      2. 程序員能夠調用System.gc(),該方法只是通知JVM,並非運行垃圾回收器。儘可能少用,會申請啓動Full GC,成本高,影響系統性能。
    
              3. finalize方法,是Java提供給程序員用來釋放對象或資源的方法,可是儘可能少用。

5. this關鍵字

  • 對象建立的過程和this的本質
  • 構造方法是建立Java對象的重要途徑,經過new關鍵字調用構造器時,構造器也確實返回該類的對象,但這個對象並非徹底由構造器負責建立。建立一個對象分爲以下四步:

    1. 分配對象空間,並將對象成員變量初始化爲0或空

    2. 執行屬性值的顯示初始化

    3. 執行構造方法

    4. 返回對象的地址給相關的變量

  • this的本質就是「建立好的對象的地址」! 因爲在構造方法調用前,對象已經建立。所以,在構造方法中也能夠使用this表明「當前對象」 。

  • this最常的用法:

    1. 在程序中產生二義性之處,應使用this來指明當前對象;普通方法中,this老是指向調用該方法的對象。構造方法中,this老是指向正要初始化的對象。

    2. 使用this關鍵字調用重載的構造方法,避免相同的初始化代碼。但只能在構造方法中用,而且必須位於構造方法的第一句。

    3. this不能用於static方法中。

6. static 關鍵字

  • 在類中,用static聲明的成員變量爲靜態成員變量,也稱爲類變量。 類變量的生命週期和類相同,在整個應用程序執行期間都有效。它有以下特色:

    1. 爲該類的公用變量,屬於類,被該類的全部實例共享,在類被載入時被顯式初始化。

    2. 對於該類的全部對象來講,static成員變量只有一份。被該類的全部對象共享!

    3. 通常用「類名.類屬性/方法」來調用。(也能夠經過對象引用或類名(不須要實例化)訪問靜態成員。)

    4. 在static方法中不可直接訪問非static的成員。

  • static修飾的成員變量和方法,從屬於類。

  • 普通變量和方法從屬於對象的。

2.png

7. 靜態初始化塊

  • 構造方法用於對象的初始化!靜態初始化塊,用於類的初始化操做!在靜態初始化塊中不能直接訪問非static成員。

  • 靜態初始化塊執行順序:

    1. 上溯到Object類,先執行Object的靜態初始化塊,再向下執行子類的靜態初始化塊,直到咱們的類的靜態初始化塊爲止。

    2. 構造方法執行順序和上面順序同樣!!

8. 參數傳值機制

  • Java中,方法中全部參數都是「值傳遞」,也就是「傳遞的是值的副本」。 也就是說,咱們獲得的是「原參數的複印件,而不是原件」。所以,複印件改變不會影響原件。
  • 基本數據類型參數的傳值
    • 傳遞的是值的副本。 副本改變不會影響原件。
  • 引用類型參數的傳值
    • 傳遞的是值的副本。可是引用類型指的是「對象的地址」。所以,副本和原參數都指向了同一個「地址」,改變「副本指向地址對象的值,也意味着原參數指向對象的值也發生了改變」。

9. 包

  • 包機制是Java中管理類的重要手段。 開發中,咱們會遇到大量同名的類,經過包咱們很容易對解決類重名的問題,也能夠實現對類的有效管理。 包對於類,至關於文件夾對於文件的做用

  • 咱們經過package實現對類的管理,package的使用有兩個要點:

    1. 一般是類的第一句非註釋性語句。

    2. 包名:域名倒着寫便可,再加上模塊名,便於內部管理類。

  • 注意事項:

    1. 寫項目時都要加包,不要使用默認包。

    2. com.gao和com.gao.car,這兩個包沒有包含關係,是兩個徹底獨立的包。只是邏輯上看起來後者是前者的一部分。

  • JDK中的主要包

    img

  • 導入類import

    • 若是咱們要使用其餘包的類,須要使用import導入,從而能夠在本類中直接經過類名來調用,不然就須要書寫類的完整包名和類名。import後,便於編寫代碼,提升可維護性。

    • 注意要點:

      1. Java會默認導入java.lang包下全部的類,所以這些類咱們能夠直接使用。

      2. 若是導入兩個同名的類,只能用包名+類名來顯示調用相關類。

  • 靜態導入

    • 靜態導入(static import)是在JDK1.5新增長的功能,其做用是用於導入指定類的靜態屬性,這樣咱們能夠直接使用靜態屬性。

      package cn.sxt;
       //如下兩種靜態導入的方式二選一便可
      import static java.lang.Math.*;//導入Math類的全部靜態屬性
      import static java.lang.Math.PI;//導入Math類的PI屬性
      
      public class Test2{
          public static void main(String [] args){
              System.out.println(PI);
              System.out.println(random());
          }
      }

第5章 Java面向對象進階

1. 繼承

  • 繼承的實現

    • 繼承讓咱們更加容易實現類的擴展。 好比,咱們定義了人類,再定義Boy類就只須要擴展人類便可。實現了代碼的重用,不用再從新發明輪子(don’t reinvent wheels)。

    • 從英文字面意思理解,extends的意思是「擴展」。子類是父類的擴展。現實世界中的繼承無處不在。好比:

      圖5-1 現實世界中的繼承.png

  • 繼承使用要點

    1. 父類也稱做超類、基類、派生類等。

    2. Java中只有單繼承,沒有像C++那樣的多繼承。多繼承會引發混亂,使得繼承鏈過於複雜,系統難於維護。

    3. Java中類沒有多繼承,接口有多繼承(??實現)。

    4. 子類繼承父類,能夠獲得父類的所有屬性和方法 (除了父類的構造方法),但不見得能夠直接訪問(好比,父類私有的屬性和方法)。

    5. 若是定義一個類時,沒有調用extends,則它的父類是:java.lang.Object。
    6. 能夠使用ctrl+T方便的查看繼承結構。

  • instanceof 運算符

    • instanceof是二元運算符,左邊是對象,右邊是類;當對象是右面類或子類所建立對象時,返回true;不然,返回false。
  • 方法的重寫override

    • 子類經過重寫父類的方法,能夠用自身的行爲替換父類的行爲。方法的重寫是實現多態的必要條件

    • 方法的重寫須要符合下面的三個要點:

      ​ 1.「==」: 方法名、形參列表相同。

      ​ 2.「≤」:返回值類型和聲明異常類型,子類小於等於父類。

      ​ 3.「≥」: 訪問權限,子類大於等於父類。

2. Object類

  • Object類是全部Java類的根基類,也就意味着全部的Java對象都擁有Object類的屬性和方法。若是在類的聲明中未使用extends關鍵字指明其父類,則默認繼承Object類。

  • toString方法

    • Object類中定義有public String toString()方法,其返回值是 String 類型。

    • Object類中toString方法的源碼爲:

      public String toString() {
          return getClass().getName() + "@" + Integer.toHexString(hashCode());
      }
    • 根據如上源碼得知,默認會返回「類名+@+16進制的hashcode」。在打印輸出或者用字符串鏈接對象時,會自動調用該對象的toString()方法。

  • ==和equals方法

    • 「==」表明比較雙方是否相同。若是是基本類型則表示值相等,若是是引用類型則表示地址相等便是同一個對象。
    • Object類中定義有:public boolean equals(Object obj)方法,提供定義「對象內容相等」的邏輯。好比,咱們在公安系統中認爲id相同的人就是同一我的、學籍系統中認爲學號相同的人就是同一我的。
    • Object 的 equals 方法默認就是比較兩個對象的hashcode,是同一個對象的引用時返回 true 不然返回 false。可是,咱們能夠根據咱們本身的要求重寫equals方法。
    • JDK提供的一些類,如String、Date、包裝類等,重寫了Object的equals方法,調用這些類的equals方法, x.equals (y) ,當x和y所引用的對象是同一類對象且屬性內容相等時(並不必定是相同對象),返回 true 不然返回 false。

3. super關鍵字

  • super是直接父類對象的引用。能夠經過super來訪問父類中被子類覆蓋的方法或屬性。

  • 使用super調用普通方法,語句沒有位置限制,能夠在子類中隨便調用。

  • 如果構造方法的第一行代碼沒有顯式的調用super(...)或者this(...);那麼Java默認都會調用super(),含義是調用父類的無參數構造方法。這裏的super()能夠省略。

  • 繼承樹追溯
  • 屬性/方法查找順序:(好比:查找變量h)

    1. 查找當前類中有沒有屬性h

    2. 依次上溯每一個父類,查看每一個父類中是否有h,直到Object

    3. 若是沒找到,則出現編譯錯誤。

    4. 上面步驟,只要找到h變量,則這個過程終止。

  • 構造方法調用順序:

    • 構造方法第一句老是:super(…)來調用父類對應的構造方法。因此,流程就是:先向上追溯到Object,而後再依次向下執行類的初始化塊和構造方法,直到當前子類爲止。
      • 注:靜態初始化塊調用順序,與構造方法調用順序同樣,再也不重複。

4. 封裝

  • 須要讓用戶知道的才暴露出來,不須要讓用戶知道的所有隱藏起來,這就是封裝。說的專業一點,封裝就是把對象的屬性和操做結合爲一個獨立的總體,並儘量隱藏對象的內部實現細節。

  • 咱們程序設計要追求「高內聚,低耦合」。 高內聚就是類的內部數據操做細節本身完成,不容許外部干涉;低耦合是僅暴露少許的方法給外部使用,儘可能方便外部調用。

  • 編程中封裝的具體優勢:

    1. 提升代碼的安全性。

    2. 提升代碼的複用性。

    3. 「高內聚」:封裝細節,便於修改內部代碼,提升可維護性。

    4. 「低耦合」:簡化外部調用,便於調用者使用,便於擴展和協做。

  • Java是使用「訪問控制符」來控制哪些細節須要封裝,哪些細節須要暴露的。 Java中4種「訪問控制符」分別爲private、default、protected、public,它們說明了面向對象的封裝性,因此咱們要利用它們儘量的讓訪問權限降到最低,從而提升安全性。

    图1.png

    1. private 表示私有,只有本身類能訪問

    2. default表示沒有修飾符修飾,只有同一個包的類能訪問

    3. protected表示能夠被同一個包的類以及其餘包中的子類訪問

    4. public表示能夠被該項目的全部包中的全部類訪問

  • 類的屬性的處理:

    1. 通常使用private訪問權限。
  1. 提供相應的get/set方法來訪問相關屬性,這些方法一般是public修飾的,以提供對屬性的賦值與讀取操做(注意:boolean變量的get方法是is開頭!)。
  2. 一些只用於本類的輔助性方法能夠用private修飾,但願其餘類調用的方法用public修飾。

5. 多態(polymorphism)

  • 多態指的是同一個方法調用,因爲對象不一樣可能會有不一樣的行爲。

  • 多態的要點:

    1. 多態是方法的多態,不是屬性的多態(多態與屬性無關)。

    2. 多態的存在要有3個必要條件:繼承方法重寫父類引用指向子類對象

    3. 父類引用指向子類對象後,用該父類引用調用子類重寫的方法,此時多態就出現了。
    4. 多態最爲多見的一種用法,即父類引用作方法的形參,實參能夠是任意的子類對象,能夠經過不一樣的子類對象實現不一樣的行爲方式。
    5. 由此,咱們能夠看出多態的主要優點是提升了代碼的可擴展性,符合開閉原則。可是多態也有弊端,就是沒法調用子類特有的功能。

  • 對象的轉型(casting)

    • 父類引用指向子類對象,咱們稱這個過程爲向上轉型,屬於自動類型轉換。
    • 向上轉型後的父類引用變量只能調用它編譯類型的方法,不能調用它運行時類型的方法。這時,咱們就須要進行類型的強制轉換,咱們稱之爲向下轉型!
    • 在向下轉型過程當中,必須將引用變量轉成真實的子類類型(運行時類型)不然會出現類型轉換異常ClassCastException。

6. final關鍵字

  • 修飾變量: 被final修飾的變量不可改變。一旦賦了初值,就不能被從新賦值。
  • 修飾方法:該方法不可被子類重寫。可是能夠被重載!
  • 修飾類: 修飾的類不能被繼承。好比:Math、String等。(重寫就是同一個方法,子類和父類實現的功能不同,重載就是傳參類型啊個數啊,等等,不同的話,雖然名字相同,但也是兩個方法。)

7. 抽象方法和抽象類

  • 抽象方法:使用abstract修飾的方法,沒有方法體,只有聲明。定義的是一種「規範」,就是告訴子類必需要給抽象方法提供具體的實現。

  • 抽象類:包含抽象方法的類就是抽象類。經過abstract方法定義規範,而後要求子類必須定義具體實現。經過抽象類,咱們就能夠作到嚴格限制子類的設計,使子類之間更加通用。

  • 抽象類的使用要點:

    1. 有抽象方法的類只能定義成抽象類

    2. 抽象類不能實例化,即不能用new來實例化抽象類。

    3. 抽象類能夠包含屬性、方法、構造方法。可是構造方法不能用來new實例,只能用來被子類調用。
    4. 抽象類只能用來被繼承。

    5. 抽象方法必須被子類實現。

8. 接口

  • 爲何須要接口?接口和抽象類的區別?

    • 接口就是比「抽象類」還「抽象」的「抽象類」,能夠更加規範的對子類進行約束。全面地專業地實現了:規範和具體實現的分離。

    • 抽象類還提供某些具體實現,接口不提供任何實現,接口中全部方法都是抽象方法。接口是徹底面向規範的,規定了一批類具備的公共方法規範。

    • 從接口的實現者角度看,接口定義了能夠向外部提供的服務。

    • 從接口的調用者角度看,接口定義了實現者能提供哪些服務。

    • 接口是兩個模塊之間通訊的標準,通訊的規範。若是能把你要設計的模塊之間的接口定義好,就至關於完成了系統的設計大綱,剩下的就是添磚加瓦的具體實現了。你們在工做之後,作系統時每每就是使用「面向接口」的思想來設計系統。

    • 接口和實現類不是父子關係,是實現規則的關係。好比:我定義一個接口Runnable,Car實現它就能在地上跑,Train實現它也能在地上跑,飛機實現它也能在地上跑。就是說,若是它是交通工具,就必定能跑,可是必定要實現Runnable接口。

  • 接口的本質探討

    • 接口就是規範,定義的是一組規則,體現了現實世界中「若是你是…則必須能…」的思想。若是你是天使,則必須能飛。若是你是汽車,則必須能跑。若是你是好人,則必須能幹掉壞人;若是你是壞人,則必須欺負好人。

    • 接口的本質是契約,就像咱們人間的法律同樣。制定好後你們都遵照。

    • 面向對象的精髓,是對對象的抽象,最能體現這一點的就是接口。爲何咱們討論設計模式都只針對具有了抽象能力的語言(好比C++、Java、C#等),就是由於設計模式所研究的,實際上就是如何合理的去抽象。

  • 區別

    1. 普通類:具體實現

    2. 抽象類:具體實現,規範(抽象方法)

    3. 接口:規範!

  • 聲明格式:

    [訪問修飾符]  interface 接口名   [extends  父接口1,父接口2…]  {
    常量定義;  
    方法定義;
    }
  • 定義接口的詳細說明:

    1. 訪問修飾符:只能是public或默認。

    2. 接口名:和類名採用相同命名機制。

    3. extends:接口能夠多繼承。

    4. 常量:接口中的屬性只能是常量,老是:public static final 修飾。不寫也是。

    5. 方法:接口中的方法只能是:public abstract。 省略的話,也是public abstract。

  • 要點

    1. 子類經過implements來實現接口中的規範。

    2. 接口不能建立實例,可是可用於聲明引用變量類型

    3. 一個類實現了接口,必須實現接口中全部的方法,而且這些方法只能是public的。

    4. JDK1.7以前,接口中只能包含靜態常量、抽象方法,不能有普通屬性、構造方法、普通方法。

    5. JDK1.8後,接口中包含普通的靜態方法。

  • 接口的多繼承

    • 接口徹底支持多繼承。和類的繼承相似,子接口擴展某個父接口,將會得到父接口中所定義的一切。
    • 接口能夠多繼承(extends),類只能單繼承,但能夠實現(implements)多個接口。
  • 經過面向接口編程,而不是面向實現類編程,能夠大大下降程序模塊間的耦合性,提升整個系統的可擴展性和和可維護性。

  • 面向接口編程的概念比接口自己的概念要大得多。設計階段相對比較困難,在你沒有寫實現時就要想好接口,接口一變就亂套了,因此設計要比實現難!

9. 內部類

  • 通常狀況,咱們把類定義成獨立的單元。有些狀況下,咱們把一個類放在另外一個類的內部定義,稱爲內部類(innerclasses)

  • 內部類能夠使用public、default、protected 、private以及static修飾。而外部頂級類(咱們之前接觸的類)只能使用public和default修飾。

  • 注意:內部類只是一個編譯時概念,一旦咱們編譯成功,就會成爲徹底不一樣的兩個類。對於一個名爲Outer的外部類和其內部定義的名爲Inner的內部類。編譯完成後會出現Outer.classOuter$Inner.class兩個類的字節碼文件。因此內部類是相對獨立的一種存在,其成員變量/方法名能夠和外部類的相同。

  • 內部類的做用:

    1. 內部類提供了更好的封裝。只能讓外部類直接訪問,不容許同一個包中的其餘類直接訪問。

    2. 內部類能夠直接訪問外部類的私有屬性,內部類被當成其外部類的成員。 但外部類不能訪問內部類的內部屬性。

    3. 接口只是解決了多重繼承的部分問題,而內部類使得多重繼承的解決方案變得更加完整

  • 內部類的使用場合:

    1. 因爲內部類提供了更好的封裝特性,而且能夠很方便的訪問外部類的屬性。因此,在只爲外部類提供服務的狀況下能夠優先考慮使用內部類。

    2. 使用內部類間接實現多繼承:每一個內部類都能獨立地繼承一個類或者實現某些接口,因此不管外部類是否已經繼承了某個類或者實現了某些接口,對於內部類沒有任何影響。

  • 內部類的分類

    • 在Java中內部類主要分爲成員內部類(非靜態內部類、靜態內部類)、匿名內部類、局部內部類。

    • 成員內部類(能夠使用private、default、protected、public任意進行修飾。 類文件:外部類$內部類.class)

      • 非靜態內部類(外部類裏使用非靜態內部類和平時使用其餘類沒什麼不一樣)

        • 非靜態內部類必須寄存在一個外部類對象裏。所以,若是有一個非靜態內部類對象那麼必定存在對應的外部類對象。非靜態內部類對象單獨屬於外部類的某個對象。

        • 非靜態內部類能夠直接訪問外部類的成員,可是外部類不能直接訪問非靜態內部類成員。

        • 非靜態內部類不能有靜態方法、靜態屬性和靜態初始化塊。

        • 外部類的靜態方法、靜態代碼塊不能訪問非靜態內部類,包括不能使用非靜態內部類定義變量、建立實例。

        • 成員變量訪問要點:

          1. 內部類裏方法的局部變量:變量名。

          2. 內部類屬性:this.變量名。

          3. 外部類屬性:外部類名.this.變量名。

          class Outer {
              private int age = 10;
              class Inner {
                  int age = 20;
                  public void show() {
                      int age = 30;
                      System.out.println("內部類方法裏的局部變量age:" + age);// 30
                      System.out.println("內部類的成員變量age:" + this.age);// 20
                      System.out.println("外部類的成員變量age:" + Outer.this.age);// 10
                  }
              }
          }
        • 內部類的訪問:

          1. 外部類中定義內部類

            new Inner()
          2. 外部類之外的地方使用非靜態內部類

            Outer.Inner  varname = new Outer().new Inner()
      • 靜態內部類

        • 定義方式

          static  class   ClassName {
          //類體
          }
        • 使用要點

          1. 當一個靜態內部類對象存在,並不必定存在對應的外部類對象。 所以,靜態內部類的實例方法不能直接訪問外部類的實例方法。

          2. 靜態內部類看作外部類的一個靜態成員。 所以,外部類的方法中能夠經過:「靜態內部類.名字」的方式訪問靜態內部類的靜態成員,經過 new 靜態內部類()訪問靜態內部類的實例。

          class Outer{
              //至關於外部類的一個靜態成員
              static class Inner{
              }
          }
          public class TestStaticInnerClass {
              public static void main(String[] args) {
                  //經過 new 外部類名.內部類名() 來建立內部類對象
                  Outer.Inner inner =new Outer.Inner();
              }
          }
    • 匿名內部類

      • 適合那種只須要使用一次的類。好比:鍵盤監聽操做等等。

        new  父類構造器(實參類表) \實現接口 () {
                   //匿名內部類類體!
        }
        this.addWindowListener(new WindowAdapter(){
                @Override
                public void windowClosing(WindowEvent e) {
                    System.exit(0);
                }
            }
        );
        this.addKeyListener(new KeyAdapter(){
                @Override
                public void keyPressed(KeyEvent e) {
                    myTank.keyPressed(e);
                }      
                @Override
                public void keyReleased(KeyEvent e) {
                    myTank.keyReleased(e);
                }
            }
        );
      • 注意

        1. 匿名內部類沒有訪問修飾符。

        2. 匿名內部類沒有構造方法。由於它連名字都沒有那又何來構造方法呢。

    • 局部內部類

      • 還有一種內部類,它是定義在方法內部的,做用域只限於本方法,稱爲局部內部類。

      • 局部內部類的的使用主要是用來解決比較複雜的問題,想建立一個類來輔助咱們的解決方案,到那時又不但願這個類是公共可用的,因此就產生了局部內部類。局部內部類和成員內部類同樣被編譯,只是它的做用域發生了改變,它只能在該方法中被使用,出了該方法就會失效。

      • 局部內部類在實際開發中應用不多。

        public class Test2 {
            public void show() {
                //做用域僅限於該方法
                class Inner {
                    public void fun() {
                        System.out.println("helloworld");
                    }
                }
                new Inner().fun();
            }
            public static void main(String[] args) {
                new Test2().show();
            }
        }

10. String

  • String基礎

    1. String類又稱做不可變字符序列。

    2. String位於java.lang包中,Java程序默認導入java.lang包下的全部類。

    3. Java字符串就是Unicode字符序列,例如字符串「Java」就是4個Unicode字符’J’、’a’、’v’、’a’組成的。

    4. Java沒有內置的字符串類型,而是在標準Java類庫中提供了一個預約義的類String,每一個用雙引號括起來的字符串都是String類的一個實例。
    5. Java容許使用符號"+"把兩個字符串鏈接起來。

  • String類和常量池

    • 在Java的內存分析中,咱們會常常聽到關於「常量池」的描述,實際上常量池也分了如下三種:

      1. 全局字符串常量池(String Pool):全局字符串常量池中存放的內容是在類加載完成後存到String Pool中的,在每一個VM中只有一份,存放的是字符串常量的引用值(在堆中生成字符串對象實例)。

      2. class文件常量池(Class Constant Pool):class常量池是在編譯的時候每一個class都有的,在編譯階段,存放的是常量(文本字符串、final常量等)和符號引用。

      3. 運行時常量池(Runtime Constant Pool):運行時常量池是在類加載完成以後,將每一個class常量池中的符號引用值轉存到運行時常量池中,也就是說,每一個class都有一個運行時常量池,類在解析以後,將符號引用替換成直接引用,與全局常量池中的引用值保持一致。

      String str1 = "abc";
      String str2 = new String("def");
      String str3 = "abc";
      String str4 = str2.intern();
      String str5 = "def";
      System.out.println(str1 == str3);// true
      System.out.println(str2 == str4);// false
      System.out.println(str4 == str5);// true
    • 示例首先通過編譯以後,在該類的class常量池中存放一些符號引用,而後類加載以後,將class常量池中存放的符號引用轉存到運行時常量池中,而後通過驗證,準備階段以後,在堆中生成駐留字符串的實例對象(也就是上例中str1所指向的「abc」實例對象),而後將這個對象的引用存到全局String Pool中,也就是String Pool中,最後在解析階段,要把運行時常量池中的符號引用替換成直接引用,那麼就直接查詢String Pool,保證String Pool裏的引用值與運行時常量池中的引用值一致,大概整個過程就是這樣了。

    • 回到示例程序,如今就很容易解釋整個程序的內存分配過程了,首先,在堆中會有一個「abc」實例,全局String Pool中存放着「abc」的一個引用值,而後在運行第二句的時候會生成兩個實例,一個是「def」的實例對象,而且String Pool中存儲一個「def」的引用值,還有一個是new出來的一個「def」的實例對象,與上面那個是不一樣的實例,當在解析str3的時候查找String Pool,裏面有「abc」的全局駐留字符串引用,因此str3的引用地址與以前的那個已存在的相同,str4是在運行的時候調用intern()函數,返回String Pool中「def」的引用值,若是沒有就將str2的引用值添加進去,在這裏,String Pool中已經有了「def」的引用值了,因此返回上面在new str2的時候添加到String Pool中的 「def」引用值,最後str5在解析的時候就也是指向存在於String Pool中的「def」的引用值,那麼這樣一分析以後,結果就容易理解了。

  • String類的經常使用方法

    图2.png

11. 補充內容

  • 開閉原則
    • 開閉原則(Open-Closed Principle)就是讓設計的系統對擴展開放,對修改封閉。
    • 對擴展開放:就是指,應對需求變化要靈活。 要增長新功能時,不須要修改已有的代碼,增長新代碼便可。
    • 對修改關閉:就是指,核心部分通過精心設計後,再也不由於需求變化而改變。
  • 模板方法模式和回調機制
    • 板方法模式很經常使用,其目的是在一個方法中定義一個算法骨架,而將一些步驟延遲到子類中。模板方法使得子類能夠在不改變算法結構的狀況下,從新定義算法的某些步驟。在標準的模板方法模式實現中,主要是使用繼承的方式,來讓父類在運行期間能夠調用到子類的方法。
    • 其實在Java開發中,還有另一個方法能夠實現一樣的功能,那就是Java回調技術。回調是一種雙向的調用模式,也就是說,被調用的接口被調用時也會調用對方的接口,簡單點說明就是:A類中調用B類中的C方法,而後B類中的C方法中反過來調用A類中的D方法,那麼D這個方法就叫回調方法。
    • 經過回調在接口中定義的方法,調用到具體的實現類中的方法,其本質是利用Java的動態綁定技術,在這種實現中,能夠不把實現類寫成單獨的類,而使用內部類或匿名內部類來實現回調方法。
    • 理解的話參考文獻:Java設計模式——模板方法模式
  • 組合模式
    • 組合模式是將對象組合成樹形結構以表示「部分-總體」的層次結構。組合模式使得用戶對單個對象和組合對象的使用具備一致性。

第6章 異常機制

1. 異常

  • 異常機制本質:就是當程序出現錯誤,程序安全退出的機制。

    try {
        copyFile("d:/a.txt","e:/a.txt");
    } catch (Exception e) {
        e.printStackTrace();
    }
  • 異常(Exception)的概念

    • 異常指程序運行過程當中出現的非正常現象,例如用戶輸入錯誤、除數爲零、須要處理的文件不存在、數組下標越界等。

    • 在Java的異常處理機制中,引進了不少用來描述和處理異常的類,稱爲異常類。異常類定義中包含了該類異常的信息和對異常進行處理的方法。

    • 所謂異常處理,就是指程序在出現問題時依然能夠正確的執行完。

    • Java是採用面向對象的方式來處理異常的。處理過程:

      1. 拋出異常:在執行一個方法時,若是發生異常,則這個方法生成表明該異常的一個對象,中止當前執行路徑,並把異常對象提交給JRE。

      2. 捕獲異常:JRE獲得該異常後,尋找相應的代碼來處理該異常。JRE在方法的調用棧中查找,從生成異常的方法開始回溯,直到找到相應的異常處理代碼爲止。

  • 異常分類

    Java對異常進行了分類,不一樣類型的異常分別用不一樣的Java類表示,全部異常的根類爲java.lang.Throwable,Throwable下面又派生了兩個子類:Error和Exception。Java異常類的層次結構如圖所示。

    圖6-2 Java異常類層次結構圖.png

    • Error
      • Error是程序沒法處理的錯誤,表示運行應用程序中較嚴重問題。大多數錯誤與代碼編寫者執行的操做無關,而表示代碼運行時 JVM(Java 虛擬機)出現的問題。例如,Java虛擬機運行錯誤(Virtual MachineError),當 JVM 再也不有繼續執行操做所需的內存資源時,將出現 OutOfMemoryError。這些異常發生時,Java虛擬機(JVM)通常會選擇線程終止。
      • Error代表系統JVM已經處於不可恢復的崩潰狀態中。咱們不須要管它。
    • Exception

      • Exception是程序自己可以處理的異常,如:空指針異常(NullPointerException)、數組下標越界異常(ArrayIndexOutOfBoundsException)、類型轉換異常(ClassCastException)、算術異常(ArithmeticException)等。

      • Exception類是全部異常類的父類,其子類對應了各類各樣可能出現的異常事件。 一般Java的異常可分爲:

        1. RuntimeException 運行時異常

          • 派生於RuntimeException的異常,如被 0 除、數組下標越界、空指針等,其產生比較頻繁,處理麻煩,若是顯式的聲明或捕獲將會對程序可讀性和運行效率影響很大。 所以由系統自動檢測並將它們交給缺省的異常處理程序(用戶可沒必要對其處理)。

          • 這類異常一般是由編程錯誤致使的,因此在編寫程序時,並不要求必須使用異常處理機制來處理這類異常,常常須要經過增長「邏輯處理來避免這些異常」。

          • 在引用數據類型轉換時,有可能發生類型轉換異常(ClassCastException)。

          • 當程序訪問一個數組的某個元素時,若是這個元素的索引超出了0~數組長度-1這個範圍,則會出現數組下標越界異常(ArrayIndexOutOfBoundsException)。

          • 在使用包裝類將字符串轉換成基本數據類型時,若是字符串的格式不正確,則會出現數字格式異常(NumberFormatException)。

          • 注意事項

            1. 在方法拋出異常以後,運行時系統將轉爲尋找合適的異常處理器(exception handler)。潛在的異常處理器是異常發生時依次存留在調用棧中的方法的集合。當異常處理器所能處理的異常類型與方法拋出的異常類型相符時,即爲合適的異常處理器。

            2. 運行時系統從發生異常的方法開始,依次回查調用棧中的方法,直至找到含有合適異常處理器的方法並執行。當運行時系統遍歷調用棧而未找到合適的異常處理器,則運行時系統終止。同時,意味着Java程序的終止。

        2. CheckedException 已檢查異常
          • 全部不是RuntimeException的異常,統稱爲Checked Exception,又被稱爲「已檢查異常」,如IOException、SQLException等以及用戶自定義的Exception異常。 這類異常在編譯時就必須作出處理,不然沒法經過編譯。
          • 異常的處理方式有兩種:使用「try/catch」捕獲異常、使用「throws」聲明異常。

2. 處理異常

  • 異常的處理方式之一:捕獲異常

    • 捕獲異常是經過3個關鍵詞來實現的:try-catch-finally。用try來執行一段程序,若是出現異常,系統拋出一個異常,能夠經過它的類型來捕捉(catch)並處理它,最後一步是經過finally語句爲異常處理提供一個統一的出口,finally所指定的代碼都要被執行(catch語句可有多條;finally語句最多隻能有一條,根據本身的須要無關緊要)。圖6-10 異常處理.png

    • 1. try:

      • try語句指定了一段代碼,該段代碼就是異常捕獲並處理的範圍。在執行過程當中,當任意一條語句產生異常時,就會跳過該條語句中後面的代碼。代碼中可能會產生並拋出一種或幾種類型的異常對象,它後面的catch語句要分別對這些異常作相應的處理。

      • 一個try語句必須帶有至少一個catch語句塊或一個finally語句塊 。

      注意事項

      • 當異常處理的代碼執行結束之後,不會回到try語句去執行還沒有執行的代碼
    • 2. catch:

      • 每一個try語句塊能夠伴隨一個或多個catch語句,用於處理可能產生的不一樣類型的異常對象。

      • 經常使用方法,這些方法均繼承自Throwable類 。

      • toString()方法,顯示異常的類名和產生異常的緣由

      • getMessage()方法,只顯示產生異常的緣由,但不顯示類名。

      • printStackTrace()方法,用來跟蹤異常事件發生時堆棧的內容。

      • catch捕獲異常時的捕獲順序:若是異常類之間有繼承關係,在順序安排上需注意。越是頂層的類,越放在下面,再否則就直接把多餘的catch省略掉。也就是先捕獲子類異常再捕獲父類異常。

    • 3. finally:

      • 有些語句,無論是否發生了異常,都必需要執行,那麼就能夠把這樣的語句放到finally語句塊中。

      • 一般在finally中關閉程序塊已打開的資源,好比:關閉文件流、釋放數據庫鏈接等。

    • try-catch-finally語句塊的執行過程:程序首先執行可能發生異常的try語句塊。若是try語句沒有出現異常則執行完後跳至finally語句塊執行;若是try語句出現異常,則中斷執行並根據發生的異常類型跳至相應的catch語句塊執行處理。catch語句塊能夠有多個,分別捕獲不一樣類型的異常。catch語句塊執行完後程序會繼續執行finally語句塊。finally語句是可選的,若是有的話,則無論是否發生異常,finally語句都會被執行。

    • 注意事項

      1. 即便try和catch塊中存在return語句,finally語句也會執行。是在執行完finally語句後再經過return退出。

      2. finally語句塊只有一種狀況是不會執行的,那就是在執行finally以前遇到了System.exit(0)結束程序運行。

  • 異常的處理方式之二:聲明異常(throws子句)

    • 當CheckedException產生時,不必定馬上處理它,能夠再把異常throws出去。
    • 在方法中使用try-catch-finally是由這個方法來處理異常。可是在一些狀況下,當前方法並不須要處理髮生的異常,而是向上傳遞給調用它的方法處理。
    • 若是一個方法中可能產生某種異常,可是並不能肯定如何處理這種異常,則應根據異常規範在方法的首部聲明該方法可能拋出的異常。
    • 若是一個方法拋出多個已檢查異常,就必須在方法的首部列出全部的異常,之間以逗號隔開。
    • 注意事項:方法重寫中聲明異常原則:子類重寫父類方法時,若是父類方法有聲明異常,那麼子類聲明的異常範圍不能超過父類聲明的範圍。

3. 自定義異常

  1. 在程序中,可能會遇到JDK提供的任何標準異常類都沒法充分描述清楚咱們想要表達的問題,這種狀況下能夠建立本身的異常類,即自定義異常類。
  2. 自定義異常類只需從Exception類或者它的子類派生一個子類便可。
  3. 自定義異常類若是繼承Exception類,則爲受檢查異常,必須對其進行處理;若是不想處理,可讓自定義異常類繼承運行時異常RuntimeException類。
  4. 習慣上,自定義異常類應該包含2個構造器:一個是默認的構造器,另外一個是帶有詳細信息的構造器。
  5. 使用異常機制的建議
    1. 要避免使用異常處理代替錯誤處理,這樣會下降程序的清晰性,而且效率低下。
    2. 處理異常不能夠代替簡單測試---只在異常狀況下使用異常機制。
    3. 不要進行小粒度的異常處理---應該將整個任務包裝在一個try語句塊中。
    4. 異常每每在高層處理 。

第7章 數組

1. 數組概述和特色

  • 數組的定義

    數組是相同類型數據的有序集合。數組描述的是相同類型的若干個數據,按照必定的前後次序排列組合而成。其中,每個數據稱做一個元素,每一個元素能夠經過一個索引(下標)來訪問它們。數組的三個基本特色:

    1. 長度是肯定的。數組一旦被建立,它的大小就是不能夠改變的。

    2. 其元素必須是相同類型,不容許出現混合類型。

    3. 數組類型能夠是任何數據類型,包括基本類型和引用類型。

  • 數組變量屬引用類型,數組也能夠當作是對象,數組中的每一個元素至關於該對象的成員變量。數組自己就是對象,Java中對象是在堆中的,所以數組不管保存原始類型仍是其餘對象類型,數組對象自己是在堆中存儲的。

  • 數組聲明

    • 數組的聲明方式有兩種(以一維數組爲例)

      type[]   arr_name; //(推薦使用這種方式)
      type    arr_name[];
    • 注意事項

      1. 聲明的時候並無實例化任何對象,只有在實例化數組對象時,JVM才分配空間,這時才與長度有關。

      2. 聲明一個數組的時候並無數組真正被建立。

      3. 構造一個數組,必須指定長度。

    圖7-1 基本類型數組內存分配圖.png

  • 初始化

    ​ 數組的初始化方式總共有三種:靜態初始化、動態初始化、默認初始化。下面針對這三種方式分別講解。

    1. 靜態初始化

    ​ 除了用new關鍵字來產生數組之外,還能夠直接在定義數組的同時就爲數組元素分配空間並賦值。

    int[] a = { 1, 2, 3 };// 靜態初始化基本類型數組;
    Man[] mans = { new Man(1, 1), new Man(2, 2) };// 靜態初始化引用類型數組;

    2.動態初始化

    ​ 數組定義與爲數組元素分配空間並賦值的操做分開進行。

    int[] a1 = new int[2];//動態初始化數組,先分配空間;
    a1[0]=1;//給數組元素賦值;
    a1[1]=2;//給數組元素賦值;

    3.數組的默認初始化

    ​ 數組是引用類型,它的元素至關於類的實例變量,所以數組一經分配空間,其中的每一個元素也被按照實例變量一樣的方式被隱式初始化。

    int a2[] = new int[2]; // 默認值:0,0
    boolean[] b = new boolean[2]; // 默認值:false,false
    String[] s = new String[2]; // 默認值:null, null

2. 數組的遍歷

  • 數組元素下標的合法區間:[0, length-1]。咱們能夠經過下標來遍歷數組中的元素,遍歷時能夠讀取元素的值或者修改元素的值。使用循環遍歷初始化和讀取數組。

    public class Test {
        public static void main(String[] args) {
            int[] a = new int[4];
            //初始化數組元素的值
            for(int i=0;i<a.length;i++){
                a[i] = 100*i;
            }
            //讀取元素的值
            for(int i=0;i<a.length;i++){
                System.out.println(a[i]);
            }
        }
    }
  • for-each循環:加強for循環for-each是JDK1.5新增長的功能,專門用於讀取數組或集合中全部的元素,即對數組進行遍歷。

    public class Test {
        public static void main(String[] args) {
            String[] ss = { "aa", "bbb", "ccc", "ddd" };
            for (String temp : ss) {
                System.out.println(temp);
            }
        }
    }
  • 注意事項

    1. for-each加強for循環在遍歷數組過程當中不能修改數組中某元素的值。

    2. for-each僅適用於遍歷,不涉及有關索引(下標)的操做。

3. 數組的拷貝

  • System類裏也包含了一個static void arraycopy(object src,int srcpos,object dest, int destpos,int length)方法,該方法能夠將src數組裏的元素值賦給dest數組的元素,其中srcpos指定從src數組的第幾個元素開始賦值,length參數指定將src數組的多少個元素賦給dest數組的元素。

    public class testCopy {
        public static void main(String[] args) {
            String[] s1 = {"aa", "bb", "cc", "dd", "ee"};
            String[] s2 = new String[10];
            System.arraycopy(s1, 2, s2, 6, 3);
    
            for(int i=0; i<s2.length; i++){
                System.out.println(i+"--"+s2[i]);
            }
    
            System.out.println("########");
            removeElement(s1, 2);
            System.out.println("########");
            extendRange(s1);
        }
    
        // 刪除數組中指定索引位置的元素,並將原數組返回
        private static String[] removeElement(String[] s, int index){
            System.arraycopy(s, index+1, s, index, s.length-index-1);
            s[s.length-1] = null;
    
            for (String m:
                 s) {
                System.out.println(m);
            }
            return s;
        }
    
        // 數組的擴容(本質上時:先定義一個更大的數組,而後將原數組內容原封不動拷貝到新數組中)
        private static String[] extendRange(String[] s){
            String[] s2 = new String[s.length + 10];
            System.arraycopy(s, 0, s2, 0, s.length);
    
            for(String x: s2){
                System.out.println(x);
            }
            return s2;
        }
    }

4. java.util.Arrays類

  • JDK提供的java.util.Arrays類,包含了經常使用的數組操做,方便咱們平常開發。Arrays類包含了:排序、查找、填充、打印內容等常見的操做。

    import java.util.Arrays;
    
    public class TestArrays {
        public static void main(String[] args) {
            int[] a = {100, 20, 30, 5, 150, 80, 200};
            System.out.println(a);
            System.out.println(Arrays.toString(a));
            Arrays.sort(a);
            System.out.println(Arrays.toString(a));
    
            // 二分法查找
            System.out.println(Arrays.binarySearch(a, 30));
    
            // 數組填充,前閉後開
            Arrays.fill(a, 2, 4, 800);
            System.out.println(Arrays.toString(a));
        }
    }

5. 多維數組

  • 多維數組能夠當作以數組爲元素的數組。能夠有二維、三維、甚至更多維數組,可是實際開發中用的很是少。最多到二維數組(學習容器後,咱們通常使用容器,二維數組用的都不多)。

    • 二維數組的聲明

      public class Test {
          public static void main(String[] args) {
              // Java中多維數組的聲明和初始化應按從低維到高維的順序進行
              int[][] a = new int[3][];
              a[0] = new int[2];
              a[1] = new int[4];
              a[2] = new int[3];
              // int a1[][]=new int[][4];//非法
          }
      }
    • 二維數組的靜態初始化

      public class Test {
          public static void main(String[] args) {
              int[][] a = { { 1, 2, 3 }, { 3, 4 }, { 3, 5, 6, 7 } };
              System.out.println(a[2][3]);
          }
      }
    • 二維數組的動態初始化

      import java.util.Arrays;
      public class Test {
          public static void main(String[] args) {
              int[][] a = new int[3][];
              // a[0] = {1,2,5}; //錯誤,沒有聲明類型就初始化
              a[0] = new int[] { 1, 2 };
              a[1] = new int[] { 2, 2 };
              a[2] = new int[] { 2, 2, 3, 4 };
              System.out.println(a[2][3]);
              System.out.println(Arrays.toString(a[0]));
              System.out.println(Arrays.toString(a[1]));
              System.out.println(Arrays.toString(a[2]));
          }
      }
    • 獲取數組長度

      //獲取的二維數組第一維數組的長度。
      System.out.println(a.length);
      //獲取第二維第一個數組長度。
      System.out.println(a[0].length);
    • 數組存儲表格數據

      import java.util.Arrays;
      public class Test {
          public static void main(String[] args) {
              Object[] a1 = {1001,"高淇",18,"講師","2006-2-14"};
              Object[] a2 = {1002,"高小七",19,"助教","2007-10-10"};
              Object[] a3 = {1003,"高小琴",20,"班主任","2008-5-5"};
              Object[][]  emps = new Object[3][];
              emps[0] = a1;
              emps[1] = a2;
              emps[2] = a3;
              System.out.println(Arrays.toString(emps[0]));
              System.out.println(Arrays.toString(emps[1]));
              System.out.println(Arrays.toString(emps[2]));  
          }
      }
      • 注意事項:此處基本數據類型」1001」,本質不是Object對象。JAVA編譯器會自動把基本數據類型「自動裝箱」成包裝類對象。你們在下一章學了包裝類後就懂了。

6. 冒泡排序和二分法查找

  • 冒泡排序

    import java.util.Arrays;
    
    public class TestBubbleSort {
        public static void main(String[] args) {
            int[] values = {3, 1, 6, 2, 9, 0, 7, 4, 5, 8};
            bubble(values);
            bubble_improve(values);
        }
    
        public static void bubble(int[] values){
            /*冒泡排序*/
            int temp = 0;
    
            // 遍歷前n-1個數
            for (int i = 0; i < values.length-1; i++) {
                // 每次循環都把最大值放到後面,因此後面的就沒必要在比較了
                for (int j = 0; j < values.length-1-i; j++) {
                    // 若是前一個值大於後一個值,則交換位置
                    if(values[j]>values[j+1]){
                        temp = values[j];
                        values[j] = values[j+1];
                        values[j+1] = temp;
                    }
                }
            }
            System.out.println(Arrays.toString(values));
        }
    
        public static void bubble_improve(int[] values){
            /* 改良冒泡排序 */
            /* 也就是當一次外循環沒有發生交換的時候,那麼中止 */
            int temp = 0;
    
            // 遍歷前n-1個數
            for (int i = 0; i < values.length-1; i++) {
                // 每次循環都把最大值放到後面,因此後面的就沒必要在比較了
                boolean flag = true;
                for (int j = 0; j < values.length-1-i; j++) {
                    // 若是前一個值大於後一個值,則交換位置
                    if(values[j]>values[j+1]){
                        temp = values[j];
                        values[j] = values[j+1];
                        values[j+1] = temp;
    
                        flag = false;
                    }
                }
    
                if(flag){
                    break;
                }
            }
            System.out.println(Arrays.toString(values));
        }
    }
    • 冒泡排序算法的運做以下:

      1. 比較相鄰的元素。若是第一個比第二個大,就交換他們兩個。

      2. 對每一對相鄰元素做一樣的工做,從開始第一對到結尾的最後一對。在這一點,最後的元素應該會是最大的數。

      3. 針對全部的元素重複以上的步驟,除了最後一個。

      4. 持續每次對愈來愈少的元素重複上面的步驟,直到沒有任何一對數字須要比較。

    • 優化冒泡排序:

      1. 整個數列分紅兩部分:前面是無序數列,後面是有序數列。

      2. 初始狀態下,整個數列都是無序的,有序數列是空。

      3. 每一趟循環可讓無序數列中最大數排到最後,(也就是說有序數列的元素個數增長1),也就是不用再去顧及有序序列。

      4. 每一趟循環都從數列的第一個元素開始進行比較,依次比較相鄰的兩個元素,比較到無序數列的末尾便可(而不是數列的末尾);若是前一個大於後一個,交換。

      5. 判斷每一趟是否發生了數組元素的交換,若是沒有發生,則說明此時數組已經有序,無需再進行後續趟數的比較了。此時能夠停止比較。

  • 二分法查找

    import java.util.Arrays;
    
    public class TestBinarySearch {
        public static void main(String[] args) {
            int[] arr = {30, 20, 50 ,10, 80, 9, 7, 12, 100, 40, 8};
            Arrays.sort(arr);
            System.out.println(Arrays.toString(arr));
            System.out.println(myBinarySearch(arr, 400));
        }
    
        public static int myBinarySearch(int[] arr, int value){
            /* 二分法查找 */
            int low = 0;
            int high = arr.length - 1;
    
            while (low <= high){
                int mid = (low + high)/2;
    
                if (value == arr[mid]){
                    return mid;
                }
    
                if (value > arr[mid]){
                    low = mid + 1;
                }
    
                if (value < arr[mid]){
                    high = mid - 1;
                }
                System.out.println(low + "+" + high);
    
            }
            return -1;
        }
    }
    • 分法檢索(binary search)又稱折半檢索,二分法檢索的基本思想是設數組中的元素從小到大有序地存放在數組(array)中,首先將給定值key與數組中間位置上元素的關鍵碼(key)比較,若是相等,則檢索成功;
    • 不然,若key小,則在數組前半部分中繼續進行二分法檢索;
    • 若key大,則在數組後半部分中繼續進行二分法檢索。
    • 這樣,通過一次比較就縮小一半的檢索區間,如此進行下去,直到檢索成功或檢索失敗。
    • 二分法檢索是一種效率較高的檢索方法。

第8章 經常使用類

1. 包裝類

  • Java是面向對象的語言,但並非「純面向對象」的,由於咱們常常用到的基本數據類型就不是對象。可是咱們在實際應用中常常須要將基本數據轉化成對象,以便於操做。好比:將基本數據類型存儲到Object[]數組或集合中的操做等等。

  • 爲了解決這個不足,Java在設計類時爲每一個基本數據類型設計了一個對應的類進行表明,這樣八個和基本數據類型對應的類統稱爲包裝類(Wrapper Class)。

  • 在這八個類名中,除了Integer和Character類之外,其它六個類的類名和基本數據類型一致,只是類名的第一個字母大寫而已。

  • 包裝類的用途

    • 對於包裝類來講,這些類的用途主要包含兩種:

      1. 做爲和基本數據類型對應的類型存在,方便涉及到對象的操做,如Object[]、集合等的操做。

      2. 包含每種基本數據類型的相關屬性如最大值、最小值等,以及相關的操做方法(這些操做方法的做用是在基本數據類型、包裝類對象、字符串之間提供相互之間的轉化!)。

  • 自動裝箱和拆箱

    • 自動裝箱和拆箱就是將基本數據類型和包裝類之間進行自動的互相轉換。JDK1.5後,Java引入了自動裝箱(autoboxing)/拆箱(unboxing)。

    • 自動裝箱:基本類型的數據處於須要對象的環境中時,會自動轉爲「對象」。

      Integer i = 100;//自動裝箱
      //至關於編譯器自動爲您做如下的語法編譯:
      Integer i = Integer.valueOf(100);//調用的是valueOf(100),而不是new Integer(100)
    • 自動拆箱:每當須要一個值時,對象會自動轉成基本數據類型,不必再去顯式調用intValue()、doubleValue()等轉型方法。

      Integer i = 100;
      int j = i;//自動拆箱
      //至關於編譯器自動爲您做如下的語法編譯:
      int j = i.intValue();
    • 咱們能夠用一句話總結自動裝箱/拆箱:自動裝箱過程是經過調用包裝類的valueOf()方法實現的,而自動拆箱過程是經過調用包裝類的xxxValue()方法實現的(xxx表明對應的基本數據類型,如intValue()、doubleValue()等)。自動裝箱與拆箱的功能事實上是編譯器來幫的忙,編譯器在編譯時依據您所編寫的語法,決定是否進行裝箱或拆箱動做。

  • 包裝類空指針異常問題

    public class Test1 {
        public static void main(String[] args) {
            Integer i = null;
            int j = i;
        }
    }
    • null表示i沒有指向任何對象的實體,但做爲對象名稱是合法的(無論這個對象名稱存是否指向了某個對象的實體)。因爲實際上i並無指向任何對象的實體,因此也就不可能操做intValue()方法,這樣上面的寫法在運行時就會出現NullPointerException錯誤。
  • 包裝類的緩存問題

    • 整型、char類型所對應的包裝類,在自動裝箱時,對於-128~127之間的值會進行緩存處理,其目的是提升效率。

    • 緩存處理的原理爲:若是數據在-128~127這個區間,那麼在類加載時就已經爲該區間的每一個數值建立了對象,並將這256個對象存放到一個名爲cache的數組中。每當自動裝箱過程發生時(或者手動調用valueOf()時),就會先判斷數據是否在該區間,若是在則直接獲取數組中對應的包裝類對象的引用,若是不在該區間,則會經過new調用包裝類的構造方法來建立對象。

      public class Test3 {
          public static void main(String[] args) {
              Integer in1 = -128;
              Integer in2 = -128;
              System.out.println(in1 == in2);//true 由於123在緩存範圍內
              System.out.println(in1.equals(in2));//true
              Integer in3 = 1234;
              Integer in4 = 1234;
              System.out.println(in3 == in4);//false 由於1234不在緩存範圍內
              System.out.println(in3.equals(in4));//true
          }
      }
    • 內存分析

      圖8-7 示例8-9的內存分析圖.png

  • 注意

    1. JDK1.5之後,增長了自動裝箱與拆箱功能,如:

      Integer i = 100;  int j = new Integer(100);
    2. 自動裝箱調用的是valueOf()方法,而不是new Integer()方法。

    3. 自動拆箱調用的xxxValue()方法。

    4. 包裝類在自動裝箱時爲了提升效率,對於-128~127之間的值會進行緩存處理。超過範圍後,對象之間不能再使用==進行數值的比較,而是使用equals方法。

2. String類

  • String 類對象表明不可變的Unicode字符序列,所以咱們能夠將String對象稱爲「不可變對象」。 那什麼叫作「不可變對象」呢?指的是對象內部的成員變量的值沒法再改變。

  • 咱們發如今前面學習String的某些方法,好比:substring()是對字符串的截取操做,但本質是讀取原字符串內容生成了新的字符串

  • 在遇到字符串常量之間的拼接時,編譯器會作出優化,即在編譯期間就會完成字符串的拼接。所以,在使用==進行String對象之間的比較時,咱們須要特別注意。

    public class TestString2 {
        public static void main(String[] args) {
            //編譯器作了優化,直接在編譯的時候將字符串進行拼接
            String str1 = "hello" + " java";//至關於str1 = "hello java";
            String str2 = "hello java";
            System.out.println(str1 == str2);//true
            String str3 = "hello";
            String str4 = " java";
            //編譯的時候不知道變量中存儲的是什麼,因此沒辦法在編譯的時候優化
            String str5 = str3 + str4;
            System.out.println(str2 == str5);//false
        }
    }
  • String類經常使用的方法有:

    1. String類的下述方法能建立並返回一個新的String對象: concat()、 replace()、substring()、 toLowerCase()、 toUpperCase()、trim()。

    2. 提供查找功能的有關方法: endsWith()、 startsWith()、 indexOf()、lastIndexOf()。

    3. 提供比較功能的方法: equals()、equalsIgnoreCase()、compareTo()。

    4. 其它方法: charAt() 、length()。

  • StringBuffer和StringBuilder

    • StringBuffer和StringBuilder很是相似,均表明可變的字符序列。 這兩個類都是抽象類AbstractStringBuilder的子類,方法幾乎如出一轍。

      1. StringBuffer JDK1.0版本提供的類,線程安全,作線程同步檢查, 效率較低。

      2. StringBuilder JDK1.5版本提供的類,線程不安全,不作線程同步檢查,所以效率較高。 建議採用該類。

    • 經常使用方法列表:

      1. 重載的public StringBuilder append(…)方法:能夠爲該StringBuilder 對象添加字符序列,仍然返回自身對象。

      2. 方法 public StringBuilder delete(int start,int end):能夠刪除從start開始到end-1爲止的一段字符序列,仍然返回自身對象。

      3. 方法 public StringBuilder deleteCharAt(int index):移除此序列指定位置上的 char,仍然返回自身對象。

      4. 重載的public StringBuilder insert(…)方法:能夠爲該StringBuilder 對象在指定位置插入字符序列,仍然返回自身對象。

      5. 方法 public StringBuilder reverse():用於將字符序列逆序,仍然返回自身對象。
      6. 方法 public String toString() 返回此序列中數據的字符串表示形式。

      7. 和 String 類含義相似的方法:

        public int indexOf(String str)
        public int indexOf(String str,int fromIndex)
        public String substring(int start)
        public String substring(int start,int end)
        public int length() 
        char charAt(int index)
    • StringBuffer/StringBuilder基本用法

      public class TestStringBufferAndBuilder 1{
          public static void main(String[] args) {
              /**StringBuilder*/
              StringBuilder sb = new StringBuilder();
              for (int i = 0; i < 7; i++) {
                  sb.append((char) ('a' + i));//追加單個字符
              }
              System.out.println(sb.toString());//轉換成String輸出
              sb.append(", I can sing my abc!");//追加字符串
              System.out.println(sb.toString());
              /**StringBuffer*/
              StringBuffer sb2 = new StringBuffer("中華人民共和國");
              sb2.insert(0, "愛").insert(0, "我");//插入字符串
              System.out.println(sb2);
              sb2.delete(0, 2);//刪除子字符串
              System.out.println(sb2);
              sb2.deleteCharAt(0).deleteCharAt(0);//刪除某個字符
              System.out.println(sb2.charAt(0));//獲取某個字符
              System.out.println(sb2.reverse());//字符串逆序
          }
      }
  • 不可變和可變字符序列使用陷阱

    • String使用的陷阱:String一經初始化後,就不會再改變其內容了。對String字符串的操做其實是對其副本(原始拷貝)的操做,原來的字符串一點都沒有改變。好比:

      ​ String s ="a"; 建立了一個字符串

      s = s+"b"; 實際上原來的"a"字符串對象已經丟棄了,如今又產生了另外一個字符串s+"b"(也就是"ab")。 若是屢次執行這些改變串內容的操做,會致使大量副本字符串對象存留在內存中,下降效率。若是這樣的操做放到循環中,會極大影響程序的時間和空間性能,甚至會形成服務器的崩潰。

    • 相反,StringBuilder和StringBuffer類是對原字符串自己操做的,能夠對字符串進行修改而不產生副本拷貝或者產生少許的副本。所以能夠在循環中使用。

3. 時間處理相關類

图8-14 日期时间相关类.png

  • Date時間類(java.util.Date)

    • 在標準Java類庫中包含一個Date類。它的對象表示一個特定的瞬間,精確到毫秒。

      1. Date() 分配一個Date對象,並初始化此對象爲系統當前的日期和時間,能夠精確到毫秒)。

      2. Date(long date) 分配 Date 對象並初始化此對象,以表示自從標準基準時間(稱爲「曆元(epoch)」,即 1970 年 1 月 1 日 00:00:00 GMT)以來的指定毫秒數。

      3. boolean after(Date when) 測試此日期是否在指定日期以後。

      4. booleanbefore(Date when) 測試此日期是否在指定日期以前。

      5. boolean equals(Object obj) 比較兩個日期的相等性。

      6. long getTime() 返回自 1970 年 1 月 1 日 00:00:00 GMT 以來此 Date 對象表示的毫秒數。

      7. String toString() 把此 Date 對象轉換爲如下形式的 String:

      ​ dow mon dd hh:mm:ss zzz yyyy 其中: dow 是一週中的某一天 (Sun、 Mon、Tue、Wed、 Thu、 Fri、 Sat)。

  • DateFormat類的做用

    • 把時間對象轉化成指定格式的字符串。反之,把指定格式的字符串轉化成時間對象。

    • DateFormat是一個抽象類,通常使用它的的子類SimpleDateFormat類來實現。

      import java.text.ParseException;
      import java.text.SimpleDateFormat;
      import java.util.Date;
      public class TestDateFormat {
          public static void main(String[] args) throws ParseException {
              // new出SimpleDateFormat對象
              SimpleDateFormat s1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
              SimpleDateFormat s2 = new SimpleDateFormat("yyyy-MM-dd");
              // 將時間對象轉換成字符串
              String daytime = s1.format(new Date());
              System.out.println(daytime);
              System.out.println(s2.format(new Date()));
              System.out.println(new SimpleDateFormat("hh:mm:ss").format(new Date()));
              // 將符合指定格式的字符串轉成成時間對象.字符串格式須要和指定格式一致。
              String time = "2007-10-7";
              Date date = s2.parse(time);
              System.out.println("date1: " + date);
              time = "2007-10-7 20:15:30";
              date = s1.parse(time);
              System.out.println("date2: " + date);
          }
      }
    • 代碼中的格式化字符的具體含義:

      表8-2 æ ¼å¼åŒ–å­—ç¬¦çš„å«ä¹‰.png

    • 時間格式字符也能夠爲咱們提供其餘的便利。好比:得到當前時間是今年的第幾天。

      import java.text.SimpleDateFormat;
      import java.util.Date;
      public class TestDateFormat2 {
          public static void main(String[] args) {
              SimpleDateFormat s1 = new SimpleDateFormat("D");
              String daytime = s1.format(new Date());
              System.out.println(daytime);
          }
      }
  • Calendar日曆類

    • Calendar 類是一個抽象類,爲咱們提供了關於日期計算的相關功能,好比:年、月、日、時、分、秒的展現和計算。

    • GregorianCalendar 是 Calendar 的一個具體子類,提供了世界上大多數國家/地區使用的標準日曆系統。

    • 注意月份的表示,一月是0,二月是1,以此類推,12月是11。

    • GregorianCalendar類和Calendar類的使用

      import java.util.*;
      public class TestCalendar {
          public static void main(String[] args) {
              // 獲得相關日期元素
              GregorianCalendar calendar = new GregorianCalendar(2999, 10, 9, 22, 10, 50);
              int year = calendar.get(Calendar.YEAR); // 打印:1999
              int month = calendar.get(Calendar.MONTH); // 打印:10
              int day = calendar.get(Calendar.DAY_OF_MONTH); // 打印:9
              int day2 = calendar.get(Calendar.DATE); // 打印:9
              // 日:Calendar.DATE和Calendar.DAY_OF_MONTH同義
              int date = calendar.get(Calendar.DAY_OF_WEEK); // 打印:3
              // 星期幾 這裏是:1-7.週日是1,週一是2,。。。週六是7
              System.out.println(year);
              System.out.println(month);
              System.out.println(day);
              System.out.println(day2);
              System.out.println(date);
              // 設置日期
              GregorianCalendar calendar2 = new GregorianCalendar();
              calendar2.set(Calendar.YEAR, 2999);
              calendar2.set(Calendar.MONTH, Calendar.FEBRUARY); // 月份數:0-11
              calendar2.set(Calendar.DATE, 3);
              calendar2.set(Calendar.HOUR_OF_DAY, 10);
              calendar2.set(Calendar.MINUTE, 20);
              calendar2.set(Calendar.SECOND, 23);
              printCalendar(calendar2);
              // 日期計算
              GregorianCalendar calendar3 = new GregorianCalendar(2999, 10, 9, 22, 10, 50);
              calendar3.add(Calendar.MONTH, -7); // 月份減7
              calendar3.add(Calendar.DATE, 7); // 增長7天
              printCalendar(calendar3);
              // 日曆對象和時間對象轉化
              Date d = calendar3.getTime();
              GregorianCalendar calendar4 = new GregorianCalendar();
              calendar4.setTime(new Date());
              long g = System.currentTimeMillis();
          }
          static void printCalendar(Calendar calendar) {
              int year = calendar.get(Calendar.YEAR);
              int month = calendar.get(Calendar.MONTH) + 1;
              int day = calendar.get(Calendar.DAY_OF_MONTH);
              int date = calendar.get(Calendar.DAY_OF_WEEK) - 1; // 星期幾
              String week = "" + ((date == 0) ? "日" : date);
              int hour = calendar.get(Calendar.HOUR);
              int minute = calendar.get(Calendar.MINUTE);
              int second = calendar.get(Calendar.SECOND);
              System.out.printf("%d年%d月%d日,星期%s %d:%d:%d\n", year, month, day,  
                              week, hour, minute, second);
          }
      }
    • 可視化日曆的編寫

      import java.text.ParseException;
      import java.util.Calendar;
      import java.util.GregorianCalendar;
      import java.util.Scanner;
      public class TestCalendar2 {
          public static void main(String[] args) throws ParseException {
              System.out.println("請輸入日期(格式爲:2010-3-3):");
              Scanner scanner = new Scanner(System.in);
              String dateString = scanner.nextLine(); // 2010-3-1
              // 將輸入的字符串轉化成日期類
              System.out.println("您剛剛輸入的日期是:" + dateString);
              String[] str = dateString.split("-");
              int year = Integer.parseInt(str[0]);
              int month = new Integer(str[1]);
              int day = new Integer(str[2]);
              Calendar c = new GregorianCalendar(year, month - 1, day); // Month:0-11
              // 你們本身補充另外一種方式:將字符串經過SImpleDateFormat轉化成Date對象,
              //再將Date對象轉化成日期類
              // SimpleDateFormat sdfDateFormat = new SimpleDateFormat("yyyy-MM-dd");
              // Date date = sdfDateFormat.parse(dateString);
              // Calendar c = new GregorianCalendar();
              // c.setTime(date);
              // int day = c.get(Calendar.DATE);
              c.set(Calendar.DATE, 1);
              int dow = c.get(Calendar.DAY_OF_WEEK); // week:1-7 日一二三四五六
              System.out.println("日\t一\t二\t三\t四\t五\t六");
              for (int i = 0; i < dow - 1; i++) {
                  System.out.print("\t");
              }
              int maxDate = c.getActualMaximum(Calendar.DATE);
              // System.out.println("maxDate:"+maxDate);
              for (int i = 1; i <= maxDate; i++) {
                  StringBuilder sBuilder = new StringBuilder();
                  if (c.get(Calendar.DATE) == day) {
                      sBuilder.append(c.get(Calendar.DATE) + "*\t");
                  } else {
                      sBuilder.append(c.get(Calendar.DATE) + "\t");
                  }
                  System.out.print(sBuilder);
                  // System.out.print(c.get(Calendar.DATE)+
                  //                ((c.get(Calendar.DATE)==day)?"*":"")+"\t");
      
                  if (c.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY) {
                      System.out.print("\n");
                  }
                  c.add(Calendar.DATE, 1);
              }
          }
      }

4. Math類

  • java.lang.Math提供了一系列靜態方法用於科學計算;其方法的參數和返回值類型通常爲double型。若是須要更增強大的數學運算能力,計算高等數學中的相關內容,能夠使用apache commons下面的Math類庫。

  • Math類的經常使用方法:

    1. abs 絕對值

    2. acos,asin,atan,cos,sin,tan 三角函數

    3. sqrt 平方根

    4. pow(double a, double b) a的b次冪

    5. max(double a, double b) 取大值

    6. min(double a, double b) 取小值

    7. ceil(double a) 大於a的最小整數

    8. floor(double a) 小於a的最大整數

    9. random() 返回 0.0 到 1.0 的隨機數

    10. long round(double a) double型的數據a轉換爲long型(四捨五入)

    11. toDegrees(double angrad) 弧度->角度

    12. toRadians(double angdeg) 角度->弧度

  • Random類的經常使用方法

    import java.util.Random;
    public class TestRandom {
        public static void main(String[] args) {
            Random rand = new Random();
            //隨機生成[0,1)之間的double類型的數據
            System.out.println(rand.nextDouble());
            //隨機生成int類型容許範圍以內的整型數據
            System.out.println(rand.nextInt());
            //隨機生成[0,1)之間的float類型的數據
            System.out.println(rand.nextFloat());
            //隨機生成false或者true
            System.out.println(rand.nextBoolean());
            //隨機生成[0,10)之間的int類型的數據
            System.out.print(rand.nextInt(10));
            //隨機生成[20,30)之間的int類型的數據
            System.out.print(20 + rand.nextInt(10));
            //隨機生成[20,30)之間的int類型的數據(此種方法計算較爲複雜)
            System.out.print(20 + (int) (rand.nextDouble() * 10));
        }
    }
    • Math類中雖然爲咱們提供了產生隨機數的方法Math.random(),可是一般咱們須要的隨機數範圍並非[0, 1)之間的double類型的數據,這就須要對其進行一些複雜的運算。
    • 若是使用Math.random()計算過於複雜的話,咱們能夠使用例外一種方式獲得隨機數,即Random類,這個類是專門用來生成隨機數的,而且Math.random()底層調用的就是Random的nextDouble()方法。
    • Random類位於java.util包下。

5. File類

  • java.io.File類:表明文件和目錄。 在開發中,讀取文件、生成文件、刪除文件、修改文件的屬性時常常會用到本類。

  • File類的常見構造方法:public File(String pathname)

    • 以pathname爲路徑建立File對象,若是pathname是相對路徑,則默認的當前路徑在系統屬性user.dir中存儲

    • 文件的建立

      import java.io.File;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: pycharm
       * @file: FileTest1.java
       * @time: 2019/9/22 15:09
       * @desc: File類的常見構造方法
       */
      
      public class FileTest1{
          public static void main(String[] args) throws Exception{
              test1();
          }
      
          public static void test1() throws Exception{
              System.out.println(System.getProperty("user.dir"));
      
              // 相對路徑,默認放到user.dir目錄下
              File f = new File("a.txt");
              // 建立文件
              f.createNewFile();
              // 絕對路徑
              File f2 = new File("D:\\李添的數據哦!!!\\BookStudy\\else\\JavaWorkSpace\\b.txt");
              f2.createNewFile();
          }
      
      }
  • 經過File對象能夠訪問文件的屬性:

    表8-3 File類訪問屬性的方法列表.png

  • 測試File類訪問屬性的基本用法

    import java.io.File;
    import java.util.Date;
    public class TestFile2 {
        public static void main(String[] args) throws Exception {
            File f = new File("d:/b.txt");
            System.out.println("File是否存在:"+f.exists());
            System.out.println("File是不是目錄:"+f.isDirectory());
            System.out.println("File是不是文件:"+f.isFile());
            System.out.println("File最後修改時間:"+new Date(f.lastModified()));
            System.out.println("File的大小:"+f.length());
            System.out.println("File的文件名:"+f.getName());
            System.out.println("File的目錄路徑:"+f.getPath());
        }
    }
  • 經過File對象建立空文件或目錄(在該對象所指的文件或目錄不存在的狀況下)

    表8-4 File類建立文件或目錄的方法列表.png

  • 使用mkdir建立目錄

    import java.io.File;
    public class TestFile3 {
        public static void main(String[] args) throws Exception {
            File f = new File("d:/c.txt");
            f.createNewFile(); // 會在d盤下面生成c.txt文件
            f.delete(); // 將該文件或目錄從硬盤上刪除
            File f2 = new File("d:/電影/華語/大陸");
            boolean flag = f2.mkdir(); //目錄結構中有一個不存在,則不會建立整個目錄樹
            System.out.println(flag);//建立失敗
        }
    }
  • 使用mkdirs建立目錄

    import java.io.File;
    public class TestFile4 {
        public static void main(String[] args) throws Exception {
            File f = new File("d:/c.txt");
            f.createNewFile(); // 會在d盤下面生成c.txt文件
            f.delete(); // 將該文件或目錄從硬盤上刪除
            File f2 = new File("d:/電影/華語/大陸");
            boolean flag = f2.mkdirs();//目錄結構中有一個不存在也不要緊;建立整個目錄樹
            System.out.println(flag);//建立成功
        }
    }
  • File類的綜合應用

    import java.io.File;
    import java.io.IOException;
    public class TestFile5 {
        public static void main(String[] args) {
            //指定一個文件
            File file = new File("d:/sxt/b.txt");
            //判斷該文件是否存在
            boolean flag= file.exists();
            //若是存在就刪除,若是不存在就建立
            if(flag){
                //刪除
                boolean flagd = file.delete();
                if(flagd){
                    System.out.println("刪除成功");
                }else{
                    System.out.println("刪除失敗");
                }
            }else{
                //建立
                boolean flagn = true;
                try {
                    //若是目錄不存在,先建立目錄
                    File dir = file.getParentFile();
                    dir.mkdirs();
                    //建立文件
                    flagn = file.createNewFile();
                    System.out.println("建立成功");
                } catch (IOException e) {
                    System.out.println("建立失敗");
                    e.printStackTrace();
                }          
            }
            //文件重命名(同窗能夠本身測試一下)
            //file.renameTo(new File("d:/readme.txt"));
        }
    }
  • 遞歸遍歷目錄結構和樹狀展示

    import java.io.File;
    public class TestFile6 {
        public static void main(String[] args) {
            File f = new File("d:/電影");
            printFile(f, 0);
        }
        /**
         * 打印文件信息
         * @param file 文件名稱
         * @param level 層次數(實際就是:第幾回遞歸調用)
         */
        static void printFile(File file, int level) {
            //輸出層次數
            for (int i = 0; i < level; i++) {
                System.out.print("-");
            }
            //輸出文件名
            System.out.println(file.getName());
            //若是file是目錄,則獲取子文件列表,並對每一個子文件進行相同的操做
            if (file.isDirectory()) {
                File[] files = file.listFiles();
                for (File temp : files) {
                    //遞歸調用該方法:注意等+1
                    printFile(temp, level + 1);
                }
            }
        }
    }

6. 枚舉

  • JDK1.5引入了枚舉類型。枚舉類型的定義包括枚舉聲明和枚舉體。格式以下:

    enum  枚舉名 {
          枚舉體(常量列表)
    }
  • 枚舉體就是放置一些常量。咱們能夠寫出咱們的第一個枚舉類型。

    enum Season {
        SPRING, SUMMER, AUTUMN, WINDER 
    }
  • 全部的枚舉類型隱性地繼承自 java.lang.Enum。枚舉實質上仍是類!而每一個被枚舉的成員實質就是一個枚舉類型的實例,他們默認都是public static final修飾的。能夠直接經過枚舉類型名使用它們。

  • 注意:

    1. 當你須要定義一組常量時,能夠使用枚舉類型。

    2. 儘可能不要使用枚舉的高級特性,事實上高級特性均可以使用普通類來實現,沒有必要引入枚舉,增長程序的複雜性!

  • 枚舉的使用

    import java.util.Random;
    public class TestEnum {
        public static void main(String[] args) {
            // 枚舉遍歷
            for (Week k : Week.values()) {
                System.out.println(k);
            }
            // switch語句中使用枚舉
            int a = new Random().nextInt(4); // 生成0,1,2,3的隨機數
            switch (Season.values()[a]) {
            case SPRING:
                System.out.println("春天");
                break;
            case SUMMER:
                System.out.println("夏天");
                break;
            case AUTUMN:
                System.out.println("秋天");
                break;
            case WINDTER:
                System.out.println("冬天");
                break;
            }
        }
    }
    /**季節*/
    enum Season {
        SPRING, SUMMER, AUTUMN, WINDTER
    }
    /**星期*/
    enum Week {
        星期一, 星期二, 星期三, 星期四, 星期五, 星期六, 星期日
    }

第9章 容器

  • 什麼是「容器」呢?生活中的容器不難理解,是用來容納物體的,如鍋碗瓢盆、箱子和包等。程序中的「容器」也有相似的功能,就是用來容納和管理數據。

  • 數組就是一種容器,能夠在其中放置對象或基本類型數據。

  • 數組的優點:是一種簡單的線性序列,能夠快速地訪問數組元素,效率高。若是從效率和類型檢查的角度講,數組是最好的。

  • 數組的劣勢:不靈活。容量須要事先定義好,不能隨着需求的變化而擴容。

  • 容器,也叫集合(Collection)。

    圖9-1容器的接口層次結構圖.png

1. 泛型

  • 泛型是JDK1.5之後增長的,它能夠幫助咱們創建類型安全的集合。在使用了泛型的集合中,遍歷時沒必要進行強制類型轉換。JDK提供了支持泛型的編譯器,將運行時的類型檢查提早到了編譯時執行,提升了代碼可讀性和安全性。

  • 泛型的本質就是「數據類型的參數化」。 咱們能夠把「泛型」理解爲數據類型的一個佔位符(形式參數),即告訴編譯器,在調用泛型時必須傳入實際類型。

  • 咱們能夠在類的聲明處增長泛型列表,如:<T,E,V>。

  • 此處,字符能夠是任何標識符,通常採用這3個字母。

  • 自定義泛型

    • 泛型類的聲明

      class MyCollection<E> {// E:表示泛型;
          Object[] objs = new Object[5];
      
          public E get(int index) {// E:表示泛型;
              return (E) objs[index];
          }
          public void set(E e, int index) {// E:表示泛型;
              objs[index] = e;
          }
      }
    • 泛型E像一個佔位符同樣表示「未知的某個數據類型」,咱們在真正調用的時候傳入這個「數據類型」。

    • 泛型類的應用

      public class TestGenerics {
          public static void main(String[] args) {
              // 這裏的」String」就是實際傳入的數據類型;
              MyCollection<String> mc = new MyCollection<String>();
              mc.set("aaa", 0);
              mc.set("bbb", 1);
              String str = mc.get(1); //加了泛型,直接返回String類型,不用強制轉換;
              System.out.println(str);
          }
      }
  • 容器中使用泛型

    • 經過閱讀源碼,咱們發現Collection、List、Set、Map、Iterator接口都定義了泛型。所以,咱們在使用這些接口及其實現類時,都強烈建議使用泛型。(事實上,不使用編譯器也不會報錯!)

2. Collection接口

  • Collection 表示一組對象,它是集中、收集的意思。Collection接口的兩個子接口是List、Set接口。

  • Collection接口中定義的方法

    表9-1 Collection接口中定義的方法.png

  • 因爲List、Set是Collection的子接口,意味着全部List、Set的實現類都有上面的方法。

3. List

  • List是有序、可重複的容器。

  • 有序:List中每一個元素都有索引標記。能夠根據元素的索引標記(在List中的位置)訪問元素,從而精確控制這些元素。

  • 可重複:List容許加入重複的元素。更確切地講,List一般容許知足 e1.equals(e2) 的元素重複加入容器。

  • 除了Collection接口中的方法,List多了一些跟順序(索引)有關的方法

    表9-2 List接口中定义的方法.png

  • List接口經常使用的實現類有3個:ArrayList、LinkedList和Vector。

  • List的經常使用方法

    public class TestList {
        /**
         * 測試add/remove/size/isEmpty/contains/clear/toArrays等方法
         */
        public static void test01() {
            List<String> list = new ArrayList<String>();
            System.out.println(list.isEmpty()); // true,容器裏面沒有元素
            list.add("高淇");
            System.out.println(list.isEmpty()); // false,容器裏面有元素
            list.add("高小七");
            list.add("高小八");
            System.out.println(list);
            System.out.println("list的大小:" + list.size());
            System.out.println("是否包含指定元素:" + list.contains("高小七"));
            list.remove("高淇");
            System.out.println(list);
            Object[] objs = list.toArray();
            System.out.println("轉化成Object數組:" + Arrays.toString(objs));
            list.clear();
            System.out.println("清空全部元素:" + list);
        }
        public static void main(String[] args) {
            test01();
        }
    }
  • 兩個List之間的元素處理

    public class TestList {
        public static void main(String[] args) {
            test02();
        }
        /**
         * 測試兩個容器之間元素處理
         */
        public static void test02() {
            List<String> list = new ArrayList<String>();
            list.add("高淇");
            list.add("高小七");
            list.add("高小八");
    
            List<String> list2 = new ArrayList<String>();
            list2.add("高淇");
            list2.add("張三");
            list2.add("李四");
            System.out.println(list.containsAll(list2)); //false list是否包含list2中全部元素
            System.out.println(list);
            list.addAll(list2); //將list2中全部元素都添加到list中
            System.out.println(list);
            list.removeAll(list2); //從list中刪除同時在list和list2中存在的元素
            System.out.println(list);
            list.retainAll(list2); //取list和list2的交集
            System.out.println(list);
        }
    }
  • List中操做索引的經常使用方法

    public class TestList {
        public static void main(String[] args) {
            test03();
        }
        /**
         * 測試List中關於索引操做的方法
         */
        public static void test03() {
            List<String> list = new ArrayList<String>();
            list.add("A");
            list.add("B");
            list.add("C");
            list.add("D");
            System.out.println(list); // [A, B, C, D]
            list.add(2, "高");
            System.out.println(list); // [A, B, 高, C, D]
            list.remove(2);
            System.out.println(list); // [A, B, C, D]
            list.set(2, "c");
            System.out.println(list); // [A, B, c, D]
            System.out.println(list.get(1)); // 返回:B
            list.add("B");
            System.out.println(list); // [A, B, c, D, B]
            System.out.println(list.indexOf("B")); // 1 從頭至尾找到第一個"B"
            System.out.println(list.lastIndexOf("B")); // 4 從尾到頭找到第一個"B"
        }
    }
  • ArrayList特色和底層實現

    • ArrayList底層是用數組實現的存儲。 特色:查詢效率高,增刪效率低(LinkedList高),線程不安全(Vector安全)。咱們通常使用它。
    • 咱們知道,數組長度是有限的,而ArrayList是能夠存聽任意數量的對象,長度不受限制,那麼它是怎麼實現的呢?本質上就是經過定義新的更大的數組,將舊數組中的內容拷貝到新數組,來實現擴容。 ArrayList的Object數組初始化長度爲10,若是咱們存儲滿了這個數組,須要存儲第11個對象,就會定義新的長度更大的數組,並將原數組內容和新的元素一塊兒加入到新數組中。
  • LinkedList特色和底層實現

    • LinkedList底層用雙向鏈表實現的存儲。特色:查詢效率低,增刪效率高,線程不安全。

    • 雙向鏈表也叫雙鏈表,是鏈表的一種,它的每一個數據節點中都有兩個指針,分別指向前一個節點和後一個節點。 因此,從雙向鏈表中的任意一個節點開始,均可以很方便地找到全部節點。

      图9-8 LinkedList的存储结构图.png

    • 每一個節點都應該有3部份內容:

      class  Node {
          Node  previous;     //前一個節點
          Object  element;    //本節點保存的數據
          Node  next;         //後一個節點
      }
  • Vector向量

    • Vector底層是用數組實現的List,相關的方法都加了同步檢查,所以「線程安全,效率低」。 好比,indexOf方法就增長了synchronized同步標記。
  • 如何選用ArrayList、LinkedList、Vector?

    1. 須要線程安全時,用Vector。

    2. 不存在線程安全問題時,而且查找較多用ArrayList(通常使用它)。

    3. 不存在線程安全問題時,增長或刪除元素較多用LinkedList。

4. Map

  • Map接口

    • Map就是用來存儲「鍵(key)-值(value) 對」的。 Map類中存儲的「鍵值對」經過鍵來標識,因此「鍵對象」不能重複。

    • Map 接口的實現類有HashMap、TreeMap、HashTable、Properties等。

    • Map接口中經常使用的方法

      表9-3 Map接口中常用的方法.png

  • HashMap和HashTable

    • HashMap採用哈希算法實現,是Map接口最經常使用的實現類。 因爲底層採用了哈希表存儲數據,咱們要求鍵不能重複,若是發生重複,新的鍵值對會替換舊的鍵值對。 HashMap在查找、刪除、修改方面都有很是高的效率。

      import java.util.HashMap;
      import java.util.Map;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: pycharm
       * @file: TestMap.java
       * @time: 2019/10/5 14:05
       * @desc:
       */
      
      public class TestMap {
          public static void main(String[] args){
              Map<Integer, String> m1 = new HashMap<>();
      
              m1.put(1, "one");
              m1.put(2, "two");
              m1.put(3, "three");
      
              System.out.println(m1.get(1));
              System.out.println(m1.size());
              System.out.println(m1.isEmpty());
              System.out.println(m1.containsKey(2));
              System.out.println(m1.containsValue("four"));
      
              Map<Integer, String> m2 = new HashMap<>();
              m2.put(4, "四");
              m2.put(5, "五");
              m1.putAll(m2);
      
              System.out.println(m1);
      
              // map中鍵不能重複!若是重複(是否重複是根據equals方法),則新的覆蓋舊的!
              m1.put(3, "三");
              System.out.println(m1);
          }
      }
    • HashTable類和HashMap用法幾乎同樣,底層實現幾乎同樣,只不過HashTable的方法添加了synchronized關鍵字確保線程同步檢查,效率較低。

    • HashMap與HashTable的區別

      1. HashMap: 線程不安全,效率高。容許key或value爲null。

      2. HashTable: 線程安全,效率低。不容許key或value爲null。

  • HashMap底層實現詳解

    • HashMap底層實現採用了哈希表,這是一種很是重要的數據結構。對於咱們之後理解不少技術都很是有幫助(好比:redis數據庫的核心技術和HashMap同樣)。

    • 數據結構中由數組和鏈表來實現對數據的存儲,他們各有特色。

      ​ (1) 數組:佔用空間連續。 尋址容易,查詢速度快。可是,增長和刪除效率很是低。

      ​ (2) 鏈表:佔用空間不連續。 尋址困難,查詢速度慢。可是,增長和刪除效率很是高。

    • 那麼,咱們能不能結合數組和鏈表的優勢(即查詢快,增刪效率也高)呢? 答案就是「哈希表」。 哈希表的本質就是「數組+鏈表」。

    • Entry數組存儲結構圖图9-15 Entry数组存储结构图.png

    • 存儲數據過程put(key,value)

      图9-16 HashMap存储数据过程示意图.png

    • 咱們的目的是將「key-value兩個對象」成對存放到HashMap的Entry[]數組中。參見如下步驟:

      1. 得到key對象的hashcode:首先調用key對象的hashcode()方法,得到hashcode。

      2. 根據hashcode計算出hash值(要求在[0, 數組長度-1]區間):hashcode是一個整數,咱們須要將它轉化成[0, 數組長度-1]的範圍。咱們要求轉化後的hash值儘可能均勻地分佈在[0,數組長度-1]這個區間,減小「hash衝突」。

        • 一種極端簡單和低下的算法是:hash值 = hashcode/hashcode

          也就是說,hash值老是1。意味着,鍵值對對象都會存儲到數組索引1位置,這樣就造成一個很是長的鏈表。至關於每存儲一個對象都會發生「hash衝突」,HashMap也退化成了一個「鏈表」。

        • 一種簡單和經常使用的算法是(相除取餘算法):hash值 = hashcode%數組長度

          這種算法可讓hash值均勻的分佈在[0,數組長度-1]的區間。 早期的HashTable就是採用這種算法。可是,這種算法因爲使用了「除法」,效率低下。JDK後來改進了算法。首先約定數組長度必須爲2的整數冪,這樣採用位運算便可實現取餘的效果:hash值 = hashcode&(數組長度-1)。

        • 事實上,爲了得到更好的散列效果,JDK對hashcode進行了兩次散列處理(核心目標就是爲了分佈更散更均勻)

      3. 生成Entry對象:如上所述,一個Entry對象包含4部分:key對象、value對象、hash值、指向下一個Entry對象的引用。咱們如今算出了hash值。下一個Entry對象的引用爲null。

      4. 將Entry對象放到table數組中:若是本Entry對象對應的數組索引位置尚未放Entry對象,則直接將Entry對象存儲進數組;若是對應索引位置已經有Entry對象,則將已有Entry對象的next指向本Entry對象,造成鏈表。

    • 總結如上過程:當添加一個元素(key-value)時,首先計算key的hash值,以此肯定插入數組中的位置,可是可能存在同一hash值的元素已經被放在數組同一位置了,這時就添加到同一hash值的元素的後面,他們在數組的同一位置,就造成了鏈表,同一個鏈表上的Hash值是相同的,因此說數組存放的是鏈表。 JDK8中,當鏈表長度大於8時,鏈表就轉換爲紅黑樹,這樣又大大提升了查找的效率。

    • 取數據過程get(key):咱們須要經過key對象得到「鍵值對」對象,進而返回value對象。明白了存儲數據過程,取數據就比較簡單了,參見如下步驟:

      1. 得到key的hashcode,經過hash()散列算法獲得hash值,進而定位到數組的位置。
      2. 在鏈表上挨個比較key對象。 調用equals()方法,將key對象和鏈表上全部節點的key對象進行比較,直到碰到返回true的節點對象爲止。
      3. 返回equals()爲true的節點對象的value對象。
    • hashcode()和equals方法的關係:Java中規定,兩個內容相同(equals()爲true)的對象必須具備相等的hashCode。由於若是equals()爲true而兩個對象的hashcode不一樣;那在整個存儲過程當中就發生了悖論。

    • 擴容問題:HashMap的位桶數組,初始大小爲16。實際使用時,顯然大小是可變的。若是位桶數組中的元素達到(0.75*數組 length), 就從新調整數組大小變爲原來2倍大小。擴容很耗時。擴容的本質是定義新的更大的數組,並將舊數組內容挨個拷貝到新數組中。

    • JDK8將鏈表在大於8狀況下變爲紅黑二叉樹:JDK8中,HashMap在存儲一個元素時,當對應鏈表長度大於8時,鏈表就轉換爲紅黑樹,這樣又大大提升了查找的效率。

  • 二叉樹和紅黑二叉樹

    • 二叉樹(BinaryTree)由一個節點及兩棵互不相交的、分別稱做這個根的左子樹和右子樹的二叉樹組成。

    • 二叉樹的左子樹和右子樹是嚴格區分而且不能隨意顛倒的。

    • 排序二叉樹特性以下:

      ​ (1) 左子樹上全部節點的值均小於它的根節點的值。

      ​ (2) 右子樹上全部節點的值均大於它的根節點的值。

    • 平衡二叉樹(AVL)

      • 爲了不出現上述一邊倒的存儲,科學家提出了「平衡二叉樹」。
      • 在平衡二叉樹中任何節點的兩個子樹的高度最大差異爲1,因此它也被稱爲高度平衡樹。 增長和刪除節點可能須要經過一次或屢次樹旋轉來從新平衡這個樹。
      • 在平衡二叉樹中任何節點的兩個子樹的高度最大差異爲1,因此它也被稱爲高度平衡樹。 增長和刪除節點可能須要經過一次或屢次樹旋轉來從新平衡這個樹。
      • 平衡二叉樹追求絕對平衡,實現起來比較麻煩,每次插入新節點須要作的旋轉操做次數不能預知。
    • 紅黑二叉樹

      • 紅黑二叉樹(簡稱:紅黑樹),它首先是一棵二叉樹,同時也是一棵自平衡的排序二叉樹。

      • 紅黑樹在原有的排序二叉樹增長了以下幾個要求:

        1. 每一個節點要麼是紅色,要麼是黑色。

        2. 根節點永遠是黑色的。

        3. 全部的葉節點都是空節點(即 null),而且是黑色的。

        4. 每一個紅色節點的兩個子節點都是黑色。(從每一個葉子到根的路徑上不會有兩個連續的紅色節點)

        5. 從任一節點到其子樹中每一個葉子節點的路徑都包含相同數量的黑色節點。

      • 這些約束強化了紅黑樹的關鍵性質:從根到葉子的最長的可能路徑很少於最短的可能路徑的兩倍長。這樣就讓樹大體上是平衡的。

      • 紅黑樹是一個更高效的檢索二叉樹,JDK 提供的集合類 TreeMap、TreeSet 自己就是一個紅黑樹的實現。

      • 紅黑樹的基本操做:插入、刪除、左旋、右旋、着色。 每插入或者刪除一個節點,可能會致使樹不在符合紅黑樹的特徵,須要進行修復,進行 「左旋、右旋、着色」操做,使樹繼續保持紅黑樹的特性。

  • TreeMap的使用和底層實現

    • TreeMap是紅黑二叉樹的典型實現。
    • TreeMap和HashMap實現了一樣的接口Map,所以,用法對於調用者來講沒有區別。HashMap效率高於TreeMap;在須要排序的Map時才選用TreeMap。

5. Set

  • Set接口

    • Set接口繼承自Collection,Set接口中沒有新增方法,方法和Collection保持徹底一致。咱們在前面經過List學習的方法,在Set中仍然適用。
    • Set容器特色:無序、不可重複。無序指Set中的元素沒有索引,咱們只能遍歷查找;不可重複指不容許加入重複的元素。更確切地講,新元素若是和Set中某個元素經過equals()方法對比爲true,則不能加入;甚至,Set中也只能放入一個null元素,不能多個。
    • Set經常使用的實現類有:HashSet、TreeSet等,咱們通常使用HashSet。
  • HashSet基本使用

    • 重點體會「Set是無序、不可重複」的核心要點。

      public class Test {
          public static void main(String[] args) {
              Set<String> s = new HashSet<String>();
              s.add("hello");
              s.add("world");
              System.out.println(s);
              s.add("hello"); //相同的元素不會被加入
              System.out.println(s);
              s.add(null);
              System.out.println(s);
              s.add(null);
              System.out.println(s);
          }
      }
  • HashSet底層實現

    • HashSet是採用哈希算法實現,底層實際是用HashMap實現的(HashSet本質就是一個簡化版的HashMap),所以,查詢效率和增刪效率都比較高。
    • 說白了,就是「往set中加入元素,本質就是把這個元素做爲key加入到了內部的map中」。
    • 因爲map中key都是不可重複的,所以,Set自然具備「不可重複」的特性。
  • TreeSet的使用和底層實現

    • TreeSet底層實際是用TreeMap實現的,內部維持了一個簡化版的TreeMap,經過key來存儲Set的元素。 TreeSet內部須要對存儲的元素進行排序,所以,咱們對應的類須要實現Comparable接口。這樣,才能根據compareTo()方法比較對象之間的大小,才能進行內部排序。

      public class Test {
          public static void main(String[] args) {
              User u1 = new User(1001, "高淇", 18);
              User u2 = new User(2001, "高希希", 5);
              Set<User> set = new TreeSet<User>();
              set.add(u1);
              set.add(u2);
          }
      }
      
      class User implements Comparable<User> {
          int id;
          String uname;
          int age;
      
          public User(int id, String uname, int age) {
              this.id = id;
              this.uname = uname;
              this.age = age;
          }
          /**
           * 返回0 表示 this == obj 返回正數表示 this > obj 返回負數表示 this < obj
           */
          @Override
          public int compareTo(User o) {
              if (this.id > o.id) {
                  return 1;
              } else if (this.id < o.id) {
                  return -1;
              } else {
                  return 0;
              }
          }
      }
    • 使用TreeSet要點:

      ​ (1) 因爲是二叉樹,須要對元素作內部排序。 若是要放入TreeSet中的類沒有實現Comparable接口,則會拋出異常:java.lang.ClassCastException。

      ​ (2) TreeSet中不能放入null元素。

6. 迭代器

  • 迭代器爲咱們提供了統一的遍歷容器的方式。

  • 若是遇到遍歷容器時,判斷刪除元素的狀況,使用迭代器遍歷!

    public class Test {
        public static void main(String[] args) {
            List<String> aList = new ArrayList<String>();
            for (int i = 0; i < 5; i++) {
                aList.add("a" + i);
            }
            System.out.println(aList);
            for (Iterator<String> iter = aList.iterator(); iter.hasNext();) {
                String temp = iter.next();
                System.out.print(temp + "\t");
                if (temp.endsWith("3")) {// 刪除3結尾的字符串
                    iter.remove();
                }
            }
            System.out.println();
            System.out.println(aList);
        }
    }
  • 遍歷集合的方法總結

    import java.util.*;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: TestIterator.java
     * @time: 2019/10/8 14:57
     * @desc: 迭代器學習
     */
    
    public class TestIterator {
        public static void main(String[] args){
            testIteratorList();
            testIteratorSet();
            testIteratorMap1();
            testIteratorMap2();
        }
    
        // 遍歷List
        public static void testIteratorList(){
            List<String> list = new ArrayList<>();
            list.add("aa");
            list.add("bb");
            list.add("cc");
    
            for(Iterator<String> iter=list.iterator(); iter.hasNext();){
                String temp = iter.next();
                System.out.println(temp);
            }
        }
    
        // 遍歷Set
        public static void testIteratorSet(){
            Set<String> set = new HashSet<>();
            set.add("aa");
            set.add("bb");
            set.add("cc");
    
            for(Iterator<String> iter=set.iterator(); iter.hasNext();){
                String temp = iter.next();
                System.out.println(temp);
            }
        }
    
        // 遍歷Map:方法1
        public static void testIteratorMap1(){
            Map<Integer, String> map = new HashMap<>();
            map.put(100, "aa");
            map.put(200, "bb");
            map.put(300, "cc");
    
            Set<Map.Entry<Integer, String>> ss = map.entrySet();
            for(Iterator<Map.Entry<Integer, String>> iter = ss.iterator(); iter.hasNext();){
                Map.Entry<Integer, String> temp = iter.next();
                System.out.println(temp.getKey() + "--" + temp.getValue());
            }
        }
    
        // 遍歷Map:方法2
        public static void testIteratorMap2(){
            Map<Integer, String> map = new HashMap<>();
            map.put(100, "aa");
            map.put(200, "bb");
            map.put(300, "cc");
    
            Set<Integer> keySet = map.keySet();
    
            for(Iterator<Integer> iter = keySet.iterator(); iter.hasNext();){
                Integer key = iter.next();
                System.out.println(key + "--" + map.get(key));
            }
        }
    }

7. Collections輔助類

  • 類 java.util.Collections 提供了對Set、List、Map進行排序、填充、查找元素的輔助方法。

    1. void sort(List) //對List容器內的元素排序,排序的規則是按照升序進行排序。

    2. void shuffle(List) //對List容器內的元素進行隨機排列。

    3. void reverse(List) //對List容器內的元素進行逆續排列 。

    4. void fill(List, Object) //用一個特定的對象重寫整個List容器。

    5. int binarySearch(List, Object)//對於順序的List容器,採用折半查找的方法查找特定對象。

    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: TestCollections.java
     * @time: 2019/10/8 15:24
     * @desc: 學習Collections輔助類
     */
    
    public class TestCollections {
        public static void main(String[] args){
            List<String> list = new ArrayList<>();
            for(int i =0; i<10; i++){
                list.add("li" + i);
            }
            System.out.println(list);
    
            // 隨機排列list中的元素
            Collections.shuffle(list);
            System.out.println(list);
            // 逆序排列
            Collections.reverse(list);
            System.out.println(list);
            // 遞增排序
            Collections.sort(list);
            System.out.println(list);
            // 二分查找
            System.out.println(Collections.binarySearch(list, "li"));
            System.out.println(Collections.binarySearch(list, "li2"));
    
        }
    }

第10章 IO技術

1. IO入門

  • 對於任何程序設計語言而言,輸入輸出(Input/Output)系統都是很是核心的功能。程序運行須要數據,數據的獲取每每須要跟外部系統進行通訊,外部系統多是文件、數據庫、其餘程序、網絡、IO設備等等。外部系統比較複雜多變,那麼咱們有必要經過某種手段進行抽象、屏蔽外部的差別,從而實現更加便捷的編程。

  • 數據源:數據源data source,提供數據的原始媒介。常見的數據源有:數據庫、文件、其餘程序、內存、網絡鏈接、IO設備。

    • 數據源分爲:源設備、目標設備。

      1. 源設備:爲程序提供數據,通常對應輸入流。

      2. 目標設備:程序數據的目的地,通常對應輸出流。

      圖10-1 數據源示意圖.png

  • 流的概念

    • 流是一個抽象、動態的概念,是一連串連續動態的數據集合。

    • 對於輸入流而言,數據源就像水箱,流(stream)就像水管中流動着的水流,程序就是咱們最終的用戶。咱們經過流(A Stream)將數據源(Source)中的數據(information)輸送到程序(Program)中。

    • 對於輸出流而言,目標數據源就是目的地(dest),咱們經過流(A Stream)將程序(Program)中的數據(information)輸送到目的數據源(dest)中。

      圖10-2 流與源數據源和目標數據源之間的關係.png

  • 第一個簡單的IO流程序及深刻理解

    • 當程序須要讀取數據源的數據時,就會經過IO流對象開啓一個通向數據源的流,經過這個IO流對象的相關方法能夠順序讀取數據源中的數據。

      import java.io.*;
      public class TestIO1 {
          public static void main(String[] args) {
              try {
                  //建立輸入流
                  FileInputStream fis = new FileInputStream("d:/a.txt"); // 文件內容是:abc
                  //一個字節一個字節的讀取數據
                  int s1 = fis.read(); // 打印輸入字符a對應的ascii碼值97
                  int s2 = fis.read(); // 打印輸入字符b對應的ascii碼值98
                  int s3 = fis.read(); // 打印輸入字符c 對應的ascii碼值99
                  int s4 = fis.read(); // 因爲文件內容已經讀取完畢,返回-1
                  System.out.println(s1);
                  System.out.println(s2);
                  System.out.println(s3);
                  System.out.println(s4);
                  // 流對象使用完,必須關閉!否則,總佔用系統資源,最終會形成系統崩潰!
                  fis.close();
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      }
    • 注意:

      1. 在示例10-1中咱們讀取的文件內容是已知的,所以能夠使用固定次數的「int s= fis.read();」語句讀取內容,可是在實際開發中一般咱們根本不知道文件的內容,所以咱們在讀取的時候須要配合while循環使用。

      2. 爲了保證出現異常後流的正常關閉,一般要將流的關閉語句要放到finally語句塊中,而且要判斷流是否是null。

    • 使用流讀取文件內容(經典代碼,必定要掌握)

      import java.io.*;
      public class TestIO2 {
          public static void main(String[] args) {
              FileInputStream fis = null;
              try {
                  fis = new FileInputStream("d:/a.txt"); // 內容是:abc
                  StringBuilder sb = new StringBuilder();
                  int temp = 0;
                  //當temp等於-1時,表示已經到了文件結尾,中止讀取
                  while ((temp = fis.read()) != -1) {
                      sb.append((char) temp);
                  }
                  System.out.println(sb);
              } catch (Exception e) {
                  e.printStackTrace();
              } finally {
                  try {
                      //這種寫法,保證了即便遇到異常狀況,也會關閉流對象。
                      if (fis != null) {
                          fis.close();
                      }
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }
      }
  • Java中流的概念細分

    • 按流的方向分類:

      1. 輸入流:數據流向是數據源到程序(以InputStream、Reader結尾的流)。

      2. 輸出流:數據流向是程序到目的地(以OutPutStream、Writer結尾的流)。

    • 按處理的數據單元分類:

      1. 字節流:以字節爲單位獲取數據,命名上以Stream結尾的流通常是字節流,如FileInputStream、FileOutputStream。

      2. 字符流:以字符爲單位獲取數據,命名上以Reader/Writer結尾的流通常是字符流,如FileReader、FileWriter。

    • 按處理對象不一樣分類:

      1. 節點流:能夠直接從數據源或目的地讀寫數據,如FileInputStream、FileReader、DataInputStream等。

      2. 處理流:不直接鏈接到數據源或目的地,是」處理流的流」。經過對其餘流的處理提升程序的性能,如BufferedInputStream、BufferedReader等。處理流也叫包裝流。

      ​ 節點流處於IO操做的第一線,全部操做必須經過它們進行;處理流能夠對節點流進行包裝,提升性能或提升程序的靈活性。

      圖10-6 節點流處理流示意圖.png

  • Java中IO流類的體系

    圖10-7 Java中的IO流體系.png

    1. InputStream/OutputStream:字節流的抽象類。

    2. Reader/Writer:字符流的抽象類。

    3. FileInputStream/FileOutputStream:節點流:以字節爲單位直接操做「文件」。

    4. ByteArrayInputStream/ByteArrayOutputStream:節點流:以字節爲單位直接操做「字節數組對象」。

    5. ObjectInputStream/ObjectOutputStream:處理流:以字節爲單位直接操做「對象」。

    6. DataInputStream/DataOutputStream:處理流:以字節爲單位直接操做「基本數據類型與字符串類型」。

    7. FileReader/FileWriter:節點流:以字符爲單位直接操做「文本文件」(注意:只能讀寫文本文件)。

    8. BufferedReader/BufferedWriter:處理流:將Reader/Writer對象進行包裝,增長緩存功能,提升讀寫效率。

    9. BufferedInputStream/BufferedOutputStream:處理流:將InputStream/OutputStream對象進行包裝,增長緩存功能,提升 讀寫效率。

    10. InputStreamReader/OutputStreamWriter:處理流:將字節流對象轉化成字符流對象。

    11. PrintStream:處理流:將OutputStream進行包裝,能夠方便地輸出字符,更加靈活。

  • 四大IO抽象類

    • InputStream/OutputStream和Reader/writer類是全部IO流類的抽象父類,咱們有必要簡單瞭解一下這個四個抽象類的做用。而後,經過它們具體的子類熟悉相關的用法。

      img

    • IO文件字節輸入流操做標準步驟

      1. 建立源
      2. 選擇流
      3. 操做
      4. 釋放
    • 字節輸入流測試:

      import java.io.*;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: TestIO05.java
       * @time: 2019/10/17 16:39
       * @desc: 理解操做步驟
       */
      
      public class TestIO05 {
          public static void main(String[] args){
              // 1. 建立源
              File src = new File("abc.txt");
              // 2. 選擇流
              try{
                  InputStream is = new FileInputStream(src);
                  System.out.println(src.getAbsolutePath());
                  // 3. 操做(讀取)
                  int data1 = is.read();      // 第1個數據
                  int data2 = is.read();      // 第2個數據
                  int data3 = is.read();      // 第3個數據
                  System.out.println((char)data1);
                  System.out.println((char)data2);
                  System.out.println((char)data3);
                  // 4. 釋放資源
                  is.close();
              }catch(FileNotFoundException e){
                  e.printStackTrace();
              }catch (IOException e){
                  e.printStackTrace();
              }
          }
      }
    • 字節輸入流標準操做

      import java.io.*;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: TestIO06.java
       * @time: 2019/10/17 16:39
       * @desc: 理解操做步驟 標準
       */
      
      public class TestIO06 {
          public static void main(String[] args){
              // 1. 建立源
              File src = new File("abc.txt");
              // 2. 選擇流
              InputStream is = null;
              try{
                  is = new FileInputStream(src);
                  // 3. 操做(讀取)
                  int temp;
                  while((temp=is.read()) != -1){
                      System.out.println((char)temp);
                  }
              }catch(FileNotFoundException e){
                  e.printStackTrace();
              }catch (IOException e){
                  e.printStackTrace();
              }finally{
                  // 4. 釋放資源
                  if (null != is) {
                      try {
                          is.close();
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
              }
          }
      }
    • 字節輸出流標準操做

      import java.io.*;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: TestIO07.java
       * @time: 2019/10/17 16:39
       * @desc: 讀入字符數組
       */
      
      public class TestIO07 {
          public static void main(String[] args){
              // 1. 建立源
              File src = new File("abc.txt");
              // 2. 選擇流
              InputStream is = null;
              try{
                  is = new FileInputStream(src);
                  // 3. 操做(讀取)
                  // 緩衝容器,這裏設爲3個字節
                  byte[] car = new byte[3];
                  // 接受長度
                  int len = -1;
                  while((len=is.read(car)) != -1){
                      // 字節數組 --> 字符串(解碼)
                      String str = new String(car, 0, len);
                      System.out.println(str);
                  }
              }catch(FileNotFoundException e){
                  e.printStackTrace();
              }catch (IOException e){
                  e.printStackTrace();
              }finally{
                  // 4. 釋放資源
                  if (null != is) {
                      try {
                          is.close();
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
              }
          }
      }
    • IO文件字節輸出流操做標準步驟

      1. 建立源
      2. 選擇流
      3. 操做(寫出內容)
      4. 釋放資源
    • 輸出流實戰

      import java.io.*;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: TestIO08.java
       * @time: 2019/10/17 18:10
       * @desc: 文件字節輸出流
       */
      
      public class TestIO08 {
          public static void main(String[] args){
              // 1. 建立源
              File dest = new File("dest.txt");
              // 2. 選擇流
              OutputStream os = null;
              try{
                  // true則是增長,false則是不增長
                  os = new FileOutputStream(dest, true);
                  // 3. 操做(寫出)
                  String temp = "IO is so easy!";
                  byte[] datas = temp.getBytes();
                  os.write(datas, 0, datas.length);
                  os.flush();
              }catch(FileNotFoundException e){
                  e.printStackTrace();
              }catch (IOException e){
                  e.printStackTrace();
              }finally{
                  // 釋放資源
                  if(null != os){
                      try {
                          os.close();
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
              }
          }
      }
    • 文件的拷貝

      import java.io.*;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: TestFileCopy.java
       * @time: 2019/10/17 18:22
       * @desc: 文件的拷貝
       */
      
      public class TestFileCopy {
          public static void main(String[] args) {
              copy("test.png", "copy_test.png");
          }
      
          public static void copy(String srcPath, String destPath){
              // 1. 建立源
              // 源頭
              File src = new File(srcPath);
              File dest = new File(destPath);
              // 2. 選擇流
              InputStream is = null;
              OutputStream os = null;
              try{
                  is = new FileInputStream(src);
                  os = new FileOutputStream(dest, true);
                  // 3. 操做(分段讀取)
                  // 緩衝容器
                  byte[] flush = new byte[1024];
                  // 接受長度
                  int len = -1;
                  while((len=is.read(flush)) != -1){
                      // 字節數組 --> 字符串(解碼)
                      String str = new String(flush, 0, len);
                      os.write(flush, 0, len);
                  }
                  os.flush();
              } catch (FileNotFoundException e) {
                  e.printStackTrace();
              } catch (IOException e) {
                  e.printStackTrace();
              } finally {
                  // 釋放資源 先打開的後關閉
                  try{
                      if(null != os){
                          os.close();
                      }
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
      
                  try{
                      if(null != is){
                          is.close();
                      }
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }
      }
    • 字符輸入流

      import java.io.*;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: TestIO07.java
       * @time: 2019/10/17 16:39
       * @desc: 文件字符輸入流
       */
      
      public class TestIO09 {
          public static void main(String[] args){
              // 1. 建立源
              File src = new File("abc.txt");
              // 2. 選擇流
              Reader reader = null;
              try{
                  reader = new FileReader(src);
                  // 3. 操做(讀取)
                  char[] flush = new char[1024];
                  // 接受長度
                  int len = -1;
                  while((len=reader.read(flush)) != -1){
                      String str = new String(flush, 0, len);
                      System.out.println(str);
                  }
              }catch(FileNotFoundException e){
                  e.printStackTrace();
              }catch (IOException e){
                  e.printStackTrace();
              }finally{
                  // 4. 釋放資源
                  if (null != reader) {
                      try {
                          reader.close();
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
              }
          }
      }
    • 字符輸出流

      import java.io.*;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: TestIO08.java
       * @time: 2019/10/17 18:10
       * @desc: 文件字符輸出流
       */
      
      public class TestIO10 {
          public static void main(String[] args){
              // 1. 建立源
              File dest = new File("dest.txt");
              // 2. 選擇流
              Writer writer = null;
              try{
                  // true則是增長,false則是不增長
                  writer = new FileWriter(dest, false);
                  // 3. 操做(寫出)
      
                  // 寫法1
                  String temp = "IO is so easy!我是你大爺";
                  char[] datas = temp.toCharArray();
                  writer.write(datas, 0, datas.length);
                  writer.flush();
      
                  // 寫法2
                  String temp = "IO is so easy!我是你大爺";
                  writer.write(temp);
                  writer.flush();
      
                  // 寫法3
                  writer.append("IO is so easy!").append("我是你大爺");
                  writer.flush();
      
              }catch(FileNotFoundException e){
                  e.printStackTrace();
              }catch (IOException e){
                  e.printStackTrace();
              }finally{
                  // 釋放資源
                  if(null != writer){
                      try {
                          writer.close();
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
              }
          }
      }
    • 字節數組輸入流

      1. 建立源:字節數組不要太大
      2. 選擇流
      3. 操做
      4. 釋放資源:能夠不作處理
      import java.io.*;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: TestIO07.java
       * @time: 2019/10/17 16:39
       * @desc: 字節數組輸入流
       */
      
      public class TestIO11 {
          public static void main(String[] args){
              // 1. 建立源
              byte[] src = "talk is cheap show me the code. ".getBytes();
              // 2. 選擇流
              InputStream is = null;
              try{
                  is = new ByteArrayInputStream(src);
                  // 3. 操做(讀取)
                  // 緩衝容器,這裏設爲5個字節
                  byte[] car = new byte[5];
                  // 接受長度
                  int len = -1;
                  while((len=is.read(car)) != -1){
                      // 字節數組 --> 字符串(解碼)
                      String str = new String(car, 0, len);
                      System.out.println(str);
                  }
              }catch (IOException e){
                  e.printStackTrace();
              }finally{
                  // 4. 釋放資源
                  if (null != is) {
                      try {
                          is.close();
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
              }
          }
      }
    • 字節數組輸出流:

      1. 建立源:內部維護
      2. 選擇流:不關聯源
      3. 操做:寫出內容
      4. 獲取數據:toByteArray()
      5. 釋放資源:能夠不用處理
      import java.io.*;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: TestIO08.java
       * @time: 2019/10/17 18:10
       * @desc: 字節數組輸出流
       */
      
      public class TestIO12 {
          public static void main(String[] args){
              // 1. 建立源:不用建立源
              byte[] dest = null;
              // 2. 選擇流:新增方法
              ByteArrayOutputStream baos = null;
              try{
                  // true則是增長,false則是不增長
                  baos = new ByteArrayOutputStream();
                  // 3. 操做(寫出)
                  String temp = "show me the code bie bibi";
                  byte[] datas = temp.getBytes();
                  baos.write(datas, 0, datas.length);
                  baos.flush();
                  // 獲取數據
                  dest = baos.toByteArray();
                  System.out.println(dest.length + "-->" + new String(dest, 0, baos.size()));
              } catch(IOException e){
                  e.printStackTrace();
              } finally{
                  // 釋放資源
                  if(null != baos){
                      try {
                          baos.close();
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
              }
      
          }
      }
    • 從文件拷貝到字節數組,再從字節數組輸出到文件。

      import java.io.*;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: TestIO08.java
       * @time: 2019/10/17 18:10
       * @desc: 圖片讀取到字節數組,字節數組寫出到文件
       */
      
      public class TestIO13 {
          public static void main(String[] args){
              byte[] datas = fileToByteArray("test.png");
              System.out.println(datas.length);
              byteArrayToFile(datas, "p-byte.png");
          }
      
          public static byte[] fileToByteArray(String filePath){
              /*
                1. 圖片讀取到字節數組中
                1). 圖片到程序:FileInputStream
                2). 程序到字節數組:ByteArrayOutputStream
               */
      
              // 1. 建立源與目的地
              File src = new File(filePath);
              byte[] dest = null;
              // 2. 選擇流
              InputStream is = null;
              ByteArrayOutputStream baos = null;
              try{
                  is = new FileInputStream(src);
                  baos = new ByteArrayOutputStream();
                  // 3. 操做:分段讀取
                  byte[] flush = new byte[1024*10];
                  int len = -1;
                  while((len = is.read(flush)) != -1){
                      baos.write(flush, 0, len);      // 寫出到字節數組中
                  }
                  baos.flush();
                  return baos.toByteArray();
              } catch (FileNotFoundException e) {
                  e.printStackTrace();
              } catch (IOException e) {
                  e.printStackTrace();
              }finally {
                  // 4. 釋放資源
                  try{
                      if(null != is){
                          is.close();
                      }
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
              return null;
          }
      
          public static void byteArrayToFile(byte[] src, String filePath){
              /*
              2. 字節數組寫出到文件
              1). 字節數組到程序:ByteArrayInputStream
              2). 程序寫出到文件:FileOutputStream
               */
              // 1. 建立源
              File dest = new File(filePath);
              // 2. 選擇流
              InputStream is = null;
              OutputStream os = null;
              try{
                  is = new ByteArrayInputStream(src);
                  os = new FileOutputStream(dest, false);
                  // 3. 操做:分段讀取
                  byte[] flush = new byte[5];     // 緩衝容器
                  int len = -1;
                  while((len = is.read(flush)) != 1){
                      os.write(flush, 0, len);
                  }
                  os.flush();
              } catch (FileNotFoundException e) {
                  e.printStackTrace();
              } catch (IOException e) {
                  e.printStackTrace();
              } finally {
                  // 4. 釋放資源
                  try {
                      if (null != os) {
                          os.close();
                      }
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }
      }
    • java1.7以後能夠用try…with…resource自動釋放

      try(is;os){}
      try(InputStream is = new FileInputStream(src); 
      OutputStream os = new FileOutputStream(dest);){}

2. IO的API

  • IO基礎操做

    import java.io.File;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: TestIO1.java
     * @time: 2019/10/9 17:19
     * @desc: IO學習1
     */
    
    public class TestIO1 {
        public static void main(String[] args){
    
            // 輸出文件分隔符
            System.out.println(File.separator);
    
            // 1. 構建File對象
            String path = "F:/BookStudy/else/Java知識點思惟導圖.png";
            File src = new File(path);
    
            // 輸出文件大小
            System.out.println(src.length());
    
            // 2. 第二種構建File對象的方法
            File src2 = new File("F:/BookStudy/else", "Java知識點思惟導圖.png");
            System.out.println(src2.length());
    
            // 3. 第三種構建File對象的方法
            File src3 = new File(new File("F:/BookStudy/else"), "Java知識點思惟導圖.png");
            System.out.println(src3.length());
    
            // 相對路徑的源路徑
            System.out.println(System.getProperty("user.dir"));
    
            // 絕對路徑
            System.out.println(src3.getAbsolutePath());
    
            // 構建一個不存在的對象
            File src4 = new File("aaa/asdf.jpg");
            System.out.println(src4.getAbsolutePath());
    
      }
    }
  • 文件操做

    import java.io.File;
    import java.io.IOException;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: TestIO2.java
     * @time: 2019/10/11 17:31
     * @desc: IO操做api
     */
    
    public class TestIO2 {
        public static void main(String[] args) throws IOException {
            File src = new File("F:/BookStudy/else/Java知識點思惟導圖.png");
    
            // 基本信息
            System.out.println("名稱:" + src.getName());
            System.out.println("路徑:" + src.getPath());
            System.out.println("絕對路徑:" + src.getAbsolutePath());
            System.out.println("父路徑:" + src.getParent());
            System.out.println("父對象:" + src.getParentFile().getName());
    
            // 文件狀態
            System.out.println("是否存在:" + src.exists());
            System.out.println("是否文件:" + src.isFile());
            System.out.println("是否文件夾:" + src.isDirectory());
    
            // 獲取文件的字節數,若是是文件夾,則爲0。
            System.out.println("長度:" + src.length());
    
            // 建立文件:不存在才建立,返回true,否則返回false;不帶後綴只是文件名,不是文件夾
            boolean flag = src.createNewFile();
            System.out.println(flag);
    
            // 文件的刪除:刪除已經存在的文件
            flag = src.delete();
            System.out.println(flag);
    
        }
    }
  • 文件夾的建立和遍歷

    import java.io.File;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: TestIO3.java
     * @time: 2019/10/11 17:50
     * @desc: 文件夾建立和遍歷
     */
    
    public class TestIO3 {
        public static void main(String[] args){
            // mkdir():確保上級目錄存在,不存在則建立失敗
            // mkdirs():上級目錄能夠不存在,不存在則一同建立
            File dir = new File("D:/");
    
            boolean flag1 = dir.mkdir();
            boolean flag2 = dir.mkdirs();
            System.out.println(flag1);
            System.out.println(flag2);
    
            // list():列出下級名稱
            // listFiles():列出下級File對象
            String[] subNames = dir.list();
            for(String s: subNames){
                System.out.println(s);
            }
    
            File[] subFiles = dir.listFiles();
            for(File s: subFiles){
                System.out.println(s.getAbsolutePath());
            }
    
            // listRoots():列出全部盤符
            File[] roots = dir.listRoots();
            for(File r: roots){
                System.out.println(r.getAbsolutePath());
            }
    
            // 遞歸:方法本身調用本身
            // 遞歸頭:什麼時候結束遞歸
            // 遞歸體:重複調用
            printName(dir, 0);
    
        }
    
        public static void printName(File src, int deep){
            /* 打印子孫級目錄和文件的名稱 */
            for(int i=0; i<deep; i++){
                System.out.print("-");
            }
            System.out.println(src.getName());
            if(null == src || !src.exists()){
                return;
            } else if(src.isDirectory()){
                for(File s: src.listFiles()){
                    printName(s, deep + 1);
                }
            }
        }
    }
  • 統計文件夾的大小

    import java.io.File;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: TestIO4.java
     * @time: 2019/10/15 15:20
     * @desc: 統計文件夾的大小
     */
    
    public class TestIO4 {
        public static void main(String[] args){
            File src = new File("F:\\BookStudy");
            count(src);
            System.out.println(LEN);
        }
    
        private static long LEN = 0;
        public static void count(File src){
            // 獲取大小
            if(null != src && src.exists()){
                if(src.isFile()){
                    LEN += src.length();
                }else{
                    for(File s: src.listFiles()){
                        count(s);
                    }
                }
            }
        }
    }
  • 使用面向對象:統計文件夾大小

    import java.io.File;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: DirCount.java
     * @time: 2019/10/15 15:58
     * @desc: 使用面向對象:統計文件夾大小
     */
    
    public class DirCount {
        // 大小
        private long len;
        // 文件夾路徑
        private String path;
        // 源
        private File src;
        // 文件的個數
        private int fileSize;
        // 文件夾的個數
        private int dirSize;
    
        public DirCount(String path){
            this.path = path;
            this.src = new File(path);
            count(this.src);
        }
    
        private void count(File src){
            // 獲取大小
            if(null != src && src.exists()){
                if(src.isFile()){
                    this.len += src.length();
                    this.fileSize++;
                }else{
                    this.dirSize++;
                    for(File s: src.listFiles()){
                        count(s);
                    }
                }
            }
        }
    
        public long getLen() {
            return len;
        }
    
        public int getFileSize() {
            return fileSize;
        }
    
        public int getDirSize() {
            return dirSize;
        }
    
        public static void main(String[] args){
            DirCount dir = new DirCount("F:\\BookStudy");
            System.out.println(dir.getLen());
            System.out.println("文件的數量" + "--->" + dir.getFileSize());
            System.out.println("文件夾的數量" + "--->" + dir.getDirSize());
    
    
            DirCount dir2 = new DirCount("F:\\BookStudy\\else");
            System.out.println(dir2.getLen());
            System.out.println("文件的數量" + "--->" + dir2.getFileSize());
            System.out.println("文件夾的數量" + "--->" + dir2.getDirSize());
    
        }
    }
  • 編碼和解碼

    import java.io.UnsupportedEncodingException;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: ContentEncode.java
     * @time: 2019/10/15 16:26
     * @desc: 編碼:字符串-->字節;解碼:字節-->字符串
     */
    
    public class ContentEncode {
        public static void main(String[] args) throws UnsupportedEncodingException {
            String msg = "你怕不是個鐵憨憨";
            // 編碼:字節數組
            byte[] datas = msg.getBytes();
            System.out.println(datas);
            // 中文utf-8:一個字符佔3個字節;默認使用工程的字符集
            System.out.println(datas.length);
    
            // 編碼:其餘字符集
            datas = msg.getBytes("UTF-16LE");
            System.out.println(datas.length);
            datas = msg.getBytes("GBK");
            System.out.println(datas.length);
    
            // 解碼
            msg = new String(datas, 0, datas.length, "gbk");
            System.out.println(msg);
        }
    }
  • 亂碼的緣由:

    1. 字節數不夠
    2. 字符集不統一

3. 裝飾流

  • 裝飾器模式原理剖析

    1. 抽象組件:須要裝飾的抽象對象(接口或抽象父類)
    2. 具體組件:須要裝飾的對象
    3. 抽象裝飾類:包含了對抽象組件的引用以及裝飾者共有的方法
    4. 具體裝飾類:被裝飾的對象
    • 模擬聲音

      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: DecorateTest01.java
       * @time: 2019/10/18 15:53
       * @desc: 實現放大器對聲音放大的功能
       */
      
      public class DecorateTest01 {
          public static void main(String[] args){
              Person p = new Person();
              p.say();
      
              // 裝飾
              Amplifier am = new Amplifier(p);
              am.say();
          }
      }
      
      interface Say{
          void say();
      }
      
      class Person implements Say{
          // 屬性
          private int voice = 10;
      
          @Override
          public void say() {
              System.out.println("人的聲音爲:" + this.getVoice());
          }
      
          public int getVoice() {
              return voice;
          }
      
          public void setVoice(int voice) {
              this.voice = voice;
          }
      }
      
      class Amplifier implements Say{
          private Person p;
          Amplifier(Person p){
              this.p = p;
          }
      
          @Override
          public void say() {
              System.out.println("人的聲音爲:" + p.getVoice()*100);
              System.out.println("噪音...");
          }
      }
    • 模擬咖啡

      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: DecorateTest02.java
       * @time: 2019/10/18 15:53
       * @desc: 模擬咖啡
       */
      
      public class DecorateTest02 {
          public static void main(String[] args){
              Drink coffee = new Coffee();
              Drink suger = new Suger(coffee);    // 裝飾
              System.out.println(suger.info() + "-->" + suger.cost());
              Drink milk = new Milk(coffee);    // 裝飾
              System.out.println(milk.info() + "-->" + milk.cost());
      
              Drink mixed = new Milk(suger);    // 裝飾
              System.out.println(mixed.info() + "-->" + mixed.cost());
          }
      }
      
      // 抽象組件
      interface Drink{
          double cost();      // 費用
          String info();      // 說明
      }
      
      // 具體組件
      class Coffee implements Drink{
          private String name = "原味咖啡";
      
          @Override
          public double cost() {
              return 10;
          }
      
          @Override
          public String info() {
              return name;
          }
      }
      
      // 抽象裝飾類
      abstract class Decorate implements Drink{
          // 對抽象組件的引用
          private Drink drink;
          public Decorate(Drink drink){
              this.drink = drink;
          }
      
          @Override
          public double cost() {
              return this.drink.cost();
          }
      
          @Override
          public String info() {
              return this.drink.info();
          }
      }
      
      // 具體裝飾類
      class Milk extends Decorate{
          public Milk(Drink drink) {
              super(drink);
          }
      
          @Override
          public double cost() {
              return super.cost()*4;
          }
      
          @Override
          public String info() {
              return super.info() + "加入了牛奶";
          }
      }
      
      class Suger extends Decorate{
          public Suger(Drink drink) {
              super(drink);
          }
      
          @Override
          public double cost() {
              return super.cost()*2;
          }
      
          @Override
          public String info() {
              return super.info() + "加入了糖";
          }
      }
  • 字節緩衝流(輸入輸出同理)

    import java.io.*;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: TestIO07.java
     * @time: 2019/10/17 16:39
     * @desc: 加入緩衝流(只須要釋放底層的is)
     */
    
    public class BufferedTest02 {
        public static void main(String[] args){
            // 1. 建立源
            File src = new File("abc.txt");
            // 2. 選擇流
            InputStream is = null;
            try{
                is = new BufferedInputStream(new FileInputStream(src));
                // 3. 操做(讀取)
                byte[] flush = new byte[1024];
                // 接受長度
                int len = -1;
                while((len=is.read(flush)) != -1){
                    // 字節數組 --> 字符串(解碼)
                    String str = new String(flush, 0, len);
                    System.out.println(str);
                }
            }catch(FileNotFoundException e){
                e.printStackTrace();
            }catch (IOException e){
                e.printStackTrace();
            }finally{
                // 4. 釋放資源
                if (null != is) {
                    try {
                        is.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
  • 字符緩衝流(輸入輸出同理)

    import java.io.*;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: TestIO07.java
     * @time: 2019/10/17 16:39
     * @desc: 文件字符輸入流 加入緩衝流
     */
    
    public class BufferedTest03 {
        public static void main(String[] args){
            // 1. 建立源
            File src = new File("abc.txt");
            // 2. 選擇流
            BufferedReader reader = null;
            try{
                reader = new BufferedReader(new FileReader(src));
                // 3. 操做(讀取)
                String line = null;
                while((line = reader.readLine()) != null){
                    System.out.println(line);
                }
            }catch(FileNotFoundException e){
                e.printStackTrace();
            }catch (IOException e){
                e.printStackTrace();
            }finally{
                // 4. 釋放資源
                if (null != reader) {
                    try {
                        reader.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
  • 轉換流:InputStreamReader OutputStreamWriter

    1. 以字符流的形式操做字節流(純文本的)
    2. 指定字符集
    • 循環讀取鍵盤輸入
    import java.io.*;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: ConvertTest1.java
     * @time: 2019/10/19 14:48
     * @desc: 轉換流:InputStreamReader OutputStreamWriter
     */
    
    public class ConvertTest1 {
        public static void main(String[] args){
            // 操做System.in和System.out
            try(BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));){
                // 循環獲取鍵盤的輸入(exit退出),輸入此內容
                String msg = "";
                while(!msg.equals("exit")){
                    msg = reader.readLine();        // 循環讀取
                    writer.write(msg);              // 循環寫出
                    writer.newLine();
                    writer.flush();                 // 強制刷新
                }
            } catch (IOException e) {
                System.out.println("操做異常");
            }
        }
    }
    • 讀取網站的內容並保存
    import java.io.*;
    import java.net.URL;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: ConvertTest2.java
     * @time: 2019/10/19 14:48
     * @desc: 轉換流:InputStreamReader OutputStreamWriter
     */
    
    public class ConvertTest2 {
        public static void main(String[] args) {
            // 中文亂碼
            test1();
            // 中文不是亂碼
            test2();
            // 效率更高
            test3();
        }
        public static void test1(){
            // 操做網絡流,下載百度的源代碼
            try(InputStream is = new URL("http://www.baidu.com").openStream();){
                int temp;
                while((temp=is.read()) != -1){
                    System.out.print((char)temp);
                }
            } catch (IOException e) {
                System.out.println("操做異常");
            }
        }
        public static void test2(){
            // 操做網絡流,下載百度的源代碼
            try(InputStreamReader is = new InputStreamReader(new URL("http://www.baidu.com").openStream(), "UTF-8")){
                int temp;
                while((temp=is.read()) != -1){
                    System.out.print((char)temp);
                }
            } catch (IOException e) {
                System.out.println("操做異常");
            }
        }
        public static void test3(){
            // 操做網絡流,下載百度的源代碼
            try(BufferedReader reader =
                        new BufferedReader(
                                new InputStreamReader(
                                        new URL("http://www.baidu.com").openStream(), "UTF-8"
                                )
                        );
                BufferedWriter writer =
                        new BufferedWriter(
                                new OutputStreamWriter(
                                        new FileOutputStream("baidu.html"), "UTF-8"
                                )
                        )
            ){
                String msg;
                while((msg = reader.readLine()) != null){
                    writer.write(msg);      // 字符集不統一,字節數不夠出現亂碼
                    writer.newLine();
                }
            } catch (IOException e) {
                System.out.println("操做異常");
            }
        }
    }
  • 數據流

    1. 先寫出後讀取
    2. 讀取的順序與寫出保持一致
    import com.sun.xml.internal.messaging.saaj.util.ByteInputStream;
    
    import java.io.*;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: DataTest1.java
     * @time: 2019/10/19 15:57
     * @desc: 數據流
     */
    
    public class DataTest1 {
        public static void main(String[] args) throws IOException {
            // 寫出
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            DataOutputStream dos = new DataOutputStream(
                    new BufferedOutputStream(baos)
            );
            // 操做數據類型 + 數據
            dos.writeUTF("編碼辛酸淚啊");
            dos.writeInt(18);
            dos.writeBoolean(false);
            dos.writeChar('a');
            dos.flush();
    
            byte[] datas = baos.toByteArray();
            System.out.println(datas.length);
    
            // 讀取
            DataInputStream dis = new DataInputStream(
                    new BufferedInputStream(
                        new ByteArrayInputStream(datas)
                    )
            );
            // 順序與寫出一致
            String msg = dis.readUTF();
            int age = dis.readInt();
            boolean flag = dis.readBoolean();
            char ch = dis.readChar();
            System.out.println(flag);
        }
    }
  • 對象流

    1. 先寫出後讀取
    2. 讀取的順序與寫出保持一致
    3. 不是全部的對象均可以序列化,必需要實現接口Serializable
    • 將對象數據序列化並反序列化

      import java.io.*;
      import java.util.Date;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: DataTest1.java
       * @time: 2019/10/19 15:57
       * @desc: 對象流
       */
      
      public class ObjectTest1 {
          public static void main(String[] args) throws IOException, ClassNotFoundException {
              // 寫出:序列化
              ByteArrayOutputStream baos = new ByteArrayOutputStream();
              ObjectOutputStream dos = new ObjectOutputStream(
                      new BufferedOutputStream(baos)
              );
              // 操做數據類型 + 數據
              dos.writeUTF("編碼辛酸淚");
              dos.writeInt(18);
              dos.writeBoolean(false);
              dos.writeChar('a');
              // 對象
              dos.writeObject("誰解其中味");
              dos.writeObject(new Date());
              Employee emp = new Employee("馬雲", 400);
              dos.writeObject(emp);
              dos.flush();
      
              byte[] datas = baos.toByteArray();
              System.out.println(datas.length);
      
              // 讀取:反序列化
              ObjectInputStream dis = new ObjectInputStream(
                      new BufferedInputStream(
                          new ByteArrayInputStream(datas)
                      )
              );
              // 順序與寫出一致
              String msg = dis.readUTF();
              int age = dis.readInt();
              boolean flag = dis.readBoolean();
              char ch = dis.readChar();
              System.out.println(flag);
              // 對象的數據還原
              Object str = dis.readObject();
              Object date = dis.readObject();
              Object employee = dis.readObject();
      
              if (str instanceof String){
                  String strObj = (String) str;
                  System.out.println(strObj);
              }
              if (date instanceof Date){
                  Date strObj = (Date) date;
                  System.out.println(strObj);
              }
              if (employee instanceof Employee){
                  Employee strObj = (Employee) employee;
                  System.out.println(strObj.getName() + "-->" + strObj.getSalary());
              }
          }
      }
      
      // javabean 封裝數據
      class Employee implements java.io.Serializable{
          private transient String name;      // 該數據不須要序列化
          private double salary;
      
          public Employee() {
          }
      
          public Employee(String name, double salary) {
              this.name = name;
              this.salary = salary;
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          public double getSalary() {
              return salary;
          }
      
          public void setSalary(double salary) {
              this.salary = salary;
          }
      }
    • 在上面的基礎上,序列化成文件並反序列化

      import java.io.*;
      import java.util.Date;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: DataTest2.java
       * @time: 2019/10/19 15:57
       * @desc: 對象流
       */
      
      public class ObjectTest2 {
          public static void main(String[] args) throws IOException, ClassNotFoundException {
              // 寫出:序列化
              ObjectOutputStream oos = new ObjectOutputStream(
                      new BufferedOutputStream(
                              new FileOutputStream("obj.txt")
                      )
              );
              // 操做數據類型 + 數據
              oos.writeUTF("編碼辛酸淚");
              oos.writeInt(18);
              oos.writeBoolean(false);
              oos.writeChar('a');
              // 對象
              oos.writeObject("誰解其中味");
              oos.writeObject(new Date());
              Employee emp = new Employee("馬雲", 400);
              oos.writeObject(emp);
              oos.flush();
              oos.close();
      
              // 讀取:反序列化
              ObjectInputStream ois = new ObjectInputStream(
                      new BufferedInputStream(
                          new FileInputStream("obj.txt")
                      )
              );
              // 順序與寫出一致
              String msg = ois.readUTF();
              int age = ois.readInt();
              boolean flag = ois.readBoolean();
              char ch = ois.readChar();
              System.out.println(flag);
              // 對象的數據還原
              Object str = ois.readObject();
              Object date = ois.readObject();
              Object employee = ois.readObject();
      
              if (str instanceof String){
                  String strObj = (String) str;
                  System.out.println(strObj);
              }
              if (date instanceof Date){
                  Date strObj = (Date) date;
                  System.out.println(strObj);
              }
              if (employee instanceof Employee){
                  Employee strObj = (Employee) employee;
                   System.out.println(strObj.getName() + "-->" + strObj.getSalary());
              }
          }
      }
  • 打印流

    • PrintStream

      import java.io.*;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: PrintTest1.java
       * @time: 2019/10/20 15:43
       * @desc: 打印流
       */
      
      public class PrintTest1 {
          public static void main(String[] args) throws FileNotFoundException {
              // 打印流System.out
              PrintStream ps = System.out;
              ps.println("打印流");
              ps.println(true);
      
              ps = new PrintStream(
                      new BufferedOutputStream(
                              new FileOutputStream("print.txt")
                      ), true
              );
              ps.println("打印流");
              ps.println(true);
              ps.close();
      
              // 重定向輸出端
              System.setOut(ps);
              System.out.println("change");
              // 重定向回控制檯
              System.setOut(
                      new PrintStream(
                              new BufferedOutputStream(
                                      new FileOutputStream(FileDescriptor.out)
                              ), true
                      )
              );
              System.out.println("i am backing...");
          }
      }
    • PrintWriter

      import java.io.*;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: PrintTest2.java
       * @time: 2019/10/20 15:43
       * @desc: 打印流
       */
      
      public class PrintTest2 {
          public static void main(String[] args) throws FileNotFoundException {
      
              PrintWriter pw = new PrintWriter(
                      new BufferedOutputStream(
                              new FileOutputStream("print.txt")
                      ), true
              );
              pw.println("打印流");
              pw.println(true);
              pw.close();
          }
      }

4. IO實戰

  • 文件分割

    • 隨機讀取和寫入流

      import java.io.File;
      import java.io.FileNotFoundException;
      import java.io.IOException;
      import java.io.RandomAccessFile;
      import java.util.Random;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: RanTest1.java
       * @time: 2019/10/21 9:01
       * @desc: 隨機讀取和寫入流 RandomAccessFile
       */
      
      public class RanTest1 {
          public static void main(String[] args) throws IOException {
              // 分多少塊
              File src = new File("D:\\李添的數據哦!!!\\BookStudy\\else\\JAVAPro\\src\\PrintTest2.java");
              // 總長度
              long len = src.length();
              // 每塊大小
              int blockSize = 240;
              // 塊數:多少塊
              int size = (int)Math.ceil(len*1.0/blockSize);
              System.out.println(size);
              int beginPos = 0;
              int actualSize = (int)(blockSize>len?len:blockSize);
      
              for(int i=0; i<size; i++){
                  beginPos = i*blockSize;
                  if(i == size-1){
                      // 最後一塊
                      actualSize = (int)len;
                  }else{
                      actualSize = blockSize;
                      // 剩餘量
                      len -= actualSize;
                  }
                  System.out.println(i + "-->" + beginPos + "-->" + actualSize);
                  test1(i, beginPos, actualSize);
              }
          }
      
          // 指定起始位置,讀取剩餘指定長度內容
          public static void test1(int i, int beginPos, int actualSize) throws IOException {
              RandomAccessFile raf = new RandomAccessFile(new File("D:\\李添的數據哦!!!\\BookStudy\\else\\JAVAPro\\src\\PrintTest2.java"), "r");
              // 指定起始位置
      //        int beginPos = 2;
              // 實際大小
      //        int actualSize = 128;
              // 隨機讀取
              raf.seek(beginPos);
              byte[] flush = new byte[124];
              // 接受長度
              int len = -1;
              while((len = raf.read(flush)) != -1){
                  if (actualSize > len){
                      // 實際大小大於接受長度,則獲取本次讀取的全部內容
                      System.out.println(new String(flush, 0, len));
                      actualSize -= len;
                  }else{
                      System.out.println(new String(flush, 0, actualSize));
                      break;
                  }
              }
      
              raf.close();
          }
      }
    • 增長輸出流

      import java.io.File;
      import java.io.IOException;
      import java.io.RandomAccessFile;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: RanTest1.java
       * @time: 2019/10/21 9:01
       * @desc: 隨機讀取和寫入流 RandomAccessFile 並增長輸出流
       */
      
      public class RanTest2 {
          public static void main(String[] args) throws IOException {
              // 分多少塊
              File src = new File("D:\\李添的數據哦!!!\\BookStudy\\else\\JAVAPro\\src\\PrintTest2.java");
              // 總長度
              long len = src.length();
              // 每塊大小
              int blockSize = 240;
              // 塊數:多少塊
              int size = (int)Math.ceil(len*1.0/blockSize);
              System.out.println(size);
              int beginPos = 0;
              int actualSize = (int)(blockSize>len?len:blockSize);
      
              for(int i=0; i<size; i++){
                  beginPos = i*blockSize;
                  if(i == size-1){
                      // 最後一塊
                      actualSize = (int)len;
                  }else{
                      actualSize = blockSize;
                      // 剩餘量
                      len -= actualSize;
                  }
                  System.out.println(i + "-->" + beginPos + "-->" + actualSize);
                  test1(i, beginPos, actualSize);
              }
          }
      
          // 指定起始位置,讀取剩餘指定長度內容
          public static void test1(int i, int beginPos, int actualSize) throws IOException {
              RandomAccessFile raf = new RandomAccessFile(new File("D:\\李添的數據哦!!!\\BookStudy\\else\\JAVAPro\\src\\PrintTest2.java"), "r");
              RandomAccessFile raf2 = new RandomAccessFile(new File("Print_Copy_" + i + ".java"), "rw");
              // 指定起始位置
      //        int beginPos = 2;
              // 實際大小
      //        int actualSize = 128;
              // 隨機讀取
              raf.seek(beginPos);
              byte[] flush = new byte[124];
              // 接受長度
              int len = -1;
              while((len = raf.read(flush)) != -1){
                  if (actualSize > len){
                      // 實際大小大於接受長度,則獲取本次讀取的全部內容
                      raf2.write(flush, 0, len);
                      actualSize -= len;
                  }else{
                      raf2.write(flush, 0, actualSize);
                      break;
                  }
              }
              raf2.close();
              raf.close();
          }
      }
    • 對RanTest進行封裝,功能是拆分文件,面向對象思想封裝

      import java.io.File;
      import java.io.IOException;
      import java.io.RandomAccessFile;
      import java.util.ArrayList;
      import java.util.List;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: SplitFile.java
       * @time: 2019/10/21 9:35
       * @desc: 對RanTest進行封裝,功能是拆分文件,面向對象思想封裝
       */
      
      public class SplitFile {
          // 源頭
          private File src;
          // 目的地(文件夾)
          private String destDir;
          // 全部分割後的文件存儲路徑
          private List<String> destPaths;
          // 每塊大小
          private int blockSize;
          // 塊數:多少塊
          private int size;
      
          public SplitFile(String srcPath, String destDir, int blockSize){
              this.src = new File(srcPath);
              this.destDir = destDir;
              this.blockSize = blockSize;
              this.destPaths = new ArrayList<>();
      
              // 初始化
              init();
          }
      
          // 初始化
          private void init(){
              // 總長度
              long len = this.src.length();
              // 塊數:多少塊
              this.size = (int)Math.ceil(len*1.0/blockSize);
              // 路徑
              for(int i=0; i<size; i++){
                  this.destPaths.add(this.destDir + "/" + i + "-" + this.src.getName());
              }
      
          }
      
          // 分割
          public void split() throws IOException {
              /*
              1. 計算每一塊起始位置及大小
              2. 分割
               */
              // 總長度
              long len = this.src.length();
              // 每塊大小
              int size = (int)Math.ceil(len*1.0/blockSize);
              System.out.println(size);
              int beginPos = 0;
              int actualSize = (int)(this.blockSize>len?len:this.blockSize);
      
              for(int i=0; i<size; i++){
                  beginPos = i*blockSize;
                  if(i == size-1){
                      // 最後一塊
                      actualSize = (int)len;
                  }else{
                      actualSize = blockSize;
                      // 剩餘量
                      len -= actualSize;
                  }
                  splitDetail(i, beginPos, actualSize);
              }
          }
      
          // 指定起始位置,讀取剩餘指定長度內容
          private void splitDetail(int i, int beginPos, int actualSize) throws IOException {
              RandomAccessFile raf = new RandomAccessFile((this.src), "r");
              RandomAccessFile raf2 = new RandomAccessFile((this.destPaths.get(i)), "rw");
              raf.seek(beginPos);
              byte[] flush = new byte[124];
              // 接受長度
              int len = -1;
              while((len = raf.read(flush)) != -1){
                  if (actualSize > len){
                      // 實際大小大於接受長度,則獲取本次讀取的全部內容
                      raf2.write(flush, 0, len);
                      actualSize -= len;
                  }else{
                      raf2.write(flush, 0, actualSize);
                      break;
                  }
              }
              raf2.close();
              raf.close();
          }
      
          public static void main(String[] args) throws IOException {
              SplitFile sf = new SplitFile("test.png", "dest", 1024*10);
              sf.split();
          }
      }
  • 增長文件的合併功能

    import java.io.*;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: SplitFile.java
     * @time: 2019/10/21 9:35
     * @desc: 對RanTest進行封裝,功能是拆分文件,面向對象思想封裝
     */
    
    public class SplitFile {
        // 源頭
        private File src;
        // 目的地(文件夾)
        private String destDir;
        // 全部分割後的文件存儲路徑
        private List<String> destPaths;
        // 每塊大小
        private int blockSize;
        // 塊數:多少塊
        private int size;
    
        public SplitFile(String srcPath, String destDir, int blockSize){
            this.src = new File(srcPath);
            this.destDir = destDir;
            this.blockSize = blockSize;
            this.destPaths = new ArrayList<>();
    
            // 初始化
            init();
        }
    
        // 初始化
        private void init(){
            // 總長度
            long len = this.src.length();
            // 塊數:多少塊
            this.size = (int)Math.ceil(len*1.0/blockSize);
            // 路徑
            for(int i=0; i<size; i++){
                this.destPaths.add(this.destDir + "/" + i + "-" + this.src.getName());
            }
    
        }
    
        // 分割
        public void split() throws IOException {
            /*
            1. 計算每一塊起始位置及大小
            2. 分割
             */
            // 總長度
            long len = this.src.length();
            // 每塊大小
            int size = (int)Math.ceil(len*1.0/blockSize);
            System.out.println(size);
            int beginPos = 0;
            int actualSize = (int)(this.blockSize>len?len:this.blockSize);
    
            for(int i=0; i<size; i++){
                beginPos = i*blockSize;
                if(i == size-1){
                    // 最後一塊
                    actualSize = (int)len;
                }else{
                    actualSize = blockSize;
                    // 剩餘量
                    len -= actualSize;
                }
                splitDetail(i, beginPos, actualSize);
            }
        }
    
        // 指定起始位置,讀取剩餘指定長度內容
        private void splitDetail(int i, int beginPos, int actualSize) throws IOException {
            RandomAccessFile raf = new RandomAccessFile((this.src), "r");
            RandomAccessFile raf2 = new RandomAccessFile((this.destPaths.get(i)), "rw");
            raf.seek(beginPos);
            byte[] flush = new byte[124];
            // 接受長度
            int len = -1;
            while((len = raf.read(flush)) != -1){
                if (actualSize > len){
                    // 實際大小大於接受長度,則獲取本次讀取的全部內容
                    raf2.write(flush, 0, len);
                    actualSize -= len;
                }else{
                    raf2.write(flush, 0, actualSize);
                    break;
                }
            }
            raf2.close();
            raf.close();
        }
    
        // 文件的合併
        private void merge(String destPath) throws IOException {
            // 輸出流
            OutputStream os = new BufferedOutputStream(
                    new FileOutputStream(destPath, true)
            );
            // 輸入流
            for (int i = 0; i < destPaths.size(); i++) {
                InputStream is = new BufferedInputStream((new FileInputStream(destPaths.get(i))));
                // 拷貝
                byte[] flush = new byte[1024];
                int len = -1;
                while((len = is.read(flush)) != -1){
                    os.write(flush, 0, len);
                }
                os.flush();
                is.close();
            }
            os.close();
        }
    
        public static void main(String[] args) throws IOException {
            SplitFile sf = new SplitFile("test.png", "dest", 1024*10);
            sf.split();
            sf.merge("merge.png");
        }
    }
  • 利用SequenceInputStream增長文件合併功能

    import java.io.*;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Vector;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: SplitFile.java
     * @time: 2019/10/21 9:35
     * @desc: 對RanTest進行封裝,功能是拆分文件,面向對象思想封裝
     */
    
    public class SplitFile {
        // 源頭
        private File src;
        // 目的地(文件夾)
        private String destDir;
        // 全部分割後的文件存儲路徑
        private List<String> destPaths;
        // 每塊大小
        private int blockSize;
        // 塊數:多少塊
        private int size;
    
        public SplitFile(String srcPath, String destDir, int blockSize){
            this.src = new File(srcPath);
            this.destDir = destDir;
            this.blockSize = blockSize;
            this.destPaths = new ArrayList<>();
    
            // 初始化
            init();
        }
    
        // 初始化
        private void init(){
            // 總長度
            long len = this.src.length();
            // 塊數:多少塊
            this.size = (int)Math.ceil(len*1.0/blockSize);
            // 路徑
            for(int i=0; i<size; i++){
                this.destPaths.add(this.destDir + "/" + i + "-" + this.src.getName());
            }
    
        }
    
        // 分割
        public void split() throws IOException {
            /*
            1. 計算每一塊起始位置及大小
            2. 分割
             */
            // 總長度
            long len = this.src.length();
            // 每塊大小
            int size = (int)Math.ceil(len*1.0/blockSize);
            System.out.println(size);
            int beginPos = 0;
            int actualSize = (int)(this.blockSize>len?len:this.blockSize);
    
            for(int i=0; i<size; i++){
                beginPos = i*blockSize;
                if(i == size-1){
                    // 最後一塊
                    actualSize = (int)len;
                }else{
                    actualSize = blockSize;
                    // 剩餘量
                    len -= actualSize;
                }
                splitDetail(i, beginPos, actualSize);
            }
        }
    
        // 指定起始位置,讀取剩餘指定長度內容
        private void splitDetail(int i, int beginPos, int actualSize) throws IOException {
            RandomAccessFile raf = new RandomAccessFile((this.src), "r");
            RandomAccessFile raf2 = new RandomAccessFile((this.destPaths.get(i)), "rw");
            raf.seek(beginPos);
            byte[] flush = new byte[124];
            // 接受長度
            int len = -1;
            while((len = raf.read(flush)) != -1){
                if (actualSize > len){
                    // 實際大小大於接受長度,則獲取本次讀取的全部內容
                    raf2.write(flush, 0, len);
                    actualSize -= len;
                }else{
                    raf2.write(flush, 0, actualSize);
                    break;
                }
            }
            raf2.close();
            raf.close();
        }
    
        // 文件的合併
        private void merge(String destPath) throws IOException {
            // 輸出流
            OutputStream os = new BufferedOutputStream(
                    new FileOutputStream(destPath, true)
            );
            // 輸入流
            for (int i = 0; i < destPaths.size(); i++) {
                InputStream is = new BufferedInputStream((new FileInputStream(destPaths.get(i))));
                // 拷貝
                byte[] flush = new byte[1024];
                int len = -1;
                while((len = is.read(flush)) != -1){
                    os.write(flush, 0, len);
                }
                os.flush();
                is.close();
            }
            os.close();
        }
    
        // 利用合併流來進行文件的合併
        private void seq_merge(String destPath) throws IOException {
            // 輸出流
            OutputStream os = new BufferedOutputStream(
                    new FileOutputStream(destPath, true)
            );
            Vector<InputStream> vi = new Vector<InputStream>();
            SequenceInputStream sis = null;
            // 輸入流
            for (int i = 0; i < destPaths.size(); i++) {
                InputStream is = new BufferedInputStream((new FileInputStream(destPaths.get(i))));
            }
            sis = new SequenceInputStream(vi.elements());
    
            // 拷貝
            byte[] flush = new byte[1024];
            int len = -1;
            while((len = sis.read(flush)) != -1){
                os.write(flush, 0, len);
            }
            os.flush();
            sis.close();
            os.close();
        }
    
        public static void main(String[] args) throws IOException {
            SplitFile sf = new SplitFile("test.png", "dest", 1024*10);
            sf.split();
            sf.seq_merge("merge-seq.png");
        }
    }

5. CommonsIO

  • 經常使用核心操做和拷貝核心操做

    import org.apache.commons.io.FileUtils;
    import org.apache.commons.io.IOUtils;
    import org.apache.commons.io.LineIterator;
    import org.apache.commons.io.filefilter.DirectoryFileFilter;
    import org.apache.commons.io.filefilter.EmptyFileFilter;
    import org.apache.commons.io.filefilter.FileFilterUtils;
    import org.apache.commons.io.filefilter.SuffixFileFilter;
    
    import javax.imageio.stream.FileCacheImageInputStream;
    import java.io.File;
    import java.io.IOException;
    import java.net.URL;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.List;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: CIOTest1.java
     * @time: 2019/10/22 16:00
     * @desc:
     */
    
    public class CIOTest1 {
        public static void main(String[] args) throws IOException {
            // 文件大小
            long len = FileUtils.sizeOf(new File("D:\\李添的數據哦!!!\\BookStudy\\else\\JAVAPro\\src\\CIOTest1.java"));
            System.out.println(len);
    
            // 目錄大小
            len = FileUtils.sizeOf(new File("D:\\李添的數據哦!!!\\BookStudy"));
            System.out.println(len);
    
            // 列出子孫集
            /*
            第一個參數:目標路徑
            第二個參數:過濾文件:
                NOT_EMPTY,即只要非空文件
                SuffixFileFilter,即只要該後綴名的文件
            第三個參數:過濾目錄:
                INSTANCE,即只看子孫集
             */
            Collection<File> files = FileUtils.listFiles(
                    new File("D:\\李添的數據哦!!!\\BookStudy\\else\\JAVAPro"),
                    FileFilterUtils.or(EmptyFileFilter.NOT_EMPTY, new SuffixFileFilter("java"), new SuffixFileFilter("class")), DirectoryFileFilter.INSTANCE
            );
            for (File file : files) {
                System.out.println(file.getAbsolutePath());
            }
    
            // 讀取文件內容
            String path = "D:\\李添的數據哦!!!\\BookStudy\\else\\【參考】3. 代碼快捷鍵操做.md";
            String msg = FileUtils.readFileToString(new File(path), "UTF-8");
            System.out.println(msg);
            byte[] datas = FileUtils.readFileToByteArray(new File(path));
            System.out.println(datas.length);
    
            // 逐行讀取
            List<String> msgs = FileUtils.readLines(new File((path)), "UTF-8");
            for (String str : msgs) {
                System.out.println(str);
            }
            // 逐行讀取2
            LineIterator it = FileUtils.lineIterator(new File(path), "UTF-8");
            while (it.hasNext()) {
                System.out.println(it.nextLine());
            }
    
            // 寫出內容到文件
            FileUtils.write(new File("happy.txt"), "學習是一件偉大的事業\n", "UTF-8");
            FileUtils.writeStringToFile(new File("happy.txt"), "學習是一件辛苦的事業\n", "UTF-8", true);
            FileUtils.writeByteArrayToFile(new File("happy.txt"), "學習是一件快樂的事業\n".getBytes("UTF-8"), true);
    
            // 寫出列表
            List<String> dd = new ArrayList<>();
            dd.add("馬雲");
            dd.add("馬化騰");
            dd.add("禮拜");
            FileUtils.writeLines(new File("happy.txt"), dd, "-", true);
    
            // 拷貝
            FileUtils.copyFile(new File("test.png"), new File("p-copy.png"));
            // 複製文件到目錄
            FileUtils.copyFileToDirectory(new File("test.png"), new File("lib"));
            // 複製目錄到目錄下
            FileUtils.copyDirectoryToDirectory(new File("lib"), new File("lib2"));
            // 複製當前路徑的某個目錄到當前目錄的新目錄
            FileUtils.copyDirectory(new File("lib"), new File("lib2"));
            // 拷貝URL內容
            // 方法1:保存網上的圖片到本地文件
            String url = "https://img-blog.csdnimg.cn/2019062009044675.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIxNTc5MDQ1,size_16,color_FFFFFF,t_70";
            FileUtils.copyURLToFile(new URL(url), new File("what.jpg"));
            // 方法2:獲取網頁的源碼
            String dat = IOUtils.toString(new URL("http://www.baidu.com"), "UTF-8");
            System.out.println(dat);
        }
    }

第11章 多線程技術

1. 概念

  • Process與Thread

    img

  • 核心概念

    1. 線程就是獨立的執行路徑。
    2. 在程序運行時,即便沒有本身建立線程,後臺也會存在多個線程,如gc線程、主線程。
    3. main()稱之爲主線程,爲系統的入口點,用於執行整個程序。
    4. 在一個進程中,若是開闢了多個線程,線程的運行由調度器安排調度,調度器是與操做系統緊密相關的,前後順序是不能認爲干預的。
    5. 對同一份資源操做時,會存在資源搶奪的問題,須要加入併發控制。
    6. 線程會帶來額外的開銷,如cpu調度時間,併發控制開銷。
    7. 每一個線程在本身的工做內存交互,加載和存儲主內存控制不當會形成數據不一致。
  • 少用繼承多用實現,由於java裏面只能單繼承

  • 線程Thread的使用方式

    1. 繼承Thread,重寫run()方法,經過start()方法去啓動線程
    2. 實現Runnable接口,重寫run()方法,經過new一個Thead對象調start()方法。
  • start方法不保證當即運行,由cpu調用

    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: ThreadStudy01.java
     * @time: 2019/10/25 12:37
     * @desc: 進程學習1
     */
    
    public class StartThread1 extends Thread{
    
        public void run(){
            for (int i = 0; i < 20; i++) {
                System.out.println("一邊聽歌一邊敲代碼。");
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            // 建立子類對象
            StartThread1 st = new StartThread1();
            // 啓動
            st.start();
            // run是普通方法的調用
    //        st.run();
            for (int i = 0; i < 20; i++) {
                System.out.println("coding。");
                Thread.sleep(1);
            }
        }
    }
  • 建立線程方式1:利用線程下載圖片案例

    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: TDownloader.java
     * @time: 2019/10/28 15:58
     * @desc: 進程學習2:下載圖片
     */
    
    public class TDownloader extends Thread{
        // 遠程路徑
        private String url;
        // 存儲名字
        private String name;
    
        public TDownloader(String url, String name) {
            this.url = url;
            this.name = name;
        }
    
        @Override
        public void run() {
            WebDownloader wd = new WebDownloader();
            wd.download(url, name);
            System.out.println(name);
        }
    
        public static void main(String[] args){
            TDownloader td1 = new TDownloader("https://img-blog.csdnimg.cn/20181107085145510.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hhcHB5Um9ja2luZw==,size_16,color_FFFFFF,t_70", "lstm.png");
            TDownloader td2 = new TDownloader("https://img-blog.csdnimg.cn/20181107095455442.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hhcHB5Um9ja2luZw==,size_16,color_FFFFFF,t_70", "peephole_connection.png");
            TDownloader td3 = new TDownloader("https://img-blog.csdnimg.cn/20181107101049389.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hhcHB5Um9ja2luZw==,size_16,color_FFFFFF,t_70", "gru.png");
    
            // 啓動三個線程
            td1.start();
            td2.start();
            td3.start();
        }
    }
  • 利用線程方式2:(推薦使用這種方式)

    • 避免單繼承的侷限性,優先使用接口
    • 方便共享資源
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: ThreadStudy01.java
     * @time: 2019/10/25 12:37
     * @desc: 進程學習3
     */
    
    public class StartRun1 implements Runnable {
    
        public void run() {
            for (int i = 0; i < 20; i++) {
                System.out.println("一邊聽歌一邊敲代碼。");
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            /*
            // 建立實現類對象
            StartRun1 sr = new StartRun1();
            // 建立代理類對象
            Thread t = new Thread(sr);
            // 啓動
            t.start();
            // run是普通方法的調用
    //        st.run();
            */
    
            // 利用匿名對象
            new Thread(new StartRun1()).start();
    
            for (int i = 0; i < 20; i++) {
                System.out.println("coding。");
                Thread.sleep(1);
            }
        }
    }
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: TDownloader.java
     * @time: 2019/10/28 15:58
     * @desc: 進程學習2:下載圖片
     */
    
    public class IDownloader implements Runnable {
        // 遠程路徑
        private String url;
        // 存儲名字
        private String name;
    
        public IDownloader(String url, String name) {
            this.url = url;
            this.name = name;
        }
    
        @Override
        public void run() {
            WebDownloader wd = new WebDownloader();
            wd.download(url, name);
            System.out.println(name);
        }
    
        public static void main(String[] args) {
            IDownloader td1 = new IDownloader("https://img-blog.csdnimg.cn/20181107085145510.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hhcHB5Um9ja2luZw==,size_16,color_FFFFFF,t_70", "lstm.png");
            IDownloader td2 = new IDownloader("https://img-blog.csdnimg.cn/20181107095455442.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hhcHB5Um9ja2luZw==,size_16,color_FFFFFF,t_70", "peephole_connection.png");
            IDownloader td3 = new IDownloader("https://img-blog.csdnimg.cn/20181107101049389.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hhcHB5Um9ja2luZw==,size_16,color_FFFFFF,t_70", "gru.png");
    
            // 啓動三個線程
            new Thread(td1).start();
            new Thread(td2).start();
            new Thread(td3).start();
        }
    }
  • 共享資源:模擬買票

    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Web12306.java
     * @time: 2019/10/30 12:36
     * @desc: 共享資源:模擬買票
     */
    
    public class Web12306 implements Runnable {
        // 票數
        private int ticketNums = 99;
    
        @Override
        public void run() {
            while(true){
                if(ticketNums<0){
                    break;
                }
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "-->" + ticketNums--);
            }
        }
    
        public static void main(String[] args){
            // 一份資源
            Web12306 web = new Web12306();
            // 多個代理
            new Thread(web, "張三").start();
            new Thread(web, "李四").start();
            new Thread(web, "王五").start();
        }
    }
  • 共享資源:模擬龜兔賽跑

    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Racer.java
     * @time: 2019/10/30 14:55
     * @desc: 共享資源:模擬龜兔賽跑
     */
    
    public class Racer implements Runnable {
        private String winner;       // 勝利者
    
        @Override
        public void run() {
            for (int steps = 1; steps <= 100; steps++) {
                // 模擬休息
                if(Thread.currentThread().getName().equals("rabit") && steps % 10 == 0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + "-->" + steps);
                // 比賽是否結束
                boolean flag = gameOver(steps);
                if (flag) {
                    break;
                }
            }
        }
    
        private boolean gameOver(int steps) {
            if (winner != null) {
                // 存在勝利者
                return true;
            } else {
                if (steps == 100) {
                    winner = Thread.currentThread().getName();
                    System.out.println("winner==>" + winner);
                    return true;
                }
            }
            return false;
        }
    
        public static void main(String[] args) {
            Racer racer = new Racer();
            new Thread(racer, "tortoise").start();
            new Thread(racer, "rabbit").start();
        }
    }
  • Callable:能拋出異常,有返回值(瞭解)

    import com.sun.org.apache.xpath.internal.operations.Bool;
    import jdk.nashorn.internal.codegen.CompilerConstants;
    
    import java.util.concurrent.*;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: TDownloader.java
     * @time: 2019/10/28 15:58
     * @desc: Callable瞭解學習
     */
    
    public class CDownloader implements Callable<Boolean> {
        // 遠程路徑
        private String url;
        // 存儲名字
        private String name;
    
        public CDownloader(String url, String name) {
            this.url = url;
            this.name = name;
        }
    
        @Override
        public Boolean call() throws Exception {
            WebDownloader wd = new WebDownloader();
            wd.download(url, name);
            System.out.println(name);
            return true;
        }
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            CDownloader cd1 = new CDownloader("https://img-blog.csdnimg.cn/20181107085145510.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hhcHB5Um9ja2luZw==,size_16,color_FFFFFF,t_70", "lstm.png");
            CDownloader cd2 = new CDownloader("https://img-blog.csdnimg.cn/20181107095455442.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hhcHB5Um9ja2luZw==,size_16,color_FFFFFF,t_70", "peephole_connection.png");
            CDownloader cd3 = new CDownloader("https://img-blog.csdnimg.cn/20181107101049389.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hhcHB5Um9ja2luZw==,size_16,color_FFFFFF,t_70", "gru.png");
    
            // 建立執行服務
            ExecutorService ser = Executors.newFixedThreadPool(3);
            // 提交執行
            Future<Boolean> result1 = ser.submit(cd1);
            Future<Boolean> result2 = ser.submit(cd2);
            Future<Boolean> result3 = ser.submit(cd3);
            // 獲取結果
            boolean r1 = result1.get();
            boolean r2 = result1.get();
            boolean r3 = result1.get();
            // 關閉服務
            ser.shutdownNow();
        }
    }
  • 建立線程有幾種方式:經常使用的有兩種,繼承Thread類,重寫Runnable接口。還有一種方式,JUC併發包下,實現Callable接口。

  • 靜態代理設計模式

    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: StaticProxy.java
     * @time: 2019/10/30 15:29
     * @desc: 靜態代理設計模式學習
     */
    
    public class StaticProxy {
        public static void main(String[] args) {
            new WeddingCompany(new You()).happyMarry();
        }
    }
    
    interface Marry {
        void happyMarry();
    }
    
    // 真實角色
    class You implements Marry {
        @Override
        public void happyMarry() {
            System.out.println("你和你的廣寒仙子本月了...");
        }
    }
    
    //代理角色,婚慶公司
    class WeddingCompany implements Marry {
        // 真實角色
        private Marry target;
    
        public WeddingCompany(Marry target) {
            this.target = target;
        }
    
        @Override
        public void happyMarry() {
            ready();
            this.target.happyMarry();
            after();
        }
    
        private void ready() {
            System.out.println("佈置豬窩...");
        }
    
        private void after() {
            System.out.println("鬧玉兔...");
        }
    }
  • Lambda表達式 簡化線程(用一次)的使用

    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: LambdaThread.java
     * @time: 2019/10/30 16:00
     * @desc: Lambda表達式 簡化線程(用一次)的使用
     */
    
    public class LambdaThread {
        // 類中類:靜態內部類
        static class Test implements Runnable {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("一邊聽歌");
                }
            }
        }
    
        public static void main(String[] args) {
            new Thread(new Test()).start();
    
            // 方法中類:局部內部類
            class Test2 implements Runnable {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        System.out.println("一邊聽歌");
                    }
                }
            }
            new Thread(new Test2()).start();
    
            // 參數中類:匿名內部類
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 20; i++) {
                        System.out.println("一邊聽歌");
                    }
                }
            }).start();
    
            // jdk8簡化匿名內部類,lambda
            new Thread(
                    () -> {
                        for (int i = 0; i < 20; i++) {
                            System.out.println("一邊聽歌");
                        }
                    }
            ).start();
        }
    }
  • lambda推導:必須存在類型

    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: LambdaTest1.java
     * @time: 2019/10/31 15:18
     * @desc: lambda推導
     */
    
    public class LambdaTest1 {
        static class Like2 implements ILike {
            public void lambda() {
                System.out.println("2. 我喜歡你大爺!");
            }
        }
    
        public static void main(String[] args) {
            class Like3 implements ILike {
                public void lambda() {
                    System.out.println("3. 我喜歡你大爺!");
                }
            }
    
            // 外部類
            ILike like = new Like();
            like.lambda();
            // 靜態內部類
            like = new Like2();
            like.lambda();
            // 方法內部類
            like = new Like3();
            like.lambda();
    
            // 匿名類
            like = new ILike() {
                @Override
                public void lambda() {
                    System.out.println("4. 我喜歡你大爺!");
                }
            };
            like.lambda();
    
            // lambda
            like = () -> {
                System.out.println("5. 我喜歡你大爺!");
            };
            like.lambda();
        }
    }
    
    interface ILike {
        void lambda();
    }
    
    class Like implements ILike {
        @Override
        public void lambda() {
            System.out.println("1. 我喜歡你大爺!");
        }
    }
  • lambda推導 + 參數

    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: LambdaTest1.java
     * @time: 2019/10/31 15:18
     * @desc: lambda推導 + 參數
     */
    
    public class LambdaTest2 {
        public static void main(String[] args) {
            ILove love = (int a) -> {
                System.out.println("偶買噶!-->" + a);
            };
            love.lambda(100);
    
            // 參數類型能夠省略
            ILove love2 = s -> {
                System.out.println("偶買噶!-->" + s);
            };
            love2.lambda(10);
    
            // 花括號也能夠省略
            ILove love3 = s -> System.out.println("偶買噶!-->" + s);
            love3.lambda(1);
        }
    }
    
    interface ILove {
        void lambda(int a);
    }
    
    class Love implements ILove {
        @Override
        public void lambda(int a) {
            System.out.println("偶買噶!-->" + a);
        }
    }
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: LambdaTest1.java
     * @time: 2019/10/31 15:18
     * @desc: lambda推導 + 參數
     */
    
    public class LambdaTest2 {
        public static void main(String[] args) {
            ILove love = (int a) -> {
                System.out.println("偶買噶!-->" + a);
            };
            love.lambda(100);
    
            // 參數類型能夠省略
            ILove love2 = s -> {
                System.out.println("偶買噶!-->" + s);
            };
            love2.lambda(10);
    
            // 花括號也能夠省略
            ILove love3 = s -> System.out.println("偶買噶!-->" + s);
            love3.lambda(1);
        }
    }
    
    interface ILove {
        void lambda(int a);
    }
    
    class Love implements ILove {
        @Override
        public void lambda(int a) {
            System.out.println("偶買噶!-->" + a);
        }
    }
  • lambda推導 + 參數 + 返回值

    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: LambdaTest1.java
     * @time: 2019/10/31 15:18
     * @desc: lambda推導 + 參數 + 返回值
     */
    
    public class LambdaTest3 {
        public static void main(String[] args) {
            IInterest in = (int q, int p) -> {
                System.out.println(q + p);
                return q + p;
            };
            in.lambda(100, 50);
    
            // 簡化版本
            IInterest in2 = (q, p) -> q + p / 2;
            System.out.println(in2.lambda(10, 20));
        }
    }
    
    interface IInterest {
        int lambda(int a, int b);
    }
    
    // 參考,下面內容能夠不要
    class Interest implements IInterest {
        @Override
        public int lambda(int aa, int bb) {
            System.out.println(aa + bb);
            return aa + bb;
        }
    }
  • lambda推導實現線程

    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: LambdaTest1.java
     * @time: 2019/10/31 15:18
     * @desc: lambda推導實現線程
     */
    
    public class LambdaTest4 {
        public static void main(String[] args) {
            new Thread(() -> {
                System.out.println("一邊學習lambda");
            }).start();
    
            // 簡化:花括號能夠不要
            new Thread(() -> System.out.println("一邊淚流滿面")).start();
    
            // 若是是多個語句,就不能省略
            new Thread(() -> {
                for (int i = 0; i < 20; i++) {
                    System.out.println("我瘋了,你呢?");
                }
            }).start();
        }
    }

2. 線程狀態

  • 一個線程對象在它的生命週期內,須要經歷5個狀態。

    图11-4 线程生命周期图.png

  1. 新生狀態(New)

    用new關鍵字創建一個線程對象後,該線程對象就處於新生狀態。處於新生狀態的線程有本身的內存空間,經過調用start方法進入就緒狀態。

  2. 就緒狀態(Runnable)

    處於就緒狀態的線程已經具有了運行條件,可是尚未被分配到CPU,處於「線程就緒隊列」,等待系統爲其分配CPU。就緒狀態並非執行狀態,當系統選定一個等待執行的Thread對象後,它就會進入執行狀態。一旦得到CPU,線程就進入運行狀態並自動調用本身的run方法。有4中緣由會致使線程進入就緒狀態:

    1. 新建線程:調用start()方法,進入就緒狀態;

    2. 阻塞線程:阻塞解除,進入就緒狀態;

    3. 運行線程:調用yield()方法,直接進入就緒狀態;

    4. 運行線程:JVM將CPU資源從本線程切換到其餘線程。

  3. 運行狀態(Running)

    在運行狀態的線程執行本身run方法中的代碼,直到調用其餘方法而終止或等待某資源而阻塞或完成任務而死亡。若是在給定的時間片內沒有執行結束,就會被系統給換下來回到就緒狀態。也可能因爲某些「致使阻塞的事件」而進入阻塞狀態。

  4. 阻塞狀態(Blocked)

    阻塞指的是暫停一個線程的執行以等待某個條件發生(如某資源就緒)。有4種緣由會致使阻塞:

    1. 執行sleep(int millsecond)方法,使當前線程休眠,進入阻塞狀態。當指定的時間到了後,線程進入就緒狀態。

    2. 執行wait()方法,使當前線程進入阻塞狀態。當使用nofity()方法喚醒這個線程後,它進入就緒狀態。

    3. 線程運行時,某個操做進入阻塞狀態,好比執行IO流操做(read()/write()方法自己就是阻塞的方法)。只有當引發該操做阻塞的緣由消失後,線程進入就緒狀態。

    4. join()線程聯合: 當某個線程等待另外一個線程執行結束後,才能繼續執行時,使用join()方法。

  5. 死亡狀態(Terminated)

    死亡狀態是線程生命週期中的最後一個階段。線程死亡的緣由有兩個。一個是正常運行的線程完成了它run()方法內的所有工做; 另外一個是線程被強制終止,如經過執行stop()或destroy()方法來終止一個線程(注:stop()/destroy()方法已經被JDK廢棄,不推薦使用)。

    當一個線程進入死亡狀態之後,就不能再回到其它狀態了。

  • 線程的終止

    1. 線程正常執行完畢-->次數
    2. 外部干涉–>加入標識

    不要使用stop和destroy

    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: TerminateThread.java
     * @time: 2019/11/1 14:32
     * @desc: 終止線程
     */
    
    public class TerminateThread implements Runnable {
        // 1. 設置標識,標記線程體是否能夠運行
        private boolean flag = true;
        private String name;
    
        public TerminateThread(String name) {
            this.name = name;
        }
    
        @Override
        public void run() {
            int i = 0;
            // 2. 關聯標識,true-->運行,False-->中止
            while (flag) {
                System.out.println(name + "-->" + i++);
            }
        }
    
        // 3. 對外提供方法改變標識
        public void terminate() {
            this.flag = false;
        }
    
        public static void main(String[] args) {
            TerminateThread tt = new TerminateThread("你大爺");
            new Thread(tt).start();
            for (int i = 0; i < 99; i++) {
                if (i == 88){
                    tt.terminate();     // 線程終止
                    System.out.println("tt game over!");
                }
                System.out.println("main-->" + i);
            }
        }
    }
  • 線程的暫停-sleep: 可讓正在運行的線程進入阻塞狀態,直到休眠時間滿了,進入就緒狀態。

    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: BlockedSleep1.java
     * @time: 2019/11/1 14:46
     * @desc: sleep模擬倒計時
     */
    
    public class BlockedSleep1 {
        public static void main(String[] args) throws InterruptedException {
            // 倒計時
            Date endTime = new Date(System.currentTimeMillis() + 1000 * 10);
            long end = endTime.getTime();
            while (true) {
                System.out.println(new SimpleDateFormat("mm:ss").format(endTime));
                Thread.sleep(1000);
                endTime = new Date(endTime.getTime()-1000);
                if(end-10000 > endTime.getTime()){
                    break;
                }
            }
        }
    
        public static void test() throws InterruptedException {
            // 倒數10個數,1秒一個
            int num = 10;
            while (true) {
                Thread.sleep(1000);
                System.out.println(num--);
            }
        }
    }
  • 線程的暫停-yield: 可讓正在運行的線程直接進入就緒狀態,讓出CPU的使用權。

    import org.omg.PortableServer.THREAD_POLICY_ID;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: YieldDemo1.java
     * @time: 2019/11/1 14:55
     * @desc: yield禮讓線程,暫停線程,直接進入就緒狀態不是阻塞狀態
     */
    
    public class YieldDemo1 {
        public static void main(String[] args) {
            MyYield my = new MyYield();
            new Thread(my, "a").start();
            new Thread(my, "b").start();
    
            // lambda實現
            new Thread(() -> {
                for (int i = 0; i < 100; i++) {
                    System.out.println("lambda..." + i);
                }
            }).start();
            for (int i = 0; i < 100; i++) {
                if (i % 20 == 0) {
                    Thread.yield();     // main禮讓
                }
                System.out.println("main..." + i);
            }
        }
    }
    
    class MyYield implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "-->start");
            Thread.yield();     // 禮讓
            System.out.println(Thread.currentThread().getName() + "-->end");
        }
    }
  • 線程的聯合-join:合併線程,插隊線程。

    import sun.java2d.loops.TransformHelper;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: BlockedJoin1.java
     * @time: 2019/11/1 15:05
     * @desc: 爸爸和兒子買菸的故事
     */
    
    public class BlockedJoin1 {
        public static void main(String[] args){
            new Father().start();
        }
    }
    
    class Father extends Thread{
        @Override
        public void run() {
            System.out.println("想抽菸,發現沒了");
            System.out.println("讓兒子去買中華");
            Thread t = new Son();
            t.start();
            try {
                t.join();       // father被阻塞
                System.out.println("老爸接過煙,把零錢給了兒子");
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("孩子走丟了,老爸出去找孩子去了...");
            }
        }
    }
    
    class Son extends Thread{
        @Override
        public void run() {
            System.out.println("接過老爸的錢出去了...");
            System.out.println("路邊有個遊戲廳,玩了10秒");
            for (int i = 0; i < 10; i++) {
                System.out.println(i+"秒過去了...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("趕忙買菸去...");
            System.out.println("手拿一包中華回家了...");
        }
    }
  • 觀察線程的各個狀態

    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: AllState.java
     * @time: 2019/11/1 15:22
     * @desc: 觀察線程的各個狀態
     */
    
    public class AllState {
        public static void main(String[] args) {
            Thread t = new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("...");
            });
            // 觀察狀態
            Thread.State state = t.getState();
            System.out.println(state);  // NEW
            t.start();
            state = t.getState();
            System.out.println(state);  // RUNNABLE
    
            while (state != Thread.State.TERMINATED) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                state = t.getState();   // TIMED_WAITING
                System.out.println(state);
            }
            state = t.getState();   // TERMINATED
            System.out.println(state);
        }
    }

3. 線程的優先級

  1. NORM_PRIORITY 5

  2. MIN_PRIORITY 1

  3. MAX_PRIORITY 10

    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: PriorityTest1.java
     * @time: 2019/11/4 12:38
     * @desc: 多線程優先級
     */
    
    public class PriorityTest1 {
        public static void main(String[] args) {
            MyPriority mp = new MyPriority();
            Thread t1 = new Thread(mp);
            Thread t2 = new Thread(mp);
            Thread t3 = new Thread(mp);
            Thread t4 = new Thread(mp);
            Thread t5 = new Thread(mp);
            Thread t6 = new Thread(mp);
    
            t1.setPriority(Thread.MAX_PRIORITY);
            t2.setPriority(Thread.MAX_PRIORITY);
            t3.setPriority(Thread.MAX_PRIORITY);
            t4.setPriority(Thread.MIN_PRIORITY);
            t5.setPriority(Thread.MIN_PRIORITY);
            t6.setPriority(Thread.MIN_PRIORITY);
    
            t1.start();
            t2.start();
            t3.start();
            t4.start();
            t5.start();
            t6.start();
        }
    }
    
    class MyPriority implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
            Thread.yield();
        }
    }

4. 守護線程

  • 是爲用戶線程服務的;JVM中止不用等待守護線程執行完畢

  • 默認:用戶線程,JVM等待用戶線程執行完畢纔會中止

    import org.omg.PortableServer.THREAD_POLICY_ID;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: DaemonTest.java
     * @time: 2019/11/4 13:35
     * @desc: 守護線程學習
     */
    
    public class DaemonTest {
        public static void main(String[] args) {
            Thread t1 = new Thread(new You1());
            t1.run();
            Thread t2 = new Thread(new God1());
            // 將用戶線程調整爲守護線程
            t2.setDaemon(true);
            t2.start();
        }
    }
    
    class You1 extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 365 * 100; i++) {
                System.out.println("happy life!");
            }
            System.out.println("ooo...");
        }
    }
    
    class God1 extends Thread {
        @Override
        public void run() {
            for (;true;) {
                System.out.println("bless you!");
            }
        }
    }

5. 獲取線程基本信息的方法

  • 經常使用方法

    表11-1线程的常用方法.png

  • 案例

    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: InfoTest.java
     * @time: 2019/11/4 13:46
     * @desc: 獲取線程基本信息的方法
     */
    
    public class InfoTest {
        public static void main(String[] args) throws InterruptedException {
            // 線程是否活着
            System.out.println(Thread.currentThread().isAlive());
            // 設置名稱:真是角色+代理角色
            MyInfo info = new MyInfo("戰鬥機");
            Thread t = new Thread(info);
            t.setName("公雞");
            t.start();
            Thread.sleep(1000);
            System.out.println(t.isAlive());
        }
    }
    
    class MyInfo implements Runnable{
        private String name;
        public MyInfo(String name) {
            this.name = name;
        }
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "-->" + name);
        }
    }

6. 併發控制

  • 併發:同一個對象多個線程同時操做

1. 同步

  • 線程不安全案例1

    package com.sxt.thread;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: UnsafeTest.java
     * @time: 2019/11/4 13:57
     * @desc: 線程同步
     */
    
    public class UnsafeTest {
        public static void main(String[] args) {
            // 帳戶
            Account account = new Account(100, "結婚禮金");
            Drawing you = new Drawing(account, 80, "可悲的你");
            Drawing wife = new Drawing(account, 90, "happy的她");
            you.start();
            wife.start();
        }
    }
    
    // 帳戶
    class Account {
        int money;
        String name;
    
        public Account(int money, String name) {
            this.money = money;
            this.name = name;
        }
    }
    
    // 模擬取款
    class Drawing extends Thread {
        // 取錢的帳戶
        Account accout;
        // 取多少錢
        int drawingMoney;
        // 口袋裏的總數
        int packetTotal;
    
        public Drawing(Account accout, int drawingMoney, String name) {
            super(name);
            this.accout = accout;
            this.drawingMoney = drawingMoney;
        }
    
        @Override
        public void run() {
            if(accout.money - drawingMoney < 0){
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            accout.money -= drawingMoney;
            packetTotal += drawingMoney;
            System.out.println(this.getName() + "-->帳戶餘額爲:" + accout.money);
            System.out.println(this.getName() + "-->口袋裏的錢爲:" + packetTotal);
        }
    }
  • 線程不安全案例2

    package com.sxt.thread;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: UnsafeTest.java
     * @time: 2019/11/4 13:57
     * @desc: 線程同步
     */
    
    public class UnsafeTest2 {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            for (int i = 0; i < 10000; i++) {
                new Thread(()->{
                    list.add(Thread.currentThread().getName());
                }).start();
            }
            System.out.println(list.size());
        }
    }
  • 鎖機制

    • 爲了保證數據在方法中被訪問時的正確性,在訪問時加入鎖機制(synchronized),當一個線程得到對象的排它鎖,獨佔資源,其餘線程必須等待,使用後釋放鎖便可。存在如下問題:

      1. 一個線程持有鎖會致使其它全部須要此鎖的線程掛起;
      2. 在多線程競爭下,加鎖、釋放鎖會致使比較多的上下文切換和調度延時,引發性能問題;
      3. 若是一個優先級高的線程等待一個優先級低的線程釋放鎖會致使優先級倒置,引發性能問題。
    • 線程安全:在併發時保證數據的正確性、效率儘量高(synchronized)

      • 同步方法
      • 同步塊(java有四種塊,普通塊局部塊,構造塊,靜態塊,同步塊)
    • 樣例1:

      package com.sxt.thread;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: UnsafeTest.java
       * @time: 2019/11/4 13:57
       * @desc: 線程同步
       */
      
      public class SafeTest {
          public static void main(String[] args) {
              // 帳戶
              Account account = new Account(100, "結婚禮金");
              SafeDrawing you = new SafeDrawing(account, 80, "可悲的你");
              SafeDrawing wife = new SafeDrawing(account, 90, "happy的她");
              you.start();
              wife.start();
          }
      }
      
      // 模擬取款
      class SafeDrawing extends Thread {
          // 取錢的帳戶
          Account accout;
          // 取多少錢
          int drawingMoney;
          // 口袋裏的總數
          int packetTotal;
      
          public SafeDrawing(Account accout, int drawingMoney, String name) {
              super(name);
              this.accout = accout;
              this.drawingMoney = drawingMoney;
          }
      
          @Override
          public void run() {
              test();
          }
      
          public void test() {
              if (accout.money <= 0) {
                  return;
              }
              synchronized (accout) {
                  if (accout.money - drawingMoney < 0) {
                      return;
                  }
                  try {
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  accout.money -= drawingMoney;
                  packetTotal += drawingMoney;
                  System.out.println(this.getName() + "-->帳戶餘額爲:" + accout.money);
                  System.out.println(this.getName() + "-->口袋裏的錢爲:" + packetTotal);
              }
          }
      }
    • 樣例2

      package com.sxt.thread;
      
      import java.util.ArrayList;
      import java.util.List;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: UnsafeTest.java
       * @time: 2019/11/4 13:57
       * @desc: 線程同步
       */
      
      public class SafeTest2 {
          public static void main(String[] args) {
              List<String> list = new ArrayList<>();
              for (int i = 0; i < 10000; i++) {
                  new Thread(() -> {
                      // 同步塊
                      synchronized (list) {
                          list.add(Thread.currentThread().getName());
                      }
                  }).start();
              }
              try {
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              System.out.println(list.size());
          }
      }
  • 雙重檢測:考慮臨界值的問題

package com.sxt.thread;

/**
 * @author: Li Tian
 * @contact: litian_cup@163.com
 * @software: IntelliJ IDEA
 * @file: Web12306.java
 * @time: 2019/10/30 12:36
 * @desc: 線程安全買票
 */

public class Safe12306 implements Runnable {
    // 票數
    private int ticketNums = 10;
    private boolean flag = true;

    @Override
    public void run() {
        while (flag) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            test();
        }
    }

    private void test() {
        if (ticketNums <= 0) {  // 考慮的是沒有票的狀況
            flag = false;
            return;
        }
        synchronized (this) {
            if (ticketNums <= 0) {  // 考慮的是最後一張票的狀況
                flag = false;
                return;
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "-->" + ticketNums--);
        }
    }

    public static void main(String[] args) {
        // 一份資源
        Safe12306 web = new Safe12306();
        // 多個代理
        new Thread(web, "張三").start();
        new Thread(web, "李四").start();
        new Thread(web, "王五").start();
    }
}
  • 案例1:快樂影院

    package com.sxt.thread;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: HappyCinema.java
     * @time: 2019/11/5 12:57
     * @desc: 快樂電影院搶座位案例
     */
    
    public class HappyCinema {
        public static void main(String[] args) {
            Cinema c = new Cinema(2, "happy sxt");
            new Thread(new Customer(c, 2), "老高").start();
            new Thread(new Customer(c, 1), "老李").start();
        }
    }
    
    class Customer implements Runnable {
        Cinema cinema;
        int seats;
    
        public Customer(Cinema cinema, int seats) {
            this.cinema = cinema;
            this.seats = seats;
        }
    
        @Override
        public void run() {
            synchronized (cinema) {
                boolean flag = cinema.bookTickets(seats);
                if (flag) {
                    System.out.println("出票成功" + Thread.currentThread().getName() + "-<位置爲:" + seats);
                } else {
                    System.out.println("出票失敗" + Thread.currentThread().getName() + "-<位置不夠!");
                }
            }
        }
    }
    
    class Cinema {
        // 可用的位置
        int available;
        // 名稱
        String name;
    
        public Cinema(int available, String name) {
            this.available = available;
            this.name = name;
        }
    
        // 購票
        public boolean bookTickets(int seats) {
            System.out.println("可用位置爲:" + available);
            if (seats > available) {
                return false;
            }
            available -= seats;
            return true;
        }
    }
  • 案例2:快樂影院真實List座位

    package com.sxt.thread;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: HappyCinema.java
     * @time: 2019/11/5 12:57
     * @desc: 快樂電影院搶座位案例
     */
    
    public class HappyCinema2 {
        public static void main(String[] args) {
            // 可用位置
            List<Integer> available = new ArrayList<>();
            for (int i = 1; i < 8; i++) {
                available.add(i);
            }
    
            // 顧客須要的位置
            List<Integer> seats1 = new ArrayList<>();
            seats1.add(1);
            seats1.add(2);
            List<Integer> seats2 = new ArrayList<>();
            seats2.add(4);
            seats2.add(5);
            seats2.add(6);
    
            SxtCinema c = new SxtCinema(available, "happy sxt");
            new Thread(new HappyCustomer(c, seats1), "老高").start();
            new Thread(new HappyCustomer(c, seats2), "老李").start();
        }
    }
    
    class HappyCustomer implements Runnable {
        SxtCinema cinema;
        List<Integer> seats;
    
        public HappyCustomer(SxtCinema cinema, List<Integer> seats) {
            this.cinema = cinema;
            this.seats = seats;
        }
    
        @Override
        public void run() {
            synchronized (cinema) {
                boolean flag = cinema.bookTickets(seats);
                if (flag) {
                    System.out.println("出票成功" + Thread.currentThread().getName() + "-<位置爲:" + seats);
                } else {
                    System.out.println("出票失敗" + Thread.currentThread().getName() + "-<位置不夠!");
                }
            }
        }
    }
    
    class SxtCinema {
        // 可用的位置
        List<Integer> available;
        // 名稱
        String name;
    
        public SxtCinema(List<Integer> available, String name) {
            this.available = available;
            this.name = name;
        }
    
        // 購票
        public boolean bookTickets(List<Integer> seats) {
            System.out.println("可用位置爲:" + available);
            List<Integer> copy = new ArrayList<>();
            copy.addAll(available);
    
            // 相減
            copy.removeAll(seats);
            // 判斷大小
            if (available.size() - copy.size() != seats.size()) {
                return false;
            }
            // 成功
            available = copy;
    
            return true;
        }
    }
  • 案例3:快樂火車票

    package com.sxt.thread;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Happy12306.java
     * @time: 2019/11/7 19:24
     * @desc: 快樂火車票
     */
    
    public class Happy12306 {
        public static void main(String[] args) {
            Web12306 c = new Web12306(2, "happy sxt");
            new Passenger(c, "老高", 2).start();
            new Passenger(c, "老李", 1).start();
        }
    }
    
    // 乘客
    class Passenger extends Thread {
        int seats;
    
        public Passenger(Runnable target, String name, int seats) {
            super(target, name);
            this.seats = seats;
        }
    }
    
    // 火車票網
    class Web12306 implements Runnable {
        // 可用的位置
        int available;
        // 名稱
        String name;
    
        public Web12306(int available, String name) {
            this.available = available;
            this.name = name;
        }
    
        @Override
        public void run() {
            Passenger p = (Passenger) Thread.currentThread();
            boolean flag = this.bookTickets(p.seats);
            if (flag) {
                System.out.println("出票成功" + Thread.currentThread().getName() + "-<位置爲:" + p.seats);
            } else {
                System.out.println("出票失敗" + Thread.currentThread().getName() + "-<位置不夠!");
            }
        }
    
        // 購票
        public synchronized boolean bookTickets(int seats) {
            System.out.println("可用位置爲:" + available);
            if (seats > available) {
                return false;
            }
            available -= seats;
            return true;
        }
    }
  • 併發容器:import java.util.concurrent.CopyOnWriteArrayList

    package com.sxt.thread;
    
    import java.util.concurrent.CopyOnWriteArrayList;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: SynContainer.java
     * @time: 2019/11/8 14:09
     * @desc: 線程同步:併發容器
     */
    
    public class SynContainer {
        public static void main(String[] args) {
            CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
            for (int i = 0; i < 10000; i++) {
                new Thread(() -> {
                    // 同步塊
                    list.add(Thread.currentThread().getName());
                }).start();
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(list.size());
        }
    }

2. 死鎖

  • 死鎖指的是:多個線程各自佔有一些共享資源,而且互相等待其餘線程佔有的資源才能進行,而致使兩個或者多個線程都在等待對方釋放資源,都中止執行的情形。

  • 避免方式:不要在同一個代碼塊中持有多個對象鎖。

  • 死鎖案例:

    package com.sxt.thread;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: DeadLock.java
     * @time: 2019/11/8 14:16
     * @desc: 死鎖
     */
    
    public class DeadLock {
        public static void main(String[] args) {
            Makeup g1 = new Makeup(1, "豐光");
            Makeup g2 = new Makeup(2, "師兄");
            g1.start();
            g2.start();
        }
    }
    
    // 口紅
    class Lipstick {
    
    }
    
    // 鏡子
    class Mirror {
    
    }
    
    // 化妝
    class Makeup extends Thread {
        static Lipstick lip = new Lipstick();
        static Mirror mir = new Mirror();
        // 選擇
        int choice;
        // 名字
        String girlname;
    
        public Makeup(int choice, String girlname) {
            this.choice = choice;
            this.girlname = girlname;
        }
    
        @Override
        public void run() {
            // 化妝
            makeup();
        }
    
        private void makeup() {
            // 相互持有對方的對象鎖,這樣纔有可能形成死鎖
            if (choice == 1) {
                // 得到口紅的鎖
                synchronized (lip) {
                    System.out.println(this.girlname + "-->塗口紅");
                    // 1秒後想擁有鏡子的鎖
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (mir) {
                        System.out.println(this.girlname + "-->照鏡子");
                    }
                }
            } else {
                synchronized (mir) {
                    System.out.println(this.girlname + "-->照鏡子");
                    // 2秒後想擁有口紅的鎖
                    try {
                        Thread.sleep(1100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (lip) {
                        System.out.println(this.girlname + "-->塗口紅");
                    }
                }
            }
        }
    }
  • 死鎖的解決案例:

    package com.sxt.thread;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: DeadLock.java
     * @time: 2019/11/8 14:16
     * @desc: 解決死鎖
     */
    
    public class DeadLock2 {
        public static void main(String[] args) {
            Makeup2 g1 = new Makeup2(1, "豐光");
            Makeup2 g2 = new Makeup2(2, "師兄");
            g1.start();
            g2.start();
        }
    }
    
    
    // 化妝
    class Makeup2 extends Thread {
        static Lipstick lip = new Lipstick();
        static Mirror mir = new Mirror();
        // 選擇
        int choice;
        // 名字
        String girlname;
    
        public Makeup2(int choice, String girlname) {
            this.choice = choice;
            this.girlname = girlname;
        }
    
        @Override
        public void run() {
            // 化妝
            makeup();
        }
    
        private void makeup() {
            // 相互持有對方的對象鎖,這樣纔有可能形成死鎖
            if (choice == 1) {
                // 得到口紅的鎖
                synchronized (lip) {
                    System.out.println(this.girlname + "-->塗口紅");
                    // 1秒後想擁有鏡子的鎖
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                synchronized (mir) {
                    System.out.println(this.girlname + "-->照鏡子");
                }
            } else {
                synchronized (mir) {
                    System.out.println(this.girlname + "-->照鏡子");
                    // 2秒後想擁有口紅的鎖
                    try {
                        Thread.sleep(1100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                synchronized (lip) {
                    System.out.println(this.girlname + "-->塗口紅");
                }
            }
        }
    }

3. 併發協做

  • 生產者消費者模式

  • view簡介

    • pv:page view
    • uv:unique view
    • vv:visit view
  • 在生產者消費者問題中,僅有synchronized是不夠的

    • synchronized可組織併發更新同一個共享資源,實現了同步
    • synchronized不能用來實現不一樣線程之間的消息傳遞(通訊)
  • 實現生產者消費者的方法:

    • 管程法
    • 信號燈法
  • 實現方式:用wait()等待,notify()喚醒

  • 管程法:藉助緩衝區

    package com.sxt.cooperation;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: CoTest1.java
     * @time: 2019/11/8 15:36
     * @desc: 協做模型:生產者消費者實現方式1:管程法
     */
    
    public class CoTest1 {
        public static void main(String[] args) {
            SynContainer container = new SynContainer();
            new Productor(container).start();
            new Consumer(container).start();
        }
    }
    
    // 生產者
    class Productor extends Thread {
        SynContainer container;
    
        public Productor(SynContainer container) {
            this.container = container;
        }
    
        @Override
        public void run() {
            // 開始生產
            for (int i = 0; i < 100; i++) {
                System.out.println("生產-->第" + i + "個饅頭");
                container.push(new SteamedBun(i));
            }
        }
    }
    
    // 消費者
    class Consumer extends Thread {
        SynContainer container;
    
        public Consumer(SynContainer container) {
            this.container = container;
        }
    
        @Override
        public void run() {
            // 開始消費
            for (int i = 0; i < 1000; i++) {
                System.out.println("消費-->第" + container.pop().id + "個饅頭");
            }
        }
    }
    
    // 緩衝區
    class SynContainer {
        SteamedBun[] buns = new SteamedBun[10];
        int count = 0;
    
        // 存儲:生產
        public synchronized void push(SteamedBun bun) {
            // 什麼時候能生產:容器存在空間
            if (count == buns.length) {
                try {
                    // 線程阻塞,消費者通知生產解除
                    this.wait();
                } catch (InterruptedException e) {
                }
            }
            // 存在空間,能夠生產
            buns[count++] = bun;
            // 存在數據了,能夠通知消費了
            this.notifyAll();
        }
    
        // 獲取:消費
        public synchronized SteamedBun pop() {
            // 什麼時候消費:容器中是否存在數據,存在數據則能夠消費,沒有數據就只能等待
            if (count == 0) {
                try {
                    // 線程阻塞:生產者通知消費則接觸阻塞
                    this.wait();
                } catch (InterruptedException e) {
                }
            }
            SteamedBun bun = buns[--count];
            // 存在空間,能夠喚醒對方生產
            this.notifyAll();
            return bun;
        }
    }
    
    // 數據。饅頭
    class SteamedBun {
        int id;
    
        public SteamedBun(int id) {
            this.id = id;
        }
    }
  • 信號燈法:藉助標誌位

    package com.sxt.cooperation;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: CoTest2.java
     * @time: 2019/11/8 16:38
     * @desc: 信號燈法
     */
    
    public class CoTest2 {
        public static void main(String[] args) {
            Tv tv = new Tv();
            new Player(tv).start();
            new Watcher(tv).start();
        }
    }
    
    // 生產者:演員
    class Player extends Thread {
        Tv tv;
    
        public Player(Tv tv) {
            this.tv = tv;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                if (i % 2 == 0) {
                    this.tv.play("奇葩說");
                } else {
                    this.tv.play("倚天屠龍記");
                }
            }
        }
    }
    
    // 消費者:觀衆
    class Watcher extends Thread {
        Tv tv;
    
        public Watcher(Tv tv) {
            this.tv = tv;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                tv.watch();
            }
        }
    }
    
    // 同一個資源:電視
    class Tv {
        String voice;
        // T:演員表演,觀衆等待;F:觀衆觀看,演員等待
        boolean flag = true;
    
        // 表演
        public synchronized void play(String voice) {
            // 演員等待
            if (!flag) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("表演了" + voice);
            this.voice = voice;
            // 喚醒
            this.notifyAll();
            this.flag = !this.flag;
        }
    
        // 觀看
        public synchronized void watch() {
            // 觀衆等待
            if (flag) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("聽到了" + voice);
            // 喚醒
            this.notifyAll();
            this.flag = !this.flag;
        }
    }

7. 高級主題

  • 定時調度(簡單):Timer和TimerTask類

    package com.sxt.cooperation;
    
    import com.sun.deploy.cache.CacheEntry;
    import com.sun.deploy.security.MozillaMyKeyStore;
    
    import java.util.Calendar;
    import java.util.GregorianCalendar;
    import java.util.Timer;
    import java.util.TimerTask;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: TimerTest1.java
     * @time: 2019/11/9 18:27
     * @desc: 定時調度
     */
    
    public class TimerTest1 {
        public static void main(String[] args) {
            Timer timer = new Timer();
            // 執行安排
            // 執行一次
            timer.schedule(new MyTask(), 1000);
            // 執行屢次
            timer.schedule(new MyTask(), 1000, 200);
            // 指定時間執行
            Calendar cal = new GregorianCalendar(2099, 11, 3, 11, 22, 22);
            timer.schedule(new MyTask(), cal.getTime(), 200);
        }
    }
    
    class MyTask extends TimerTask {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println("放空大腦休息一下子~");
            }
        }
    }
  • 定時調度(複雜):QUARTZ

    package com.sxt.others;
    
    import static org.quartz.DateBuilder.evenSecondDate;
    import static org.quartz.JobBuilder.newJob;
    import static org.quartz.TriggerBuilder.newTrigger;
    import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
    
    import org.quartz.JobDetail;
    import org.quartz.Scheduler;
    import org.quartz.SchedulerFactory;
    import org.quartz.Trigger;
    import org.quartz.impl.StdSchedulerFactory;
    
    import java.util.Date;
    
    /**
     * quartz學習入門
     */
    public class QuartzTest {
        public void run() throws Exception {
            // 1. 建立Scheduler的工廠
            SchedulerFactory sf = new StdSchedulerFactory();
            // 2. 從工廠中獲取調度器
            Scheduler sched = sf.getScheduler();
            // 時間
            Date runTime = evenSecondDate(new Date());
            // 3. 建立JobDetail
            JobDetail job = newJob(HelloJob.class).withIdentity("job1", "group1").build();
    
            // 4. 觸發器
            // Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(runTime).build();
            // 4 | 2:若是想要循環屢次呢,每5秒一次,循環三次
            Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(runTime)
                    .withSchedule(simpleSchedule().withIntervalInSeconds(5).withRepeatCount(2)).build();
            // 5. 註冊任務和觸發條件
            sched.scheduleJob(job, trigger);
            // 6. 啓動
            sched.start();
            try {
                // 5秒後中止
                Thread.sleep(30L * 1000L);
                // executing...
            } catch (Exception e) {
            }
    
            // shut down the scheduler
            sched.shutdown(true);
        }
    
        public static void main(String[] args) throws Exception {
            QuartzTest example = new QuartzTest();
            example.run();
        }
    }
  • 指令重排HappenBefore

    • 執行代碼的順序可能與編寫代碼不一致,即虛擬機優化代碼順序,則爲指令重排(HappenBefore)——優化程序性能。
    • 機器語言運行步驟
      1. 從內存中獲取要執行的下一個指令
      2. 將指令解碼翻譯,從寄存器中取值
      3. 操做,計算結果
      4. 將結果寫回到對應的寄存器中
  • volatile:

    • 保證線程間變量的可見性,即保證數據的同步。

    • volatile是不錯的機制,可是volatile不能保證原子性。

      package com.sxt.others;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: VolatileTest.java
       * @time: 2019/11/11 9:29
       * @desc: volatile測試
       * 不加volatile則程序不會停,加了以後會停
       */
      
      public class VolatileTest {
          private volatile static int num = 0;
          public static void main(String[] args) throws InterruptedException {
              new Thread(() -> {
                  while(num == 0){
                      // 此處不要編寫代碼,這是爲了讓系統沒有時間更新數據
                  }
              }).start();
      
              Thread.sleep(1000);
              num = 1;
          }
      }
  • dcl單例模式

    • 懶漢式套路的基礎上加入併發控制,保證在多線程環境下,對外存在一個對象
    1. 構造器私有化 --> 避免外部new構造器

    2. 提供私有的靜態屬性 --> 存儲對象的地址

    3. 提供公共的靜態方法 --> 獲取屬性

      package com.sxt.others;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: DoubleCheckedLocking.java
       * @time: 2019/11/11 9:34
       * @desc: 單例模式
       */
      
      public class DoubleCheckedLocking {
          // 2. 提供私有的靜態屬性
          // 沒有volatile其餘線程可能訪問一個沒有初始化的對象
          private static volatile DoubleCheckedLocking instance;
      
          // 1. 構造器私有化
          private DoubleCheckedLocking() {
      
          }
      
          // 3. 提供公共的靜態方法 --> 獲取屬性
          public static DoubleCheckedLocking getInstance() {
              // 再次檢測,避免沒必要要的同步,已經存在對象
              if (null != instance) {
                  return instance;
              }
              synchronized (DoubleCheckedLocking.class) {
                  if (null == instance) {
                      instance = new DoubleCheckedLocking();
                      // new一個對象的時候,要作的三件事情
                      // 開闢空間;初始化對象信息;返回對象的地址給引用
                      // 因此這裏可能出現指令重排
                  }
                  return instance;
              }
          }
      
          public static void main(String[] args) {
              Thread t = new Thread(() -> {
                  System.out.println(DoubleCheckedLocking.getInstance());
              });
              t.start();
              System.out.println(DoubleCheckedLocking.getInstance());
          }
      }
  • ThreadLocal

    • 表示的是每一個線程自身的存儲本地、局部區域

    • 方法:get/set/initialValue

      package com.sxt.others;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: ThreadLocalTest.java
       * @time: 2019/11/11 9:52
       * @desc: ThreadLocal
       */
      
      public class ThreadLocalTest {
          //    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
          // 更改初始值
      //    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
      //        @Override
      //        protected Integer initialValue() {
      //            return 200;
      //        }
      //    };
          // 簡化上面代碼
          private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 200);
      
      
          public static void main(String[] args) {
              // 獲取值,初始值爲null
              System.out.println(Thread.currentThread().getName() + "-->" + threadLocal.get());
              // 設置值
              threadLocal.set(99);
              System.out.println(Thread.currentThread().getName() + "-->" + threadLocal.get());
      
              new Thread(new MyRun()).start();
              new Thread(new MyRun()).start();
          }
      
          public static class MyRun implements Runnable {
              @Override
              public void run() {
                  threadLocal.set((int)(Math.random()*99));
                  System.out.println(Thread.currentThread().getName() + "-->" + threadLocal.get());
              }
          }
      }
    • 每一個線程只使用自身的數據,更改不會影響其餘線程

      package com.sxt.others;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: ThreadLocalTest2.java
       * @time: 2019/11/11 10:06
       * @desc: 取數據
       */
      
      public class ThreadLocalTest2 {
          private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);
      
          public static void main(String[] args) {
              for (int i = 0; i < 5; i++) {
                  new Thread(new MyRun()).start();
              }
          }
      
          public static class MyRun implements Runnable {
              @Override
              public void run() {
                  Integer left = threadLocal.get();
                  System.out.println(Thread.currentThread().getName() + "獲得了-->" + left);
                  threadLocal.set(left - 1);
                  System.out.println(Thread.currentThread().getName() + "還剩下-->" + threadLocal.get());
              }
          }
      }
    • ThreadLocal:分析上下文環境

      • 構造器:哪裏調用,就屬於哪裏,找線程體

      • run方法:本線程本身的

        package com.sxt.others;
        
        /**
         * @author: Li Tian
         * @contact: litian_cup@163.com
         * @software: IntelliJ IDEA
         * @file: ThreadLocalTest3.java
         * @time: 2019/11/11 10:11
         * @desc: 分析上下文環境
         */
        
        public class ThreadLocalTest3 {
            private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);
        
            public static void main(String[] args) {
                new Thread(new MyRun()).start();
                new Thread(new MyRun()).start();
            }
        
            public static class MyRun implements Runnable {
                public MyRun() {
                    // 屬於main線程
                    threadLocal.set(-100);
                    System.out.println(Thread.currentThread().getName() + "-->" + threadLocal.get());
                }
        
                @Override
                public void run() {
                    // 屬於其餘線程
                    System.out.println(Thread.currentThread().getName() + "-->" + threadLocal.get());
                }
            }
        }
    • InheritableThreadLocal:繼承上下文環境的數據,拷貝一份給子線程

      package com.sxt.others;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: ThreadLocalTest4.java
       * @time: 2019/11/11 10:25
       * @desc: InheritableThreadLocal:繼承上下文環境的數據,拷貝一份給子線程。起點
       */
      
      public class ThreadLocalTest4 {
          private static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();
      
          public static void main(String[] args) {
              threadLocal.set(2);
              System.out.println(Thread.currentThread().getName() + "-->" + threadLocal.get());
      
              // 線程由main線程開闢
              new Thread(() -> {
                  System.out.println(Thread.currentThread().getName() + "-->" + threadLocal.get());
                  // 可是既然是拷貝,因此想改仍是互不影響的
                  threadLocal.set(200);
                  System.out.println(Thread.currentThread().getName() + "-->" + threadLocal.get());
              }).start();
          }
      }
  • 可重入鎖:鎖能夠延續使用 + 計數器:ReentrantLock

  • CAS(Compare and Swap)比較並交換:

    • 參考連接:CAS樂觀鎖

    • 悲觀鎖:synchronized是獨佔鎖即悲觀鎖,會致使其餘全部須要鎖的線程掛起,等待持有鎖的線程釋放鎖。

    • 樂觀鎖:每次不加鎖而是假設沒有衝突而去完成某項操做,若是由於衝突失敗就重試,直到成功爲止。

      img

      package com.sxt.others;
      
      import java.util.concurrent.atomic.AtomicInteger;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: CASTest.java
       * @time: 2019/11/11 10:51
       * @desc: CAS
       */
      
      public class CASTest {
          // 庫存
          private static AtomicInteger stock = new AtomicInteger(3);
          public static void main(String[] args){
              for (int i = 0; i < 5; i++) {
                  new Thread(()->{
                      // 模擬網絡延時
                      try {
                          Thread.sleep(1000);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      Integer left = stock.decrementAndGet();
                      if(left<1){
                          System.out.println("搶完了...");
                          return;
                      }
                      System.out.println(Thread.currentThread().getName() + "搶了一個商品" + "-->還剩" + left);
                  }).start();
              }
          }
      }
  • Java 常見的鎖分類及其特色JAVA鎖有哪些種類,以及區別

第12章 網絡編程

1. 概念

  • BS架構和CS架構的區別BS架構與CS架構的區別(全)

  • 網絡:通信協議+通訊接口

  • 網絡分層:OSI(Open System Interconnect)開放系統互連參考模型

    圖12-1 七層協議模型.png

  • 網絡分層:OSI網絡通訊協議模型只是一個參考模型,而TCP/IP協議是事實上的標準。TCP/IP協議參考了OSI模型,可是並無嚴格按照OSI規定的七層標準去劃分,而只劃分了四層。

    圖12-2 開放系統互連參考模型與TCPIP參考模型對比.png

  • 數據封裝與解封:

  • 因爲用戶傳輸的數據通常都比較大,有的能夠達到MB字節,一次性發送出去十分困難,因而就須要把數據分紅許多片斷,再按照必定的次序發送出去。這個過程就須要對數據進行封裝。
  • 數據封裝(Data Encapsulation)是指將協議數據單元(PDU)封裝在一組協議頭和協議尾中的過程。在OSI七層參考模型中,每層主要負責與其它機器上的對等層進行通訊。該過程是在協議數據單元(PDU)中實現的,其中每層的PDU通常由本層的協議頭、協議尾和數據封裝構成。

  1. 數據發送處理過程

​ (1) 應用層將數據交給傳輸層,傳輸層添加上TCP的控制信息(稱爲TCP頭部),這個數據單元稱爲段(Segment),加入控制信息的過程稱爲封裝。而後,將段交給網絡層。

​ (2) 網絡層接收到段,再添加上IP頭部,這個數據單元稱爲包(Packet)。而後,將包交給數據鏈路層。

​ (3) 數據鏈路層接收到包,再添加上MAC頭部和尾部,這個數據單元稱爲幀(Frame)。而後,將幀交給物理層。

​ (4) 物理層將接收到的數據轉化爲比特流,而後在網線中傳送。

  1. 數據接收處理過程

​ (1) 物理層接收到比特流,通過處理後將數據交給數據鏈路層。

​ (2) 數據鏈路層將接收到的數據轉化爲數據幀,再除去MAC頭部和尾部,這個除去控制信息的過程稱爲解封,而後將包交給網絡層。

​ (3) 網絡層接收到包,再除去IP頭部,而後將段交給傳輸層。

​ (4) 傳輸層接收到段,再除去TCP頭部,而後將數據交給應用層。

從以上傳輸過程當中,能夠總結出如下規則:

​ (1) 發送方數據處理的方式是從高層到底層,逐層進行數據封裝。

​ (2) 接收方數據處理的方式是從底層到高層,逐層進行數據解封裝。

​ 接收方的每一層只把對該層有意義的數據拿走,或者說每一層只能處理髮送方同等層的數據,而後把其他的部分傳遞給上一層,這就是對等層通訊的概念。

圖12-3 數據封裝.png

圖12-4 數據解封.png

  • IP地址:用來標識網絡中的一個通訊實體的地址。通訊實體能夠是計算機、路由器等。 好比互聯網的每一個服務器都要有本身的IP地址,而每一個局域網的計算機要通訊也要配置IP地址。路由器是鏈接兩個或多個網絡的網絡設備。

    • 目前主流使用的IP地址是IPV4,可是隨着網絡規模的不斷擴大,IPV4面臨着枯竭的危險,因此推出了IPV6。

      IPV4:32位地址,並以8位爲一個單位,分紅四部分,以點分十進制表示,如192.168.0.1。由於8位二進制的計數範圍是00000000---11111111,對應十進制的0-255,因此-4.278.4.1是錯誤的IPV4地址。

      IPV6:128位(16個字節)寫成8個16位的無符號整數,每一個整數用四個十六進制位表示,每一個數之間用冒號(:)分開,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984

    • 注意事項

    • 127.0.0.1 本機地址
    • 192.168.0.0--192.168.255.255爲私有地址,屬於非註冊地址,專門爲組織機構內部使用。

    • InetAddress:

      1. getLocalHost:本機
      2. getByName:根據域名DNS | IP地址 --> IP
    • 兩個成員方法

      • getHostAddress:返回地址
      • getHostName:返回計算機名
  • 端口:

  • IP地址用來標識一臺計算機,可是一臺計算機上可能提供多種網絡應用程序,如何來區分這些不一樣的程序呢?這就要用到端口。

  • 端口是虛擬的概念,並非說在主機上真的有若干個端口。經過端口,能夠在一個主機上運行多個網絡應用程序。 端口的表示是一個16位的二進制整數,對應十進制的0-65535。

  • Oracle、MySQL、Tomcat、QQ、msn、迅雷、電驢、360等網絡程序都有本身的端口。

  • 查看命令

    • 查看全部端口:netstat -aon
    • 查看指定端口:netstat -aon | findstr 「808」
    • 查看指定進程:tasklist | findstr 「808」
    • 查看具體程序:使用任務管理器查看PID
  • 須要掌握的知識:

    1. 端口是用來區分軟件的
    2. 2個字節,0-65535,UDP和TCP同樣多
    3. 同一個協議端口不能衝突
    4. 定義的端口越大越好
  • InetSocketAddress

    1. 構造器 new InetSocketAddress(地址|域名, 端口);
    2. 方法:getAddress(),getPort(), getHostName()
    package com.sxt.loc;
    
    import java.net.InetSocketAddress;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: PortTest.java
     * @time: 2019/11/12 14:24
     * @desc: 端口
     */
    
    public class PortTest {
        public static void main(String[] args){
            // 包含端口
            InetSocketAddress socketAddress1 = new InetSocketAddress("127.0.0.1", 8080);
            InetSocketAddress socketAddress2 = new InetSocketAddress("localhost", 9000);
            System.out.println(socketAddress1.getHostName());
            System.out.println(socketAddress1.getAddress());
            System.out.println(socketAddress1.getPort());
            System.out.println(socketAddress2.getHostName());
            System.out.println(socketAddress2.getAddress());
            System.out.println(socketAddress2.getPort());
        }
    }
  • URL:

  • 在www上,每一信息資源都有統一且惟一的地址,該地址就叫URL(Uniform Resource Locator),它是www的統一資源定位符。URL由4部分組成:協議 、存放資源的主機域名、資源文件名和端口號。若是未指定該端口號,則使用協議默認的端口。例如http協議的默認端口爲80。在瀏覽器中訪問網頁時,地址欄顯示的地址就是URL。
  • 網絡三大基石:html、http、url
  • 由4部分組成:
    1. 協議
    2. 域名、計算機
    3. 端口:默認80
    4. 請求資源
package com.sxt.loc;

import java.net.MalformedURLException;
import java.net.URL;

/**
 * @author: Li Tian
 * @contact: litian_cup@163.com
 * @software: IntelliJ IDEA
 * @file: URLTest.java
 * @time: 2019/11/14 9:27
 * @desc: URL練習
 */

public class URLTest {
    public static void main(String[] args) throws MalformedURLException {
        URL url = new URL("http://www.baidu.com:80/index.html?uname=shsxt&age=18#a");
        // 獲取四個值
        System.out.println("協議:" + url.getProtocol());
        System.out.println("域名|ip:" + url.getHost());
        System.out.println("端口:" + url.getPort());
        System.out.println("請求資源1:" + url.getFile());
        System.out.println("請求資源2:" + url.getPath());

        // 參數
        System.out.println("參數:" + url.getQuery());
        // 錨點
        System.out.println("錨點:" + url.getRef());
    }
}
  • 爬蟲

    • 簡單爬蟲

      package com.sxt.loc;
      
      import java.io.BufferedReader;
      import java.io.IOException;
      import java.io.InputStream;
      import java.io.InputStreamReader;
      import java.net.MalformedURLException;
      import java.net.URL;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: SpiderTest1.java
       * @time: 2019/11/14 10:20
       * @desc: 網絡爬蟲
       */
      
      public class SpiderTest1 {
          public static void main(String[] args) throws IOException {
              // 獲取URL
              URL url = new URL("https://www.jd.com");
              // 下載資源
              InputStream is = url.openStream();
              BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
              String msg = null;
              while(null != (msg=br.readLine())){
                  System.out.println(msg);
              }
          }
      }
    • 若是爬蟲被拒絕,能夠模擬瀏覽器爬蟲

      package com.sxt.loc;
      
      import java.io.BufferedReader;
      import java.io.IOException;
      import java.io.InputStream;
      import java.io.InputStreamReader;
      import java.net.HttpURLConnection;
      import java.net.MalformedURLException;
      import java.net.URL;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: SpiderTest2.java
       * @time: 2019/11/14 10:26
       * @desc: 網絡爬蟲,對於那些403拒絕的,模擬瀏覽器
       */
      
      public class SpiderTest2 {
          public static void main(String[] args) throws IOException {
              // 獲取URL
              URL url = new URL("https://www.dianping.com");
              // http協議打開
              HttpURLConnection conn = (HttpURLConnection) url.openConnection();
              // 設置請求方式
              conn.setRequestMethod("GET");
              conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36");
              BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
              String msg = null;
              while (null != (msg = br.readLine())) {
                  System.out.println(msg);
              }
          }
      }
  • Socket:

    • 咱們開發的網絡應用程序位於應用層,TCP和UDP屬於傳輸層協議,在應用層如何使用傳輸層的服務呢?在應用層和傳輸層之間,則是使用套接Socket來進行分離。

    • 套接字(Socket)就像是傳輸層爲應用層開的一個小口,應用程序經過這個小口向遠程發送數據,或者接收遠程發來的數據;而這個小口之內,也就是數據進入這個口以後,或者數據從這個口出來以前,是不知道也不須要知道的,也不會關心它如何傳輸,這屬於網絡其它層次工做。

    • Socket實際是傳輸層供給應用層的編程接口。Socket就是應用層與傳輸層之間的橋樑。使用Socket編程能夠開發客戶機和服務器應用程序,能夠在本地網絡上進行通訊,也可經過Internet在全球範圍內通訊。

      圖12-5 Socket的做用.png

  • TCP協議和UDP協議的聯繫和區別

  • TCP協議和UDP協議是傳輸層的兩種協議。Socket是傳輸層供給應用層的編程接口,因此Socket編程就分爲TCP編程和UDP編程兩類。

  • 在網絡通信中,TCP方式就相似於撥打電話,使用該種方式進行網絡通信時,須要創建專門的虛擬鏈接,而後進行可靠的數據傳輸,若是數據發送失敗,則客戶端會自動重發該數據。而UDP方式就相似於發送短信,使用這種方式進行網絡通信時,不須要創建專門的虛擬鏈接,傳輸也不是很可靠,若是發送失敗則客戶端沒法得到。

  • 這兩種傳輸方式都在實際的網絡編程中使用,重要的數據通常使用TCP方式進行數據傳輸,而大量的非核心數據則能夠經過UDP方式進行傳遞,在一些程序中甚至結合使用這兩種方式進行數據傳遞。

  • 因爲TCP須要創建專用的虛擬鏈接以及確認傳輸是否正確,因此使用TCP方式的速度稍微慢一些,並且傳輸時產生的數據量要比UDP稍微大一些。

  • 總結

    1. TCP是面向鏈接的,傳輸數據安全,穩定,效率相對較低。

    2. UDP是面向無鏈接的,傳輸數據不安全,效率較高。

2. UDP編程

  • 接收端

    1. 使用DatagramSocket指定端口,建立接收端
    2. 準備容器,封裝成DatagramPacket包裹
    3. 阻塞式接受包裹receeive(DatagramPacket p)
    4. 分析數據,byte[] getData,getLength()
    5. 釋放資源
    package com.sxt.udp;
    
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: UDPServer.java
     * @time: 2019/11/14 14:14
     * @desc: 接收端
     */
    
    public class UDPServer {
        public static void main(String[] args) throws Exception{
            System.out.println("接收方啓動中...");
            //  1. 使用DatagramSocket指定端口,建立接收端
            DatagramSocket server = new DatagramSocket(9999);
            //  2. 準備容器,封裝成DatagramPacket包裹
            byte[] container = new byte[1024*60];
            DatagramPacket packet = new DatagramPacket(container, 0, container.length);
            //  3. 阻塞式接受包裹receeive(DatagramPacket p)
            //  阻塞式
            server.receive(packet);
            //  4. 分析數據,byte[] getData,getLength()
            byte[] datas = packet.getData();
            int len = packet.getLength();
            System.out.println(new String(datas, 0, len));
            //  5. 釋放資源
            server.close();
        }
    }
  • 發送端

    1. 使用DatagramSocket指定端口,建立發送端
    2. 準備數據,必定轉成字節數組
    3. 封裝成DatagramPacket包裹,須要指定目的地
    4. 發送包裹send(DatagramPacket p)
    5. 釋放資源
    package com.sxt.udp;
    
    import java.io.IOException;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.InetSocketAddress;
    import java.net.SocketException;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: UDPClient.java
     * @time: 2019/11/14 14:14
     * @desc: 發送端
     */
    
    public class UDPClient {
        public static void main(String[] args) throws IOException {
            System.out.println("發送方啓動中...");
            //  1. 使用DatagramSocket指定端口,建立發送端
            DatagramSocket client = new DatagramSocket(8888);
            //  2. 準備數據,必定轉成字節數組
            String data = "上海尚學堂";
            byte[] datas = data.getBytes();
            //  3. 封裝成DatagramPacket包裹,須要指定目的地
            DatagramPacket packet = new DatagramPacket(datas, 0, datas.length, new InetSocketAddress("localhost", 9999));
            //  4. 發送包裹send(DatagramPacket p)
            client.send(packet);
            //  5. 釋放資源
            client.close();
        }
    }
  • 注意:Address already in use: Cannot bind,同一個協議下端口不容許衝突

  • 操做基本數據類型使用Data流

    • 接收端

      package com.sxt.udp;
      
      import java.io.BufferedInputStream;
      import java.io.ByteArrayInputStream;
      import java.io.DataInputStream;
      import java.net.DatagramPacket;
      import java.net.DatagramSocket;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: UDPServer.java
       * @time: 2019/11/14 14:14
       * @desc: 接收端
       */
      
      public class UDPTypeServer {
          public static void main(String[] args) throws Exception{
              System.out.println("接收方啓動中...");
              //  1. 使用DatagramSocket指定端口,建立接收端
              DatagramSocket server = new DatagramSocket(9999);
              //  2. 準備容器,封裝成DatagramPacket包裹
              byte[] container = new byte[1024*60];
              DatagramPacket packet = new DatagramPacket(container, 0, container.length);
              //  3. 阻塞式接受包裹receeive(DatagramPacket p)
              //  阻塞式
              server.receive(packet);
              //  4. 分析數據,將字節數組還原爲對應的類型便可
              byte[] datas = packet.getData();
              int len = packet.getLength();
              DataInputStream dis = new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(datas)));
              // 順序與寫出一致
              String msg = dis.readUTF();
              boolean flag = dis.readBoolean();
              System.out.println(msg + "-->" + flag);
              //  5. 釋放資源
              server.close();
          }
      }
    • 發送端

      package com.sxt.udp;
      
      import java.io.BufferedOutputStream;
      import java.io.ByteArrayOutputStream;
      import java.io.DataOutputStream;
      import java.io.IOException;
      import java.net.DatagramPacket;
      import java.net.DatagramSocket;
      import java.net.InetSocketAddress;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: UDPClient.java
       * @time: 2019/11/14 14:14
       * @desc: 發送端
       */
      
      public class UDPTypeClient {
          public static void main(String[] args) throws IOException {
              System.out.println("發送方啓動中...");
              //  1. 使用DatagramSocket指定端口,建立發送端
              DatagramSocket client = new DatagramSocket(8888);
              //  2. 將基本類型,轉成字節數組
              ByteArrayOutputStream baos = new ByteArrayOutputStream();
              DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(baos));
              // 操做類型+數據
              dos.writeUTF("上海尚學堂");
              dos.writeBoolean(false);
              dos.flush();
              byte[] datas = baos.toByteArray();
              //  3. 封裝成DatagramPacket包裹,須要指定目的地
              DatagramPacket packet = new DatagramPacket(datas, 0, datas.length, new InetSocketAddress("localhost", 9999));
              //  4. 發送包裹send(DatagramPacket p)
              client.send(packet);
              //  5. 釋放資源
              client.close();
          }
      }
  • 操做引用數據類型使用Object流

  • 操做文件經過將文件轉換成字節數組

  • 實現屢次交流,單方面聊天

    • 發送端:

      package com.sxt.udp;
      
      import java.io.BufferedReader;
      import java.io.IOException;
      import java.io.InputStreamReader;
      import java.net.DatagramPacket;
      import java.net.DatagramSocket;
      import java.net.InetSocketAddress;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: UDPClient.java
       * @time: 2019/11/14 14:14
       * @desc: 發送端
       */
      
      public class UDPTalkClient {
          public static void main(String[] args) throws IOException {
              System.out.println("發送方啓動中...");
              //  1. 使用DatagramSocket指定端口,建立發送端
              DatagramSocket client = new DatagramSocket(8888);
              //  2. 準備數據,必定轉成字節數組
              BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
              while (true) {
                  String data = reader.readLine();
                  byte[] datas = data.getBytes();
                  //  3. 封裝成DatagramPacket包裹,須要指定目的地
                  DatagramPacket packet = new DatagramPacket(datas, 0, datas.length, new InetSocketAddress("localhost", 9999));
                  //  4. 發送包裹send(DatagramPacket p)
                  client.send(packet);
                  if (data.equals("q")) {
                      break;
                  }
              }
              //  5. 釋放資源
              client.close();
          }
      }
    • 接收端:

      package com.sxt.udp;
      
      import java.net.DatagramPacket;
      import java.net.DatagramSocket;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: UDPServer.java
       * @time: 2019/11/14 14:14
       * @desc: 接收端
       */
      
      public class UDPTalkServer {
          public static void main(String[] args) throws Exception {
              System.out.println("接收方啓動中...");
              //  1. 使用DatagramSocket指定端口,建立接收端
              DatagramSocket server = new DatagramSocket(9999);
              while (true) {
                  //  2. 準備容器,封裝成DatagramPacket包裹
                  byte[] container = new byte[1024 * 60];
                  DatagramPacket packet = new DatagramPacket(container, 0, container.length);
                  //  3. 阻塞式接受包裹receeive(DatagramPacket p)
                  //  阻塞式
                  server.receive(packet);
                  //  4. 分析數據,byte[] getData,getLength()
                  byte[] datas = packet.getData();
                  int len = packet.getLength();
                  String data = new String(datas, 0, len);
                  System.out.println(data);
                  if (data.equals("q")) {
                      break;
                  }
              }
              //  5. 釋放資源
              server.close();
          }
      }
  • 在線諮詢

    • 發送端:

      package com.sxt.udp;
      
      import java.io.BufferedReader;
      import java.io.IOException;
      import java.io.InputStreamReader;
      import java.net.DatagramPacket;
      import java.net.DatagramSocket;
      import java.net.InetSocketAddress;
      import java.net.SocketException;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: TalkSend.java
       * @time: 2019/11/16 20:03
       * @desc: 使用面向對象封裝
       */
      
      public class TalkSend implements Runnable {
          private DatagramSocket client;
          private BufferedReader reader;
          private String toIP;
          private int toPort;
      
          public TalkSend(int port, String toIP, int toPort) {
              this.toIP = toIP;
              this.toPort = toPort;
              try {
                  client = new DatagramSocket(port);
                  reader = new BufferedReader((new InputStreamReader(System.in)));
              } catch (SocketException e) {
                  e.printStackTrace();
              }
          }
      
          @Override
          public void run() {
              while (true) {
                  String data = null;
                  try {
                      data = reader.readLine();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
                  byte[] datas = data.getBytes();
                  //  3. 封裝成DatagramPacket包裹,須要指定目的地
                  DatagramPacket packet = new DatagramPacket(datas, 0, datas.length, new InetSocketAddress(this.toIP, this.toPort));
                  //  4. 發送包裹send(DatagramPacket p)
                  try {
                      client.send(packet);
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
                  if (data.equals("q")) {
                      break;
                  }
              }
              //  5. 釋放資源
              client.close();
          }
      }
    • 接收端

      package com.sxt.udp;
      
      import java.io.IOException;
      import java.net.DatagramPacket;
      import java.net.DatagramSocket;
      import java.net.SocketException;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: TalkReceive.java
       * @time: 2019/11/16 20:11
       * @desc: 封裝接收器
       */
      
      public class TalkReceive implements Runnable {
          private DatagramSocket server;
          private String from;
      
          public TalkReceive(int port, String from) {
              this.from = from;
              try {
                  server = new DatagramSocket(port);
              } catch (SocketException e) {
                  e.printStackTrace();
              }
          }
      
          @Override
          public void run() {
              while (true) {
                  //  2. 準備容器,封裝成DatagramPacket包裹
                  byte[] container = new byte[1024 * 60];
                  DatagramPacket packet = new DatagramPacket(container, 0, container.length);
                  //  3. 阻塞式接受包裹receeive(DatagramPacket p)
                  //  阻塞式
                  try {
                      server.receive(packet);
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
                  //  4. 分析數據,byte[] getData,getLength()
                  byte[] datas = packet.getData();
                  int len = packet.getLength();
                  String data = new String(datas, 0, len);
                  System.out.println(from + "說:" + data);
                  if (data.equals("q")) {
                      break;
                  }
              }
              //  5. 釋放資源
              server.close();
          }
      }
    • 模擬學生

      package com.sxt.udp;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: TalkStudent.java
       * @time: 2019/11/16 20:13
       * @desc: 模擬學生端
       */
      
      public class TalkStudent {
          public static void main(String[] args) {
              System.out.println("學生加入聊天室...");
              new Thread(new TalkSend(7777, "localhost", 9999)).start();
              new Thread(new TalkReceive(8888, "老師")).start();
          }
      }
    • 模擬老師

      package com.sxt.udp;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: TalkTeacher.java
       * @time: 2019/11/16 20:13
       * @desc: 模擬老師端
       */
      
      public class TalkTeacher {
          public static void main(String[] args) {
              System.out.println("老師加入聊天室...");
              new Thread(new TalkReceive(9999, "學生")).start();
              new Thread(new TalkSend(5555, "localhost", 8888)).start();
          }
      }

3. TCP編程

  • 建立服務器

    1. 指定端口,使用ServerSocket建立服務器
    2. 阻塞式等待鏈接 accept
    3. 操做:輸入輸出流操做
    4. 釋放資源
  • 建立客戶端

    1. 創建鏈接:使用Socket建立客戶端 + 服務的地址和端口
    2. 操做:輸入輸出流操做
    3. 釋放資源
  • 基本步驟

    • 服務器

      package com.sxt.tcp;
      
      import java.io.DataInputStream;
      import java.io.IOException;
      import java.net.ServerSocket;
      import java.net.Socket;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Server.java
       * @time: 2019/11/18 14:45
       * @desc: 熟悉流程,建立服務器
       */
      
      public class Server {
          public static void main(String[] args) throws IOException {
              System.out.println("-----Server-----");
              //  1. 指定端口,使用ServerSocket建立服務器
              ServerSocket server = new ServerSocket(8888);
              //  2. 阻塞式等待鏈接 accept
              Socket client = server.accept();
              System.out.println("一個客戶端創建了鏈接...");
              //  3. 操做:輸入輸出流操做
              DataInputStream dis = new DataInputStream(client.getInputStream());
              String data = dis.readUTF();
              System.out.println(data);
              //  4. 釋放資源
              dis.close();
              client.close();
              //  關閉服務器的話
              server.close();
          }
      }
    • 客戶端

      package com.sxt.tcp;
      
      import java.io.DataOutputStream;
      import java.io.IOException;
      import java.net.Socket;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Client.java
       * @time: 2019/11/18 14:50
       * @desc: 建立客戶端
       */
      
      public class Client {
          public static void main(String[] args) throws IOException {
              System.out.println("-----Client-----");
              //  1. 創建鏈接:使用Socket建立客戶端 + 服務的地址和端口
              Socket client = new Socket("localhost", 8888);
              //  2. 操做:輸入輸出流操做
              DataOutputStream dos = new DataOutputStream(client.getOutputStream());
              String data = "Hello";
              dos.writeUTF(data);
              dos.flush();
              //  3. 釋放資源
              dos.close();
              client.close();
          }
      }
  • 模擬登錄 單向

    • 服務器

      package com.sxt.tcp;
      
      import java.io.DataInputStream;
      import java.io.IOException;
      import java.net.ServerSocket;
      import java.net.Socket;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: LoginServer.java
       * @time: 2019/11/18 15:13
       * @desc: 模擬登錄 單向 服務器
       */
      
      public class LoginServer {
          public static void main(String[] args) throws IOException {
              System.out.println("-----Server-----");
              //  1. 指定端口,使用ServerSocket建立服務器
              ServerSocket server = new ServerSocket(8888);
              //  2. 阻塞式等待鏈接 accept
              Socket client = server.accept();
              System.out.println("一個客戶端創建了鏈接...");
              //  3. 操做:輸入輸出流操做
              DataInputStream dis = new DataInputStream(client.getInputStream());
              String datas = dis.readUTF();
              //  分析
              String[] dataArray = datas.split("&");
              for(String info: dataArray){
                  String[] userInfo = info.split("=");
                  System.out.println(userInfo[0] + "-->" + userInfo[1]);
              }
              //  4. 釋放資源
              dis.close();
              client.close();
              //  關閉服務器的話
              server.close();
          }
      }
    • 客戶端

      package com.sxt.tcp;
      
      import java.io.BufferedReader;
      import java.io.DataOutputStream;
      import java.io.IOException;
      import java.io.InputStreamReader;
      import java.net.Socket;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: LoginClient.java
       * @time: 2019/11/18 15:13
       * @desc: 模擬登錄 單向 客戶端
       */
      
      public class LoginClient {
          public static void main(String[] args) throws IOException {
              System.out.println("-----Client-----");
              BufferedReader console = new BufferedReader(new InputStreamReader((System.in)));
              System.out.println("請輸入用戶名:");
              String uname = console.readLine();
              System.out.println("請輸入密碼:");
              String upwd = console.readLine();
              //  1. 創建鏈接:使用Socket建立客戶端 + 服務的地址和端口
              Socket client = new Socket("localhost", 8888);
              //  2. 操做:輸入輸出流操做
              DataOutputStream dos = new DataOutputStream(client.getOutputStream());
              dos.writeUTF("uname=" + uname + "&upwd=" + upwd);
              dos.flush();
              //  3. 釋放資源
              dos.close();
              client.close();
          }
      }
  • 模擬登錄 雙向

    • 服務器

      package com.sxt.tcp;
      
      import java.io.DataInputStream;
      import java.io.DataOutputStream;
      import java.io.IOException;
      import java.net.ServerSocket;
      import java.net.Socket;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: LoginTwoWayServer.java
       * @time: 2019/11/18 15:23
       * @desc: 模擬登錄 雙向 服務器
       */
      
      public class LoginTwoWayServer {
          public static void main(String[] args) throws IOException {
              System.out.println("-----Server-----");
              //  1. 指定端口,使用ServerSocket建立服務器
              ServerSocket server = new ServerSocket(8888);
              //  2. 阻塞式等待鏈接 accept
              Socket client = server.accept();
              System.out.println("一個客戶端創建了鏈接...");
              //  3. 操做:輸入輸出流操做
              DataInputStream dis = new DataInputStream(client.getInputStream());
              String datas = dis.readUTF();
              String uname = "";
              String upwd = "";
      
              //  分析
              String[] dataArray = datas.split("&");
              for (String info : dataArray) {
                  String[] userInfo = info.split("=");
                  if (userInfo[0].equals("uname")) {
                      System.out.println("你的用戶名爲:" + userInfo[1]);
                      uname = userInfo[1];
                  } else if (userInfo[0].equals("upwd")) {
                      System.out.println("你的密碼爲:" + userInfo[1]);
                      upwd = userInfo[1];
                  }
              }
      
              // 輸出
              DataOutputStream dos = new DataOutputStream(client.getOutputStream());
              if (uname.equals("litian") && upwd.equals("123")) {
                  dos.writeUTF("登錄成功,歡迎回來!");
              } else {
                  dos.writeUTF("登錄失敗,用戶名或密碼錯誤!");
              }
              dos.flush();
      
              //  4. 釋放資源
              dis.close();
              client.close();
              //  關閉服務器的話
              server.close();
          }
      }
    • 客戶端

      package com.sxt.tcp;
      
      import java.io.*;
      import java.net.Socket;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: LoginTwoWayClient.java
       * @time: 2019/11/18 15:23
       * @desc: 模擬登錄 雙向 客戶端
       */
      
      public class LoginTwoWayClient {
          public static void main(String[] args) throws IOException {
              System.out.println("-----Client-----");
              BufferedReader console = new BufferedReader(new InputStreamReader((System.in)));
              System.out.println("請輸入用戶名:");
              String uname = console.readLine();
              System.out.println("請輸入密碼:");
              String upwd = console.readLine();
              //  1. 創建鏈接:使用Socket建立客戶端 + 服務的地址和端口
              Socket client = new Socket("localhost", 8888);
              //  2. 操做:輸入輸出流操做
              DataOutputStream dos = new DataOutputStream(client.getOutputStream());
              dos.writeUTF("uname=" + uname + "&upwd=" + upwd);
              dos.flush();
      
              // 接受
              DataInputStream dis = new DataInputStream(client.getInputStream());
              String result = dis.readUTF();
              System.out.println(result);
      
              //  3. 釋放資源
              dos.close();
              client.close();
          }
      }
  • 文件上傳

    • 服務器

      package com.sxt.tcp;
      
      import java.io.*;
      import java.net.ServerSocket;
      import java.net.Socket;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: FileServer.java
       * @time: 2019/11/18 15:32
       * @desc: 服務器:存儲文件
       */
      
      public class FileServer {
          public static void main(String[] args) throws IOException {
              System.out.println("-----Server-----");
              //  1. 指定端口,使用ServerSocket建立服務器
              ServerSocket server = new ServerSocket(8888);
              //  2. 阻塞式等待鏈接 accept
              Socket client = server.accept();
              System.out.println("一個客戶端創建了鏈接...");
              //  3. 操做:文件拷貝 存儲
              InputStream is = new BufferedInputStream(client.getInputStream());
              OutputStream os = new BufferedOutputStream(new FileOutputStream("./快樂保存.jpg"));
              byte[] flush = new byte[1024];
              int len = -1;
              while ((len = is.read(flush)) != -1) {
                  os.write(flush, 0, len);
              }
              //  4. 釋放資源
              os.close();
              is.close();
              client.close();
              //  關閉服務器的話
              server.close();
          }
      }
    • 客戶端

      package com.sxt.tcp;
      
      import java.io.*;
      import java.net.Socket;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: FileClient.java
       * @time: 2019/11/18 15:32
       * @desc: 客戶端:上傳文件
       */
      
      public class FileClient {
          public static void main(String[] args) throws IOException {
              System.out.println("-----Client-----");
              //  1. 創建鏈接:使用Socket建立客戶端 + 服務的地址和端口
              Socket client = new Socket("localhost", 8888);
              //  2. 操做:文件拷貝 上傳
              InputStream is = new BufferedInputStream(new FileInputStream("./快樂.jpg"));
              OutputStream os = new BufferedOutputStream(client.getOutputStream());
              byte[] flush = new byte[1024];
              int len = -1;
              while ((len = is.read(flush)) != -1) {
                  os.write(flush, 0, len);
              }
              //  3. 釋放資源
              os.close();
              is.close();
              client.close();
          }
      }
  • 多用戶登錄

    • 服務器

      package com.sxt.tcp;
      
      import java.io.DataInputStream;
      import java.io.DataOutputStream;
      import java.io.IOException;
      import java.net.ServerSocket;
      import java.net.Socket;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: LoginMultiServer.java
       * @time: 2019/11/19 9:18
       * @desc:
       */
      
      public class LoginMultiServer {
          public static void main(String[] args) throws IOException {
              System.out.println("-----Server-----");
              //  1. 指定端口,使用ServerSocket建立服務器
              ServerSocket server = new ServerSocket(8888);
              boolean isRunning = true;
              while (isRunning) {
                  //  2. 阻塞式等待鏈接 accept
                  Socket client = server.accept();
                  System.out.println("一個客戶端創建了鏈接...");
                  new Thread(new Channel(client)).start();
              }
              //  關閉服務器的話
              server.close();
          }
      
          static class Channel implements Runnable {
              private Socket client;
              // 輸入流封裝
              private DataInputStream dis;
              // 輸出流封裝
              private DataOutputStream dos;
      
              public Channel(Socket client) {
                  this.client = client;
                  try {
                      dis = new DataInputStream(client.getInputStream());
                      dos = new DataOutputStream(client.getOutputStream());
                  } catch (IOException e) {
                      release();
                  }
              }
      
              @Override
              public void run() {
                  //  3. 操做:輸入輸出流操做
                  String uname = "";
                  String upwd = "";
                  //  分析
                  String datas = receive();
                  String[] dataArray = datas.split("&");
                  for (String info : dataArray) {
                      String[] userInfo = info.split("=");
                      if (userInfo[0].equals("uname")) {
                          System.out.println("你的用戶名爲:" + userInfo[1]);
                          uname = userInfo[1];
                      } else if (userInfo[0].equals("upwd")) {
                          System.out.println("你的密碼爲:" + userInfo[1]);
                          upwd = userInfo[1];
                      }
                  }
                  if (uname.equals("litian") && upwd.equals("123")) {
                      send("登錄成功,歡迎回來!");
                  } else {
                      send("登錄失敗,用戶名或密碼錯誤!");
                  }
      
                  release();
              }
      
              // 接受數據
              private String receive() {
                  String datas = "";
                  try {
                      datas = dis.readUTF();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
                  return datas;
              }
      
              // 發送數據
              private void send(String msg) {
                  try {
                      dos.writeUTF(msg);
                      dos.flush();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
      
              // 釋放資源
              private void release() {
                  //  4. 釋放資源
                  try {
                      if (null != dos) {
                          dos.close();
                      }
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
                  try {
                      if (null != dis) {
                          dis.close();
                      }
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
                  try {
                      if (null != client) {
                          client.close();
                      }
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }
      }
    • 客戶端

      package com.sxt.tcp;
      
      import java.io.*;
      import java.net.Socket;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: LoginMultiClient.java
       * @time: 2019/11/19 9:18
       * @desc:
       */
      
      public class LoginMultiClient {
          public static void main(String[] args) throws IOException {
              System.out.println("-----Client-----");
              //  1. 創建鏈接:使用Socket建立客戶端 + 服務的地址和端口
              Socket client = new Socket("localhost", 8888);
              //  2. 操做:輸入輸出流操做
              new Send(client).send();
              new Receive(client).receive();
              //  3. 釋放資源
              client.close();
          }
      
          static class Send {
              private Socket client;
              private DataOutputStream dos;
              private BufferedReader console;
              private String msg;
      
              public Send(Socket client) {
                  console = new BufferedReader(new InputStreamReader((System.in)));
                  this.client = client;
                  this.msg = init();
                  try {
                      dos = new DataOutputStream(client.getOutputStream());
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
      
              private String init() {
                  try {
                      System.out.println("請輸入用戶名:");
                      String uname = console.readLine();
                      System.out.println("請輸入密碼:");
                      String upwd = console.readLine();
                      return "uname=" + uname + "&upwd=" + upwd;
                  } catch (IOException e) {
                      e.printStackTrace();
                      return "???";
                  }
              }
      
              public void send() {
                  try {
                      dos.writeUTF(msg);
                      dos.flush();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }
      
          static class Receive {
              private Socket client;
              private DataInputStream dis;
      
              public Receive(Socket client) {
                  this.client = client;
                  try {
                      dis = new DataInputStream(client.getInputStream());
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
      
              public void receive() {
                  String result = null;
                  try {
                      result = dis.readUTF();
                      System.out.println(result);
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }
      }

4. 在線聊天室

  • 實現一個客戶能夠正常收發信息

    • 服務端

      package com.sxt.chat1;
      
      import java.io.DataInputStream;
      import java.io.DataOutputStream;
      import java.io.IOException;
      import java.net.ServerSocket;
      import java.net.Socket;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Chat.java
       * @time: 2019/11/19 10:45
       * @desc: 在線聊天室:服務端
       * 目標:實現一個客戶能夠正常收發信息
       */
      
      public class Chat {
          public static void main(String[] args) throws IOException {
              System.out.println("-----Server-----");
              //  1. 指定端口,使用ServerSocket建立服務器
              ServerSocket server = new ServerSocket(8888);
              //  2. 阻塞式等待鏈接 accept
              Socket client = server.accept();
              System.out.println("一個客戶端創建了鏈接...");
              //  3. 接收消息
              DataInputStream dis = new DataInputStream(client.getInputStream());
              String msg = dis.readUTF();
              //  4. 返回消息
              DataOutputStream dos = new DataOutputStream(client.getOutputStream());
              dos.writeUTF(msg);
              dos.flush();
              //  5. 釋放資源
              dos.close();
              dis.close();
              client.close();
          }
      }
    • 客戶端

      package com.sxt.chat1;
      
      import java.io.*;
      import java.net.ServerSocket;
      import java.net.Socket;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Client.java
       * @time: 2019/11/19 10:45
       * @desc: 在線聊天室:客戶端
       * 目標:實現一個客戶能夠正常收發信息
       */
      
      public class Client {
          public static void main(String[] args) throws IOException {
              System.out.println("-----Client-----");
              //  1. 創建鏈接:使用Socket建立客戶端 + 服務的地址和端口
              Socket client = new Socket("localhost", 8888);
              //  2. 客戶端發送消息
              BufferedReader console = new BufferedReader(new InputStreamReader((System.in)));
              String msg = console.readLine();
              DataOutputStream dos = new DataOutputStream(client.getOutputStream());
              dos.writeUTF(msg);
              dos.flush();
              //  3. 獲取消息
              DataInputStream dis = new DataInputStream(client.getInputStream());
              msg = dis.readUTF();
              System.out.println(msg);
              //  4. 釋放資源
              dos.close();
              dis.close();
              client.close();
          }
      }
  • 實現一個客戶能夠正常收發多人信息——基礎簡易版

    • 服務端

      package com.sxt.chat1;
      
      import java.io.DataInputStream;
      import java.io.DataOutputStream;
      import java.io.IOException;
      import java.net.ServerSocket;
      import java.net.Socket;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: MultiChat.java
       * @time: 2019/11/19 14:57
       * @desc: 在線聊天室:服務端
       * 目標:實現一個客戶能夠正常收發多人信息
       */
      
      public class MultiChat {
          public static void main(String[] args) throws IOException {
              System.out.println("-----Server-----");
              //  1. 指定端口,使用ServerSocket建立服務器
              ServerSocket server = new ServerSocket(8888);
              //  2. 阻塞式等待鏈接 accept
              Socket client = server.accept();
              System.out.println("一個客戶端創建了鏈接...");
      
              DataInputStream dis = new DataInputStream(client.getInputStream());
              DataOutputStream dos = new DataOutputStream(client.getOutputStream());
              boolean isRunning = true;
              while (isRunning) {
                  //  3. 接收消息
                  String msg = dis.readUTF();
                  //  4. 返回消息
                  dos.writeUTF(msg);
                  dos.flush();
              }
              //  5. 釋放資源
              dos.close();
              dis.close();
              client.close();
          }
      }
    • 客戶端

      package com.sxt.chat1;
      
      import java.io.*;
      import java.net.Socket;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: MultiClient.java
       * @time: 2019/11/19 14:57
       * @desc: 在線聊天室:客戶端
       * 目標:實現一個客戶能夠正常收發多條信息
       */
      
      public class MultiClient {
          public static void main(String[] args) throws IOException {
              System.out.println("-----Client-----");
              //  1. 創建鏈接:使用Socket建立客戶端 + 服務的地址和端口
              Socket client = new Socket("localhost", 8888);
              //  2. 客戶端發送消息
              BufferedReader console = new BufferedReader(new InputStreamReader((System.in)));
              DataOutputStream dos = new DataOutputStream(client.getOutputStream());
              DataInputStream dis = new DataInputStream(client.getInputStream());
              boolean isRunning = true;
              while (isRunning) {
                  String msg = console.readLine();
                  dos.writeUTF(msg);
                  dos.flush();
                  //  3. 獲取消息
                  msg = dis.readUTF();
                  System.out.println(msg);
              }
              //  4. 釋放資源
              dos.close();
              dis.close();
              client.close();
          }
      }
  • 使用多線程實現多個客戶能夠正常收發多人信息——oop封裝版

    • 問題:其餘客戶必須等待以前的客戶退出,才能繼續排隊

    • 工具類:釋放資源

      package com.sxt.chat3;
      
      import java.io.Closeable;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: SxtUtils.java
       * @time: 2019/11/25 13:40
       * @desc: 工具類:釋放資源
       */
      
      public class SxtUtils {
          public static void close(Closeable... targets){
              for (Closeable target: targets){
                  try{
                      if(null != target){
                          target.close();
                      }
                  }catch (Exception e){
                      e.printStackTrace();
                  }
              }
          }
      }
    • 在線聊天室:服務端

      package com.sxt.chat3;
      
      import java.io.DataInputStream;
      import java.io.DataOutputStream;
      import java.io.IOException;
      import java.net.ServerSocket;
      import java.net.Socket;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: MultiChat.java
       * @time: 2019/11/19 14:57
       * @desc: 在線聊天室:服務端
       * 目標:封裝:使用多線程實現多個客戶能夠正常收發多人信息
       */
      
      public class MultiChat {
          public static void main(String[] args) throws IOException {
              System.out.println("-----Server-----");
              //  1. 指定端口,使用ServerSocket建立服務器
              ServerSocket server = new ServerSocket(8888);
              //  2. 阻塞式等待鏈接 accept
              while (true) {
                  Socket client = server.accept();
                  System.out.println("一個客戶端創建了鏈接...");
                  new Thread(new Channel(client)).start();
      
              }
          }
      
          // 一個客戶表明一個Channel
          static class Channel implements Runnable {
              private DataInputStream dis;
              private DataOutputStream dos;
              private Socket client;
              private boolean isRunning;
      
              public Channel(Socket client) {
                  this.client = client;
                  try {
                      dis = new DataInputStream(client.getInputStream());
                      dos = new DataOutputStream(client.getOutputStream());
                      isRunning = true;
                  } catch (IOException e) {
                      release();
                  }
              }
      
              // 接受消息
              private String receive() {
                  String msg = "";
                  try {
                      msg = dis.readUTF();
                  } catch (IOException e) {
                      release();
                  }
                  return msg;
              }
      
              // 發送消息
              private void send(String msg) {
                  try {
                      dos.writeUTF(msg);
                      dos.flush();
                  } catch (IOException e) {
                      release();
                  }
              }
      
              // 釋放資源
              private void release() {
                  this.isRunning = false;
                  SxtUtils.close(dis, dos, client);
              }
      
              @Override
              public void run() {
                  while(isRunning){
                      String msg = receive();
                      if(!msg.equals("")){
                          send(msg);
                      }
                  }
              }
          }
      }
    • 使用多線程封裝了發送端

      package com.sxt.chat3;
      
      import java.io.BufferedReader;
      import java.io.DataOutputStream;
      import java.io.IOException;
      import java.io.InputStreamReader;
      import java.net.Socket;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Send.java
       * @time: 2019/11/25 14:37
       * @desc: 使用多線程封裝了發送端
       */
      
      public class Send implements Runnable {
          private BufferedReader console;
          private DataOutputStream dos;
          private Socket client;
          private boolean isRunning;
      
          public Send(Socket client) {
              this.client = client;
              console = new BufferedReader(new InputStreamReader(System.in));
              try {
                  dos = new DataOutputStream(client.getOutputStream());
                  isRunning = true;
              } catch (IOException e) {
                  this.release();
              }
      
          }
      
          @Override
          public void run() {
              while (isRunning) {
                  String msg = getStrFromConsole();
                  if (!msg.equals("")) {
                      send(msg);
                  }
              }
      
          }
      
          private void send(String msg) {
              try {
                  dos.writeUTF(msg);
                  dos.flush();
              } catch (IOException e) {
                  release();
              }
          }
      
          private String getStrFromConsole() {
              try {
                  return console.readLine();
              } catch (IOException e) {
                  release();
              }
              return "";
          }
      
          // 釋放資源
          private void release() {
              this.isRunning = false;
              SxtUtils.close(dos, client);
          }
      }
    • 使用多線程封裝了接收端

      package com.sxt.chat3;
      
      import java.io.DataInputStream;
      import java.io.IOException;
      import java.net.Socket;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Receive.java
       * @time: 2019/11/25 14:37
       * @desc: 使用多線程封裝了接收端
       */
      
      public class Receive implements Runnable {
          private Socket client;
          private boolean isRunning;
          private DataInputStream dis;
      
          public Receive(Socket client) {
              this.client = client;
              try {
                  dis = new DataInputStream(client.getInputStream());
                  isRunning = true;
              } catch (IOException e) {
                  release();
              }
          }
      
          // 接受消息
          private String receive() {
              String msg = "";
              try {
                  msg = dis.readUTF();
              } catch (IOException e) {
                  release();
              }
              return msg;
          }
      
          @Override
          public void run() {
              while (isRunning) {
                  String msg = receive();
                  if (!msg.equals("")) {
                      System.out.println(msg);
                  }
              }
          }
      
          // 釋放資源
          private void release() {
              this.isRunning = false;
              SxtUtils.close(dis, client);
          }
      }
    • 在線聊天室:客戶端

      package com.sxt.chat3;
      
      import java.io.*;
      import java.net.Socket;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: MultiClient.java
       * @time: 2019/11/19 14:57
       * @desc: 在線聊天室:客戶端
       * 目標:封裝:使用多線程實現多個客戶能夠正常收發多條信息
       */
      
      public class MultiClient {
          public static void main(String[] args) throws IOException {
              System.out.println("-----Client-----");
              //  1. 創建鏈接:使用Socket建立客戶端 + 服務的地址和端口
              Socket client = new Socket("localhost", 8888);
              //  2. 客戶端發送消息
              new Thread(new Send(client)).start();
              new Thread(new Receive(client)).start();
          }
      }
  • 手寫聊天室——羣聊過渡版

    • 服務器

      package com.sxt.chat4;
      
      import java.io.DataInputStream;
      import java.io.DataOutputStream;
      import java.io.IOException;
      import java.net.ServerSocket;
      import java.net.Socket;
      import java.util.concurrent.CopyOnWriteArrayList;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: MultiChat.java
       * @time: 2019/11/19 14:57
       * @desc: 在線聊天室:服務端
       * 目標:加入容器實現羣聊
       */
      
      public class Chat {
          private static CopyOnWriteArrayList<Channel> all = new CopyOnWriteArrayList<>();
      
          public static void main(String[] args) throws IOException {
              System.out.println("-----Server-----");
              //  1. 指定端口,使用ServerSocket建立服務器
              ServerSocket server = new ServerSocket(8888);
              //  2. 阻塞式等待鏈接 accept
              while (true) {
                  Socket client = server.accept();
                  System.out.println("一個客戶端創建了鏈接...");
                  Channel c = new Channel(client);
                  // 管理全部的成員
                  all.add(c);
                  new Thread(c).start();
      
              }
          }
      
          // 一個客戶表明一個Channel
          static class Channel implements Runnable {
              private DataInputStream dis;
              private DataOutputStream dos;
              private Socket client;
              private boolean isRunning;
              private String name;
      
              public Channel(Socket client) {
                  this.client = client;
                  try {
                      dis = new DataInputStream(client.getInputStream());
                      dos = new DataOutputStream(client.getOutputStream());
                      isRunning = true;
                      // 獲取名稱
                      this.name = receive();
                      // 歡迎你的到來
                      this.send("歡迎你的到來");
                      sendOthers(this.name + "來了shsxt聊天室", true);
                  } catch (IOException e) {
                      release();
                  }
              }
      
              // 接受消息
              private String receive() {
                  String msg = "";
                  try {
                      msg = dis.readUTF();
                  } catch (IOException e) {
                      release();
                  }
                  return msg;
              }
      
              // 發送消息
              private void send(String msg) {
                  try {
                      dos.writeUTF(msg);
                      dos.flush();
                  } catch (IOException e) {
                      release();
                  }
              }
      
              // 羣聊
              private void sendOthers(String msg, boolean isSys) {
                  for(Channel other: all){
                      if(other == this){  // 本身
                          continue;
                      }
                      if(!isSys) {
                          // 羣聊消息
                          other.send(this.name + "說:" + msg);
                      }else{
                          // 系統消息
                          other.send(msg);
                      }
                  }
              }
      
              // 釋放資源
              private void release() {
                  this.isRunning = false;
                  SxtUtils.close(dis, dos, client);
                  // 退出
                  all.remove(this);
                  sendOthers(this.name + "離開了...", true);
              }
      
              @Override
              public void run() {
                  while (isRunning) {
                      String msg = receive();
                      if (!msg.equals("")) {
                          // send(msg);
                          sendOthers(msg, false);
                      }
                  }
              }
          }
      }
    • 客戶端

      package com.sxt.chat4;
      
      import java.io.BufferedReader;
      import java.io.IOException;
      import java.io.InputStreamReader;
      import java.net.Socket;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: MultiClient.java
       * @time: 2019/11/19 14:57
       * @desc: 在線聊天室:客戶端
       * 目標:加入容器實現羣聊
       */
      
      public class Client {
          public static void main(String[] args) throws IOException {
              System.out.println("-----Client-----");
              BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
              System.out.println("請輸入用戶名:");
              String name = br.readLine();
              //  1. 創建鏈接:使用Socket建立客戶端 + 服務的地址和端口
              Socket client = new Socket("localhost", 8888);
              //  2. 客戶端發送消息
              new Thread(new Send(client, name)).start();
              new Thread(new Receive(client)).start();
          }
      }
    • 工具類同上

    • 發送端

      package com.sxt.chat4;
      
      import java.io.BufferedReader;
      import java.io.DataOutputStream;
      import java.io.IOException;
      import java.io.InputStreamReader;
      import java.net.Socket;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Send.java
       * @time: 2019/11/25 14:37
       * @desc: 使用多線程封裝了發送端
       */
      
      public class Send implements Runnable {
          private BufferedReader console;
          private DataOutputStream dos;
          private Socket client;
          private boolean isRunning;
          private String name;
      
          public Send(Socket client, String name) {
              this.client = client;
              this.name = name;
              console = new BufferedReader(new InputStreamReader(System.in));
              try {
                  dos = new DataOutputStream(client.getOutputStream());
                  // 發送名稱
                  send(name);
                  isRunning = true;
              } catch (IOException e) {
                  this.release();
              }
      
          }
      
          @Override
          public void run() {
              while (isRunning) {
                  String msg = getStrFromConsole();
                  if (!msg.equals("")) {
                      send(msg);
                  }
              }
      
          }
      
          private void send(String msg) {
              try {
                  dos.writeUTF(msg);
                  dos.flush();
              } catch (IOException e) {
                  release();
              }
          }
      
          private String getStrFromConsole() {
              try {
                  return console.readLine();
              } catch (IOException e) {
                  release();
              }
              return "";
          }
      
          // 釋放資源
          private void release() {
              this.isRunning = false;
              SxtUtils.close(dos, client);
          }
      }
    • 接收端

      package com.sxt.chat4;
      
      import java.io.DataInputStream;
      import java.io.IOException;
      import java.net.Socket;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: Receive.java
       * @time: 2019/11/25 14:37
       * @desc: 使用多線程封裝了接收端
       */
      
      public class Receive implements Runnable {
          private Socket client;
          private boolean isRunning;
          private DataInputStream dis;
      
          public Receive(Socket client) {
              this.client = client;
              try {
                  dis = new DataInputStream(client.getInputStream());
                  isRunning = true;
              } catch (IOException e) {
                  release();
              }
          }
      
          // 接受消息
          private String receive() {
              String msg = "";
              try {
                  msg = dis.readUTF();
              } catch (IOException e) {
                  release();
              }
              return msg;
          }
      
          @Override
          public void run() {
              while (isRunning) {
                  String msg = receive();
                  if (!msg.equals("")) {
                      System.out.println(msg);
                  }
              }
          }
      
          // 釋放資源
          private void release() {
              this.isRunning = false;
              SxtUtils.close(dis, client);
          }
      }
  • 實現私聊

    • 修改客戶端爲:

      package com.sxt.chat4;
      
      import java.io.DataInputStream;
      import java.io.DataOutputStream;
      import java.io.IOException;
      import java.net.ServerSocket;
      import java.net.Socket;
      import java.util.concurrent.CopyOnWriteArrayList;
      
      /**
       * @author: Li Tian
       * @contact: litian_cup@163.com
       * @software: IntelliJ IDEA
       * @file: MultiChat.java
       * @time: 2019/11/19 14:57
       * @desc: 在線聊天室:服務端
       * 目標:加入容器實現羣聊
       */
      
      public class Chat {
          private static CopyOnWriteArrayList<Channel> all = new CopyOnWriteArrayList<>();
      
          public static void main(String[] args) throws IOException {
              System.out.println("-----Server-----");
              //  1. 指定端口,使用ServerSocket建立服務器
              ServerSocket server = new ServerSocket(8888);
              //  2. 阻塞式等待鏈接 accept
              while (true) {
                  Socket client = server.accept();
                  System.out.println("一個客戶端創建了鏈接...");
                  Channel c = new Channel(client);
                  // 管理全部的成員
                  all.add(c);
                  new Thread(c).start();
      
              }
          }
      
          // 一個客戶表明一個Channel
          static class Channel implements Runnable {
              private DataInputStream dis;
              private DataOutputStream dos;
              private Socket client;
              private boolean isRunning;
              private String name;
      
              public Channel(Socket client) {
                  this.client = client;
                  try {
                      dis = new DataInputStream(client.getInputStream());
                      dos = new DataOutputStream(client.getOutputStream());
                      isRunning = true;
                      // 獲取名稱
                      this.name = receive();
                      // 歡迎你的到來
                      this.send("歡迎你的到來");
                      sendOthers(this.name + "來了shsxt聊天室", true);
                  } catch (IOException e) {
                      release();
                  }
              }
      
              // 接受消息
              private String receive() {
                  String msg = "";
                  try {
                      msg = dis.readUTF();
                  } catch (IOException e) {
                      release();
                  }
                  return msg;
              }
      
              // 發送消息
              private void send(String msg) {
                  try {
                      dos.writeUTF(msg);
                      dos.flush();
                  } catch (IOException e) {
                      release();
                  }
              }
      
              // 羣聊
              private void sendOthers(String msg, boolean isSys) {
                  boolean isPrivate = msg.startsWith("@");
                  if(isPrivate){
                      // 私聊
                      int idx = msg.indexOf(":");
                      // 獲取目標和數據
                      String targetName = msg.substring(1, idx);
                      msg = msg.substring(idx+1);
                      for(Channel other: all){
                          if(other.name.equals(targetName)){
                              other.send(this.name + "悄悄的對你說:" + msg);
                              break;
                          }
                      }
                  }else{
                      for(Channel other: all){
                          if(other == this){  // 本身
                              continue;
                          }
                          if(!isSys) {
                              // 羣聊消息
                              other.send(this.name + "說:" + msg);
                          }else{
                              // 系統消息
                              other.send(msg);
                          }
                      }
                  }
              }
      
              // 釋放資源
              private void release() {
                  this.isRunning = false;
                  SxtUtils.close(dis, dos, client);
                  // 退出
                  all.remove(this);
                  sendOthers(this.name + "離開了...", true);
              }
      
              @Override
              public void run() {
                  while (isRunning) {
                      String msg = receive();
                      if (!msg.equals("")) {
                          // send(msg);
                          sendOthers(msg, false);
                      }
                  }
              }
          }
      }

第13章 J20飛機遊戲

  • 一些常量

    package com.sxt.planegame2;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Constant.java
     * @time: 2019/11/28 10:15
     * @desc: 定義常量的類
     */
    
    public class Constant {
        public static final int GAME_WIDTH = 500;
        public static final int GAME_HEIGHT = 500;
    }
  • 物體父類

    package com.sxt.planegame2;
    
    import java.awt.*;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: GameObject.java
     * @time: 2019/11/28 9:15
     * @desc:
     */
    
    public class GameObject {
        Image img;
        double x, y;
        int speed;
        int width, height;
    
        public void drawSelf(Graphics g){
            g.drawImage(img, (int)x, (int)y, null);
        }
    
        public GameObject(Image img, double x, double y) {
            this.img = img;
            this.x = x;
            this.y = y;
        }
    
    
        public GameObject(){
        }
    
        public Rectangle getRect(){
            // 返回物體所在的矩形,便於後續的碰撞檢測
            return new Rectangle((int)x, (int)y, width, height);
        }
    }
  • 炮彈類

    package com.sxt.planegame2;
    
    import java.awt.*;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Shell.java
     * @time: 2019/11/28 10:08
     * @desc: 炮彈類
     */
    
    public class Shell extends GameObject {
        double degree;
    
        public Shell() {
            x = 200;
            y = 200;
            width = 10;
            height = 10;
            speed = 3;
    
            degree = Math.random() * Math.PI * 2;
        }
    
        public void draw(Graphics g) {
            Color c = g.getColor();
            g.setColor(Color.yellow);
    
            g.fillOval((int) x, (int) y, width, height);
    
            // 炮彈沿着任意角度去飛
            x += speed * Math.cos(degree);
            y += speed * Math.sin(degree);
    
            // 炮彈遇到牆壁以後反彈
            if (x < 0 || x > Constant.GAME_WIDTH - width) {
                degree = Math.PI - degree;
            }
    
            if (y < 30 || y > Constant.GAME_HEIGHT - height) {
                degree = -degree;
            }
    
            g.setColor(c);
        }
    }
  • 飛機類

    package com.sxt.planegame2;
    
    import java.awt.*;
    import java.awt.event.KeyEvent;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Plane.java
     * @time: 2019/11/28 9:27
     * @desc:
     */
    
    public class Plane extends GameObject {
        boolean left, up, right, down;
        boolean live = true;
    
        public Plane(Image img, double x, double y) {
            super(img, x, y);
            speed = 10;
            width = img.getWidth(null);
            height = img.getHeight(null);
        }
    
        @Override
        public void drawSelf(Graphics g) {
            if(live){
                g.drawImage(img, (int) x, (int) y, null);
    
                if (left) {
                    x -= speed;
                }
                if (right) {
                    x += speed;
                }
                if (up) {
                    y -= speed;
                }
                if (down) {
                    y += speed;
                }
            }else{
            }
        }
    
        // 按下某個鍵增長相應的方向
        public void addDirection(KeyEvent e) {
            switch (e.getKeyCode()) {
                case KeyEvent.VK_LEFT:
                    left = true;
                    break;
                case KeyEvent.VK_UP:
                    up = true;
                    break;
                case KeyEvent.VK_RIGHT:
                    right = true;
                    break;
                case KeyEvent.VK_DOWN:
                    down = true;
                    break;
            }
        }
    
        // 擡起某個鍵取消相應的方向
        public void minusDirection(KeyEvent e) {
            switch (e.getKeyCode()) {
                case KeyEvent.VK_LEFT:
                    left = false;
                    break;
                case KeyEvent.VK_UP:
                    up = false;
                    break;
                case KeyEvent.VK_RIGHT:
                    right = false;
                    break;
                case KeyEvent.VK_DOWN:
                    down = false;
                    break;
            }
        }
    }
  • 爆炸類

    package com.sxt.planegame2;
    
    import java.awt.*;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: Explode.java
     * @time: 2019/11/28 14:59
     * @desc: 爆炸效果
     */
    
    public class Explode {
        double x, y;
        static Image[] imgs = new Image[16];
    
        static {
            for (int i = 0; i < 16; i++) {
                imgs[i] = GameUtil.getImage("images/explode/e" + (i + 1) + ".gif");
                imgs[i].getWidth(null);
            }
        }
    
        int count;
    
        public void draw(Graphics g) {
            if (count <= 15) {
                g.drawImage(imgs[count], (int) x, (int) y, null);
                count++;
            }
        }
    
        public Explode(double x, double y) {
            this.x = x;
            this.y = y;
        }
    }
  • 主程序類

    package com.sxt.planegame2;
    
    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.KeyAdapter;
    import java.awt.event.KeyEvent;
    import java.awt.event.WindowAdapter;
    import java.awt.event.WindowEvent;
    import java.util.Date;
    
    /**
     * @author: Li Tian
     * @contact: litian_cup@163.com
     * @software: IntelliJ IDEA
     * @file: MyGameFrame.java
     * @time: 2019/11/27 10:33
     * @desc: 飛機遊戲的主窗口
     */
    
    public class GameFrame extends Frame {
    
        Image planeImg = GameUtil.getImage("images/plane.png");
        Image bg = GameUtil.getImage("images/bg.jpg");
    
        Plane plane = new Plane(planeImg, 250, 250);
        Shell[] shells = new Shell[50];
    
        Explode bao;
        Date startTime = new Date();
        Date endTime;
        int period;     // 遊戲持續的時間
    
        @Override
        public void paint(Graphics g) {
            // paint是自動調用
            g.drawImage(bg, 0, 0, null);
            plane.drawSelf(g);
    
            // 畫出全部的炮彈
            for (int i = 0; i < shells.length; i++) {
                shells[i].draw(g);
                boolean peng = shells[i].getRect().intersects(plane.getRect());
    
                if (peng) {
                    plane.live = false;
    
                    // 只須要生成一次爆炸效果就ok
                    if (bao == null) {
                        bao = new Explode(plane.x, plane.y);
                        endTime = new Date();
                        period = (int) ((endTime.getTime() - startTime.getTime()) / 1000);
                    }
                    bao.draw(g);
                }
    
                if(!plane.live){
                    g.setColor(Color.red);
                    Font f = new Font("宋體", Font.BOLD, 20);
                    g.setFont(f);
                    g.drawString("時間:" + period + "秒", (int) plane.x, (int) plane.y);
                }
            }
        }
    
        // 幫助咱們反覆重畫窗口!
        class PaintThread extends Thread {
            @Override
            public void run() {
                while (true) {
                    // 重畫
                    repaint();
                    try {
                        Thread.sleep(40);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        // 增長鍵盤監聽內部類
        class KeyMonitor extends KeyAdapter {
            @Override
            public void keyPressed(KeyEvent e) {
                plane.addDirection(e);
            }
    
            @Override
            public void keyReleased(KeyEvent e) {
                plane.minusDirection(e);
            }
        }
    
        // 初始化窗口
        public void launchFrame() {
            this.setTitle("李英俊本俊的打飛機遊戲");
            this.setVisible(true);
            this.setSize(Constant.GAME_WIDTH, Constant.GAME_HEIGHT);
            this.setLocation(300, 300);
    
            // 點x就關閉程序了
            this.addWindowListener(
                    new WindowAdapter() {
                        @Override
                        public void windowClosing(WindowEvent e) {
                            System.exit(0);
                        }
                    }
            );
            // 啓動重畫窗口的線程
            new PaintThread().start();          // 啓動重畫窗口的線程
            addKeyListener(new KeyMonitor());   // 給窗口增長鍵盤的監聽
    
            // 初始化50個炮彈
            for (int i = 0; i < shells.length; i++) {
                shells[i] = new Shell();
            }
        }
    
        public static void main(String[] args) {
            GameFrame f = new GameFrame();
            f.launchFrame();
        }
    
        private Image offScreenImage = null;
    
        @Override
        public void update(Graphics g) {
            if (offScreenImage == null)
                offScreenImage = this.createImage(Constant.GAME_WIDTH, Constant.GAME_HEIGHT);//這是遊戲窗口的寬度和高度
    
            Graphics gOff = offScreenImage.getGraphics();
            paint(gOff);
            g.drawImage(offScreenImage, 0, 0, null);
        }
    }

個人CSDN:https://blog.csdn.net/qq_21579045

個人博客園:https://www.cnblogs.com/lyjun/

個人Github:https://github.com/TinyHandsome

紙上得來終覺淺,絕知此事要躬行~

歡迎你們過來OB~

by 李英俊小朋友

相關文章
相關標籤/搜索