主頁 >  其他 > 五萬字15張導圖Java自學路線,小白零基礎入門,程式員進階,收藏這篇就夠了

五萬字15張導圖Java自學路線,小白零基礎入門,程式員進階,收藏這篇就夠了

2021-10-13 07:44:17 其他

本文收錄于《技術專家修煉》

文中配套資料合集
路線導圖高清源檔案

點擊跳轉到文末點擊底部卡片回復「資料」領取

哈嘍,大家好,我是一條~

最近粉絲問我有沒有Java自學路線,有了方向才能按圖索驥,事半功倍,

我一想確實如此,自己去年總結了一份,但有些地方需要修改,索性利用國慶假期,重新整理一份,

沒錯,就是這篇文章,本文分為學習路線配套資料兩部分,

文章目錄

  • 自學路線
    • 0.路線導學
      • 按計劃行事
      • 抱團生長
      • 貴在堅持
    • 1.Java基礎
      • 基本資料型別
      • 參考資料型別
      • 訪問修飾符
      • static關鍵字
      • final關鍵字
      • 面向物件三大特性
      • 完整講解
      • 入門練習案例
    • 2.JavaWeb
      • HTTP網路請求方式
      • GET和POST
      • 冪等性
      • 如何保證冪等性
      • 常見的網路狀態碼
      • 轉發和重定向
      • Servlet
      • Servlet的生命周期
      • session、cookie、token
      • MVC與三層架構
      • 完整講解
    • 3.集合
      • ArrayList
      • LinkedList
      • 和ArrayList對比一下
      • 完整講解
    • 4.JVM
      • JVM的體系架構
      • 類裝載器classloader
      • 垃圾回收
      • 完整講解
    • 5.多執行緒
      • 并行和并發
      • 執行緒和行程
      • 守護執行緒
      • 創建執行緒4種方式
      • synchronized 底層實作
      • synchronized 和 volatile 的區別
      • synchronized 和 Lock 區別
      • synchronized 和 ReentrantLock 區別
    • 6.設計模式
      • 原型模式
      • 更多設計模式
    • 7.SSM框架
      • ORM 框架?
      • MyBatis 中 #{}和 的區別
      • 什么是Spring
      • 什么是 aop
      • 什么是 ioc
      • spring mvc 運行流程
      • 什么是 spring boot?
      • 為什么要用 spring boot?
    • 8.Redis
      • 什么是Redis
      • Redis 的功能
      • Redis 和 memcache
      • Redis 為什么是單執行緒的
      • 快取穿透
      • Redis 資料型別
    • 9.Zookeeper
      • 什么是zookeeper
      • zookeeper 的功能
      • zookeeper 的部署模式
    • 10.Kafka
      • kafka和zookeeper的關系
      • kafka的資料保留的策略?
      • kafka性能瓶頸
      • kafka集群
    • 11.ES
    • 12.Dubbo
    • 13.SpringCloud
      • 什么是 spring cloud
      • spring cloud 的核心組件
      • 斷路器的作用
    • 14.Nginx
    • 15.Netty
    • 16.架構設計
      • V0——單體架構
      • V1——負載均衡
      • V2——服務限流
      • V3 同步狀態
      • V4執行緒優化
      • V5業務邏輯
      • V6流量削峰
    • 17.Linux
    • 18.Git
      • 命令大全
    • 19.資料結構和演算法
      • 排序演算法
      • 1eetcode刷題
    • 20.計算機網路
    • 21.作業系統
    • 22.計算機組成原理
    • 23.程式員英語
    • 24.寫作能力
    • 25.演講能力
    • 26.管理能力
  • 配套資料
    • 電子書
      • 1.Java核心技術卷一、二
      • 2.深入理解Java虛擬機
      • 3.java并發編程的藝術
      • 4.阿里巴巴Java開發手冊
      • Redis 深度歷險:核心原理與應用實踐
      • Apache Kafka實戰
    • 視頻
      • 1.零基礎入門
      • 2.JVM
      • 3.SpringCloud
      • 4.程式員一條
    • 面試集
      • 一條整理
      • 204道全路線面試題合集
      • Java核心知識點
  • 粉絲福利

自學路線

0.路線導學

Java學習,如逆水行舟,不進則退,而自學,逆水還得加個水逆,難上加難,

所以我們要做好打持久戰的準備,

按計劃行事

凡事預則立,不預則廢,一個好的計劃是成功的一半,而這一半,一條已經幫你整理好了,你只需要收藏即可,

該路線圖左側為主路線,需循序漸進,步步為營;右側為輔助路線,需貫穿始終,熟練掌握,

建議做好時間規劃,不斷的提高自己的學習效率,學習程序中盡量把手機調至靜音給自己一個安靜的學習環境和氛圍,

抱團生長

獨腳難行,孤掌難鳴,一個人的力量終究是有限的,一個人的旅途也注定是孤獨的,當你定好計劃,懷著滿腔熱血準備出發的時候,一定要找個伙伴,和唐僧西天取經一樣,師徒四人團結一心才能通過九九八十一難,

在學習程序中看下自己身邊有沒有Java這方面的大神,盡量多問,多交流,如果沒有的話,來找我,我一定知無不言言無不盡,還可以給你找一群志同道合的人,水漲船高,柴多火旺,就是這個道理,閉門造車注定會半途而廢,

貴在堅持

駑馬十駕,功在不舍,自學Java非一日之功,你知道的越多,不知道的也越多,所以,為自己找一個動力,為了改變命運,或是為了心愛的人,或是為了讓別人高看一眼,男兒何不帶吳鉤,收取關山十五州,歲月無情,余生有涯,請將生活扛在肩上,只顧風雨兼程,

1.Java基礎

學習任何語言,都是先從他的基本語法開始,如果你有C語言的基礎,會容易許多,沒有也不用現學,

image-20211011130649245

基本資料型別

Java 語言提供了 8 種基本型別,大致分為 4 類(8位=1位元組)

  • 整數型
    • byte - 1位元組
    • short - 2位元組
    • int - 4位元組
    • long - 8位元組,賦值時一般在數字后加上 lL
  • 浮點型
    • float - 4位元組,直接賦值時必須在數字后加上 fF
    • double - 8位元組,賦值時一般在數字后加 dD
  • 字符型
    • char - 2位元組,存盤 Unicode 碼,用單引號賦值
  • 布爾型
    • boolean - 1位元組,只有 true 和 false 兩個取值,一個位元組就夠了

參考資料型別

簡單來說,所有的非基本資料型別都是參考資料型別,除了基本資料型別對應的參考型別外,類、 介面型別、 陣列型別、 列舉型別、 注解型別、 字串型都屬于參考型別,

主要有以下區別:

1、存盤位置

  • 基本變數型別在方法中定義的非全域基本資料型別變數的具體內容是存盤在堆疊中的
  • 參考資料型別變數其具體內容都是存放在堆中的,而堆疊中存放的是其具體內容所在記憶體的地址

2、傳遞方式

  • 基本資料型別是按值傳遞
  • 參考資料型別是按參考傳遞

訪問修飾符

訪問修飾符就是限制變數的訪問權限的,

比如你有個“賺錢”的方法,誰都不想給用,那就把方法設成private(私有);

