Java知識積累-基礎篇

把前段時間看過的內容,結合面試經歷,做以整理,持續改進:Dhtml

Java基礎

JVM

emmmm,爲何把JVM放第一個咧……java

主要是由於以前某次面試的時候被問到「從宏觀角度怎麼看Java」才發現用了N年的Java居然都沒好好看過這個mysql

回想一下也是這感受,從大二學Java以來一直沒把JVM當回事,回過來看才發現錯過了一個億orzgit

Java跨平臺的特性,主要歸功於Java虛擬機JVM——Java virtual machinegithub

如圖,經過編譯.java源碼文件得到.class字節碼文件,而後JVM經過類加載器加載字節碼文件,在虛擬機中運行面試

運行時的數據區主要有兩塊:線程共享區和獨佔區/非共享區算法

例如一塊psvm裏面Idea黨運行的代碼:sql

String s = new String("123");
複製代碼

將它拆分爲數個部分並對照上圖可得:編程

  • String s = xxxx → 對象s是保存字符串的引用的本地變量 → 本地方法棧Stack(獨佔區)
  • new String(xxxx) → 經過帶參數的構造器實例化String對象並返回其引用 → 堆內存Heap(共享區)
  • "123" → 字符串常量 → 常量池 → 方法區Non-Heap(共享區)

這樣一解析,運行時數據存儲的區域就清晰不少了:Dubuntu

Garbage Collection 垃圾回收

恰好前面恆生羣面的時候有同窗被問到了,這裏也趁這機會總結一下

3種垃圾回收算法

1. 標記-清除(Mark-Sweep) 算法

標記可回收的對象,而後進行清除。

如圖,清理了紅色標記的部分,清理結束後多出綠色部分可用。

