03_基礎篇-NumPy(下):深度學習中的常用操作

03_基礎篇-NumPy(下):深度學習中的常用操作

通過上節課的學習,我們已經對NumPy數組有了一定的了解,正所謂實踐出真知,今天我們就以一個圖像分類的項目為例,看看NumPy的在實際項目中都有哪些重要功能。

我們先從一個常見的工作場景出發,互聯網教育推薦平臺,每天都有千萬量級的文字與圖片的廣告信息流入。為了給用戶提供更加精準的推薦,你的老板交代你設計一個模型,讓你把包含各個平臺Logo(比如包含極客時間Logo)的圖片自動找出來。

圖片

想要解決這個圖片分類問題,我們可以分解成數據加載、訓練與模型評估三部分(其實基本所有深度學習的項目都可以這樣劃分)。其中數據加載跟模型評估中,就經常會用到NumPy數組的相關操作。

那么我們先來看看數據的加載。

數據加載階段

這個階段我們要做的就是把訓練數據讀進來,然后給模型訓練使用。訓練數據不外乎這三種:圖片、文本以及類似二維表那樣的結構化數據。

不管使用PyTorch還是TensorFlow,或者是傳統機器學習的scikit-learn,我們在讀入數據這一塊,都會先把數據轉換成NumPy的數組,然后再進行后續的一系列操作。

對應到我們這個項目中,需要做的就是把訓練集中的圖片讀入進來。對于圖片的處理,我們一般會使用Pillow與OpenCV這兩個模塊。

雖然Pillow和OpenCV功能看上去都差不多,但還是有區別的。在PyTorch中,很多圖片的操作都是基于Pillow的,所以當使用PyTorch編程出現問題,或者要思考、解決一些圖片相關問題時,要從Pillow的角度出發。

下面我們先以單張圖片為例,將極客時間的那張Logo圖片分別用Pillow與OpenCV讀入,然后轉換為NumPy的數組。

Pillow方式

首先,我們需要使用Pillow中的下述代碼讀入上面的圖片。

from PIL import Image
im = Image.open('jk.jpg')
im.size
輸出: 318, 116

Pillow是以二進制形式讀入保存的,那怎么轉為NumPy格式呢?這個并不難,我們只需要利用NumPy的asarray方法,就可以將Pillow的數據轉換為NumPy的數組格式。

import numpy as npim_pillow = np.asarray(im)im_pillow.shape
輸出:(116, 318, 3)

OpenCV方式:

OpenCV的話,不再需要我們手動轉格式,它直接讀入圖片后,就是以NumPy數組的形式來保存數據的,如下面的代碼所示。

import cv2
im_cv2 = cv2.imread('jk.jpg')
type(im_cv2)
輸出:numpy.ndarrayim_cv2.shape
輸出:(116, 318, 3)

結合代碼輸出可以發現,我們讀入后的數組的最后一個維度是3,這是因為圖片的格式是RGB格式,表示有R、G、B三個通道。對于計算視覺任務來說,絕大多數處理的圖片都是RGB格式,如果不是RGB格式的話,要記得事先轉換成RGB格式。
這里有個地方需要你關注,Pillow讀入后通道的順序就是R、G、B,而OpenCV讀入后順序是B、G、R

模型訓練時的通道順序需與預測的通道順序要保持一致。也就是說使用Pillow訓練,使用OpenCV讀入圖片直接進行預測的話,不會報錯,但結果會不正確,所以大家一定要注意。

接下來,我們就驗證一下Pillow與OpenCV讀入數據通道的順序是否如此,借此引出有關Numpy數組索引與切片、合并等常見問題。

怎么驗證這條結論呢?只需要將R、G、B三個通道的數據單獨提取出來,然后令另外兩個通道的數據全為0即可。

這里我給你說說為什么這樣做。RGB色彩模式是工業界的一種顏色標準,RGB分別代表紅、綠、藍三個通道的顏色,將這三種顏色混合在一起,就形成了我們眼睛所能看到的所有顏色。

RGB三個通道各有256個亮度,分別用數字0到255表示,數字越高代表亮度越強,數字0則是代表最弱的亮度。在我們的例子中,如果一個通道的數據再加另外兩個全0的通道(相當于關閉另外兩個通道),最終圖像以紅色格調(可以先看一下后文中的最終輸出結果)呈現出來的話,我們就可以認為該通道的數據是來源于R通道,G與B通道的證明同樣可以如此。

