Java 進階學習筆記

https://www.bilibili.com/video/BV1Kb411W75N?p=406

在學習中存在疑惑的點,都已經用“問題”標記。

幾個重點,從網上查得的參考,邏輯上合理。

常量區的位置,在不同版本中不同,JDK1.6在方法區,1.7在堆,1.8在方法區,這時,方法區的具體實現是元空間

注意這個說法,並不一定正確,網上說法繁多,不深究。

多線程:

每個線程獨享棧和計數器,公用一個進程的堆和方法區。

cpu一個核就是一個處理器。同一時刻,只處理一個線程。但可以實現併發。線程交替執行。

通過超線程,把一個核模擬成兩個用,否則,一個核一個時刻只能處理一個線程。

多線程的創建:main方法是一個主線程,run方法重寫的內容在一個新線程裏。

創建多個線程注意點:不能start同一個線程兩次,如果要再執行同一個操作,需要在new一個線程對象。

通過匿名子類方式創建多線程:前提是,這個線程的對象只用一次。

Thread中常用的方法:

yield(),釋放cpu的執行權。

join(),插隊並且讓當前線程執行完。

可以用靜態變量解決線程間數據不同步問題,但依舊會存在問題,還是有髒讀。

還是調用了thread的構造方法,向上轉型了。

第二種更優的原因,因爲類是單繼承,第一種自己的線程類繼承了thread 就無法繼承其他類了。其次,第二種方式可以只new一個實現類,然後用多態創建多個Thread, 這個線程共享這一個類裏面的變量,實現半線程同步,實際上仍存在線程安全問題。如賣票還是會賣多個同號的,上例。

線程的生命週期:

線程同步:

髒讀問題:

 

 

解決方案:加鎖。

synchronized()原理,爲什麼括號是對象。

https://www.cnblogs.com/aspirant/p/11470858.html

類也是對象。用類做鎖,因爲類只會加載一次。

使用同步方法來同步:同步的代碼,不能包多,也不能少。多影響效率,少了影響正確性。

同步方法解決一個鎖的問題:注意同步方法的監視器是誰的問題。

用線程安全解決單例模式的懶漢式。也可以直接在getInstance上面加 synchronized 關鍵字。

效率差的原因是,第一次實例化之後,後面的線程可以直接執行return了, 而不需要再等同步。

使用方式二來提高效率:

死鎖問題:

lock鎖:

https://blog.csdn.net/zc19921215/article/details/84780335

線程間的通信:wait和notify

兩個線程交替打印:調用wait會釋放鎖。sleep不會。

notify和wait都是this在調,至於notify怎麼知道喚醒哪一個,得自己去搜索這個問題。

這個老師從線程開始,就極少講底層原理,導致積累了一些問題。

面試題:這裏依舊存在原理問題,使用問題關鍵字mark一下:

Jdk5 新增創建線程方式:

一,實現Callable接口:

 

方式二:使用線程池。

注意ExecutorService 接口的ThreadPoolExecutor是線程池,Executors 是工具,提供靜態方法來建對象,

Java常用類:

String:

注意string的不可變性,是指內容不可變,但是指向內容的引用是可變的。還有常量池在方法區

如果有方法或者操作看起來是改變了原來的字符串內容,那實際上肯定是新開闢了一個字符創,或再修改了引用。

注意使用new 創建String 和 字面量創建的區別:new 在棧裏面新建了一個String 對象,不過對象裏的value成員,並沒實際存值,而是存了一個常量池中字符串的地址。

問題:字面量創建和new對象創建的實現原理的區別是什麼,字面量創建的還是對象,應該在堆中嗎,怎麼就能直接指向常量池。

只要字符串運算中,出現了變量,就會在堆中重新開闢一個String對象,並且將String 的值指向常量池的字符串。

intern()的作用。修改變量指向常量池。

下面代碼結果有大問題,不理解,得查。

char【】 在堆,所以可以根據地址修改值。

package cn.itcast.day01.demo2;

public class DemoScanner {