存在問題:

  1. 標記-清除過程的效率有限
  2. 內存碎片化

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

    1. String 內容不可變(賦值改變的是引用)

      String s = "123"; //s只保存「123」的引用地址
      s = "456"; // s保存了「456」的引用地址,"123"還鴿在內存裏等回收
      複製代碼
    2. StringBuffer 線程安全Synchronized

    3. StringBuilder 非線程安全

    4. StringJoiner Java 1.8新寶貝,非線程安全(實際經過StringBuilder實現), [文檔]

    • 線程安全性:String > StringBuffer > StringBuilder ≈ StringJoiner
    • 內存消耗:String > StringBuffer > StringBuilder ≈ StringJoiner
    • 執行效率: String < StringBuffer < StringBuilder ≈ StringJoiner
  • 字符串拼接問題

    衆所周知String在循環裏作拼接會耗時間耗內存,就想看看耗到什麼程度

    代碼paste,如有問題歡迎指正qwq

    • 準備一個String的List,其餘方法寫成靜態方法調用並返回String結果
    List<String> stringList = new ArrayList<>();
    for (int i = 0; i < N; i++) {
        stringList.add(""+i);
    }
    for (int i = 1; i <= 5; i++) {
        runTestWithClock(i, stringList);
        // Thread.sleep(10000L); //用於查看內存時分隔每種方法
    }
    複製代碼
    • 準備一個計時器
    private static void runTestWithClock(int n, List<String> stringList){
        String result = null;
        long clock = System.currentTimeMillis(); // 記錄運行前時間
        long timer = 0;
        System.out.println("--------"+n+"-------");
        switch (n){
            case 1:
                result = sTest(stringList);
                break;
            case 2:
                result = sBufferTest(stringList);
                break;
            case 3:
                result = sBuilderTest(stringList);
                break;
            case 4:
                result = sJoiner(stringList);
                break;
            case 5:
                result = sjStreamTest(stringList);
        }
        timer = System.currentTimeMillis() - clock ; // 計算時間差
        System.out.println("Timer: "+timer);
        System.out.println(result);
    }
    複製代碼
    1. 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......
    複製代碼
    1. 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.....
    複製代碼
    1. 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......
    複製代碼
    1. 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......]
    複製代碼
    1. streamCollection.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最慘
    --------1-------String
    Timer: 26841
    --------2-------StringBuffer
    Timer: 14
    --------3-------StringBuilder
    Timer: 11
    --------4-------StringJoiner
    Timer: 10
    --------5-------stream+joining
    Timer: 43
    複製代碼
    • 正常狀態下的內存消耗曲線(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…

  1. 重寫overwrite
class User {
    public void communicate(){ System.out.println("User is communicating."); }
}
class Teacher extends User{ //繼承自User
    public void communicate(){ System.out.println("Teacher is communicating."); } //重寫超類的方法
}
class Student extends User{ //繼承自User
    public void communicate(){ System.out.println("Student is communicating."); } //重寫超類的方法
}
public class Communication {
    public static void main(String[] args) {
        List<User> users = new ArrayList<>();
        users.add(new Teacher()); //向上轉型
        users.add(new Student()); //向上轉型
        for( User user: users ){
            user.communicate(); // 執行子類重寫的方法
        }
    }
}
// 繼承→重寫→向上轉型
複製代碼
  • 區別→重載overload
    • 重寫須要方法名、參數、返回值類型都相同
    • 重載須要方法名相同可是參數不一樣,返回值類型能夠相同也能夠不一樣
public class Student{
    public Course searchCourse(int courseId){...}
    public Course searchCourse(String courseName){...}
    public List<Course> searchCourse(String instructorName, String courseType){...}
} 
複製代碼
  1. 實現抽象類的抽象方法 User可改成抽象類,communicate()改爲抽象方法
abstract class User {
    public abstract void communicate();
}
class Teacher extends User{ //繼承自User
    public void communicate(){ System.out.println("Teacher is communicating."); } //實現超類的抽象方法
}
class Student extends User{ //繼承自User
    public void communicate(){ System.out.println("Student is communicating."); } //實現超類的抽象方法
}
public class Communication {
    public static void main(String[] args) {
        List<User> users = new ArrayList<>();
        users.add(new Teacher()); //向上轉型
        users.add(new Student()); //向上轉型
        for( User user: users ){
            user.communicate(); // 執行實現類實現的方法
        }
    }
}
// 繼承→實現→向上轉型
複製代碼
  1. 接口

這種實現方式在MVC裏挺經常使用, 業務層Service和數據處理層Dao(或Repository)都會應用, 此處以Service爲例,不一樣的實現使用的是不一樣的數據處理層

public interface UserService {  User findUserById(Integer userId);  }
public class UserServiceImplByRepository implements UserService {
    private UserRepository userRepository;
    @Override
    public User findUserById(Integer userId) {  return userRepository.findUserByUserId(userId);    }
}
public class UserServiceImplByDao implements UserService {
    private UserDao userDao;
    @Override
    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.forNamejava.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高級

  1. 加載(Loading)

    讀取二進制內容

  2. 驗證(Verification)

    驗證class文件格式規範、語義分析、引用驗證、字節碼驗證

  3. 準備(Preparation)

    分配內存、設置類static修飾的變量初始值(此時除了final修飾之外的其餘static變量均先給0或null值,只有final值是在此時肯定的)

  4. 解析(Resolution)

    類、接口、字段、類方法等解析

  5. 初始化(Initialization)

    靜態變量賦值(對,static的值如今纔給賦上),執行靜態代碼塊

  6. 使用(Using)

    建立實例對象

  7. 卸載(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…

  1. 建立階段(Created)

  2. 應用階段(In Use)

    對象至少被一個強引用持有

  3. 不可見階段(Invisible)

    超出做用域

    for(int i = 0; i < 10; i++){ // in loop
        // i is visible
    }// out of the loop
    // i is invisible
    複製代碼
  4. 不可達階段(Unreachable)

    無任何強引用持有

  5. 收集階段(Collected)

    gc已經對該對象的內存空間從新分配作好準備

  6. 終結階段(Finalized)

    當對象執行完finalize()方法後仍然處於不可達狀態時,則該對象進入終結階段。在該階段是等待垃圾回收器對該對象空間進行回收

  7. 對象空間重分配階段(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); // 申請容量n

byteBuffer.put((byte) 1); // 寫
byteBuffer.flip(); // 轉換讀寫
byte b = byteBuffer.get(); // 讀

byteBuffer.compact(); // 清除已閱讀的數據。轉爲寫入模式
byteBuffer.clear(); // 清除整個緩衝區
複製代碼

三屬性

  1. capacity 容量
  2. position 位置
  3. limit 限制

倆模式

  1. 寫入模式
  2. 讀取模式

以上方的使用爲例解析: 容量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

基本操做:

  1. Client- SocketChannel
SocketChannel sc = SocketChannel.open();
sc.configureBlocking(false); // 設置爲非阻塞模式
sc.connect( new InetSocketAddress("http://domain.com", port));

sc.write(byteBuffer);
sc.read(byteBuffer);

sc.close();
複製代碼
  1. Server- ServerSocketChannel
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false); // 同理非阻塞
ssc.socket().bind(new InetSocketAddress(8080));
while(true){
    SocketChannel sc = ssc.accept();
    if( sc != null ){
        sc.write(xxx)
        sc.read(xxx)
        ....
    }
}
複製代碼

Really? While loop? Need to be improved:

3. Selector 選擇器

用於管理多個NIO通道。

channel事件類型使用SelectionKey常量來存儲:

  1. 鏈接: SelectionKey.OP_CONNECT
  2. 準備就緒: SelectionKey.OP_ACCEPT
  3. 讀取: SelectionKey.OP_READ
  4. 寫入: SelectionKey.OP_WRITE

使用:

// 建立選擇器
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();
    }
}
複製代碼
相關文章
相關標籤/搜索