好,首先我們提取出RGB三個通道的數據,這可以從數組的索引與切片說起。

索引與切片

如果你了解Python,那么索引和切片的概念你應該不陌生。

就像圖書目錄里的索引,我們可以根據索引標注的頁碼快速找到需要的內容,而Python

里的索引也是同樣的功能,它用來定位數組中的某一個值。而切片意思就相當于提取圖書中從某一頁到某一頁的內容。

NumPy數組的索引方式與Python的列表的索引方式相同,也同樣支持切片索引。

這里需要你注意的是在NumPy數組中經常會出現用冒號來檢索數據的形式,如下所示:

im_pillow[:, :, 0]

這是什么意思呢?我們一起來看看。“:”代表全部選中的意思。我們的圖片讀入后,會以下圖的狀態保存在數組中。

上述代碼的含義就是取第三個維度索引為0的全部數據,換句話說就是,取圖片第0個通道的所有數據。

這樣的話,通過下面的代碼,我們就可以獲得每個通道的數據了。

im_pillow_c1 = im_pillow[:, :, 0]
im_pillow_c2 = im_pillow[:, :, 1]
im_pillow_c3 = im_pillow[:, :, 2]

獲得了每個通道的數據,接下來就需要生成一個全0數組,該數組要與im_pillow具有相同的寬高。

全0數組你還記得怎么生成嗎?可以自己先思考一下,生成的代碼如下所示。

zeros = np.zeros((im_pillow.shape[0], im_pillow.shape[1], 1))
zeros.shape
輸出:(116, 318, 1)

然后,我們只需要將全0的數組與im_pillow_c1、im_pillow_c2、im_pillow_c3進行拼接,就可以獲得對應通道的圖像數據了。

數組的拼接

剛才我們拿到了單獨通道的數據,接下來就需要把一個分離出來的數據跟一個全0數組拼接起來。如下圖所示,紅色的可以看作單通道數據,白色的為全0數據。

圖片

NumPy數組為我們提供了np.concatenate((a1, a2, …), axis=0)方法進行數組拼接。其中,a1,a2, …就是我們要合并的數組;axis是我們要沿著哪一個維度進行合并,默認是沿著0軸方向。

對于我們的問題,是要沿著2軸的方向進行合并,也是我們最終的目標是要獲得下面的三幅圖像。

那么,我們先將im_pillow_c1與全0數組進行合并,生成上圖中最左側的數組,有了圖像的數組才能獲得最終圖像。合并的代碼跟輸出結果如下:

im_pillow_c1_3ch = np.concatenate((im_pillow_c1, zeros, zeros),axis=2)
---------------------------------------------------------------------------
AxisError                                 Traceback (most recent call last)
<ipython-input-21-e3d53c33c94d> in <module>
----> 1 im_pillow_c1_3ch = np.concatenate((im_pillow_c1, zeros, zeros),axis=2)
<__array_function__ internals> in concatenate(*args, **kwargs)
AxisError: axis 2 is out of bounds for array of dimension 2

看到這里你可能很驚訝,竟然報錯了?錯誤的原因是在2維數組中,axis如果等于2的話會越界。

我們看看im_pillow_c1與zeros的形狀。

im_pillow_c1.shape
輸出:(116, 318)
zeros.shape
輸出:(116, 318, 1)

原來是我們要合并的兩個數組維度不一樣啊。那么如何統一維度呢?將im_pillow_c1變成(116, 318, 1)即可。

方法一:使用np.newaxis

我們可以使用np.newaxis讓數組增加一個維度,使用方式如下。

im_pillow_c1 = im_pillow_c1[:, :, np.newaxis]
im_pillow_c1.shape
輸出:(116, 318, 1)

運行上面的代碼,就可以將2個維度的數組轉換為3個維度的數組了。
這個操作在你看深度學習相關代碼的時候經常會看到,只不過PyTorch中的函數名unsqueeze(), TensorFlow的話是與NumPy有相同的名字,直接使用tf.newaxis就可以了。

然后我們再次將im_pillow_c1與zeros進行合并,這時就不會報錯了,代碼如下所示:

im_pillow_c1_3ch = np.concatenate((im_pillow_c1, zeros, zeros),axis=2)
im_pillow_c1_3ch.shape
輸出:(116, 318, 3)
方法二:直接賦值