后來你有了老婆孩子,你想讓他們也會賺錢,就得設定成default(同一個包);

后來你又有了第二個孩子,但你發現他不會賺錢的方法,為啥呢?因為你被了(default不支持不同包的子類);

可為了大局,你還是選擇接受這個孩子,悄悄把方法設定成了proteced(保護子類,即使不同包);

后來你老了,明白了開源才是共贏,就設定成了public(公有的);

不知道你聽懂了嗎,估計看到被那啥了就不想看了吧,沒關系,看圖(也是綠的)

image-20210805183734693

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例》

2.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

  1. 作用不同:GET 用于獲取資源,而 POST 用于傳輸物體主體,
  2. 引數位置不一樣: GET 的引數是以查詢字串出現在 URL 中,而 POST 的引數存盤在物體主體中,雖然GET的引數暴露在外面,但可以通過加密的方式處理,而 POST 引數即使存盤在物體主體中,我們也可以通過一些抓包工具如(Fiddler)查看,
  3. 冪等性:GET是冪等性,而POST不是冪等性,(面試官緊接著可能就會問你什么是冪等性?如何保證冪等性?)
  4. 安全性:安全的 HTTP 方法不會改變服務器狀態,也就是說它只是可讀的, GET 方法是安全的,而 POST 卻不是,因為 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處理請求的程序:

image-20210812201447743

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完整講解

3.集合

工欲善其事必先利其器,集合就是我們的器,

ArrayList

底層實作

由什么組成,我說了不算,看原始碼,怎么看呢?

List<Object> list = new ArrayList<>();

新建一個ArrayList,按住ctrlcommand用滑鼠點擊,

    /**
     * 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中,通過CopyOnWriteArrayListCopyOnWriteArraySet容器實作了 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

LinkedListArrayList同屬于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對比一下

共同點:有序,可重復,執行緒不安全,

不同點:底層架構,查詢和刪改的速度

完整講解

集合完整講解

4.JVM

重點來了,Java程式員一定要深入研究的內容

JVM的體系架構

JVM體系結構如下圖所示,將按照從上到下的順序講解

類裝載器classloader

負責將class檔案的位元組碼內容加載到記憶體中并將這些內容轉換成方法區中的運行時資料結構,class檔案在檔案開頭有特定標識(cafe babe:Java圖示——咖啡和橡樹),

通俗來講:classloader相當于快遞員的作用,只負責加載,至于是否能運行,由Execution Engine決定

雙親委派機制

當一個類收到類加載請求,他會先把這個請求交給他的父類,只有父類無法完成這個請求時,子加載器才會嘗試自己去加載,

雙親委派的好處是保護Java核心類,比如加載位于rt.jar中的java.lang.Object,不管是哪個加載器加載的,最終都會交給啟動類加載器,這樣就保證了不同的類加載器得到的都是同一個Object物件,

代碼舉例:查看類是被那個加載器加載的

/**
 * @Author: 一條IT
 * @Date: 2020/12/3 21:28
 */
public class Test {
    public static void main(String[] args) {
        System.out.println(Object.class.getClassLoader());
        System.out.println(Test.class.getClassLoader().getParent().getParent());
        System.out.println(Test.class.getClassLoader().getParent());
        System.out.println(Test.class.getClassLoader());
    }
}

輸出

null
null
sun.misc.Launcher$ExtClassLoader@1b6d3586
sun.misc.Launcher$AppClassLoader@14dad5dc

因為Object是jdk自帶的,所以在加載的時候是走Bootstrap啟動類加載器,而Bootstrap加載器是C++語言寫的,所以在查的時候是null,報了NullPointException();Test類自己寫的,走AppClassLoder,他的父類是擴展加載器,再父類是啟動類加載器,也輸出Null

沙箱安全機制

主要是防止惡意代碼污染java源代碼,比如定義了一個類名為String所在包為java.lang,因為這個類本來是屬于jdk的,如果沒有沙箱安全機制的話,這個類將會污染到我所有的String,但是由于沙箱安全機制,所以就委托頂層的bootstrap加載器查找這個類,如果沒有的話就委托extsion,extsion沒有就到appclassloader,但是由于String就是jdk的源代碼,所以在bootstrap那里就加載到了,先找到先使用,所以就使用bootstrap里面的String,后面的一概不能使用,這就保證了不被惡意代碼污染,

垃圾回收

垃圾回收是重點難,先理解了垃圾回收,才能理解調優的思路,

判斷垃圾

判斷是否是垃圾共有兩種方法,參考計數法和可達性分析

1.參考計數法

非常好理解,參考一次標記一次,沒有被標記的就是垃圾,

在堆中存盤物件時,在物件頭處維護一個counter計數器,如果一個物件增加了一個參考與之相連,則將counter++

如果一個參考關系失效則counter--,如果一個物件的counter變為0,則說明該物件已經被廢棄,不處于存活狀態,此時可以被回收,

2.參考計數的缺點

  • 效率低
  • 無法分析回圈參考問題

3.可達性分析

類似的樹結構,從根結點出發,即GC root,把有關系的物件用一顆樹鏈接起來

那么我們遍歷這棵樹,沒遍歷到的物件,就是垃圾

4.有哪些可以做GC Roots的物件?

  • 虛擬機堆疊(堆疊楨中的本地變數表)中的參考的物件
  • 方法區中的類靜態屬性參考的物件
  • 方法區中的常量參考的物件
  • 本地方法堆疊中JNI(Native方法)的參考的物件

回收演算法

回收演算法是垃圾回收的思想,回收器是垃圾回收的實作

1.標記-清除

兩次遍歷:

  • 標記垃圾
  • 清除垃圾

優點:

  • 不需要格外空間,適合回收物件較少的區域

缺點:

  • 效率低,遍歷兩次,時間復雜度O(n^2)
  • 會有執行緒停頓,stop the world (STW)
  • 空間碎片,因為垃圾可能不是連續的,大量的空間碎片會導致提前GC,這也是最主要的問題,

2.標記-復制

將空間分為相等大小的兩部分,每次只使用其中的一塊,當這一塊的記憶體用完了,就將還存活著的物件復制到另外一塊上面,然后再把已使用的記憶體空間一次清理掉,這樣一來就不容易出現記憶體碎片的問題,犧牲空間解決碎片問題,

優點:

  • 高效無碎片

缺點:

  • 占用大量空間

3.標記-整理

同樣是為了解決空間碎片提出,區別是通過犧牲時間的方式,

和標記-清除類似,不一樣的是在完成標記之后,它不是直接清理可回收物件,而是將存活物件都向一端移動,然后清理掉端邊界以外的記憶體,

優點:

  • 解決空間碎片的問題
  • 不浪費空間

缺點:

  • 相對比較耗時

完整講解

JVM完整講解

5.多執行緒

理解多執行緒,才能更好的理解框架原始碼,進行高并發的架構設計,重中之重,

并行和并發

并行:多個任務在同一個 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 標記的變數可以被編譯器優化,

6.設計模式

好多人覺得設計模式模式,那是因為你學的還不夠深入,還沒有看過原始碼,所以我特意將設計模式往前放了,

原型模式

定義

官方定義

