前言 大學期間接觸 Java 的時間也不短了,不論學習還是實習,都讓我發覺基礎的重要性。互聯網發展太快了,各種框架各種技術更新迭代的速度非常快,可能你剛好掌握了一門技術的應用,它卻已經走在淘汰的邊緣了。 而學習新技術總要付出一定的時間成本,那麼怎麼降低時間成本呢?那就是打好基礎,技術再怎麼革新,底層 ...
前言
大學期間接觸 Java 的時間也不短了,不論學習還是實習,都讓我發覺基礎的重要性。互聯網發展太快了,各種框架各種技術更新迭代的速度非常快,可能你剛好掌握了一門技術的應用,它卻已經走在淘汰的邊緣了。
而學習新技術總要付出一定的時間成本,那麼怎麼降低時間成本呢?那就是打好基礎,技術再怎麼革新,底層的東西也很少會變動,牢固的基礎會幫助你在各種新技術的學習中游刃有餘,快速上手。
因為我選擇的方向是後臺開發,所以談談我認為的基礎有哪些。其他方向肯定也有自己的體系,從低層到高層,可以自己摸索。後臺的話,我覺得網路知識,各種協議,web 知識,資料庫知識,Linux 基本操作以及自己選擇的後臺語言知識,這些是最基礎最需要牢固掌握的。
所以從今天起,會出一系列與後臺基礎相關的博文,一是對自己過去學習的一個總結,二是分享出來,希望可以幫助到需要的人。
概要
Java 基礎我做了 10 個方面的總結,包括基本概念,面向對象,關鍵字,基本類型與運算,字元串與數組,異常處理,Java 平臺與記憶體管理,分散式 Java 應用,多線程,IO。以下對這些內容做一些簡單的總結,同時我也有完整的思維導圖,博客上不方便展示,若有需要,聯繫我。
細節
1. 基本概念
1.1 語言特點
- 純面向對象
- 平臺無關性
- 內置類庫
- 支持web
- 安全性
- 防止代碼攻擊
- 健壯性
- 強類型機制
- 垃圾回收器
- 異常處理
- 安全檢查機制
- 去除C++中難以理解易混淆的特性
1.2 與C++比較
- 解釋編譯混合型語言,執行速度慢,跨平臺
- 純面向對象,只有類,不存在全局變數或全局函數
- 無指針,無多繼承,可多實現
- 垃圾回收器自動管理記憶體
1.3 main函數知識
- Java程式入口方法
- 可由final,synchronized修飾,不能用abstract
1.4 Java程式初始化順序
- 靜態優於非靜態
- 父類優於子類
- 按照成員變數的定義順序
- 總共10個
1.5 作用域與可見性
- 靜態變數屬於類
- 局部變數屬於花括弧
- 成員變數看下一條
- public、protected、default、private 可見性依次遞減
1.6 構造函數
- 與類名相同,無返回值
- 可重載,不能被繼承,即不能被覆蓋
- 參數個數任意
- 伴隨new 一起調用,為系統調用
- 完成對象的初始化工作
- 子類可通過super顯式調用父類。父類沒有提供無參,子類必須顯式調用
- 未定義,預設無參,修飾符取決於類修飾符
1.7 標識介面
- 無任何方法聲明
- 表示實現它的類屬於一個特定的類型
1.8 clone 方法
- 實現Cloneable介面
- 重寫Object類中的clone()
- clone()中調用super.clone()
- 把淺複製引用指向新的克隆體
1.9 反射
- 定義:允許程式在運行時進行自我檢查,也允許對其內部成員進行操作
- 功能
- 得到一個對象所屬的類
- 獲取一個類的所有成員和方法
- 運行時創建對象
- 在運行時調用對象的方法
- 獲取類的方式
- class.forName("類路徑")
- 類名.class
- 實例.getClass()
1.10 創建對象的四種方式
- new
- 反射機制
- clone()
- 反序列化
1.11 package 作用
- 提供多層命名空間,解決命名衝突
- 對類按功能進行分類,使項目組織更加清晰
2. 面向對象
2.1 與面向過程區別
- 層次邏輯關係不同。
- 面向對象是通過類的層次結構來體現類之間的繼承與發展
- 面向過程是通過模塊的層次結構概括模塊與模塊間的關係與功能
- 數據處理方式不同與控製程序方式不同
- 面向對象是數據與操作封裝成一個整體,通過事件驅動來激活和運行程式
- 面向過程是數據單獨存儲,控製程序方式上按照設計調用或返回程式
2.2 特性
- 抽象
- 繼承
- 多態
- 封裝
2.3 這種開發方式優點
- 開發效率高。代碼重用
- 保證軟體的魯棒性。經過長期測試的已有代碼
- 保證軟體的高可維護性。設計模式成熟
2.4 繼承
- 單繼承
- 只能繼承父類的非私有成員變數和方法
- 同名成員變數,子類覆蓋,不會繼承
- 相同函數簽名,子類覆蓋,不會繼承
2.5 組合和繼承區別
- 組合:在新類中創建原有類的對象。has a
- 繼承是 is a
2.6 多態
- 方法重載
- 編譯時多態
- 方法覆蓋
- 運行時多態
- 成員變數無多態概念
2.7 覆蓋和重載區別
- 子父類關係,垂直;同類方法間關係,水平
- 一對方法發生關係;多個方法發生關係
- 參數列表相同;參數列表不同
- 調用的方法根據對象的類型決定;根據調用時的實參表決定方法體
2.8 抽象類與介面異同
同
- 不能被實例化
- 介面的實現類實現了介面,抽象類的子類實現了方法,才能被實例化
異
- 介面只能定義方法,不能實現;抽象類可以有定義和實現
- 介面需要被實現;抽象類需要被繼承
- 介面強調特定功能的實現;抽象類強調所屬關係
- 介面成員變數預設為 public static final,成員方法 public abstract
- 抽象類變數預設default,方法不能用 private、static、synchronized、native 修飾
2.9 內部類
- 靜態內部類
- static 修飾
- 只能訪問外部類中的static數據
- 成員內部類
- 與實例綁定
- 不可定義靜態屬性和方法
- 外部實例化後,該內部類才能被實例化
- 局部內部類
- 代碼塊內
- 不能被public、protected、private以及static修飾
- 只能訪問final 局部變數
- 匿名內部類
- 無類名
- 無構造函數,必須繼承或實現其他類
- 原則
- 無構造函數
- 無靜態成員,方法和類
- 不能是public、protected、private、static
- 只能創建匿名內部類的一個實例
- new 後面有繼承或實現
- 特殊的局部內部類
2.10 如何獲取父類類名
- 利用反射:obj.getClass().getSuperClass().getName()
- 不使用super.getClass()原因:該方法在 Object中為final與native,子類不能覆蓋,返回此Object運行時類
2.11 this
- 指向當前實例對象
- 區分成員變數與方法形參
2.12 super
- 訪問父類成員變數或方法
- 子類同名會覆蓋,訪問父類只能通過super
- 子類構造函數需顯示調用父類構造函數時,super()必須為構造函數的第一條語句
3. 關鍵字
3.1 變數命名
- 英文字母
- 數字
- _和$
- 不能包含空白字元
- 首字元不能為數字
- 保留字不能做標識符
- 區分大小寫
3.2 assert
- 軟體調試
- 運行時開啟 -ea
3.3 static
- 特定類的統一存儲空間,類綁定
- 成員變數:屬於類,記憶體中只有一個複製
- 成員方法:調靜態數據。可實現單例模式
- 代碼塊:初始化靜態變數,只被執行一次
- 內部類:不能與外部類重名,只能訪問外部類靜態數據(包括私有)
3.4 switch
- 多分支選擇
- 整型或字元類型變數或整數表達式
- Java 7 開始支持 String。原理是String的hashCode()返回的int類型值匹配
3.5 volatile
- 保證線程間的可見性
- 從記憶體中取數據,而不是緩存
- 不保證原子性
3.6 instanceof
- 二元運算符
- 判斷一個引用類型的變數所指向的對象是否是一個類的實例
- 即左邊對象是否是右邊類的實例
3.7 strictfp
- 精確浮點
- 確保浮點運算的準確性
- 若不指定,結果依賴於虛擬機平臺
- 指定後依賴於統一標準,保證各平臺的一致性
3.8 null
- 不是合法的Object實例
- 無記憶體
- 表明該引用目前沒有指向任何對象
4. 基本類型與運算
4.1 基本數據類型
- int長度
- byte(8 bit)
- short(16 bit)
- int(32 bit)
- long(64 bit)
- float長度
- 單精度(32 bit float)
- 雙精度(64 bit double)
- boolean 類型變數的取值
- true
- false
- char數據類型:Unicode字元(16 bit)
- void:java.lang.Void 無法直接對其進行操作
4.2 不可變類
- 實例創建後,值不可變
- 所有的基本類型的包裝類+String
- 優點
- 使用簡單
- 線程安全
- 節省記憶體
- 缺點:會因為值的不同而產生新的對象,導致無法預料的問題
4.3 類型轉換
- 隱式類型轉換
- 低精度到高精度
- byte->short->char->int->long->float->double
- 顯式類型轉換
- 反之
- 可能會損失精度
- 類型自動轉換
- 低到高
- char類型會轉換為其對應的ASCII碼
- byte、char、short參與運算自動轉為int,但"+=",不轉
- 基本數據類型與boolean不能相互轉換
- 多種類型混合運算,自動轉成容量最大類型
運算符優先順序
點 () [] +(正) -(負) ++ -- ~ ! * / % +(加) -(減) << >> >>> < <= > >= instanceof == != & | ^ && || ?: = += -= *= /= %= &= |= ^= ~= <<= >>= >>>=
5. 字元串與數組
5.1 字元串創建與存儲機制
- 堆
- 常量池
- new String("abc")創建1個或2個對象
5.2 ==、equals和hashCode區別
- == 比較引用,記憶體
- 未覆蓋,同==;比較內容
- hashCode鑒定對象是否相等,返回整數
5.3 String,StringBuffer,StringBuilder
- String:不可變,執行效率最低
- StringBuffer:可修改,線程安全,效率較高
- StringBuilder:可修改,線程不安全,效率最高
5.4 其他
- 數組初始化方式
- length屬性和length()方法
6. 異常處理
6.1 finally塊執行時機
- 若try中有return,在return前
- 若try-finally或catch-finally中都有return,finally會覆蓋
6.2 finally代碼塊不是一定會被執行
- 程式進入try之前出現異常
- try中調用System.exit(0)
6.3 Error
嚴重錯誤,不可恢復
6.4 Exception
- 可恢復,編譯器可捕捉
- 檢查性異常
- IO
- SQL
- 運行時異常
- JVM處理
- NullPointException
- ClassCastException
- ArrayIndexOutOfBoundsException
- 出現異常後,一直往上層拋,直到遇到處理代碼或最上層
- 多態。若先捕獲基類,再捕獲子類。子類處理代碼將永遠不會得到執行
7. Java平臺與記憶體管理
7.1 Java平臺與其他語言平臺的區別
- 純軟體,包括JVM與JAVA API
- JVM虛擬,不跨平臺
7.2 JAVA代碼的執行
- 代碼編譯為class:sun jdk 中javac
- 裝載class:ClassLoader
- 執行class
- 解釋執行
- 編譯執行
- client compiler
- server compiler
7.3 java源碼編譯機制
- 詞法分析器組件:Token流
- 語法分析器組件:語法樹
- 語義分析器組件:註解語法樹
- 將語法樹中的名字、表達式等元素與變數、方法、類型等聯繫到一起
- 檢查變數使用前是否已聲明
- 推導泛型方法的類型參數
- 檢查類型匹配性
- 進行常量摺疊
- 檢查所有語句都可到達
- 檢查變數的確定性賦值
- 解除語法糖
- 將泛型JAVA轉成普通Java
- 檢查所有checked exception都被捕獲或拋出
- 將含語法糖的語法樹轉成簡單語法樹eg:foreach,自動摺疊
- 代碼生成器組件:位元組碼
7.4 類載入機制
- 裝載:全限定名+類載入器載入類
- 鏈接
- 校驗
- 格式不符,拋VerifyError
- 載入引用的類失敗:拋NoClassDefFoundError
- 準備:靜態變數預設初始化
- 解析:屬性、方法驗證(可選)
- 校驗
- 初始化(不是類載入必須觸發的)
- 靜態初始化代碼
- 構造器代碼
- 靜態屬性初始化
- 觸發時機
- 調用了new
- 反射調用了類中的方法
- 子類調用了初始化
- JVM啟動過程中指定的初始化類
- Bootstrap Class Loader:$JAVA_HOME/jre/lib/rt.jar
- Extension Class Loader:$JAVA_HOME/jre/lib/ext/*.jar
- System Class Loader:$CLASSPATH
- User Defined Class Loader
7.5 類執行機制
- 解釋執行
- JVM位元組碼為中間代碼,由JVM在運行期對其解釋並執行
- invokestatic
- invokevirtual
- invokeinterface
- invokespecial
- 基於棧
- 代碼緊湊,體積小
- 線程創建後,產生PC和Stack
- 指令解釋執行
- 棧頂緩存:棧頂值緩存在寄存器上
- 部分棧幀共用
- JVM位元組碼為中間代碼,由JVM在運行期對其解釋並執行
- 編譯執行
- client compiler
- 輕量級,占記憶體少
- 方法內聯
- 去虛擬化
- 冗餘消除
- server compiler
- 重量級,占記憶體多
- 逃逸分析是C2進行很多優化的基礎
- 標量替換:用標量替換聚合量
- 棧上分配
- 若對象未逃逸,C2會選擇在棧上直接創建Point對象實例,而不是在堆上
- 棧上分配更快速,對象易回收
- 同步消除:如果發現同步的對象未逃逸,那也沒有同步的必要。C2會去掉同步代碼塊
- client compiler
7.6 記憶體空間
- 方法區:類信息,線程共用
- 堆
- 對象實例+數組
- 分代管理
- 新生代
- 舊生代
- 本地方法棧:支持native方法,Sun JDK的實現中本地方法棧和JVM方法棧是同一個
- PC寄存器:線程私有
- JVM方法棧:線程私有
7.7 記憶體分配
- Java對象,堆上分配,分配需加鎖,開銷大
- 當堆上空間不足-->GC-->仍不足-->拋OutOfMemory
- Sun JDK 為新創建的線程在Eden上分配TLAB
- 多個小對象比大對象分配更高效
- 基於逃逸分析直接從棧上分配
7.8 記憶體回收
- 收集器
- 引用計數收集器
- 計數器增減有消耗
- 不適合迴圈引用
- 跟蹤收集器
- 集中式管理
- 全局記錄數據的引用狀態
- 從根集合掃描對象,可能會造成應用程式暫停
- 三種實現演算法
- 複製
- 適用於回收空間中存活對象較少
- 缺點:需要增加一塊空的記憶體空間及進行對象的移動
- 標記-清除:會產生記憶體碎片
- 標記-壓縮:不產生記憶體碎片
- 複製
- 引用計數收集器
- Sun JDK中可用GC
- 新生代
- 串列GC(Serial GC):複製演算法
- Minor GC
- 強軟弱虛
- 並行回收GC(Parrallel Scavenge):掃描複製多線程
- 並行 GC(ParNew):配合舊生代 CMS
- 串列GC(Serial GC):複製演算法
- 舊生代和持久代可用GC
- 串列:標記壓縮+清除
- 並行:標記壓縮
- 併發:CMS
- 標記:暫停
- 併發標記:恢復,輪詢著色對象,以標記它們
- 重新標記:暫停
- 併發收集:恢復
- CMS記憶體回收易產生碎片,但是它提供了整理碎片的功能
- 浮動垃圾:CMS回收時產生應該回收但要等到下次CMS才能被回收掉的對象
- 新生代
- Full GC
- 對新生代舊生代及持久代都進行的GC
- 觸發的四種情況
- 舊生代空間不足
- 持久代空間滿
- CMS GC出現promotion failed和concurrent mode failure
- 統計得到的Minor GC晉升到舊生代的平均大小大於舊生代的剩餘空間
7.9 記憶體泄露
- 一個不再被程式使用的對象或變數還在記憶體中占有存儲空間
- 符合垃圾回收標準
- 對象賦空值null
- 給對象賦予新值,重新分配了記憶體空間
- 泄露的兩種情況
- 堆中申請的空間沒有被釋放
- 對象不再被使用,但仍然存活在記憶體中
- 泄露原因
- 靜態集合類
- 各種連接
- 監聽器
- 變數不合理的作用域
- 單例模式
8. 分散式Java應用
8.1 基於消息方式實現系統間的通信
- TCP/IP+BIO
- socket.setSoTimeOut()設置等待響應的超時時間
- 一連接一線程
- 缺點:無論連接是否真實,都要創建線程
- BIO下伺服器端所能支撐的連接數目有限
- TCP/IP+NIO
- Channel
- SocketChannel:建立連接,監聽事件,操作讀寫
- ServerSocketChannel:監聽埠,監聽連接事件
- Selector:獲取是否要處理的事件
- Buffer:存放處理的數據
- NIO Reactor模式,通過註冊感興趣的事件及掃描是否有感興趣的事件發生,從而做出相應的動作
- 多個請求,連接復用
- 只有在有真實的請求時,才會創建線程
- 一請求一線程
- Channel
- UDP/IP+BIO
- DatagramSocket:負責監聽埠,讀寫數據
- DatagramPacket:作為數據流對象進行傳輸
- UDP/IP+NIO
- DatagramChannel:監聽埠,進行讀寫
- ByteBuffer:數據流傳輸
- NIO好處:只在有流要讀取或可寫入流時才做出相應的IO操作,而不像BIO方式阻塞當前線程
8.2 基於遠程調用方式實現系統間的通信
- 遠程調用方式
- 系統間通信和系統內一樣
- 讓使用者感覺調用遠程同調用本地一樣
- 基於Java自身技術
- RMI:客戶端代理,stub,封裝對象,序列化為流,TCP/IP BIO,Skeleton,反序列化,獲取對象實例,調用
- WebService
- 服務端的服務生成WSDL文件
- 將應用+WSDL文件放入HTTP伺服器
- 借用Java輔助工具根據WSDL文件生成客戶端stub代碼
- stub將產生的對象請求信息封裝為標準化的SOAP格式數據,併發請求到伺服器端
- 服端在接收到SOAP格式數據時進行轉化,反射調用相應的Java類
- SOAP優點支持跨語言,缺點對複雜對象結構難支持
8.3 基於開源框架
- Spring RMI
9. 多線程
9.1 線程資源同步機制
- JVM保證以下操作順序
- 同一線程操作
- 對於main Memory 上的同一個變數的操作
- 對於加了鎖的main Memory上的對象操作
- 為避免資源操作的臟數據問題,JVM提供了
- synchronized
- volatile
- lock/unlock
- 目的是控制資源競爭
9.2 線程交互機制
- 基於Object的wait/notify/notifyAll
- 為避免假喚醒,需要double check
- 調用對象的wait-->wait sets--->釋放鎖--->其他線程notify---->wait sets---->執行此對象線程--->刪除sets中此線程
- 基於JDK 5 併發包,支持線程交互
- Semphore的acquire,release
- Condition的await,signal
- CountDownLatch的await和countDown
9.3 線程狀態
- New
- Runnable
- Running
- Wait
- TimedWait
- Blocked
- Terminated
9.4 sleep()與wait()方法的區別
- sleep
- 暫停一段時間執行
- Thread的靜態方法
- 不釋放鎖
- 需要捕獲異常
- wait
- 使線程暫停執行
- Object方法,用於線程間通信
- 釋放鎖
9.5 守護線程
- 後臺提供服務
- 用戶線程全部終止,只剩下守護線程時,JVM就會退出
- 調用start()之前,調用線程對象的setDaemon(true)
9.6 join
- 調用該方法的線程在執行完run()後,再執行join方法後面的代碼
- 線程合併,實現同步功能
10. IO
10.1 流本質
- 數據傳輸
10.2 流分類
- 位元組流:不使用緩存
- 字元流
- 碼表映射
- 使用緩存
10.3 裝飾者模式
- 運行時動態給對象增加額外的職責
- 是你還有你,一切拜托你
- FilterInputStream
10.4 Java Socket
- ServerSocket server = new ServerSocket(2000);
- Socker socket = server.accept();
- 客戶端:Socket socket = new Socket("localhost",2000);
10.5 NIO
- Channel--Selector--Buffer
- 反應器模式
10.6 序列化
- 對象持久化方式
- 解決在對對象流進行讀寫操作時引發的問題
- 對象寫進流里進行網路傳輸,保存到文件,資料庫
10.7 如何實現序列化
- 實現Serializable介面
- 使用FileOutputStream來構造ObjectOutputStream對象
- 使用該對象的writeObject(obj)方法將對象寫出
- 要恢復時,使用對應的輸入流
10.8 序列化特點
- 一個類能被序列化,它的子類也能被序列化
- static代表類成員,transient代表臨時數據。均不能被序列化
- 序列化影響性能,需要才使用
- 需要通過網路來發送對象,或對象的狀態需要被持久化到資料庫或文件中
- 序列化能實現深複製,即可以複製引用的對象
10.9 反序列化
- 將流轉化為對象
- UID最好自己定義。優點
- 提高程式運行效率。省去計算過程
- 提高程式不同平臺相容性。不同計算方式,反序列化失敗
- 增強程式各個版本的可相容性。加入新屬性,預設UID變化
10.10 外部序列化
- 實現Externalizable介面控制