Java知识积累-基础篇
把前段時間看過的內容,結合面試經歷,作以整理,持續改進:D
Java基礎
JVM
emmmm,為什么把JVM放第一個咧……
主要是因為之前某次面試的時候被問到“從宏觀角度怎么看Java”才發現用了N年的Java竟然都沒好好看過這個
回想一下也是這感覺,從大二學Java以來一直沒把JVM當回事,回過來看才發現錯過了一個億orz
Java跨平臺的特性,主要歸功于Java虛擬機JVM——Java virtual machine
如圖,通過編譯.java源碼文件獲得.class字節碼文件,然后JVM通過類加載器加載字節碼文件,在虛擬機中運行
運行時的數據區主要有兩塊:線程共享區和獨占區/非共享區
例如一塊psvm里面Idea黨運行的代碼:
String s = new String("123"); 復制代碼將它拆分為數個部分并對照上圖可得:
- String s = xxxx → 對象s是保存字符串的引用的本地變量 → 本地方法棧Stack(獨占區)
- new String(xxxx) → 通過帶參數的構造器實例化String對象并返回其引用 → 堆內存Heap(共享區)
- "123" → 字符串常量 → 常量池 → 方法區Non-Heap(共享區)
這樣一解析,運行時數據存儲的區域就清晰很多了:D
Garbage Collection 垃圾回收
剛好前面恒生群面的時候有同學被問到了,這里也趁這機會總結一下
3種垃圾回收算法
1. 標記-清除(Mark-Sweep) 算法
標記可回收的對象,然后進行清除。
如圖,清理了紅色標記的部分,清理結束后多出綠色部分可用。存在問題:
2. 復制(Copying) 算法
預留同樣大小的一塊內存,進行GC時復制存活的對象到復制用區并且順序放置(不留空隙)。
優勢:避免碎片化問題;
問題:復制需保留一塊內存,導致內存浪費
3. 標記-整理(Mark-Compact) 算法
類似于標記-清除,但是需對存活者進行整理
優勢:避免碎片化問題基礎類型
避免遺漏,通過類型區分,由小到大排序:
- 整形
- byte 字節 8-bit, -128~127
- short 短整型 16-bit, -32,768~32,767
- int 整形 32-bit
- long 長整型 64-bit
- 浮點
- float 單精度浮點 32-bit
- double 雙精度浮點 64-bit
- 特殊
- char 單字符 a single 16-bit Unicode character, 0~65,535
- boolean 布爾值 represents 1 bit of information, but its "size" isn't something that's precisely defined.
官方文檔《Oracle-Java教程-原始數據類型》
包裝類型
-
基礎類型都有對應的包裝類型(Integer、Char等
-
在基礎類型和包裝類型之間通過自動裝箱拆箱完成賦值
- Integer i = 3; 裝箱
- int j = i; 拆箱
-
默認值存在差異
- 基礎類型默認值大都取0
- 包裝類默認大都取null
-
類型比較( ==和equals() )需注意
- Integer var = ? 在[-128,127]范圍內的賦值,Integer 對象通過IntegerCache.cache產生,會復用已有對象,這個區間內的Integer 值可以直接使用==進行判斷,但是這個區間之外的所有數據,都會在堆上產生,并不會復用已有對象,推薦使用equals()進行比較
基本類型==equals 字符串變量 對象在內存中的首地址 字符串內容 非字符串變量 對象在內存中的首地址 對象在內存中的首地址 基本類型 值 不可用 包裝類 地址 內容 表格參考自——alexyyek.github.io/2014/12/29/…
字符串
-
String, StringBuffer, StringBuilder, StringJoiner
-
String 內容不可變(賦值改變的是引用)
String s = "123"; //s只保存“123”的引用地址 s = "456"; // s保存了“456”的引用地址,"123"還鴿在內存里等回收 復制代碼 -
StringBuffer 線程安全Synchronized
-
StringBuilder 非線程安全
-
StringJoiner Java 1.8新寶貝,非線程安全(實際通過StringBuilder實現), [文檔]
- 線程安全性:String > StringBuffer > StringBuilder ≈ StringJoiner
- 內存消耗:String > StringBuffer > StringBuilder ≈ StringJoiner
- 執行效率: String < StringBuffer < StringBuilder ≈ StringJoiner
-
字符串拼接問題
眾所周知String在循環里做拼接會耗時間耗內存,就想看看耗到什么程度
代碼paste,若有問題歡迎指正qwq
- 準備一個String的List,其他方法寫成靜態方法調用并返回String結果
- 準備一個計時器
- String + String result = ""; for (String s: stringList){result = result + s + ","; } return result; // sTest:0,1,2,3,4,5,6,7,8,9,10,11,12...... 復制代碼
- StringBuffer append() StringBuffer sBuffer = new StringBuffer(); for (String s: stringList){sBuffer.append(s).append(","); } return sBuffer.toString(); // sBufferTest:0,1,2,3,4,5,6,7,8,9,10,11..... 復制代碼
- StringBuilder append() StringBuilder sBuilder = new StringBuilder(); for (String s: stringList){sBuilder.append(s).append(","); } return sBuilder.toString(); // sBuilderTest:0,1,2,3,4,5,6,7,8,9,10,11...... 復制代碼
- StringJoiner add() /* StringJoiner Since Java1.8 * @param:分隔符,前綴,后綴 * Docs: https://docs.oracle.com/javase/8/docs/api/java/util/StringJoiner.html */ StringJoiner sj = new StringJoiner(",", "[", "]"); for (String s: stringList){sj.add(s); } return sj.toString(); // sJoiner:[0,1,2,3,4,5,6,7,8,9,10,11......] 復制代碼
- stream 和 Collection.joining (stream特性詳見Java進階-語法糖-Streams API) return stringList.stream().map(String::toString).collect(Collectors.joining(",", "[", "]")); // sjStreamTest:[0,1,2,3,4,5,6,7,8,9,10,11......] 復制代碼
- 當N為100000時, 執行效率顯然String最慘
- 正常狀態下的內存消耗曲線(Java VisualVM-監視-內存-堆)
- 執行代碼時,出現了明顯增幅
- 查看時間,均在使用String相加時發生,sleep10秒后執行其他方法沒有出現增幅明顯的跡象
面向對象編程
封裝
public class User {private String userName;private String password;public User() {}public User(String userName, String password) {this.userName = userName;this.password = password;}public String getUserName() { return userName; }public void setUserName(String userName) { this.userName = userName; }public String getPassword() { return password; }public void setPassword(String password) { this.password = password; } } 復制代碼對象的屬性和方法封裝起來,不可直接暴露給外部訪問(例如對象實例user的password屬性不能用user.password直接獲取或修改),只能通過實例化對象的方法訪問內部屬性(user.getPassword())
繼承
public class VIP extends User{private int vipId;private int accessType;public VIP() {}public VIP(int vipId, int accessType) {// super(); // 隱藏this.vipId = vipId;this.accessType = accessType;}public VIP(String userName, String password, int vipId, int accessType) {super(userName, password);this.vipId = vipId;this.accessType = accessType;}// 省略getter, setter } 復制代碼VIP繼承自User,包含User所有屬性和方法,在此基礎上又擁有自己獨立的屬性和方法
- 向上轉型
User user = new VIP();
有個疑問,使用List list = new ArrayList<>();算不算向上轉型,一方面ArrayList繼承自抽象類AbstractList,另一方面實現了List接口
多態
參考:www.runoob.com/java/java-p…
- 區別→重載overload
- 重寫需要方法名、參數、返回值類型都相同
- 重載需要方法名相同但是參數不同,返回值類型可以相同也可以不同
這種實現方式在MVC里挺常用, 業務層Service和數據處理層Dao(或Repository)都會應用, 此處以Service為例,不同的實現使用的是不同的數據處理層
public interface UserService { User findUserById(Integer userId); } public class UserServiceImplByRepository implements UserService {private UserRepository userRepository;public User findUserById(Integer userId) { return userRepository.findUserByUserId(userId); } } public class UserServiceImplByDao implements UserService {private UserDao userDao;public User findUserById(Integer userId) { return userDao.findUserByUserId(userId); } } public class UserController {public String user(int id){if( id < 1000 ){// 假設1000以下的使用Repository的實現UserService us = new UserServiceImplByRepository(); // UserService接口類型的變量引用指向UserServiceImpl實現類的對象return us.findUserById(id); // 此處使用的是UserServiceImplByRepository的方法}else{// 其他使用Dao的實現UserService us = new UserServiceImplByDao(); // UserService接口類型的變量引用指向UserServiceImplByDao實現類的對象return us.findUserById(id) ; // 此處使用的是UserServiceImplByDao的方法}} } 復制代碼反射
一開始用JDBC的時候還不知道,回過頭來才發現早已在用了……
寫完Class.forName("com.mysql.jdbc.Driver")就能用Connection conn = DriverManager.getConnection(DB_URL,USER,PASS);來拿Connection連接
可以看看JDBC例子回顧一下:D www.tutorialspoint.com/jdbc/jdbc-s…
實際上手看看Class.forName和java.lang.reflect怎么用:
public class ReflectTest {public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {Class<?> userClass = Class.forName("cn.magiklau.java.basic.model.User");// 輸出Class名System.out.println("Class name: "+userClass.getName());Collector<CharSequence, ?, String> joining = Collectors.joining(", ", "[", "]"); // 方便格式化輸出System.out.print("DeclaredFields: ");// 使用getFields()只能取得public屬性,這里要取private屬性需要用getDeclaredFields()System.out.println(Arrays.stream(userClass.getDeclaredFields()).map(Field::getName).collect(joining));;System.out.print("DeclaredMethods: ");// 使用getMethods()也會取得超類的方法,這里還是只取本身的System.out.println(Arrays.stream(userClass.getDeclaredMethods()).map(Method::getName).collect(joining));// 取一個實例User user = (User)userClass.newInstance();user.setUserName("testUserName");System.out.println("Username: "+user.getUserName());} } 運行結果: Class name: cn.magiklau.java.basic.model.User DeclaredFields: [userName, password] DeclaredMethods: [getPassword, setUserName, getUserName, setPassword] Username: testUserName 復制代碼參考文章
深入理解 Java 反射和動態代理 github.com/dunwu/blog/…
深入解析Java反射(1) - 基礎 www.sczyh30.com/posts/Java/…
類加載機制
orz為了看反射竟然把類加載機制都看了一圈
類加載的過程
參考 網易云課堂-微專業-Java高級
加載(Loading)
讀取二進制內容
驗證(Verification)
驗證class文件格式規范、語義分析、引用驗證、字節碼驗證
準備(Preparation)
分配內存、設置類static修飾的變量初始值(此時除了final修飾以外的其他static變量均先給0或null值,只有final值是在此時確定的)
解析(Resolution)
類、接口、字段、類方法等解析
初始化(Initialization)
靜態變量賦值(對,static的值現在才給賦上),執行靜態代碼塊
使用(Using)
創建實例對象
卸載(Unloading)
從JVM方法區中卸載(條件:① 該Class所有實例都已經被GC;② 加載該類的ClassLoader實例已經被GC)
雙親委派模型
除了頂層ClassLoader以外,其他類加載器都需要有超類加載器,在超類加載失敗時才會交給子類加載
// 竟然還不支持MarkDown畫圖……簡單記一下先 Bootstrap ClassLoader頂層啟動類加載器 ↑委托 ↓查找 Extension ClassLoader拓展類庫類加載器 ↑委托 ↓查找 Application ClassLoader 用戶應用程序類加載器 ↑委托 ↓查找 Custome ClassLoader 自定義類加載器 復制代碼又看到一些個神奇的做法,備用 www.cnblogs.com/chanshuyi/p…
對象生命周期
參考CSDN-Sodino-blog.csdn.net/sodino/arti…
創建階段(Created)
應用階段(In Use)
對象至少被一個強引用持有
不可見階段(Invisible)
超出作用域
for(int i = 0; i < 10; i++){ // in loop// i is visible }// out of the loop // i is invisible 復制代碼不可達階段(Unreachable)
無任何強引用持有
收集階段(Collected)
gc已經對該對象的內存空間重新分配做好準備
終結階段(Finalized)
當對象執行完finalize()方法后仍然處于不可達狀態時,則該對象進入終結階段。在該階段是等待垃圾回收器對該對象空間進行回收
對象空間重分配階段(De-allocated)
IO
圖來自:runoob - Java 流(Stream)、文件(File)和IO - www.runoob.com/java/java-f…
字節流 InputStream OutputStream
// 讀操作 String filePath = "C:\\WorkSpace\\JavaProject\\JavaLearning\\src\\ioTestFile.txt"; // 字節流輸入,FileInputStream提供文件處理功能 InputStream fis = new FileInputStream(filePath); // BufferedInputStream提供緩存功能 InputStream bis = new BufferedInputStream(fis); while( bis.available() > 0 ){System.out.println((char) bis.read()); }// 同理進行寫操作 OutputStream fos = new FileOutputStream(filePath); OutputStream bos = new BufferedOutputStream(fos); bos.write("MagikIOTest~".getBytes()); bos.flush(); 復制代碼字符流 Reader Writer
github.com/CyC2018/CS-…
和流操作基本相似,也是在Reader里疊上File和Buffered裝飾
FileReader fileReader = new FileReader(filePath); BufferedReader bufferedReader = new BufferedReader(fileReader);String gotLine; while ((gotLine = bufferedReader.readLine()) != null) {System.out.println(gotLine); }// 裝飾者模式使得 BufferedReader 組合了一個 Reader 對象 // 在調用 BufferedReader 的 close() 方法時會去調用 Reader 的 close() 方法 // 因此只要一個 close() 調用即可 bufferedReader.close();FileWriter fileWriter = new FileWriter(filePath); BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); bufferedWriter.write("MagikIOTest writer~"); bufferedWriter.close(); 復制代碼網絡 Socket
Server: ServerSocket(port, timeout)
Client: Socket(host, port)
Server: accept()
S/C: InputSteam->OutputSteam
S/C: close()
NIO
非阻塞Non-Block IO
參考 網易云課堂-微專業-Java高級
三大核心組件:
1. Buffer 緩沖區 2. Channel 通道 3. Selector 選擇器 復制代碼1. Buffer 緩沖區
使用:
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(n); // 申請容量nbyteBuffer.put((byte) 1); // 寫 byteBuffer.flip(); // 轉換讀寫 byte b = byteBuffer.get(); // 讀byteBuffer.compact(); // 清除已閱讀的數據。轉為寫入模式 byteBuffer.clear(); // 清除整個緩沖區 復制代碼三屬性
倆模式
以上方的使用為例解析: 容量c,位置p,限制l
[Write mode] 初始 0 1 2 3 ↑ ↑↑ p lc === put(x) === [Write mode] 完成put 0 1 2 3↑ ↑↑p lc === flip() === [Read mode] 轉換模式 0 1 2 3 ↑ ↑ ↑ p l c === get() === [Read mode] 完成get 0 1 2 3↑↑ ↑pl c === compact() === [Read mode] 清除已讀 0 1 2 3 x ↑↑ ↑ x pl c [Write mode] 并轉換模式 0 1 2 3↑ ↑↑p lc === clear() === 返回初始狀態 0 1 2 3 ↑ ↑↑ p lc 復制代碼2. Channel 通道
運作方式:
- BIO-Send: data -> byte[] -> outputStream
- BIO-Receive: inputStream -> read -> data
- NIO-Send: data -> buffer -> channel
- NIO-Receive: channel -> buffer -> data
API:
- FileChannel
- DatagramChannel
- SocketChannel
- ServerSocketChannel
基本操作:
Really? While loop? Need to be improved:
3. Selector 選擇器
用于管理多個NIO通道。
channel事件類型使用SelectionKey常量來存儲:
使用:
// 創建選擇器 Selector selector = Selector.open(); selector.configureBlocking(false);ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.configureBlocking(false); // 注冊通道 SelectionKey key = ssc.register(selector, SelectionKey.OP_READ);while(true){ // 一直監聽,安全保障int readyChannels = selector.select(); // 監聽事件,阻塞到有為止if( readyChannels == 0 ) continue; // 返回監聽狀態// 成功監聽到事件Set<SelectionKey> keys = selector.selectedKeys();Iterator<SelectionKey> keyIterator = keys.iterator();while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();// 判斷事件類型if (key.isAcceptable()) {// ...} else if (key.isReadable()) {// ...}keyIterator.remove();} } 復制代碼轉載于:https://juejin.im/post/5cad460d5188251b291b8773
總結
以上是生活随笔為你收集整理的Java知识积累-基础篇的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JavaScript 中数组方法 red
- 下一篇: 使用深度学习框架 Tensorflow