增加維度的第二個方法就是直接賦值,其實我們完全可以生成一個與im_pillow形狀完全一樣的全0數組,然后將每個通道的數值賦值為im_pillow_c1、im_pillow_c2與im_pillow_c3就可以了。我們用這種方式生成上圖中的中間與右邊圖像的數組。

im_pillow_c2_3ch = np.zeros(im_pillow.shape)
im_pillow_c2_3ch[:,:,1] = im_pillow_c2im_pillow_c3_3ch = np.zeros(im_pillow.shape)
im_pillow_c3_3ch[:,:,2] = im_pillow_c3

這樣的話,我們就可以將三個通道的RGB圖片打印出來了。
關于繪圖,你可以使用matplotlib進行繪圖,它是NumPy的繪圖庫。如果你需要繪圖,可以在這個網站上找到各種各樣的例子,然后根據它提供的代碼進行修改,具體如何繪圖我就不展開了。

說回我們的通道順序驗證問題,完成前面的操作后,你可以用下面的代碼將原圖、R通道、G通道與B通道的4幅圖打印出來,你看是不是RGB順序的呢?

from matplotlib import pyplot as plt
plt.subplot(2, 2, 1)
plt.title('Origin Image')
plt.imshow(im_pillow)
plt.axis('off')
plt.subplot(2, 2, 2)
plt.title('Red Channel')
plt.imshow(im_pillow_c1_3ch.astype(np.uint8))
plt.axis('off')
plt.subplot(2, 2, 3)
plt.title('Green Channel')
plt.imshow(im_pillow_c2_3ch.astype(np.uint8))
plt.axis('off')
plt.subplot(2, 2, 4)
plt.title('Blue Channel')
plt.imshow(im_pillow_c3_3ch.astype(np.uint8))
plt.axis('off')
plt.savefig('./rgb_pillow.png', dpi=150)

圖片

深拷貝(副本)與淺拷貝(視圖)

剛才我們通過獲取圖片通道數據的練習,不過操作確實比較繁瑣,介紹這些方法也主要是為了讓你掌握切片索引和數組拼接的知識點。

其實我們還有一種更加簡單的方式獲得三個通道的BGR數據,只需要將圖片讀入后,直接將其中的兩個通道賦值為0即可。代碼如下所示:

from PIL import Image
import numpy as npim = Image.open('jk.jpg')
im_pillow = np.asarray(im)
im_pillow[:,:,1:]=0
輸出:
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-146-789bda58f667> in <module>4 im = Image.open('jk.jpg')5 im_pillow = np.asarray(im)
----> 6 im_pillow[:,:,1:-1]=0
ValueError: assignment destination is read-only

運行剛才的代碼,報錯提示說數組是只讀數組,沒辦法進行修改。那怎么辦呢?我們可以使用copy來復制一個數組。
說到copy()的話,就要說到淺拷貝與深拷貝的概念,[上節課]我們說到創建數組時就提過,np.array()屬于深拷貝,np.asarray()則是淺拷貝。

簡單來說,淺拷貝或稱視圖,指的是與原數組共享數據的數組,請注意,只是數據,沒有說共享形狀。視圖我們通常使用view()來創建。常見的切片操作也會返回對原數組的淺拷貝。

請看下面的代碼,數組a與b的數據是相同的,形狀確實不同,但是修改b中的數據后,a的數據同樣會發生變化。

a = np.arange(6)
print(a.shape)
輸出:(6,)
print(a)
輸出:[0 1 2 3 4 5]b = a.view()
print(b.shape)
輸出:(6,)
b.shape = 2, 3
print(b)
輸出:[[0 1 2][3 4 5]]
b[0,0] = 111
print(a)
輸出:[111   1   2   3   4   5]
print(b)
輸出:[[111   1   2][  3   4   5]]

而深拷貝又稱副本,也就是完全復制原有數組,創建一個新的數組,修改新的數組不會影響原數組。深拷貝使用copy()方法。

所以,我們將剛才報錯的程序修改成下面的形式就可以了。

im_pillow = np.array(im)
im_pillow[:,:,1:]=0

可別小看深拷貝和淺拷貝的區別。這里講一個我以前遇到的坑吧,我曾經要開發一個部署在手機端的人像分割模型。