    public static void main(String[] args){
        String str="hello";
        System.out.println("定義時的str"+System.identityHashCode(str));
//        char[] ch = {'a'};
        final char[] ch = {'a'};
        ch[0] = 'c';
        final int num = 3;
//        num = 5;
        System.out.println("定義時的ch"+System.identityHashCode(ch));
//        change(str, ch);
        System.out.println(str); //hello

        /*
        如下,當final修飾對象,這裏爲數組的時候,這個對象ch的引用地址不能變,但裏面的值是可變的。
       就是說,對象指誰被限死,但值沒有。
         */
        System.out.println(ch[0] + " ch[0]"); // b ch[0],

        System.out.println("主程序中執行方法後的str"+System.identityHashCode(str));
        System.out.println("主程序中執行方法後的ch"+System.identityHashCode(ch));
    }

/*
 個人愚見:java機制因該是值傳遞,在方法中的形參,在調用時,實際會生成一個新的變量,這個變量保存的是值。
 如果傳入的是基本類型,則拷貝基本類型的值,如果是引用類型,則拷貝這個引用類型所指向的地址。
 不過按傳遞的是值還是地址來說,那底層應該傳遞的都是地址。讓自己也指向那個對象。
 因此如下代碼中,形參和實參並非同一個變量,但指向同一個地址。String 修改其實是把形參變量的指向,改成了常量池中world的
 並不影響實參的指向。System.identityHashCode是這個變量所指向的堆內對象的地址,無法獲取變量在棧的地址。
 */
    private static void change(String str, char ch[]) {
        System.out.println("參數中的str地址"+System.identityHashCode(str));
        System.out.println("參數中的ch地址"+System.identityHashCode(ch));
        str="world";
//        str=new String("world");
        ch[0] = 'b';
        System.out.println("方法中操作後的str"+System.identityHashCode(str));
        System.out.println("方法中操作後的str"+System.identityHashCode(ch));
    }
}

system.out.println能直接輸出char【】數組和String字符串的元素值,因爲底層做了判斷,但String【】數組和int【】數組輸出的是地址,因爲調用的是prinln object, obj這個參數,再往下調用的是他自己的toString方法,輸出的是類名+@+16進制地址。

因爲String 的private修飾,導致,這個類,內部自己不修改值,同時也不讓其他類來修改。

而且還不可繼承,final 修飾的數組還不可更改value引用

String常用方法:

compareTo方法會比較兩個字符創所有字符ASC碼的和,返回的是差,可正可負可0

 

String和其他類型的轉換:

可變對象:StringBuffer和StringBuilder

StringBuffer 在jdk1.0就出現,線程安全,方法都有synchronized修飾,缺點是因此效率低。因此在jdk1.5出了StringBuilder,線程不安全,所以在單線程項目中,用StringBuilder。兩者主要區別在此。

StringBuffer常用方法。StringBuilder方法名基本類似。index參數都是左閉右開

 

jdk8之前的時間api

sql包中的date類:

SimpleDateFormat類的使用;

Calendar類的使用:

jdk8中新日期的api:

localDate, LocalTime, LocalDateTime的使用:

指定日期後,返回一個新的對象。

Instant:瞬時。

Java比較器:Comparable接口

重寫compareTo只要返回大小就行,不用自己寫排序。

定製排序:Comparator接口

定製化,比如比較商品價格,價格相同的按名字開頭字符先後排序。

System類:

Math類

BigInteger類:

BigDecimal類

枚舉類和註解:

1.手動定義枚舉類,注意其中的方法,修飾符,類定義。因爲要常量,所以很多final

方式二,使用enum關鍵字定義枚舉類。以下一樣

註解:Annotation

自定義註解:

元數據是對現有數據進行修飾的數據,如下例,String和name

Java集合:

why 集合:

what 集合:

println能出值,是因爲上文提過,如果參數的obj類型,則調用他的toString,而ArrayList重寫過println

注意,String 和對象 使用contains的區別。區別在於,是否重寫了equals方法。

remove方法還是調用了對象的equals方法,所以對於繼承於object對象按值remove需要重寫equals方法。

 

集合元素的遍歷:Iterator接口。

foreach:

List接口:

ArrayList源碼分析:jdk7 and jdk8:

jdk7中,如果擴容1.5倍還不夠,那麼直接用添加完後的元素總個數擴容。

jdk1.8:直到第一次調用add方法,纔開始創建數組。

LinkedList源碼,和ArrayList一樣,線程不安全:

Vector源碼:

 擴容和arraylsit不同,擴容長度爲兩倍。其他除了線程安全,都一致。

List中的常用方法:

Set接口:

HashSet的無序性和不可重複性:

注意set在add對象的時候,會判斷,這個對象的equals方法和hashCode。具體以下細講。

 

擴容後,二次哈希值需要重新計算。

euqals和hashcode重寫:

String方法重寫過hashcode,導致兩個值一樣的String,hashcode也一樣。

因爲,在拉鍊裏一開始比較的還是hashcode,如果hashcode和equals不一致,會把同一個值,當成兩個看,。導致直接插入。

LinkedListSet:

還是用到了數組,和hashset的區別是,每個對象存進了雙向鏈表,來記錄添加的順序。

TreeSet

向TreeSet添加類對象的時候,,類對象需要實現Comparable接口 並重寫compareTo()。TreeSet因此可以實現自然排序。

因爲底層是紅黑樹,所以不支持相同大小的數據。問題:紅黑樹,自己再找資料。

定製化排序:

面試題:

結果爲:remove沒刪掉,因爲改爲cc後,p1的哈希值產生了變化,add成功,因爲該hashcode計算出的位置沒值,

p1還是在第一次計算出的哈希位置上,第二次add成功,因爲哈希值相同比equals,aa和cc不同,插在後面鏈表上。

 

Map接口:

HashMap底層原理:

1.7擴容步驟:不是簡單的複製。以下摘自網上:

擴容源碼,前提是size大於等於閾值,並且要插入的這個entry產生衝突。即增加鏈表長度才擴容。否則直接插入在空位置。

1:方法中會首先判斷擴容前的舊數組容量是否已經達到最大即2^30了,如果達到了就修改閾值爲int的最大取值,這樣以後就不會擴容了。
2:初始化一個新Entry數組;

3:計算rehash,判斷擴容的時候是否需要重新計算hash值,將此值作爲參數傳入到transfer方法中;這個是和jdk1.8不同的一點,待會看1.8
4:通過transfer方法將舊數組中的元素複製到新數組,在這個方法中進行了包括釋放就的Entry中的對象引用,該過程中如果需要重新計算hash值就重新計算,然後根據indexfor()方法計算索引值。而索引值的計算方法爲{ return h & (length-1) ;}即hashcode計算出的hash值和數組長度進行與運算。。。。。jdk1.7中重新插入到新數組的元素,如果原來一條鏈上的元素又被分配到同一條鏈上那麼他們的順序會發生倒置這個和1.8也不一樣

jdk1.8

二:jdk1.8的優化
1:resize方法源碼融入了紅黑樹,本質和1.7區別不大,但是在插入元素的時候循環舊數組內的元素時會進行判斷,如果是普通節點直接和1.7一樣放置;如果是紅黑樹結構,就調用split()方法進行拆分放置;如果是鏈表,則採用下面2中要分析的方式!!!!
2:在經過一次容量判斷是否大於最大值之後在進行擴容,使用的擴容方法是2次冪的擴展,**所以元素要麼在原來的位置,要麼在原位置在移動2次冪的位置,**如下圖:圖(a)表示擴容前的key1和key2兩種key確定索引位置的示例,圖(b)表示擴容後key1和key2兩種key確定索引位置的示例,其中hash1是key1對應的哈希與高位運算結果。
 

總結一下,jdk8的擴容,沒有重新計算hashcode,一個鏈表上順序不會倒置,隨機造成把衝突的元素均勻散列。

jdk8的底層代碼:

懶漢式創建。鏈表長度超過閾值,也會resize或者轉爲紅黑樹,轉不轉,看數組長度。長度小於64則resize,大於則轉爲紅黑樹。

擴容的目的,還是爲了限制鏈表長度,提高效率。

Map中的常用方法:

TreeMap:

自定義排序。

Properties:多用於讀取配置文件

 

Collections工具類:可以操作map

泛型:

泛型可以保證容器中添加的對象的類型一致。泛型不能是基本數據類型。

 

自定義泛型類和泛型接口:

泛型方法:

泛型方法就可以申明static,因爲他和泛型類無關。

通配符:爲了增加代碼的複用性:

?extends ,?<=

? super, ? >=

IO流:

File類:

IO流的原理和流的分類 

深色的比較重要。

讀完記得關閉文件流

FileWriter:

字符流不能處理圖片文件,只能用字節流。

字節流不一定能讀取文本文件。在控制檯輸出中文會亂碼。因爲一箇中文佔三個字節。但,可以實現文本文件的讀寫。

FileInputStrean 和FIleOutputStream

緩衝流字節型:緩衝流提高文本讀寫效率

例如,在操作系統中,爲了改善 CPU 與 I/O 設備速度不匹配的矛盾,設置了緩衝區,程序輸出的數據先送到緩衝區暫存,然後由I/O 設備慢慢地處理。這時,CPU不必等待,可以繼續執行程序。實現了CPU與I/O設備之間的並行工作。同時也不用轉換字符(節)數組

BufferedRead和BufferedWrite類似。

 

轉換流:

其他一些流:

打印流:

數據流:

對象流:

類類型的序列化。需要類實現兩個接口之一:

隨機存取文件流

網絡編程:

TCP看個邏輯過程。

UDP:

接收端。

URL編程:

反射:

 

 

這老師的反射講得不行。需另找教程學習。

創建運行時類的對象。注意getinstance方法。因爲反射,所以可以把造對象的工作交給IOC容器了。

反射的動態性:

反射調用指定方法:

調用指定構造器:用的比較少。class。newinstance用的比較多。

動態代理:

靜態代理舉例:

動態代理舉例:

jdk新特性:

lambda表達式:

函數式接口:lambda表達式的使用和函數式接口綁定,

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章