用原型實體指定創建物件的種類,并且通過拷貝這個原型來創建新的物件,

通俗解讀

在需要創建重復的物件,為了保證性能,本體給外部提供一個克隆體進行使用

類似我國的印刷術,省去new的程序,通過copy的方式創建物件,

結構圖

代碼實作

目錄結構

建議跟著一條學設計模式的小伙伴都建一個maven工程,并安裝lombok依賴和插件,

并建立如下包目錄,便于歸納整理,

pom如下

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.10</version>
    </dependency>

開發場景

假設一條開發了一個替代Mybatis的框架,叫YitiaoBatis,每次操作資料庫,從資料庫里面查出很多記錄,但是改變的部分是很少的,如果每次查資料庫,查到以后把所有資料都封裝一個物件,就會導致要new很多重復的物件,造成資源的浪費,

一條想到一個解決辦法,就是把查過的數據保存起來,下來查相同的資料,直接把保存好的物件回傳,也就是快取的思想,

我們用代碼模擬一下:

1.創建Yitiao物體類

/**
 * author:一條
 */
@Data
@AllArgsConstructor
public class Yitiao {
    
    private String name;
    private Integer id;
    private String wechat;

    public Yitiao(){
        System.out.println("Yitiao物件創建");
    }
}

2.創建YitiaoBatis

/**
 * author:一條
 */
public class YitiaoBatis {
    //快取Map    
    private Map<String,Yitiao> yitiaoCache = new HashMap<>();

    //從快取拿物件
    public Yitiao getYitiao(String name){
        //判斷快取中是否存在
        if (yitiaoCache.containsKey(name)){
            Yitiao yitiao = yitiaoCache.get(name);
            System.out.println("從快取查到資料:"+yitiao);
            return yitiao;
        }else {
            //模擬從資料庫查資料
            Yitiao yitiao = new Yitiao();
            yitiao.setName(name);
            yitiao.setId(1);
            yitiao.setWechat("公眾號:一條coding");
            System.out.println("從資料庫查到資料:"+yitiao);
            //放入快取
            yitiaoCache.put(name,yitiao);
            return yitiao;
        }
    }
}

3.撰寫測驗類

/**
 * author:一條
 */
public class MainTest {
    public static void main(String[] args) {
        YitiaoBatis yitiaoBatis = new YitiaoBatis();
        Yitiao yitiao1 = yitiaoBatis.getYitiao("yitiao");
        System.out.println("第一次查詢:"+yitiao1);
        Yitiao yitiao2 = yitiaoBatis.getYitiao("yitiao");
        System.out.println("第二次查詢:"+yitiao2);
    }
}

輸出結果

從結果可以看出:

  • 物件創建了一次,有點單例的感覺
  • 第一次從資料庫查,第二次從快取查

好像是實作了YitiaoBatis框架的需求,思考🤔一下有什么問題呢?

4.修改物件id

在測驗類繼續撰寫

//執行后續業務,修改id
yitiao2.setId(100);

Yitiao yitiao3 = yitiaoBatis.getYitiao("yitiao");
System.out.println("第三次查詢:"+yitiao3);

輸出結果

重點看第三次查詢,id=100?

我們在記憶體修改的資料,導致從資料庫查出來的資料也跟著改變,出現臟資料

怎么解決呢?原型模式正式開始,

5.實作Cloneable介面

本體給外部提供一個克隆體進行使用,在快取中拿到的物件不直接回傳,而是復制一份,這樣就保證了不會臟快取,

public class Yitiao implements Cloneable{
  
  	//……
  
		@Override
    protected Object clone() throws CloneNotSupportedException {
				return (Yitiao) super.clone();
    }
}

修改快取