為了提高模型的分割效果,我考慮了新的實驗方法——將前一幀的數據也作為當前幀的輸入進行考慮,訓練階段沒有發生問題,但是在調試階段發現模型的效果非常差。

后來經過研究,我才發現了問題的原因。原因是我為了可視化分割效果,我將前一幀的數據進行變換打印出來。同時,我錯誤的采用了淺拷貝的方式,將前一幀的數據傳入當前幀,所以說傳入到當前幀的數據是經過變化的,而不是原始的輸出。

這時再傳入當前幀,自然無法得到正確結果。當時因為這個坑,差點產生要放棄這個實驗的想法,后面改成深拷貝才解決了問題。

好了,講到這里,你是否可以用上述的方法對OpenCV讀取圖片讀入通道順序進行一下驗證呢?

模型評估

在模型評估時,我們一般會將模型的輸出轉換為對應的標簽。

假設現在我們的問題是將圖片分為2個類別,包含極客時間的圖片與不包含的圖片。模型會輸出形狀為(2, )的數組,我們把它叫做probs,它存儲了兩個概率,我們假設索引為0的概率是包含極客時間圖片的概率,另一個是其它圖片的概率,它們兩個概率的和為1。如果極客時間對應的概率大,則可以推斷該圖片為包含極客時間的圖片,否則為其他圖片。

簡單的做法就是判斷probs[0]是否大于0.5,如果大于0.5,則可以認為圖片是我們要尋找的。

這種方法固然可以,但是如果我們需要判斷圖片的類別有很多很多種呢?

例如,有1000個類別的ImageNet。也許你會想到遍歷這個數組,求出最大值對應的索引。

那如果老板讓你找出概率最大的前5個類別呢?有沒有更簡單點的方法?我們繼續往下看。

Argmax Vs Argmin:求最大/最小值對應的索引

NumPy的argmax(a, axis=None)方法可以為我們解決求最大值索引的問題。如果不指定axis,則將數組默認為1維。

對于我們的問題,使用下述代碼即可獲得擁有最大概率值的圖片。

np.argmax(probs)

Argmin的用法跟Argmax差不多,不過它的作用是獲得具有最小值的索引。

Argsort:數組排序后返回原數組的索引

那現在我們再把問題升級一下,比如需要你將圖片分成10個類別,要找到具有最大概率的前三個類別。

模型輸出的概率如下:

probs = np.array([0.075, 0.15, 0.075, 0.15, 0.0, 0.05, 0.05, 0.2, 0.25])

這時,我們就可以借助argsort(a, axis=-1, kind=None)函數來解決該問題。np.argsort的作用是對原數組進行從小到大的排序,返回的是對應元素在原數組中的索引。
np.argsort包括后面這幾個關鍵參數:

  • a是要進行排序的原數組;
  • axis是要沿著哪一個軸進行排序,默認是-1,也就是最后一個軸;
  • kind是采用什么算法進行排序,默認是快速排序,還有其他排序算法,具體你可以看看數據結構的排序算法。

我們還是結合例子來理解,你可以看看下面的代碼,它描述了我們使用argsort對probs進行排序,然后返回對應坐標的全過程。

probs_idx_sort = np.argsort(-probs)  #注意,加了負號,是按降序排序
probs_idx_sort
輸出:array([8, 7, 1, 3, 0, 2, 5, 6, 4])
#概率最大的前三個值的坐標
probs_idx_sort[:3]
輸出:array([8, 7, 1])

小結

恭喜你,完成了這一節課的學習。這一節介紹了一些常用且重要的功能。幾乎在所有深度學習相關的項目中,你都會常常用到這些函數,當你閱讀別人的代碼的時候也會經常看到。

讓我們一起來復習一下今天學到的這些函數,我畫了一張表格,給你總結了它們各自的關鍵功能和使用要點。

我覺得NumPy最難懂的還是上節課的軸,如果你把軸的概念理解清楚之后,理解今天的內容會更加輕松。理解了原理之后,關鍵還是動手練習。

每課一練

給定數組scores,形狀為(256,256,2),scores[: , :, 0] 與scores[:, :, 1]對應位置元素的和為1,現在我們要根據scores生產數組mask,要求scores通道0的值如果大于通道1的值,則mask對應的位置為0,否則為1。

scores如下,你可以試試用代碼實現:

scores = np.random.rand(256, 256, 2)
scores[:,:,1] = 1 - scores[:,:,0]

歡迎你在留言區記錄你的疑問或者收獲,也推薦你把這節課分享給你的朋友。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/81894.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/81894.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/81894.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

時鐘識別項目報告(深度學習、計算機視覺)

深度學習方式 一、模型架構 本模型采用雙任務學習框架&#xff0c;基于經典殘差網絡實現時鐘圖像的小時和分鐘同步識別。 主干網絡 使用預訓練的ResNet18作為特征提取器&#xff0c;移除原分類層&#xff08;fc層&#xff09;&#xff0c;保留全局平均池化后的512維特征向量。…

openai-whisper-asr-webservice接入dify

openai-whisper-asr-webservice提供的asr的api其實并不兼容openai的api&#xff0c;所以在dify中是不能直接添加到語音轉文字的模型中&#xff0c;對比了下兩個api的傳參情況&#xff0c;其實只要改動一處&#xff0c;就能支持&#xff1a; openai兼容的asr調用中formdata中音頻…

解鎖MySQL性能調優:高級SQL技巧實戰指南

高級SQL技巧&#xff1a;解鎖MySQL性能調優的終極指南 開篇 當前&#xff0c;隨著業務系統的復雜化和數據量的爆炸式增長&#xff0c;數據庫性能調優成為了技術人員面臨的核心挑戰之一。尤其是在高并發、大數據量的場景下&#xff0c;SQL 查詢的性能直接影響到整個系統的響應…

JavaScript 性能優化實戰指南

JavaScript 性能優化實戰指南 前言 隨著前端應用復雜度提升&#xff0c;JavaScript 性能瓶頸日益突出。高效的性能優化不僅能提升用戶體驗&#xff0c;還能增強系統穩定性和可維護性。本文系統梳理了 JavaScript 性能優化的核心思路、常見場景和實戰案例&#xff0c;結合代碼…

服務器磁盤按陣列劃分為哪幾類

以下是服務器磁盤陣列&#xff08;RAID&#xff09;的詳細分類及技術解析&#xff0c;基于現行行業標準與實踐應用&#xff1a; 一、主流RAID級別分類 1. ?RAID 0&#xff08;條帶化&#xff09;? ?技術原理?&#xff1a;數據分塊后并行寫入多塊磁盤&#xff0c;無…

鴻蒙 Location Kit(位置服務)

移動終端設備已經深入人們日常生活的方方面面&#xff0c;如查看所在城市的天氣、新聞軼事、出行打車、旅行導航、運動記錄。這些習以為常的活動&#xff0c;都離不開定位用戶終端設備的位置。 Location Kit 使用多種定位技術提供服務&#xff0c;可以準確地確定設備在室外/室…

二叉樹深搜:在算法森林中尋找路徑

專欄&#xff1a;算法的魔法世界 個人主頁&#xff1a;手握風云 目錄 一、搜索算法 二、回溯算法 三、例題講解 3.1. 計算布爾二叉樹的值 3.2. 求根節點到葉節點數字之和 3.3. 二叉樹剪枝 3.4. 驗證二叉搜索樹 3.5. 二叉搜索樹中第 K 小的元素 3.6. 二叉樹的所有路徑 …

企業級AI搜索解決方案:阿里云AI搜索開放平臺

隨著信息技術的飛速發展&#xff0c;搜索引擎作為信息獲取的重要工具&#xff0c;扮演著不可或缺的角色。阿里云 AI 搜索開放平臺以其強大的技術支持和靈活的開放性&#xff0c;持續為用戶提供高效的搜索解決方案。 一、阿里云 AI 搜索開放平臺 一站式的 AI 搜索開放平臺作為…

自動駕駛中的預測控制算法:用 Python 讓無人車更智能

自動駕駛中的預測控制算法:用 Python 讓無人車更智能 自動駕駛技術近年來取得了令人驚嘆的進步,AI 與邊緣計算的結合讓車輛能夠實時感知環境、規劃路徑并執行駕駛決策。其中,預測控制(Model Predictive Control,MPC) 作為一種先進的控制算法,憑借其對未來駕駛行為的優化…

量子計算機超越超級計算機——它們解決了哪些問題?

