Java完全自学手册,从外包到大厂,再到万粉博主都靠它
推薦專欄:《技術專家修煉》
點擊領取
- 15張學習路線導圖
- 3G學習資料
- 100本計算機書籍
哈嘍,大家好,我是一條~
Java學習如逆水行舟,不進則退。一條一路自學過來,踩過很多坑,吃過很多苦。
現在回想起來,當初要是能有一個完整的學習路線讓我按圖索驥就好了。
思來想去,決定總結一份學習路線來幫助正在路上或者準備出發的Java新手。
完整路線
該路線圖右側為主路線,需循序漸進,步步為營;左側為輔助路線,需貫穿始終,熟練掌握。
建議做好時間規劃,不斷的提高自己的學習效率,學習過程中盡量把手機調至靜音給自己一個安靜的學習環境和氛圍。
同時,巧婦難為無米之炊,一條學習新知識的一般方法為先看視頻學基礎,再看書學原理,最后看博客查缺補漏,沉淀消化。
最后,說一下這么多年學習java的一些心得,希望能幫助到大家。
文章目錄
- java基礎
- 基本數據類型
- 引用數據類型
- 訪問修飾符
- static關鍵字
- final關鍵字
- 面向對象三大特性
- JavaWeb
- HTTP網絡請求方式
- GET和POST
- 冪等性
- 如何保證冪等性
- 常見的網絡狀態碼
- 轉發和重定向
- Servlet
- Servlet的生命周期
- session、cookie、token
- MVC與三層架構
- ArrayList
- LinkedList
- 和ArrayList對比一下
- JVM
- 多線程
- 并行和并發
- 線程和進程
- 守護線程
- 創建線程4種方式
- synchronized 底層實現
- synchronized 和 volatile 的區別
- synchronized 和 Lock 區別
- synchronized 和 ReentrantLock 區別
- 設計模式
- 定義
- 結構圖
- 代碼演示
- 目錄結構
- 開發場景
- 代碼實現
- 接口和抽象類
- 接口和抽象類有什么區別?
- 什么時候用接口,什么時候用抽象類?
- 應用場景
- 總結
- SSM框架
- Redis
- Zookeeper
- Kafka
- 學習心得
java基礎
學習任何語言,都是先從他的基本語法開始,如果你有C語言的基礎,會容易許多,沒有也不用現學。
基本數據類型
Java 語言提供了 8 種基本類型,大致分為 4 類(8位=1字節)
- 整數型
- byte - 1字節
- short - 2字節
- int - 4字節
- long - 8字節,賦值時一般在數字后加上 l 或 L
- 浮點型
- float - 4字節,直接賦值時必須在數字后加上 f 或 F
- double - 8字節,賦值時一般在數字后加 d 或 D
- 字符型
- char - 2字節,存儲 Unicode 碼,用單引號賦值
- 布爾型
- boolean - 1字節,只有 true 和 false 兩個取值,一個字節就夠了
引用數據類型
簡單來說,所有的非基本數據類型都是引用數據類型,除了基本數據類型對應的引用類型外,類、 接口類型、 數組類型、 枚舉類型、 注解類型、 字符串型都屬于引用類型。
主要有以下區別:
1、存儲位置
- 基本變量類型在方法中定義的非全局基本數據類型變量的具體內容是存儲在棧中的
- 引用數據類型變量其具體內容都是存放在堆中的,而棧中存放的是其具體內容所在內存的地址
2、傳遞方式
- 基本數據類型是按值傳遞
- 引用數據類型是按引用傳遞
訪問修飾符
訪問修飾符就是限制變量的訪問權限的。
比如你有個“賺錢”的方法,誰都不想給用,那就把方法設成private(私有);
后來你有了老婆孩子,你想讓他們也會賺錢,就得設置成default(同一個包);
后來你又有了第二個孩子,但你發現他不會賺錢的方法,為啥呢?因為你被綠了(default不支持不同包的子類);
可為了大局,你還是選擇接受這個孩子,悄悄把方法設置成了proteced(保護子類,即使不同包);
后來你老了,明白了開源才是共贏,就設置成了public(公有的);
不知道你聽懂了嗎,估計看到被那啥了就不想看了吧,沒關系,看圖(也是綠的)
static關鍵字
主要意義:
我日常調用方法都是對象.方法,static的主要意義就是可以創建獨立于具體對象的域變量或者方法。也就是實現即使沒有創建對象,也能使用屬性和調用方法!
另一個比較關鍵的作用就是 用來形成靜態代碼塊以優化程序性能。static塊可以置于類中的任何地方,可以有多個。在類初次被加載的時候,會按照static塊的順序來執行每個static塊,并且只會執行一次,可以用來優化程序性能
通俗理解:
static是一個可以讓你升級的關鍵字,被static修飾,你就不再是你了。
final關鍵字
final翻譯成中文是“不可更改的,最終的”,顧名思義,他的功能就是不能再修改,不能再繼承。我們常見的String類就是被final修飾的。
將類、方法、變量聲明為final能夠提高性能,這樣JVM就有機會進行估計,然后優化。
按照Java代碼慣例,final變量就是常量,而且通常常量名要大寫:
- final關鍵字可以用于成員變量、本地變量、方法以及類。
- final成員變量必須在聲明的時候初始化或者在構造器中初始化,否則就會報編譯錯誤。
- 不能夠對final變量再次賦值。
- final方法不能被重寫。
- final類不能被繼承。
- 接口中聲明的所有變量本身是final的。
- final和abstract這兩個關鍵字是反相關的,final類就不可能是abstract的。
面向對象三大特性
封裝
1.什么是封裝
封裝又叫隱藏實現。就是只公開代碼單元的對外接口,而隱藏其具體實現。
其實生活中處處都是封裝,手機,電腦,電視這些都是封裝。你只需要知道如何去操作他們,并不需要知道他們里面是怎么構造的,怎么實現這個功能的。
2.如何實現封裝
在程序設計里,封裝往往是通過訪問控制實現的。也就是剛才提到的訪問修飾符。
3.封裝的意義
封裝提高了代碼的安全性,使代碼的修改變的更加容易,代碼以一個個獨立的單元存在,高內聚,低耦合。
好比只要你手機的充電接口不變,無論以后手機怎么更新,你依然可以用同樣的數據線充電或者與其他設備連接。
封裝的設計使使整個軟件開發復雜度大大降低。我只需要使用別人的類,而不必關心其內部邏輯是如何實現的。我能很容易學會使用別人寫好的代碼,這就讓軟件協同開發的難度大大降低。
封裝還避免了命名沖突的問題。
好比你家里有各種各樣的遙控器,但比還是直到哪個是電視的,哪個是空調的。因為一個屬于電視類一個屬于空調類。不同的類中可以有相同名稱的方法和屬性,但不會混淆。
繼承
繼承的主要思想就是將子類的對象作為父類的對象來使用。比如王者榮耀的英雄作為父類,后裔作為子類。后裔有所有英雄共有的屬性,同時也有自己獨特的技能。
多態
多態的定義:
指允許不同類的對象對同一消息做出響應。即同一消息可以根據發送對象的不同而采用多種不同的行為方式。(發送消息就是函數調用)
簡單來說,同樣調用攻擊這個方法,后裔的普攻和亞瑟的普攻是不一樣的。
多態的條件:
- 要有繼承
- 要有重寫
- 父類引用指向子類對象
多態的好處:
多態對已存在代碼具有可替換性。
多態對代碼具有可擴充性。
它在應用中體現了靈活多樣的操作,提高了使用效率。
多態簡化對應用軟件的代碼編寫和修改過程,尤其在處理大量對象的運算和操作時,這個特點尤為突出和重要。
Java中多態的實現方式:
- 接口實現
- 繼承父類進行方法重寫
- 同一個類中進行方法重載
完整講解
Java基礎完整講解
入門練習案例
入門練習100例
JavaWeb
JavaWeb是用Java技術來解決相關web互聯網領域的技術棧。Web就是網頁,分為靜態和動態。涉及 的知識點主要包括jsp,servlet,tomcat,http,MVC等知識。
本章難度不高,但也不可忽視。其中前端基礎不需花過多時間,重點放在Tomcat上,會陪伴你整個Java生涯。
HTTP網絡請求方式
- GET:最常用的方式,用來向服務器請求數據,沒有請求體,請求參數放在URL后面。
- POST:用于向表單提交數據,傳送的數據放在請求體中。
- PUT:用來向服務器上傳文件,一般對應修改操作,POST用于向服務器發送數據,PUT用于向服務器儲存數據。沒有驗證機制,任何人都可以操作,存在安全問題。具有冪等性。
- DELETE:用于刪除服務器上的文件,具有冪等性。同樣存在安全問題。
- HEAD:用HEAD進行請求服務器時,服務器只返回響應頭,不返回響應體。與GET一樣沒有請求體,常用于檢查請求的URL是否有效。
- PATCH:對資源進行部分修改。與PUT區別在于,PUT是修改所有資源,替代它,而PATCH只是修改部分資源。
- TRACE:用來查看一個請求,經過網關,代理到達服務器,最后請求的變換。因安全問題被禁用。
- OPTIONS:當客戶端不清楚對資源操作的方法,可以使用這個,具有冪等性。
GET和POST
冪等性
是否具有冪等性也是一個http請求的重要關注點。
冪等性:指的是同樣的請求不管執行多少次,效果都是一樣,服務器狀態也是一樣的。具有冪等性的請求方法沒有副作用。(統計用途除外)
如何保證冪等性
假設這樣一個場景:有時我們在填寫某些form表單時,保存按鈕不小心快速點了兩次,表中竟然產生了兩條重復的數據,只是id不一樣。
這是一個比較常見的冪等性問題,在高并發場景下會變得更加復雜,那怎么保證接口的冪等性呢?
1.insert前select
插入數據前先根據某一字段查詢一下數據庫,如果已經存在就修改,不存在再插入。
2.加鎖
加鎖可解決一切問題,但也要考慮并發性。
主要包括悲觀鎖,樂觀鎖,分布式鎖。
悲觀鎖的并發性較低,更適合使用在防止數據重復的場景,注意冪等性不光是防止重復還需要結果相同。
樂觀鎖可以很高的提升性能,也就是常說的版本號。
分布式鎖應用在高并發場景,主要用redis來實現。
3.唯一索引
通過數據庫的唯一索引來保證結果的一致性和數據的不重復。
4.Token
兩次請求,第一請求拿到token,第二次帶著token去完成業務請求。
常見的網絡狀態碼
網絡狀態碼共三位數字組成,根據第一個數字可分為以下幾個系列:
1xx(信息性狀態碼)
代表請求已被接受,需要繼續處理。
包括:100、101、102
這一系列的在實際開發中基本不會遇到,可以略過。
2xx(成功狀態碼)
表示成功處理了請求的狀態代碼。
200:請求成功,表明服務器成功了處理請求。
202:服務器已接受請求,但尚未處理。
204:服務器成功處理了請求,但沒有返回任何內容。
206:服務器成功處理了部分 GET 請求。
3xx(重定向狀態碼)
300:針對請求,服務器可執行多種操作。
301:永久重定向
302:臨時性重定向
303:303與302狀態碼有著相同的功能,但303狀態碼明確表示客戶端應當采用GET方法獲取資源。
301和302的區別?
301比較常用的場景是使用域名跳轉。比如,我們訪問 http://www.baidu.com 會跳轉到https://www.baidu.com,發送請求之后,就會返回301狀態碼,然后返回一個location,提示新的地址,瀏覽器就會拿著這個新的地址去訪問。
302用來做臨時跳轉比如未登陸的用戶訪問用戶中心重定向到登錄頁面。
4xx(客戶端錯誤狀態碼)
400:該狀態碼表示請求報文中存在語法錯誤。但瀏覽器會像200 OK一樣對待該狀態碼。
401:表示發送的請求需要有通過HTTP認證的認證信息。比如token失效就會出現這個問題。
403:被拒絕,表明對請求資源的訪問被服務器拒絕了。
404:找不到,表明服務器上無法找到請求的資源,也可能是拒絕請求但不想說明理由。
5xx(服務器錯誤狀態碼)
500:服務器本身發生錯誤,可能是Web應用存在的bug或某些臨時的故障。
502:該狀態碼表明服務器暫時處于超負載或正在進行停機維護,現在無法處理請求。
??有時候返回的狀態碼響應是錯誤的,比如Web應用程序內部發生錯誤,狀態碼依然返回200
轉發和重定向
上面提到了重定向,那你知道什么是轉發嗎?
1.轉發
A找B借錢,B沒有錢,B去問C,C有錢,C把錢借給A的過程。
客戶瀏覽器發送http請求,web服務器接受此請求,調用內部的一個方法在容器內部完成請求處理和轉發動作,將目標資源發送給客戶。
整個轉發一個請求,一個響應,地址欄不會發生變化,不能跨域訪問。
2.重定向
A找B借錢,B沒有錢,B讓A去找C,A又和C借錢,C有錢,C把錢借給A的過程。
客戶瀏覽器發送http請求,web服務器接受后發送302狀態碼響應及對應新的location給客戶瀏覽器,客戶瀏覽器發現是302響應,則自動再發送一個新的http請求,請求url是新的location地址,服務器根據此請求尋找資源并發送給客戶。
兩個請求,兩個響應,可以跨域。
Servlet
servlet是一個比較抽獎的概念,也是web部分的核心組件,大家回答這個問題一定要加入自己的理解,不要背定義。
servlet其實就是一個java程序,他主要是用來解決動態頁面的問題。
之前都是瀏覽器像服務器請求資源,服務器(tomcat)返回頁面,但用戶多了之后,每個用戶希望帶到不用的資源。這時就該servlet上場表演了。
servlet存在于tomcat之中,用來網絡請求與響應,但他的重心更在于業務處理,我們訪問京東和淘寶的返回的商品是不一樣的,就需要程序員去編寫,目前MVC三層架構,我們都是在service層處理業務,但這其實是從servlet中抽取出來的。
看一下servlet處理請求的過程:
Servlet的生命周期
Servlet生命周期分為三個階段:
- 初始化階段 調用init()方法
- 響應客戶請求階段 調用service()方法-àdoGet/doPost()
- 終止階段 調用destroy()方法
session、cookie、token
首先我們要明白HTTP是一種無狀態協議,怎么理解呢?很簡單
夏洛:大爺,樓上322住的是馬冬梅家吧? 大爺:馬冬什么? 夏洛:馬冬梅。 大爺:什么冬梅啊? 夏洛:馬冬梅啊。 大爺:馬什么梅? 夏洛:行,大爺你先涼快著吧。這段對話都熟悉吧,HTTP就是那個大爺,那如果我們就直接把“大爺”放給用戶,用戶不用干別的了,就不停的登錄就行了。
既然“大爺不靠譜”,我們找“大娘”去吧。
哈哈哈,開個玩笑,言歸正傳。
為了解決用戶頻繁登錄的問題,在服務端和客戶端共同維護一個狀態——會話,就是所謂session,我們根據會話id判斷是否是同一用戶,這樣用戶就開心了。
但是服務器可不開心了,因為用戶越來越多,都要把session存在服務器,這對服務器來說是一個巨大的開銷,這是服務器就找來了自己的兄弟幫他分擔(集群部署,負載均衡)。
但是問題依然存在,如果兄弟掛了怎么辦,兄弟們之間的數據怎么同步,用戶1把session存放在機器A上,下次訪問時負載均衡到了機器B,完了,找不到,用戶又要罵娘。
這時有人思考,為什么一定要服務端保存呢,讓客戶端自己保存不就好了,所以就誕生了cookie,下一次請求時客戶段把cookie發送給服務器,說我已經登錄了。
但是空口無憑,服務器怎么知道哪個cookie是我發過去的呢?如何驗證成了新的問題。
有人想到了一個辦法,用加密令牌,也就是token,服務器發給客戶端一個令牌,令牌保存加密后id和密鑰,下一次請求時通過headers傳給服務端,由于密鑰別人不知道,只有服務端知道,就實現了驗證,且別人無法偽造。
MVC與三層架構
三層架構與MVC的目標一致:都是為了解耦和、提高代碼復用。MVC是一種設計模式,而三層架構是一種軟件架構。
MVC
Model 模型
模型負責各個功能的實現(如登錄、增加、刪除功能),用JavaBean實現。
View 視圖
用戶看到的頁面和與用戶的交互。包含各種表單。 實現視圖用到的技術有html/css/jsp/js等前端技術。
常用的web 容器和開發工具
Controller 控制器
控制器負責將視圖與模型一一對應起來。相當于一個模型分發器。接收請求,并將該請求跳轉(轉發,重定向)到模型進行處理。模型處理完畢后,再通過控制器,返回給視圖中的請求處。
三層架構
表現層(UI)(web層)、業務邏輯層(BLL)(service層)、數據訪問層(DAL)(dao層) ,再加上實體類庫(Model)
- 實體類庫(Model),在Java中,往往將其稱為Entity實體類。數據庫中用于存放數據,而我們通常選擇會用一個專門的類來抽象出數據表的結構,類的屬性就一對一的對應這表的屬性。一般來說,Model實體類庫層需要被DAL層,BIL層和UI層引用。
- 數據訪問層(DAL),主要是存放對數據類的訪問,即對數據庫的添加、刪除、修改、更新等基本操作,DAL就是根據業務需求,構造SQL語句,構造參數,調用幫助類,獲取結果,DAL層被BIL層調用
- 業務邏輯層(BLL),BLL層好比是橋梁,將UI表示層與DAL數據訪問層之間聯系起來。所要負責的,就是處理涉及業務邏輯相關的問題,比如在調用訪問數據庫之前,先處理數據、判斷數據。
完整講解
JavaWeb完整講解
集合
工欲善其事必先利其器,集合就是我們的器。
ArrayList
底層實現
由什么組成,我說了不算,看源碼。怎么看呢?
List<Object> list = new ArrayList<>();新建一個ArrayList,按住ctrl或command用鼠標點擊。
/*** The array buffer into which the elements of the ArrayList are stored.* The capacity of the ArrayList is the length of this array buffer. Any* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA* will be expanded to DEFAULT_CAPACITY when the first element is added.* 翻譯* 數組緩沖區,ArrayList的元素被存儲在其中。ArrayList的容量是這個數組緩沖區的長度。* 任何空的ArrayList,如果elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA,* 當第一個元素被添加時,將被擴展到DEFAULT_CAPACITY。*/transient Object[] elementData;毋庸置疑,底層由數組組成,那數組的特點就是ArrayList的特點。
- 由于數組以一塊連續的內存空間,每一個元素都有對應的下標,查詢時間復雜度為O(1)。好比你去住酒店,每個房間都挨著,房門都寫著房間號。你想找哪一間房是不是很容易。
- 相對的,一塊連續的內存空間你想打破他就沒那么容易,牽一發而動全身,所以新增和刪除的時間復雜度為O(n),想像你在做excel表格的時候,想增加一列,后面的列是不是都要跟著移動。
- 元素有序,可重復。可用在大多數的場景,這個就不需要過多解釋了。
擴容
我們知道數組是容量不可變的數據結構,隨著元素不斷增加,必然要擴容。
所以擴容機制也是集合中非常容易愛問的問題,在源碼中都可以一探究竟。
1.初始化容量為10,也可以指定容量創建。
/*** Default initial capacity.* 定義初始化容量*/private static final int DEFAULT_CAPACITY = 10;2.數組進行擴容時,是將舊數據拷貝到新的數組中,新數組容量是原容量的1.5倍。(這里用位運算是為了提高運算速度)
private void grow(int minCapacity) {int newCapacity = oldCapacity + (oldCapacity >> 1); }3.擴容代價是很高得,因此再實際使用時,我們因該避免數組容量得擴張。盡可能避免數據容量得擴張。盡可能,就至指定容量,避免數組擴容的發生。
為什么擴容是1.5倍?
- 如果大于1.5,也就是每次擴容很多倍,但其實我就差一個元素的空間,造成了空間浪費。
- 如果小于1.5,擴容的意義就不大了,就會帶來頻繁擴容的問題。
所以,1.5是均衡了空間占用和擴容次數考慮的。
線程安全問題
怎么看線程安全?說實話我以前都不知道,看網上說安全就安全,說不安全就不安全。
其實都在源碼里。找到增加元素的方法,看看有沒有加鎖就知道了。
public void add(int index, E element) {rangeCheckForAdd(index);ensureCapacityInternal(size + 1); // Increments modCount!!System.arraycopy(elementData, index, elementData, index + 1,size - index);elementData[index] = element;size++;}沒有加鎖,所以線程不安全
在多線程的情況下,插入數據的時可能會造成數據丟失,一個線程在遍歷,另一個線程修改,會報ConcurrentModificationException(并發修改異常)錯誤.
多線程下使用怎么保證線程安全?
保證線程安全的思路很簡單就是加鎖,但是你可沒辦法修改源碼去加個鎖,但是你想想編寫java的大佬會想不到線程安全問題?
早就給你準備了線程安全的類。
1.Vector
Vector是一個線程安全的List類,通過對所有操作都加上synchronized關鍵字實現。
找到add方法,可以看到被synchronized關鍵字修飾,也就是加鎖,但synchronized是重度鎖,并發性太低,所以實際一般不使用,隨著java版本的更新,慢慢廢棄。
public void add(E e) {int i = cursor;synchronized (Vector.this) {checkForComodification();Vector.this.add(i, e);expectedModCount = modCount;}cursor = i + 1;lastRet = -1;}2.Collections
注意是Collections而不是Collection。
Collections位于java.util包下,是集合類的工具類,提供了很多操作集合類的方法。其中Collections.synchronizedList(list)可以提供一個線程安全的List。
對于Map、Set也有對應的方法
3.CopyOnWrite(寫時復制)
寫時復制,簡稱COW,是計算機程序設計領域中的一種通用優化策略。
當有多人同時訪問同一資源時,他們會共同獲取指向相同的資源的指針,供訪問者進行讀操作。
當某個調用者修改資源內容時,系統會真正復制一份副本給該調用者,而其他調用者所見到的最初的資源仍然保持不變。修改完成后,再把新的數據寫回去。
通俗易懂的講,假設現在有一份班級名單,但有幾個同學還沒有填好,這時老師把文件通過微信發送過去讓同學們填寫(復制一份),但不需要修改的同學此時查看的還是舊的名單,直到有同學修改好發給老師,老師用新的名單替換舊的名單,全班同學才能查看新的名單。
共享讀,分開寫。讀寫分離,寫時復制。
在java中,通過CopyOnWriteArrayList、CopyOnWriteArraySet容器實現了 COW 思想。
平時查詢的時候,都不需要加鎖,隨便訪問,只有在更新的時候,才會從原來的數據復制一個副本出來,然后修改這個副本,最后把原數據替換成當前的副本。修改操作的同時,讀操作不會被阻塞,而是繼續讀取舊的數據。
/** The lock protecting all mutators */final transient ReentrantLock lock = new ReentrantLock();/** The array, accessed only via getArray/setArray. */private transient volatile Object[] array;源碼里用到了ReentrantLock鎖和volatile關鍵字,會在《資深程序員修煉》專欄中做全面深度講解。
LinkedList
LinkedList和ArrayList同屬于List集合。其共同特點可歸納為:
存儲單列數據的集合,存儲的數據是有序并且是可以重復的。
但兩者也有不同,往下看吧
底層實現
LinkedList類的底層實現的數據結構是一個雙向鏈表。同時還實現了Deque接口,所以會有些隊列的特性,會在下面講。
class LinkedList<E>extends AbstractSequentialList<E>implements List<E>, Deque<E>, Cloneable, java.io.Serializable先簡單說一下鏈表這種數據結構,與數組相反,鏈表是一種物理存儲單元上非連續、非順序的存儲結構,一個最簡單的鏈表(單鏈表)有節點Node和數值value組成。通俗的講,就像串在一起的小魚干,中間用線連著。
transient Node<E> first;transient Node<E> last;鏈表中保存著對最后一個節點的引用,這就是雙端鏈表
在單鏈表的結點中增加一個指向其前驅的pre指針就是雙向鏈表,一種犧牲空間換時間的做法。
雙端鏈表不同于雙向鏈表,切記!
關于鏈表更詳細代碼級講解會放《糊涂算法》專欄更新。敬請期待!
簡單了解過后分析一下鏈表的特點:
- 查詢速度慢,因為是非連續空間,沒有下標。想像你需要在一份名單上找到你的名字,沒有序號,你只能從頭開始一個一個的看。
- 刪改速度快,因為非連續,也就沒有那么多約束。想像從一根項鏈上扣下來一塊,只需要改變引用就可以了,不會牽一發而動全身。
- 元素有序,可重復。
如何解決查詢慢的問題?
如果我查找的元素在尾部,則需要遍歷整個鏈表,所以有了雙端鏈表。
即使不在尾部,我如果只能一個方向遍歷,也很麻煩,所以有了雙向隊列,犧牲空間換時間。
那么空間可不可以再犧牲一點?
可以,就是跳躍鏈表,簡稱「跳表」。
通過建立多級索引來加快查詢速度。
線程安全問題
老辦法,看看add()方法。分為「頭插法」和「尾插法」。
/*** Inserts the specified element at the beginning of this list.** @param e the element to add*/public void addFirst(E e) {linkFirst(e);}/*** Appends the specified element to the end of this list.** <p>This method is equivalent to {@link #add}.** @param e the element to add*/public void addLast(E e) {linkLast(e);}都沒加鎖,百分之一百的不安全。
如何解決線程不安全問題
1.ConcurrentLinkedQueue
一個新的類,位于java.util.concurrent(juc)包下。實現了Queue接口。
class ConcurrentLinkedQueue<E> extends AbstractQueue<E>implements Queue<E>, java.io.Serializable{}使用violate關鍵字實現加鎖。
private transient volatile Node<E> head;private transient volatile Node<E> tail;1.Collections
和ArrayList一樣,使用Collections.synchronizedList()。
Map:存儲雙列數據的集合,通過鍵值對存儲數據,存儲 的數據是無序的,Key值不能重復,value值可以重復
和ArrayList對比一下
共同點:有序,可重復。線程不安全。
不同點:底層架構,查詢和刪改的速度
完整講解
集合完整講解
JVM
重點來了,Java程序員一定要深入研究的內容
完整講解
JVM完整講解
多線程
理解多線程,才能更好的理解框架源碼,進行高并發的架構設計,重中之重。
并行和并發
并行:多個任務在同一個 CPU 核上,按細分的時間片輪流(交替)執行,從邏輯上來看那些任務是同時執行。
并發:多個處理器或多核處理器同時處理多個任務。
舉例:
并發 = 兩個隊列和一臺咖啡機。
并行 = 兩個隊列和兩臺咖啡機。
線程和進程
一個程序下至少有一個進程,一個進程下至少有一個線程,一個進程下也可以有多個線程來增加程序的執行速度。
守護線程
守護線程是運行在后臺的一種特殊進程。它獨立于控制終端并且周期性地執行某種任務或等待處理某些發生的事件。在 Java 中垃圾回收線程就是特殊的守護線程。
創建線程4種方式
-
繼承 Thread 重新 run 方法;
-
實現 Runnable 接口;
-
實現 Callable 接口。
-
線程池
synchronized 底層實現
synchronized 是由一對 monitorenter/monitorexit 指令實現的,monitor 對象是同步的基本實現單元。
在 Java 6 之前,monitor 的實現完全是依靠操作系統內部的互斥鎖,因為需要進行用戶態到內核態的切換,所以同步操作是一個無差別的重量級操作,性能也很低。
但在 Java 6 的時候,Java 虛擬機 對此進行了大刀闊斧地改進,提供了三種不同的 monitor 實現,也就是常說的三種不同的鎖:偏向鎖(Biased Locking)、輕量級鎖和重量級鎖,大大改進了其性能。
synchronized 和 volatile 的區別
volatile 是變量修飾符;synchronized 是修飾類、方法、代碼段。
volatile 僅能實現變量的修改可見性,不能保證原子性;而 synchronized 則可以保證變量的修改可見性和原子性。
volatile 不會造成線程的阻塞;synchronized 可能會造成線程的阻塞。
synchronized 和 Lock 區別
synchronized 可以給類、方法、代碼塊加鎖;而 lock 只能給代碼塊加鎖。
synchronized 不需要手動獲取鎖和釋放鎖,使用簡單,發生異常會自動釋放鎖,不會造成死鎖。
lock 需要自己加鎖和釋放鎖,如果使用不當沒有 unLock()去釋放鎖就會造成死鎖。
通過 Lock 可以知道有沒有成功獲取鎖,而 synchronized 卻無法辦到。
synchronized 和 ReentrantLock 區別
synchronized 早期的實現比較低效,對比 ReentrantLock,大多數場景性能都相差較大,但是在 Java 6 中對 synchronized 進行了非常多的改進。
主要區別如下:
ReentrantLock 使用起來比較靈活,但是必須有釋放鎖的配合動作;
ReentrantLock 必須手動獲取與釋放鎖,而 synchronized 不需要手動釋放和開啟鎖;
ReentrantLock 只適用于代碼塊鎖,而 synchronized 可用于修飾方法、代碼塊等。
volatile 標記的變量不會被編譯器優化;synchronized 標記的變量可以被編譯器優化。
設計模式
好多人覺得設計模式模式,那是因為你學的還不夠深入,還沒有看過源碼,所以我特意將設計模式往前放了。
今天我們一塊看一下簡單工廠模式,其實他不屬于23種設計模式,但為了更好的理解后面的工廠方法和抽象工廠,我們還是需要先了解一下。
定義
官方定義
定義一個工廠類,他可以根據參數的不同返回不同類的實例,被創建的實例通常都具有共同的父類。
通俗解讀
我們不必關心對象的創建細節,只需要根據不同參數獲取不同產品即可。
難點:寫好我們的工廠。
結構圖
代碼演示
本文源碼:簡單工廠模式源碼 提取碼: qr5h
目錄結構
建議跟著一條學設計模式的小伙伴都建一個maven工程,并安裝lombok依賴和插件。
并建立如下包目錄,便于歸納整理。
pom如下
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.10</version></dependency>開發場景
汽車制造工廠,既可以生產跑車,也可以生產SUV,未來還會生產新能源汽車。
代碼實現
1.創建抽象產品Car
public abstract class Car {public String color;abstract void run(); }2.創建具體產品
SuvCar
public class SuvCar extends Car{public SuvCar(){this.color="green";}@Overridepublic void run() {System.out.println("SuvCar running---------");} }SportsCar
public class SportsCar extends Car{public SportsCar(){this.color="red";}@Overridepublic void run() {System.out.println("SportsCar running-------");} }3.創建靜態工廠
在簡單工廠模式中用于被創建實例的方法通常為靜態方法,因此簡單工廠模式又被成為靜態工廠方法(Static Factory Method)。
/*** 簡單/靜態工廠,少數產品*/ public class CarFactory {public static Car getCar(String type){if (type.equals("sport")){return new SportsCar();}else if (type.equals("suv")){return new SuvCar();}else {return null;}} }4.編寫測試類
public class MainTest {public static void main(String[] args) {SportsCar sport = (SportsCar) CarFactory.getCar("sport");sport.run();System.out.println(sport.color);} }5.輸出結果
接口和抽象類
補充一個知識:
接口和抽象類有什么區別?
什么時候用接口,什么時候用抽象類?
接口和抽象類有什么區別?
- 接口是針對方法的整合,抽象類是針對子類的整合。
- 人有男人,女人,人是抽象類。人可以吃東西,狗也可以吃東西,吃東西這個動作是接口。
- 接口可以多繼承,抽象類不行。
- 接口中基本數據類型為static, 而抽類象不是。
- 抽象類有構造器,方法可以實現,除了不能被實例化,和普通類沒有區別,接口不是。
什么時候用接口,什么時候用抽象類?
- 當你關注一個事物的本質的時候,用抽象類;當你關注一個操作的時候,用接口。
- 再簡單點說,有屬性定義的時候,用抽象類,只有方法的時候,用接口。
應用場景
-
工廠類負責創建對的對象比較少,因為不會造成工廠方法中的業務邏輯過于復雜
-
客戶端只知道傳入工廠類的參數,對如何創建對象不關心
-
由于簡單工廠很容易違反高內聚責任分配原則,因此一般只在很簡單的情況下應用。
總結
優點
- 通過工廠類,無需關注生產的細節,只需要傳遞對應參數即可。
- 可以引入配置文件,在不修改客戶端代碼的情況下更換和添加新的具體產品類。
缺點
- 違背開閉原則,擴展不易。
- 工廠類職責過重,一旦異常,系統癱瘓。
- 無法動態的增加產品,擴展困難。
問題:在不修改的工廠的前提下,怎么生產新能源汽車?下一節的工廠方法模式給大家講解。
更多設計模式
更多設計模式
SSM框架
這對于初學者來說,是一個坎,前幾年學完這些,已經可以開始找工作了,所以恭喜你能堅持帶這里,勝利就在前方。
Redis
隨著QPS的逐漸升高,傳統的mysql數據庫已經無法滿足。所以有了基于內存的redis緩存數據庫來存儲熱點數據。
Zookeeper
Zookeeper作為統一配置文件管理和集群管理框架,是后續學習其他框架的基礎,在微服務中,還可以用來做注冊中心。
Kafka
學習心得
1.按計劃行事
凡事預則立,不預則廢。一個好的計劃是成功的一半,而這一半,一條已經幫你整理好了,你只需要收藏即可。
該路線圖左側為主路線,需循序漸進,步步為營;右側為輔助路線,需貫穿始終,熟練掌握。
建議做好時間規劃,不斷的提高自己的學習效率,學習過程中盡量把手機調至靜音給自己一個安靜的學習環境和氛圍。
2.抱團生長
獨腳難行,孤掌難鳴,一個人的力量終究是有限的,一個人的旅途也注定是孤獨的。當你定好計劃,懷著滿腔熱血準備出發的時候,一定要找個伙伴,和唐僧西天取經一樣,師徒四人團結一心才能通過九九八十一難。
在學習過程中看下自己身邊有沒有Java這方面的大神,盡量多問,多交流,如果沒有的話,來找我,我一定知無不言言無不盡,還可以給你找一群志同道合的人。水漲船高,柴多火旺,就是這個道理,閉門造車注定會半途而廢。
3.貴在堅持
駑馬十駕,功在不舍。自學Java非一日之功,你知道的越多,不知道的也越多。所以,為自己找一個動力,為了改變命運,或是為了心愛的人,或是為了讓別人高看一眼。男兒何不帶吳鉤,收取關山十五州。歲月無情,余生有涯,請將生活扛在肩上,只顧風雨兼程。
??路線導圖、3G學習資料、100本電子書?? 👇點擊下方卡片關注后回復「1024」獲取👇
總結
以上是生活随笔為你收集整理的Java完全自学手册,从外包到大厂,再到万粉博主都靠它的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [洛谷P4918]信仰收集
- 下一篇: 设计模式---------门面模式