//從快取拿物件
    public Yitiao getYitiao(String name) throws CloneNotSupportedException {
        //判斷快取中是否存在
        if (yitiaoCache.containsKey(name)){
            Yitiao yitiao = yitiaoCache.get(name);
            System.out.println("從快取查到資料:"+yitiao);
            //修改回傳
            //return yitiao;
            return yitiao.clone();
        }else {
            //模擬從資料庫查資料
            Yitiao yitiao = new Yitiao();
            yitiao.setName(name);
            yitiao.setId(1);
            yitiao.setWechat("公眾號:一條coding");
            System.out.println("從資料庫查到資料:"+yitiao);
            //放入快取
            yitiaoCache.put(name,yitiao);
            //修改回傳
            //return yitiao;
            return yitiao.clone();
        }

6.再次測驗

不用改測驗類,直接看一下結果:

從輸出結果可以看出第三次查詢id依然是1,沒有臟快取現象,

基于原型模式的克隆思想,我可以快速拿到和「本體」一模一樣的「克隆體」,而且物件也只被new了一次,

不知道大家是否好奇物件是怎么被創建出來的,那我們就一起看一下「深拷貝」和「淺拷貝」是怎么回事,

深拷貝和淺拷貝

定義

深拷貝:不管拷貝物件里面是基本資料型別還是參考資料型別都是完全的復制一份到新的物件中,

淺拷貝:當拷貝物件只包含簡單的資料型別比如int、float 或者不可變的物件(字串)時,就直接將這些欄位復制到新的物件中,而參考的物件并沒有復制而是將參考物件的地址復制一份給克隆物件,

好比兩個兄弟,深拷貝是年輕的時候關系特別好,衣服買一樣的,房子住一塊,淺拷貝是長大了都成家立業,衣服可以繼續買一樣的,但房子必須要分開住了,

實作

在代碼上區分深拷貝和淺拷貝的方式就是看參考型別的變數在修改后,值是否發生變化,

淺拷貝

1.通過clone()方式的淺拷貝

新建Age類,作為Yitiao的參考屬性

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Age {
    private int age;
}

2.測驗1

public static void main(String[] args) throws CloneNotSupportedException {
        Yitiao yitiao1 = new Yitiao();
        Age age = new Age(1);
        yitiao1.setAge(age);
        yitiao1.setId(1);
        Yitiao clone = yitiao1.clone();
        yitiao1.setId(2);
        age.setAge(2);    //不能new一個age
        System.out.println("yitiao1:\n"+yitiao1+"\nclone:\n"+clone);
    }

輸出結果

結論:基本型別id沒發生改變,參考型別Age由于地址指向的同一個物件,值跟隨變化,

3.通過構造方法實作淺拷貝

Yitiao.class增加構造方法

    public Yitiao(Yitiao yitiao){
        id=yitiao.id;
        age=yitiao.age;
    }

4.測驗2

        Yitiao yitiao1 = new Yitiao();
        Age age = new Age(1);
        yitiao1.setAge(age);
        yitiao1.setId(1);
        Yitiao clone = new Yitiao(yitiao1);  //差別在這
        yitiao1.setId(2);
        age.setAge(2);
        System.out.println("yitiao1:\n"+yitiao1+"\nclone:\n"+clone);

輸出結果

測驗1無異

深拷貝

image-20210921005209828

1.通過物件序列化實作深拷貝

通過層次呼叫clone方法也可以實作深拷貝,但是代碼量太大,特別對于屬性數量比較多、層次比較深的類而言,每個類都要重寫clone方法太過繁瑣,一般不使用,亦不再舉例,

可以通過將物件序列化為位元組序列后,默認會將該物件的整個物件圖進行序列化,再通過反序列即可完美地實作深拷貝,

YitiaoAge實作Serializable介面

2.測驗

//通過物件序列化實作深拷貝
        Yitiao yitiao = new Yitiao();
        Age age = new Age(1);
        yitiao.setAge(age);
        yitiao.setId(1);
        ByteArrayOutputStream bos=new ByteArrayOutputStream();
        ObjectOutputStream oos=new ObjectOutputStream(bos);
        oos.writeObject(yitiao);
        oos.flush();
        ObjectInputStream ois=new ObjectInputStream(new 							ByteArrayInputStream(bos.toByteArray()));
        Yitiao clone = (Yitiao) ois.readObject();
        yitiao.setId(2);
        age.setAge(2);
        System.out.println("yitiao:\n"+yitiao+"\nclone:\n"+clone);

輸出結果

結論,參考物件也完全復制一個新的,值不變化,

不過要注意的是,如果某個屬性被transient修飾,那么該屬性就無法被拷貝了,

應用場景

我們說回原型模式

原型模式在我們的代碼中是很常見的,但是又容易被我們所忽視的一種模式,比如我們常用的的BeanUtils.copyProperties就是一種物件的淺拷貝

看看有哪些場景需要原型模式

  • 資源優化
  • 性能和安全要求
  • 一個物件多個修改者的場景,
  • 一個物件需要提供給其他物件訪問,而且各個呼叫者可能都需要修改其值時可以考慮使用原型模式拷貝多個物件供呼叫者使用,

原型模式已經與 Java 融為渾然一體,可以隨手拿來使用,

更多設計模式

更多設計模式

7.SSM框架

這對于初學者來說,是一個坎,前幾年學完這些,已經可以開始找作業了,所以恭喜你能堅持帶這里,勝利就在前方,

ORM 框架?

ORM(Object Relation Mapping)物件關系映射,是把資料庫中的關系資料映射成為程式中的物件,

使用 ORM 的優點:提高了開發效率降低了開發成本、開發更簡單更物件化、可移植更強,

MyBatis 中 #{}和 的區別

#是預編譯處理,{}的區別是什么?#{}是預編譯處理,的區別是什么?#是預編譯處理,{}是字符替換, 在使用 #{}時,MyBatis 會將 SQL 中的 #{}替換成“?”,配合 PreparedStatement 的 set 方法賦值,這樣可以有效的防止 SQL 注入,保證程式的運行安全,

什么是Spring

spring 提供 ioc 技術,容器會幫你管理依賴的物件,從而不需要自己創建和管理依賴物件了,更輕松的實作了程式的解耦,

spring 提供了事務支持,使得事務操作變的更加方便,

spring 提供了面向切片編程,這樣可以更方便的處理某一類的問題,

更方便的框架集成,spring 可以很方便的集成其他框架,比如 MyBatis、hibernate 等,

什么是 aop

aop 是面向切面編程,通過預編譯方式和運行期動態代理實作程式功能的統一維護的一種技術,

簡單來說就是統一處理某一“切面”(類)的問題的編程思想,比如統一處理日志、例外等,

什么是 ioc

ioc:Inversionof Control(中文:控制反轉)是 spring 的核心,對于 spring 框架來說,就是由 spring 來負責控制物件的生命周期和物件間的關系,

簡單來說,控制指的是當前物件對內部成員的控制權;控制反轉指的是,這種控制權不由當前物件管理了,由其他(類,第三方容器)來管理,

spring mvc 運行流程

spring mvc 先將請求發送給 DispatcherServlet,

DispatcherServlet 查詢一個或多個 HandlerMapping,找到處理請求的 Controller,

DispatcherServlet 再把請求提交到對應的 Controller,

Controller 進行業務邏輯處理后,會回傳一個ModelAndView,

Dispathcher 查詢一個或多個 ViewResolver 視圖決議器,找到 ModelAndView 物件指定的視圖物件,
視圖物件負責渲染回傳給客戶端,

什么是 spring boot?

spring boot 是為 spring 服務的,是用來簡化新 spring 應用的初始搭建以及開發程序的,

為什么要用 spring boot?

配置簡單

獨立運行

自動裝配

無代碼生成和 xml 配置

提供應用監控

易上手

提升開發效率

8.Redis

隨著QPS的逐漸升高,傳統的mysql資料庫已經無法滿足,所以有了基于記憶體的redis快取資料庫來存盤熱點資料,

什么是Redis

Redis 是一個使用 C 語言開發的高速快取資料庫,

Redis 使用場景:

  • 記錄帖子點贊數、點擊數、評論數;

  • 快取近期熱帖;

  • 快取文章詳情資訊;

  • 記錄用戶會話資訊,

Redis 的功能

  • 資料快取功能
  • 分布式鎖的功能
  • 支持資料持久化
  • 支持事務
  • 支持訊息佇列

Redis 和 memcache

存盤方式不同:memcache 把資料全部存在記憶體之中,斷電后會掛掉,資料不能超過記憶體大小;Redis 有部份存在硬碟上,這樣能保證資料的持久性,

資料支持型別:memcache 對資料型別支持相對簡單;Redis 有復雜的資料型別,

使用底層模型不同:它們之間底層實作方式,以及與客戶端之間通信的應用協議不一樣,Redis 自己構建了 vm 機制,因為一般的系統呼叫系統函式的話,會浪費一定的時間去移動和請求,

value 值大小不同:Redis 最大可以達到 1gb;memcache 只有 1mb,

Redis 為什么是單執行緒的

因為 cpu 不是 Redis 的瓶頸,Redis 的瓶頸最有可能是機器記憶體或者網路帶寬,既然單執行緒容易實作,而且 cpu 又不會成為瓶頸,那就順理成章地采用單執行緒的方案了,

關于 Redis 的性能,官方網站也有,普通筆記本輕松處理每秒幾十萬的請求,

而且單執行緒并不代表就慢 nginx 和 nodejs 也都是高性能單執行緒的代表,

快取穿透

快取穿透:指查詢一個一定不存在的資料,由于快取是不命中時需要從資料庫查詢,查不到資料則不寫入快取,這將導致這個不存在的資料每次請求都要到資料庫去查詢,造成快取穿透,

解決方案:最簡單粗暴的方法如果一個查詢回傳的資料為空(不管是資料不存在,還是系統故障),我們就把這個空結果進行快取,但它的過期時間會很短,最長不超過五分鐘,

Redis 資料型別

Redis 支持的資料型別:string(字串)、list(串列)、hash(字典)、set(集合)、zset(有序集合),

9.Zookeeper

Zookeeper作為統一組態檔管理和集群管理框架,是后續學習其他框架的基礎,在微服務中,還可以用來做注冊中心,

什么是zookeeper

zookeeper 是一個分布式的,開放原始碼的分布式應用程式協調服務,是 google chubby 的開源實作,是 hadoop 和 hbase 的重要組件,它是一個為分布式應用提供一致性服務的軟體,提供的功能包括:配置維護、域名服務、分布式同步、組服務等,

zookeeper 的功能

集群管理:監控節點存活狀態、運行請求等,

主節點選舉:主節點掛掉了之后可以從備用的節點開始新一輪選主,主節點選舉說的就是這個選舉的程序,使用 zookeeper 可以協助完成這個程序,

分布式鎖:zookeeper 提供兩種鎖:獨占鎖、共享鎖,獨占鎖即一次只能有一個執行緒使用資源,共享鎖是讀鎖共享,讀寫互斥,即可以有多線執行緒同時讀同一個資源,如果要使用寫鎖也只能有一個執行緒使用,zookeeper可以對分布式鎖進行控制,

命名服務:在分布式系統中,通過使用命名服務,客戶端應用能夠根據指定名字來獲取資源或服務的地址,提供者等資訊,

zookeeper 的部署模式

zookeeper 有三種部署模式:

單機部署:一臺集群上運行;

集群部署:多臺集群運行;

偽集群部署:一臺集群啟動多個 zookeeper 實體運行,

10.Kafka

kafka和zookeeper的關系

kafka 不能脫離 zookeeper 單獨使用,因為 kafka 使用 zookeeper 管理和協調 kafka 的節點服務器,

kafka的資料保留的策略?

kafka 有兩種資料保存策略:按照過期時間保留和按照存盤的訊息大小保留,

kafka 同時設定了 7 天和 10G 清除資料,到第五天的時候訊息達到了 10G,這個時候 kafka 將如何處理?
這個時候 kafka 會執行資料清除作業,時間和大小不論那個滿足條件,都會清空資料,

kafka性能瓶頸

cpu 性能瓶頸

磁盤讀寫瓶頸

網路瓶頸

kafka集群

集群的數量不是越多越好,最好不要超過 7 個,因為節點越多,訊息復制需要的時間就越長,整個群組的吞吐量就越低,

集群數量最好是單數,因為超過一半故障集群就不能用了,設定為單數容錯率更高,

11.ES

elasticsearch簡寫es,es是一個高擴展、開源的全文檢索和分析引擎,它可以準實時地快速存盤、搜索、分析海量的資料,

12.Dubbo

Dubbo是一個分布式服務框架,致力于提供高性能和透明化的RPC遠程服務呼叫方案,以及SOA服務治理方案,簡單的說,dubbo就是個服務框架

13.SpringCloud

Spring Cloud是一個微服務框架,Spring Cloud提供了全套的分布式系統解決方案,不僅對微服務基礎框架Netflix的多個開源組件進行了封裝,同時還實作了和云端平臺以及Spring Boot開發框架的集成,

什么是 spring cloud

spring cloud 是一系列框架的有序集合,它利用 spring boot 的開發便利性巧妙地簡化了分布式系統基礎設施的開發,如服務發現注冊、配置中心、訊息總線、負載均衡、斷路器、資料監控等,都可以用 spring boot 的開發風格做到一鍵啟動和部署,

spring cloud 的核心組件

Eureka:服務注冊于發現,

Feign:基于動態代理機制,根據注解和選擇的機器,拼接請求 url 地址,發起請求,

Ribbon:實作負載均衡,從一個服務的多臺機器中選擇一臺,

Hystrix:提供執行緒池,不同的服務走不同的執行緒池,實作了不同服務呼叫的隔離,避免了服務雪崩的問題,

Zuul:網關管理,由 Zuul 網關轉發請求給對應的服務,

斷路器的作用

在分布式架構中,斷路器模式的作用也是類似的,當某個服務單元發生故障(類似用電器發生短路)之后,通過斷路器的故障監控(類似熔斷保險絲),向呼叫方回傳一個錯誤回應,而不是長時間的等待,這樣就不會使得執行緒因呼叫故障服務被長時間占用不釋放,避免了故障在分布式系統中的蔓延,

14.Nginx

Nginx是一個高性能的HTTP和反向代理服務器,具有占記憶體少和并發能力強的特點,

15.Netty

netty 是一個基于nio的客戶、服務器端編程框架,netty提供異步的,事件驅動的網路應用程式框架和工具,可以快速開發高可用的客戶端和服務器,

16.架構設計

好的架構從來不是設計出來的,而是演變出來的,所以我們在日常開發中就要不斷思考性能的優化,

下面拿如何設計一個百萬人抽獎系統舉例說明架構的演進,

V0——單體架構

如果現在讓你實作幾十人的抽獎系統,簡單死了吧,直接重拳出擊!

兩貓一豚走江湖,中獎入庫,調通知服務,查庫通知,完美!

相信大家學java時可能都做過這種案例,思考🤔一下存在什么問題?

  • 單體服務,一著不慎滿盤皆輸
  • 抽了再抽,一個人就是一支軍隊
  • 惡意腳本,沒有程式員中不了的獎

接下來就聊聊怎么解決這些問題?

V1——負載均衡

當一臺服務器的單位時間內的訪問量越大時,服務器壓力就越大,大到超過自身承受能力時,服務器就會崩潰,

為了避免服務器崩潰,讓用戶有更好的體驗,我們通過負載均衡的方式來分擔服務器壓力,

負載均衡就是建立很多很多服務器,組成一個服務器集群,當用戶訪問網站時,先訪問一個中間服務器,好比管家,由他在服務器集群中選擇一個壓力較小的服務器,然后將該訪問請求引入該服務器,

如此以來,用戶的每次訪問,都會保證服務器集群中的每個服務器壓力趨于平衡,分擔了服務器壓力,避免了服務器崩潰的情況,

負載均衡是用「反向代理」的原理實作的,具體負載均衡演算法及其實作方式我們下文再續,

負載均衡雖然解決了單體架構一著不慎滿盤皆輸的問題,但服務器成本依然不能保護系統周全,我們必須想好一旦服務器宕機,如何保證用戶的體驗,

即如何緩解開獎一瞬間時的大量請求,

V2——服務限流

限流主要的作用是保護服務節點或者集群后面的資料節點,防止瞬時流量過大使服務和資料崩潰(如前端快取大量實效),造成不可用,

還可用于平滑請求,

在上一小節我們做好了負載均衡來保證集群的可用性,但公司需要需要考慮服務器的成本,不可能無限制的增加服務器數量,一般會經過計算保證日常的使用沒問題,

限流的意義就在于我們無法預測未知流量,比如剛提到的抽獎可能遇到的:

  • 重復抽獎
  • 惡意腳本

其他一些場景:

  • 熱點事件(微博)
  • 大量爬蟲

這些情況都是無法預知的,不知道什么時候會有10倍甚至20倍的流量打進來,如果真碰上這種情況,擴容是根本來不及的(彈性擴容都是虛談,一秒鐘你給我擴一下試試)

明確了限流的意義,我們再來看看如何實作限流

防止用戶重復抽獎

重復抽獎和惡意腳本可以歸在一起,同時幾十萬的用戶可能發出幾百萬的請求,

如果同一個用戶在1分鐘之內多次發送請求來進行抽獎,就認為是惡意重復抽獎或者是腳本在刷獎,這種流量是不應該再繼續往下請求的,在負載均衡層給直接屏蔽掉,

可以通過nginx配置ip的訪問頻率,或者在在網關層結合sentinel配置限流策略,

用戶的抽獎狀態可以通過redis來存盤,后面會說,

攔截無效流量

無論是抽獎還是秒殺,獎品和商品都是有限的,所以后面涌入的大量請求其實都是無用的,

舉個例子,假設50萬人抽獎,就準備了100臺手機,那么50萬請求瞬間涌入,其實前500個請求就把手機搶完了,后續的幾十萬請求就沒必要讓他再執行業務邏輯,直接暴力攔截回傳抽獎結束就可以了,

同時前端在按鈕置灰上也可以做一些文章,

那么思考一下如何才能知道獎品抽完了呢,也就是庫存和訂單之前的資料同步問題,

服務降級和服務熔斷

有了以上措施就萬無一失了嗎,不可能的,所以再服務端還有降級和熔斷機制,

在此簡單做個補充,詳細內容請持續關注作者,

有好多人容易混淆這兩個概念,通過一個小例子讓大家明白:

假設現在一條粉絲數突破100萬,沖上微博熱搜,粉絲甲和粉絲乙都打開微博觀看,但甲看到了一條新聞發布會的內容,乙卻看到”系統繁忙“,過了一會,乙也能看到內容了,

(請允許一潭訓想一下😎)

在上述程序中,首先是熱點時間造成大量請求,發生了服務熔斷,為了保證整個系統可用,犧牲了部分用戶乙,乙看到的”系統繁忙“就是服務降級(fallback),過了一會有恢復訪問,這也是熔斷器的一個特性(hystrix)

V3 同步狀態

接著回到上一節的問題,如何同步抽獎狀態?

這不得不提到redis,被廣泛用于高并發系統的快取資料庫,

我們可以基于Redis來實作這種共享抽獎狀態,它非常輕量級,很適合兩個層次的系統的共享訪問,

當然其實用ZooKeeper也是可以的,在負載均衡層可以基于zk客戶端監聽某個znode節點狀態,一旦抽獎結束,抽獎服務更新zk狀態,負載均衡層會感知到,

V4執行緒優化

對于線上環境,作業執行緒數量是一個至關重要的引數,需要根據自己的情況調節,

眾所周知,對于進入Tomcat的每個請求,其實都會交給一個獨立的作業執行緒來進行處理,那么Tomcat有多少執行緒,就決定了并發請求處理的能力,

但是這個執行緒數量是需要經過壓測來進行判斷的,因為每個執行緒都會處理一個請求,這個請求又需要訪問資料庫之類的外部系統,所以不是每個系統的引數都可以一樣的,需要自己對系統進行壓測,

但是給一個經驗值的話,Tomcat的執行緒數量不宜過多,因為執行緒過多,普通服務器的CPU是扛不住的,反而會導致機器CPU負載過高,最終崩潰,

同時,Tomcat的執行緒數量也不宜太少,因為如果就100個執行緒,那么會導致無法充分利用Tomcat的執行緒資源和機器的CPU資源,

所以一般來說,Tomcat執行緒數量在200~500之間都是可以的,但是具體多少需要自己壓測一下,不斷的調節引數,看具體的CPU負載以及執行緒執行請求的一個效率,

在CPU負載尚可,以及請求執行性能正常的情況下,盡可能提高一些執行緒數量,

但是如果到一個臨界值,發現機器負載過高,而且執行緒處理請求的速度開始下降,說明這臺機扛不住這么多執行緒并發執行處理請求了,此時就不能繼續上調執行緒數量了,

V5業務邏輯

抽獎邏輯怎么做?

好了,現在該研究一下怎么做抽獎了

在負載均衡那個層面,已經把比如50萬流量中的48萬都攔截掉了,但是可能還是會有2萬流量進入抽獎服務,

因為抽獎活動都是臨時服務,可以阿里云租一堆機器,也不是很貴,tomcat優化完了,服務器的問題也解決了,還剩啥呢?

Mysql,是的,你的Mysql能抗住2萬的并發請求嗎?

答案是很難,怎么辦呢?

把Mysql給替換成redis,單機抗2萬并發那是很輕松的一件事情,

而且redis的一種資料結構set很適合做抽獎,可以隨機選擇一個元素并剔除,

V6流量削峰

由上至下,還剩中獎通知部分沒有優化,

思考這個問題:假設抽獎服務在2萬請求中有1萬請求抽中了獎品,那么勢必會造成抽獎服務對禮品服務呼叫1萬次,

那也要和抽獎服務同樣處理嗎?

其實并不用,因為發送通知不要求及時性,完全可以讓一萬個請求慢慢發送,這時就要用到訊息中間件,進行限流削峰,

也就是說,抽獎服務把中獎資訊發送到MQ,然后通知服務慢慢的從MQ中消費中獎訊息,最終完成完禮品的發放,這也是我們會延遲一些收到中獎資訊或者物流資訊的原因,

假設兩個通知服務實體每秒可以完成100個通知的發送,那么1萬條訊息也就是延遲100秒發放完畢罷了,

同樣對MySQL的壓力也會降低,那么資料庫層面也是可以抗住的,

看一下最終結構圖:

17.Linux

作為Java程式員,不會用Linux會讓人笑掉大牙的,我們不必像運維兄弟一樣精通,基本的命令還是要熟練掌握,

  • 理解一切皆檔案
  • 檔案操作命令
  • 權限管理命令
  • 網路命令
  • 系統磁盤命令
  • ……

18.Git

網上經常傳出不會git被開除的新聞,所以還不學起來?

命令大全

200條Git命令大全

19.資料結構和演算法

問一下各位什么是程式?——資料結構+演算法,所以,學吧,刷題吧

排序演算法

《八大排序》原始碼](https://pan.baidu.com/s/1woTgwkVUT1xtgMB1ha36Uw),提取碼:5ehp

準備

古語云:“兵馬未動,糧草先行”,想跟著一條一塊把「排序演算法」弄明白的,建議先準備好以下代碼模板,

📢 觀看本教程需知道基本回圈語法兩數交換雙指標等前置知識,

📚 建議先看完代碼逐步分析后再嘗試自己寫,

  • 新建一個Java工程,本文全篇也基于Java語言實作代碼,
  • 建立如下目錄結構

  • MainTest測驗類中撰寫測驗模板,
/**
 * 測驗類
 * Author:一條
 * Date:2021/09/23
 */
public class MainTest {
    public static void main(String[] args) {
        //待排序序列
        int[] array={6,10,4,5,2,8};
        //呼叫不同排序演算法
				// BubbleSort.sort(array);

        // 創建有100000個隨機資料的陣列
        int[] costArray=new int[100000];
        for (int i = 0; i < 100000; i++) {
            // 生成一個[0,100000) 的一個數
            costArray[i] = (int) (Math.random() * 100000);
        }

        Date start = new Date();
        //過長,先注釋掉逐步列印
				//BubbleSort.sort(costArray);
        Date end = new Date();
        System.out.println("耗時:"+(end.getTime()-start.getTime())/1000+"s");
    }
}

該段代碼內容主要有兩個功能:

  • 呼叫不同的排序演算法進行測驗
  • 測驗不同排序演算法將10w個數排好序需要的時間,更加具象的理解時間復雜度的不同

1.冒泡排序

基本思想

通過對亂序序列從前向后遍歷,依次比較相鄰元素的值,若發現逆序則交換,使值較大的元素逐漸從前移向后部,

像水底下的氣泡一樣逐漸向上冒一樣,

動圖講解

代碼實作

不理解的小伙伴可以用debug模式逐步分析,

/**
 * 冒泡排序
 * Author:一條
 * Date:2021/09/23
 */
public class BubbleSort{
    public static int[] sort(int[] array){
        for (int i = 0; i < array.length; i++) {
            for (int j = 0; j < array.length-1; j++) {
              //依次比較,將最大的元素交換到最后
                if (array[j]>array[j+1]){
                  // 用臨時變數temp交換兩個值
                    int temp=array[j];
                    array[j]=array[j+1];
                    array[j+1]=temp;
                }
            }
          //輸出每一步的排序結果
            System.out.println(Arrays.toString(array));
        }
        return array;
    }
}

輸出結果

逐步分析

  1. 初始陣列:[6,10,4,5,2,8]
  2. 6拿出來和后一個10比較,6<10,不用交換,- > j++;
  3. 10拿出來和后一個4比較,10>4,交換,- > [6,4,10,5,2,8]
  4. 依次執行j++與后一個比較交換
  5. 第一層i回圈完,列印第一行- > [6, 4, 5, 2, 8, 10],此時最后一位10在正確位置上, - > i++
  6. 4開始,繼續比較交換,倒數第二位8回到正確位置,
  7. 如上回圈下去 - > ……
  8. 最終結果 - > [2, 4, 5, 6, 8, 10]

這時再回去看動圖理解,

耗時測驗

記得先注釋掉排序類逐步列印代碼,

時間復雜度O(n^2)

演算法優化

優化點一

外層第一次遍歷完,最后一位已經是正確的,j就不需要再比較,所以結束條件應改為j-i-1;

優化點二

因為排序的程序中,各元素不斷接近自己的位置,如果一趟比較下來沒有進行過交換,就說明序列有序,因此要在排序程序中設定一個標志flag判斷元素是否進行過交換,從而減少不必要的比較,

優化代碼

public static int[] sortPlus(int[] array){
        System.out.println("優化冒泡排序開始----------");
        for (int i = 0; i < array.length; i++) {
            boolean flag=false;
            for (int j = 0; j < array.length-i-1; j++) {
                if (array[j]>array[j+1]){
                    flag=true;
                    int temp=array[j];
                    array[j]=array[j+1];
                    array[j+1]=temp;
                }
            }
            if (flag==false){
                break;
            }
//            System.out.println(Arrays.toString(array));
        }
        return array;
    }

優化測驗

通過基礎測驗看到當序列已經排好序,即不發生交換后終止回圈,

耗時測驗由27s優化到17s

2.選擇排序

基本思想

選擇排序和冒泡排序很像,是從亂序序列的資料中,按指定的規則選出某一元素,再依規定交換位置后達到排序的目的,

動圖講解

代碼實作

public class SelectSort {
    public static int[] sort(int[] array) {
        System.out.println("選擇排序開始----------");
        for (int i = 0; i < array.length; i++) {
          //每個值只需與他后面的值進行比較,所以從開始
            for (int j = i; j < array.length; j++) {
              //注意此處是哪兩個值比較
                if (array[i]>array[j]){
                    int temp=array[i];
                    array[i]=array[j];
                    array[j]=temp;
                }
            }
            System.out.println(Arrays.toString(array));
        }
        return array;
    }
}

輸出結果

逐步分析

  • 初始陣列:[6,10,4,5,2,8]
  • 拿出610比較,不交換 - > j++
  • 62比較,交換 - > j++
  • 注意此時是拿2繼續比較,都不交換,確定第一位(最小的數)為2 - > i++
  • 回圈下去,依次找到第一小,第二小,……的數
  • 最終結果 - > [2, 4, 5, 6, 8, 10]

這時再回去看動圖理解,

耗時測驗

時間復雜度:O(n^2)

演算法優化

上訴代碼中使用交換的方式找到較小值,還可以通過移動的方式,即全部比較完只交換一次,

這種對空間的占有率會有些增益,但對時間的增益幾乎沒有,可忽略,亦不再演示,

3.插入排序

基本思想

把n個亂序的元素看成為一個有序表和一個無序表,開始時有序表中只包含一個元素,無序表中包含有n-1個元素,排序程序中通過不斷往有序表插入元素,獲取一個區域正確解,逐漸擴大有序序列的長度,直到完成排序,

動圖講解

2021-09-25 19.20.05

代碼實作

/**
 * 插入排序
 * Author:一條
 * Date:2021/09/23
 */
public class InsertSort {
    public static void sort(int[] array) {
        for (int i = 1; i < array.length; i++) {
            //插入有序序列,且將有序序列擴大
            for (int j = i; j > 0; j--) {
                if (array[j]>array[j-1]){
                    int temp=array[j];
                    array[j]=array[j-1];
                    array[j-1]=temp;
                }
            }
//            System.out.println(Arrays.toString(array));
        }
    }
}

輸出結果

耗時測驗

演算法優化

見下方希爾排序,就是希爾對插入排序的優化,

4.希爾排序

希爾排序是插入排序的一個優化,思考往[2,3,4,5,6]中插入1,需要將所有元素的位置都移動一遍,也就是說在某些極端情況下效率不高,也稱該演算法不穩定

希爾排序是插入排序經過改進之后的一個更高效的版本,也稱為縮小增量排序

基本思想

希爾排序是把記錄按下標的一定增量分組,對每組使用插入排序;

隨著增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至1時,整個序列恰被分成一組,演算法便終止,

和插入排序一樣,從區域到全部,希爾排序是區域再區域,

動圖講解

圖片源于網路

代碼實作

/**
 * 希爾排序
 * Author:一條
 * Date:2021/09/23
 */
public class ShellSort {
    public static void sort(int[] array) {
        System.out.println("希爾排序開始--------");
        //gap初始增量=length/2  逐漸縮小:gap/2
        for (int gap = array.length/2; gap > 0 ; gap/=2) {
            //插入排序 交換法
            for (int i = gap; i < array.length ; i++) {
                int j = i;
                while(j-gap>=0 && array[j]<array[j-gap]){
                    //插入排序采用交換法
                    int temp = array[j];
                    array[j]=array[j-gap];
                    array[j-gap]=temp;
                    j-=gap;
                }
            }
            System.out.println(Arrays.toString(array));
        }
    }
}

輸出結果

耗時測驗

演算法優化

5.快速排序

快速排序(Quicksort)是對冒泡排序的一種改進,相比冒泡排序,每次的交換都是跳躍式的,

基本思想

將要排序的資料分割成獨立的兩部分,其中一部分的所有資料都比另外一部分的所有資料都要小,然后再按此方法對這兩部分資料分別進行快速排序,整個排序程序可以遞回進行,以此達到整個資料變成有序序列,

體現出分治的思想,

動圖講解

代碼實作

思路如下:

  • 首先在這個序列中找一個數作為基準數,為了方便可以取第一個數,
  • 遍歷陣列,將小于基準數的放置于基準數左邊,大于基準數的放置于基準數右邊,此處可用雙指標實作,
  • 此時基準值把陣列分為了兩半,基準值算是已歸位(找到排序后的位置)
  • 利用遞回演算法,對分治后的子陣列進行排序,
public class QuickSort {
    public static void sort(int[] array) {
        System.out.println("快速排序開始---------");
        mainSort(array, 0, array.length - 1);
    }

    private static void mainSort(int[] array, int left, int right) {
        if(left > right) {
            return;
        }
        //雙指標
        int i=left;
        int j=right;
        //base就是基準數
        int base = array[left];
        //左邊小于基準,右邊大于基準
        while (i<j) {
            //先看右邊,依次往左遞減
            while (base<=array[j]&&i<j) {
                j--;
            }
            //再看左邊,依次往右遞增
            while (base>=array[i]&&i<j) {
                i++;
            }
            //交換
            int temp = array[j];
            array[j] = array[i];
            array[i] = temp;
        }
        //最后將基準為與i和j相等位置的數字交換
        array[left] = array[i];
        array[i] = base;
        System.out.println(Arrays.toString(array));
        //遞回呼叫左半陣列
        mainSort(array, left, j-1);
        //遞回呼叫右半陣列
        mainSort(array, j+1, right);
    }
}

輸出結果

逐步分析

  • 6作為基準數,利用左右指標使左邊的數<6,右邊的數>6
  • 對左右兩邊遞回,即左邊用5作為基準數繼續比較,
  • 直到left > right結束遞回,

耗時測驗

演算法優化

優化一

三數取中(median-of-three):我們目前是拿第一個數作為基準數,對于部分有序序列,會浪費回圈,可以用三數取中法優化,感性的小伙伴可自行了解,

優化二

快速排序對于長序列非常快,但對于短序列不如插入排序,可以綜合使用,

完整文章

1eetcode刷題

暴力窮舉被一個3w+字符的測驗用例教做人 [:吐血]

——leetcode此題熱評

Question

難度:中等

給定一個字串 s ,請你找出其中不含有重復字符的 最長子串 的長度,

示例 1:

輸入: s = "abcabcbb"
輸出: 3 
解釋: 因為無重復字符的最長子串是 "abc",所以其長度為 3,

示例 2:

輸入: s = "bbbbb"
輸出: 1
解釋: 因為無重復字符的最長子串是 "b",所以其長度為 1,

示例 3:

輸入: s = "pwwkew"
輸出: 3
解釋: 因為無重復字符的最長子串是 "wke",所以其長度為 3,
請注意,你的答案必須是 子串 的長度,"pwke" 是一個子序列,不是子串,

示例 4:

輸入: s = ""
輸出: 0

提示:

0 <= s.length <= 5 * 104
s 由英文字母、數字、符號和空格組成

Solution

這道題用暴力窮舉法我們不難想到

但是面試,一定會問你,還有別的方法嗎?

有,滑動視窗法

  1. 假設有一個可滑動且大小可變的視窗,視窗左端(start)不動,右端(end)向后移動
  2. 當end遇到重復字符,start應該放在上一個重復字符的位置的后一位,同時記錄最長的長度
  3. 怎樣判斷是否遇到重復字符,且怎么知道上一個重復字符的位置?
  4. 用哈希字典的key來判斷是否重復,用value來記錄該字符的下一個不重復的位置,

Code

class Solution {
    public int lengthOfLongestSubstring(String s) {

        int maxLength=0;
       int start = 0,end=0;
        HashMap<Character, Integer> map = new HashMap<>();
        char[] chars = s.toCharArray();
        while(end<chars.length){
            if (map.containsKey(chars[end])){
                start=Math.max(map.get(chars[end]),start);
            }
            maxLength=Math.max(maxLength,end-start+1);
            map.put(chars[end],end+1);
            end++;
        }
        return maxLength;
    }
}

Result

復雜度分析

  • 時間復雜度:O(N) ,只需要end從0移動到n就可以

20.計算機網路

計算機基礎課,非科班學生提升必備,

21.作業系統

計算機基礎課,非科班學生提升必備,

22.計算機組成原理

計算機基礎課,非科班學生提升必備,

23.程式員英語

因為經常會看一些英文的檔案,四級水平時一定要達到的,

24.寫作能力

即使不做自媒體,寫檔案的能力也要培養起來,

25.演講能力

千萬不要小瞧了這個能力,升職答辯,年終述職必備,

26.管理能力

不可能搞一輩子技術的,總要帶團隊,走管理路線,

配套資料

電子書

1.Java核心技術卷一、二

全書共14章,包括Java基本的程式結構、物件與類、繼承、介面與內部類、圖形程式設計、事件處理、Swing用戶界面組件、部署應用程式和Applet、例外日志斷言和除錯、敘述方式深入淺出,并包含大量示例,從而幫助讀者充分理解Java語言以及Java型別庫的相關特性,

img

2.深入理解Java虛擬機

附帶讀書筆記,

3.java并發編程的藝術

《Java并發編程的藝術》內容涵蓋Java并發編程機制的底層實作原理、Java記憶體模型、Java并發編程基礎、Java中的鎖、并發容器和框架、原子類、并發工具類、執行緒池、Executor框架等主題,每個主題都做了深入的講解,同時通過實體介紹了如何應用這些技術,

4.阿里巴巴Java開發手冊

《阿里巴巴Java開發手冊》是阿里內部Java工程師所遵循的開發規范,涵蓋編程規約、單元測驗規約、例外日志規約、MySQL規約、工程規約、安全規約等,這是近萬名阿里Java技術精英的經驗總結,并經歷了多次大規模一線實戰檢驗及完善,這是阿里回饋給Java社區的一份禮物,希望能夠幫助企業開發團隊在Java開發上更高效、容錯、有協作性,提高代碼質量,降低專案維護成本,

Redis 深度歷險:核心原理與應用實踐

《Redis 深度歷險:核心原理與應用實踐》分為基礎和應用篇、原理篇、集群篇、拓展篇、原始碼篇共 5 大塊內容,基礎和應用篇講解對讀者來說最有價值的內容,可以直接應用到實際作業中;原理篇、集群篇讓開發者透過簡單的技術表面看到精致的底層世界;拓展篇幫助讀者拓展技術視野和夯實基礎,便于進階學習;原始碼篇讓高階的讀者能夠讀懂原始碼,掌握核心技術實力,

Apache Kafka實戰

《Apache Kafka實戰》是一本涵蓋Apache Kafka各方面的具有實踐指導意義的工具書和參考書,作者結合典型的使用場景,對Kafka整個技術體系進行了較為全面的講解,以便讀者能夠舉一反三,直接應用于實踐,同時,本書還對Kafka的設計原理及其流式處理組件進行了較深入的探討,并給出了翔實的案例,

視頻

1.零基礎入門

image-20211012001250513

2.JVM

3.SpringCloud

4.程式員一條

就是我啦,即將更新,哈啊哈哈!

面試集

一條整理

204道全路線面試題合集

204道全路線面試題合集

Java核心知識點

粉絲福利

文中配套資料合集
路線導圖高清源檔案

👇 點擊下方卡片 關注后回復 1024 👇

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/310520.html

標籤:其他

上一篇:技術/研發經理介紹和創業的一些感想

下一篇:Joint Consensus兩階段成員變更的單步實作

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more