“ 南加州大學的研究人員取得了重大突破&#xff0c;證明量子計算機在解決某些復雜問題時甚至可以勝過最快的超級計算機。” 量子退火最終顯示出擴展優勢&#xff0c;得益于錯誤抑制的量子處理&#xff0c;它比傳統超級計算機提供更快、接近最優的解決方案。 南加州大學的研究人…

Java虛擬機 -方法調用

方法調用 方法調用靜態鏈接動態鏈接案例虛方法與非虛方法虛方法&#xff08;Virtual Method&#xff09;非虛方法&#xff08;Non-Virtual Method&#xff09; 方法返回地址 方法調用 我們編寫Java程序的時候&#xff0c;我們自己寫的類通常不僅僅是調用自己本類的方法。調用別…

【 開源:跨平臺網絡數據傳輸的萬能工具libcurl】

在當今這個互聯互通的世界中&#xff0c;數據在各種設備和平臺之間自由流動&#xff0c;而 libcurl&#xff0c;就像一把跨平臺的萬能工具&#xff0c;為開發者提供了處理各種網絡數據傳輸任務所需的強大功能。它不僅是一個庫&#xff0c;更是一種通用的解決方案&#xff0c;可…

ElasticSearch 8.x 快速上手并了解核心概念

目錄 核心概念概念總結 常見操作索引的常見操作常見的數據類型指定索引庫字段類型mapping查看索引庫的字段類型最高頻使用的數據類型 核心概念 在新版Elasticsearch中&#xff0c;文檔document就是一行記錄(json)&#xff0c;而這些記錄存在于索引庫(index)中, 索引名稱必須是…

優化 CRM 架構,解鎖企業競爭力密碼

引言 “在所有企業面臨的挑戰中&#xff0c;客戶關系管理無疑是最為關鍵的一環。” —— 彼得德魯克 在數字化浪潮席卷的當下&#xff0c;企業面臨著前所未有的機遇與挑戰。客戶關系管理&#xff08;CRM&#xff09;作為企業運營的核心環節&#xff0c;其架構的優劣直接影響著…

深入理解Docker和K8S

深入理解Docker和K8S Docker 是大型架構的必備技能&#xff0c;也是云原生核心。Docker 容器化作為一種輕量級的虛擬化技術&#xff0c;其核心思想&#xff1a;將應用程序及其所有依賴項打包在一起&#xff0c;形成一個可移植的單元。 容器的本質是進程&#xff1a; 容器是在…

list.forEach(s -> countService.refreshArticleStatisticInfo(s.getId())); 講解一下語法

這段代碼使用了Java中的forEach方法結合Lambda表達式來遍歷一個列表&#xff0c;并對列表中的每個元素執行特定操作。具體來說&#xff0c;它會遍歷列表中的每一個元素&#xff0c;并調用countService.refreshArticleStatisticInfo(s.getId())方法來刷新每個文章的統計信息。下…

AI開發者的算力革命:GpuGeek平臺全景實戰指南(大模型訓練/推理/微調全解析)

目錄 背景一、AI工業化時代的算力困局與破局之道1.1 中小企業AI落地的三大障礙1.2 GpuGeek的破局創新1.3 核心價值 二、GpuGeek技術全景剖析2.1 核心架構設計 三、核心優勢詳解?3.1 優勢1&#xff1a;工業級顯卡艦隊???3.2 優勢2&#xff1a;開箱即用生態?3.2.1 預置鏡像庫…

05算法學習_59. 螺旋矩陣 II

05算法學習_59. 螺旋矩陣 II 05算法學習_59. 螺旋矩陣 II題目描述&#xff1a;個人代碼&#xff1a;學習思路&#xff1a;第一種寫法&#xff1a;題解關鍵點&#xff1a; 個人學習時疑惑點解答&#xff1a; 05算法學習_59. 螺旋矩陣 II 力扣題目鏈接: 59. 螺旋矩陣 II 題目描…

JDK7Hashmap的頭插法造成的環問題

單線程下的擴容 多線程下的擴容 next&#xff1d;e 然后e的next變成e

JAVA|后端編碼規范

目錄 零、引言 一、基礎 二、集合 三、并發 四、日志 五、安全 零、引言 規范等級&#xff1a; 【強制】&#xff1a;強制遵守&#xff0c;來源于線上歷史故障&#xff0c;將通過工具進行檢查。【推薦】&#xff1a;推薦遵守&#xff0c;來源于日常代碼審查、開發人員反饋…