Java中ArrayList實(shí)現(xiàn)原理及基本方法
1、ArrayList的數(shù)據(jù)結(jié)構(gòu)
ArrayList是開發(fā)中非常常用的數(shù)據(jù)存儲(chǔ)容器之一,其底層是 數(shù)組 實(shí)現(xiàn)的
我們可以在集合中存儲(chǔ)任意類型的數(shù)據(jù),ArrayList是線程不安全的,擅長(zhǎng)隨機(jī)訪問元素,插入和刪除較慢 。
ArrayList的底層數(shù)據(jù)結(jié)構(gòu)就是一個(gè) 數(shù)組 ,數(shù)組元素的類型為Object類型,對(duì)ArrayList的所有操作底層都是基于數(shù)組的。
2、ArrayList是線程不安全的
對(duì)ArrayList進(jìn)行添加元素的操作的時(shí)候是分兩個(gè)步驟進(jìn)行的:
- 先在
object[size]的位置上存放需要添加的元素; - 將
size的值增加1。
由于這個(gè)過程在多線程的環(huán)境下是不能保證具有原子性 的,因此ArrayList在多線程的環(huán)境下是線程不安全的。
在單線程運(yùn)行的情況下,如果Size = 0,添加一個(gè)元素后,此元素在位置 0,而且Size=1;
而如果是在多線程情況下,比如有兩個(gè)線程,線程 A 先將元素存放在位置0。
但是此時(shí) CPU 調(diào)度線程A暫停,線程 B 得到運(yùn)行的機(jī)會(huì)。
線程B也向此ArrayList 添加元素,因?yàn)榇藭r(shí) Size 仍然等于 0
(注意:我們假設(shè)的是添加一個(gè)元素是要兩個(gè)步驟哦,而線程A僅僅完成了步驟1),所以線程B也將 元素存放在位置0。
然后線程A和線程B都繼續(xù)運(yùn)行,都增 加 Size 的值。
那好,現(xiàn)在我們來看看ArrayList 的情況,元素實(shí)際上只有一個(gè),存放在位置 0,而Size卻等于 2。這就是“線程不安全”了。
如果非要在多線程的環(huán)境下使用ArrayList,就需要保證它的線程安全性,通常有兩種解決辦法:
- 使用synchronized關(guān)鍵字;
- 可以用Collections類中的靜態(tài)方法synchronizedList(),對(duì)ArrayList進(jìn)行調(diào)用即可。
3、ArrayList的實(shí)現(xiàn)
對(duì)于ArrayList而言,它實(shí)現(xiàn)List接口、底層使用數(shù)組保存所有元素。其操作基本上是對(duì)數(shù)組的操作。
下面我們來分析ArrayList的源代碼:
1) 私有屬性:
ArrayList定義只定義類兩個(gè)私有屬性:
/** *ArrayList 的元素存儲(chǔ)在其中的數(shù)組緩沖區(qū)。 *ArrayList 的容量就是這個(gè)數(shù)組緩沖區(qū)的長(zhǎng)度。添加第一個(gè)元素時(shí),任何帶有 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的空 ArrayList 都將擴(kuò)展為 DEFAULT_CAPACITY(10)。 */ transient Object[] elementData; // ArrayList 的大小 private int size;
Java的serialization提供了一種持久化對(duì)象實(shí)例的機(jī)制。
當(dāng)持久化對(duì)象時(shí),可能有一個(gè)特殊的對(duì)象數(shù)據(jù)成員,我們不想用serialization機(jī)制來保存它。
為了在一個(gè)特定對(duì)象的一個(gè)域上關(guān)閉serialization,可以在這個(gè)域前加上關(guān)鍵字transient。
有點(diǎn)抽象,看個(gè)例子應(yīng)該能明白:
public class UserInfo implements Serializable {
private static final long serialVersionUID = 996890129747019948L;
private String name;
private transient String psw;
public UserInfo(String name, String psw) {
this.name = name;
this.psw = psw;
}
public String toString() {
return "name=" + name + ", psw=" + psw;
}
}
public class TestTransient {
public static void main(String[] args) {
UserInfo userInfo = new UserInfo("張三", "123456");
System.out.println(userInfo);
try {
// 序列化,被設(shè)置為transient的屬性沒有被序列化
ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("UserInfo.out"));
o.writeObject(userInfo);
o.close();
} catch (Exception e) {
e.printStackTrace();
}
try {
// 重新讀取內(nèi)容
ObjectInputStream in = new ObjectInputStream(new FileInputStream("UserInfo.out"));
UserInfo readUserInfo = (UserInfo) in.readObject();
// 讀取后psw的內(nèi)容為null
System.out.println(readUserInfo.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}被標(biāo)記為transient的屬性在對(duì)象被序列化的時(shí)候不會(huì)被保存?;氐紸rrayList的分析中
2) 構(gòu)造方法:
ArrayList提供了三種方式的構(gòu)造器,可以構(gòu)造一個(gè)默認(rèn)初始容量為10的空列表、構(gòu)造一個(gè)指定初始容量的空列表以及構(gòu)造一個(gè)包含指定collection的元素的列表,這些元素按照該collection的迭代器返回它們的順序排列的。
/**
* 構(gòu)造一個(gè)具有指定初始容量的空列表。
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
/**
* 構(gòu)造一個(gè)默認(rèn)初始容量為10的空列表
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 構(gòu)造一個(gè)包含指定集合元素的列表,按照集合的迭代器返回的順序。
*/
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// 替換為空數(shù)組。
elementData = EMPTY_ELEMENTDATA;
}
}3) 元素存儲(chǔ):
ArrayList提供了set(int index, E element)、add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)這些添加元素的方法。
下面我們一一講解:
// 用指定的元素替代此列表中指定位置上的元素,并返回以前位于該位置上的元素。
public E set(int index, E element) {
RangeCheck(index);
E oldValue = (E) elementData[index];
elementData[index] = element;
return oldValue;
}
// 將指定的元素添加到此列表的尾部。
public boolean add(E e) {
// 增加modCount
ensureCapacity(size + 1);
elementData[size++] = e;
return true;
}
// 將指定的元素插入此列表中的指定位置。
// 如果當(dāng)前位置有元素,則向右移動(dòng)位于該位置的元素及所有后續(xù)元素(其索引加1)。
public void add(int index, E element) {
// if (index > size || index < 0)
// throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
rangeCheckForAdd(index);
// 如果數(shù)組長(zhǎng)度不足,將進(jìn)行擴(kuò)容。
ensureCapacity(size + 1); // 增加modCount
// 將 elementData中從Index位置開始、長(zhǎng)度為size-index的元素,
// 拷貝到從下標(biāo)為index+1位置開始的新的elementData數(shù)組中。
// 即將當(dāng)前位于該位置的元素以及所有后續(xù)元素右移一個(gè)位置。
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}
// 按照指定collection的迭代器所返回的元素順序,將該collection中的所有元素添加到此列表的尾部。
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacity(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
// 從指定的位置開始,將指定collection中的所有元素插入到此列表中。
public boolean addAll(int index, Collection<? extends E> c) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacity(size + numNew); // 增加modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew, numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}ArrayList是基于數(shù)組實(shí)現(xiàn)的,屬性中也看到了數(shù)組,具體是怎么實(shí)現(xiàn)的呢?
比如就這個(gè)添加元素的方法,如果數(shù)組大,則在將某個(gè)位置的值設(shè)置為指定元素即可,如果數(shù)組容量不夠了呢?
看到add(E e)中先調(diào)用了ensureCapacity(size+1)方法,之后將元素的索引賦給elementData[size], 而后size自增。
例如初次添加時(shí),size為0,add將elementData[0]賦值為e,然后size設(shè)置為1(類似執(zhí)行以下兩條語(yǔ)句elementData[0]=e;size=1)。
將元素的索引賦給elementData[size]不是會(huì)出現(xiàn)數(shù)組越界的情況嗎?這里關(guān)鍵就在ensureCapacity(size+1)中了。
4) 元素讀?。?/h3>
// 返回此列表中指定位置的元素。
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
// 返回此列表中指定位置的元素。
public E get(int index) {
rangeCheck(index);
return elementData(index);
}5) 元素刪除:
ArrayList提供了根據(jù)下標(biāo)或者指定對(duì)象兩種方式的刪除功能。
如下:
// 刪除此列表中指定位置的元素。將任何后續(xù)元素向左移動(dòng)(從它們的索引中減去一個(gè))。
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}首先是檢查范圍,修改modCount,保留將要被移除的元素,將移除位置之后的元素向前挪動(dòng)一個(gè)位置,將list末尾元素置空(null),返回被移除的元素。
// 從此列表中刪除第一次出現(xiàn)的指定元素(如果存在)。如果列表不包含該元素,則保持不變。
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
// 私有刪除方法,跳過邊界檢查并且不返回刪除的值。
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null;
}首先通過代碼可以看到,當(dāng)移除成功后返回true,否則返回 false。remove(Object o)中通過遍歷 element 尋找是否存在傳入對(duì)象,一旦找到就調(diào)用 fastRemove 移除對(duì)象。
為什么找到了元素就知道了index,不通過remove(index) 來移除元素呢?
因?yàn)?fastRemove 跳過了判斷邊界的處理,找到元素就相當(dāng)于確定了 index 不會(huì)超過邊界,而且 fastRemove 并不返回被移除的元素。
// 從此列表中刪除索引介于兩者之間的所有元素
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved);
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}執(zhí)行過程是將 elementData 從 toIndex 位置開始的元素向前移動(dòng)到 fromIndex,然后將 toIndex 位置之后的元素全部置空順便修改size。
這個(gè)方法是protected,及受保護(hù)的方法,為什么這個(gè)方法被定義為protected呢? 先看下面這個(gè)例子
ArrayList<Integer> ints = new ArrayList<Integer>(Arrays.asList(0, 1, 2, 3, 4, 5, 6)); ints.subList(2, 4).clear(); System.out.println(ints);
輸出結(jié)果是[0, 1, 4, 5, 6],結(jié)果是不是像調(diào)用了 removeRange(int fromIndex,int toIndex)!
6) 調(diào)整數(shù)組容量ensureCapacity:
從上面介紹的向ArrayList中存儲(chǔ)元素的代碼中,我們看到,每當(dāng)向數(shù)組中添加元素時(shí),都要去檢查添加后元素的個(gè)數(shù)是否會(huì)超出當(dāng)前數(shù)組的長(zhǎng)度,如果超出,數(shù)組將會(huì)進(jìn)行擴(kuò)容,以滿足添加數(shù)據(jù)的需求。
數(shù)組擴(kuò)容通過一個(gè)公開的方法 ensureCapacity(int minCapacity) 來實(shí)現(xiàn)。
在實(shí)際添加大量元素前,我也可以使用ensureCapacity來手動(dòng)增加ArrayList實(shí)例的容量,以減少遞增式再分配的數(shù)量。
public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3) / 2 + 1; // 增加50%+1
if (newCapacity < minCapacity)
newCapacity = minCapacity;
// 通常接近 size
elementData = Arrays.copyOf(elementData, newCapacity);
}
}從上述代碼中可以看出,數(shù)組進(jìn)行擴(kuò)容時(shí),會(huì)將老數(shù)組中的元素重新拷貝一份到新的數(shù)組中,每次數(shù)組 容量的增長(zhǎng)大約是其原容量的1.5倍。
這種操作的代價(jià)是很高的,因此在實(shí)際使用時(shí),我們應(yīng)該盡量避 免數(shù)組容量的擴(kuò)張。
當(dāng)我們可預(yù)知要保存的元素的多少時(shí),要在構(gòu)造ArrayList實(shí)例時(shí),就指定其容量, 以避免數(shù)組擴(kuò)容的發(fā)生。
或者根據(jù)實(shí)際需求,通過調(diào)用ensureCapacity方法來手動(dòng)增加ArrayList實(shí)例的容量。
// 為什么要用到 oldData[]Object oldData[] = elementData;
乍一看來后面并沒有用到關(guān)于oldData, 這句話顯得多此一舉!但是這是一個(gè)牽涉到內(nèi)存管理的類, 所以要 了解內(nèi)部的問題 。
而且為什么這一句還在if的內(nèi)部,這跟 elementData = Arrays.copyOf(elementData, newCapacity); 這句是有關(guān)系的,下面這句Arrays.copyOf的實(shí)現(xiàn)時(shí)新創(chuàng)建了newCapacity大小的內(nèi)存,然后把老的elementData放入。
好像也沒有用到oldData,有什么問題 呢。
問題就在于舊的內(nèi)存的引用是elementData, elementData指向了新的內(nèi)存塊,如果有一個(gè)局部變量oldData變量引用舊的內(nèi)存塊的話,在copy的過程中就會(huì)比較安全,因?yàn)檫@樣證明這塊老的內(nèi)存依 然有引用,分配內(nèi)存的時(shí)候就不會(huì)被侵占掉,然后copy完成后這個(gè)局部變量的生命期也過去了,然后釋放才是安全的。
不然在copy的的時(shí)候萬(wàn)一新的內(nèi)存或其他線程的分配內(nèi)存侵占了這塊老的內(nèi)存,而 copy 還沒有結(jié)束,這將是個(gè)嚴(yán)重的事情。
ArrayList還給我們提供了將底層數(shù)組的容量調(diào)整為當(dāng)前列表保存的實(shí)際元素的大小的功能。
它可以通過trimToSize方法來實(shí)現(xiàn)。
代碼如下:
// 將此ArrayList實(shí)例的容量修剪為列表的當(dāng)前大小??梢允褂么瞬僮鱽碜钚』疉rrayList實(shí)例的存儲(chǔ)空間。
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size);
}
}由于elementData的長(zhǎng)度會(huì)被拓展,size標(biāo)記的是其中包含的元素的個(gè)數(shù)。
所以會(huì)出現(xiàn)size很小但elementData.length很大的情況,將出現(xiàn)空間的浪費(fèi)。
trimToSize將返回一個(gè)新的數(shù)組給elementData,元素內(nèi)容保持不變,length和size相同,節(jié)省空間。
7) 轉(zhuǎn)為靜態(tài)數(shù)組toArray
注意ArrayList的兩個(gè)轉(zhuǎn)化為靜態(tài)數(shù)組的toArray方法。
第一個(gè),調(diào)用Arrays.copyOf將返回一個(gè)數(shù)組,數(shù)組內(nèi)容是size個(gè)elementData的元素,即拷貝
elementData從0至size-1位置的元素到新數(shù)組并返回。
// 返回一個(gè)數(shù)組,該數(shù)組以適當(dāng)?shù)捻樞虬肆斜碇械乃性亍?
public Object[] toArray() { return Arrays.copyOf(elementData, size);}第二個(gè),如果傳入數(shù)組的長(zhǎng)度小于size,返回一個(gè)新的數(shù)組,大小為size,類型與傳入數(shù)組相同。
所傳入數(shù)組長(zhǎng)度與size相等,則將elementData復(fù)制到傳入數(shù)組中并返回傳入的數(shù)組。
若傳入數(shù)組長(zhǎng)度大于size,除了復(fù)制elementData外,還將把返回?cái)?shù)組的第size個(gè)元素置為空。
/* 以適當(dāng)?shù)捻樞蚍祷匾粋€(gè)包含此列表中所有元素的數(shù)組;
返回?cái)?shù)組的運(yùn)行時(shí)類型是指定數(shù)組的類型。
如果列表適合指定的數(shù)組,則在其中返回。
否則將使用指定數(shù)組的運(yùn)行時(shí)類型和此列表的大小分配一個(gè)新數(shù)組。*/
public <T> T[] toArray(T[] a) {
if (a.length < size)
// 創(chuàng)建一個(gè)運(yùn)行時(shí)類型的新數(shù)組
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}Fail-Fast機(jī)制:
ArrayList也采用了快速失敗的機(jī)制,通過記錄modCount參數(shù)來實(shí)現(xiàn)。
在面對(duì)并發(fā)的修改時(shí),迭代器很快就會(huì)完全失敗,而不是冒著在將來某個(gè)不確定時(shí)間發(fā)生任意不確定行為的風(fēng)險(xiǎn)
總結(jié)
關(guān)于ArrayList的源碼,給出幾點(diǎn)比較重要的總結(jié):
- 注意其三個(gè)不同的構(gòu)造方法。無參構(gòu)造方法構(gòu)造的ArrayList的容量默認(rèn)為10,帶有Collection參數(shù)的構(gòu)造方法,將Collection轉(zhuǎn)化為數(shù)組賦給ArrayList的實(shí)現(xiàn)數(shù)組elementData。
- 注意擴(kuò)充容量的方法ensureCapacity。ArrayList在每次增加元素(可能是1個(gè),也可能是一組) 時(shí),都要調(diào)用該方法來確保足夠的容量。當(dāng)容量不足以容納當(dāng)前的元素個(gè)數(shù)時(shí),就設(shè)置新的容量為舊的 容量的1.5倍加1,如果設(shè)置后的新容量還不夠,則直接新容量設(shè)置為傳入的參數(shù)(也就是所需的容量),而后用Arrays.copyof()方法將元素拷貝到新的數(shù)組。從中可以看出,當(dāng)容 量不夠時(shí),每次增加元素,都要將原來的元素拷貝到一個(gè)新的數(shù)組中,非常之耗時(shí),也因此建議在事先 能確定元素?cái)?shù)量的情況下,才使用ArrayList,否則建議使用LinkedList。
- ArrayList的實(shí)現(xiàn)中大量地調(diào)用了 Arrays.copyOf() 和 System.arrayCopy() 方法。
我們有必要對(duì)這兩個(gè)方法的實(shí)現(xiàn)做下深入的了解。
先看Arrays.copyof()方法,它有很多個(gè)重載的方法,但實(shí)現(xiàn)思路都是一樣的,看看泛型版本的源碼:
復(fù)制指定的數(shù)組,用空值截?cái)嗷蛱畛洌ㄈ缬斜匾?,使副本具有指定的長(zhǎng)度。
對(duì)于在原始數(shù)組和副本中都有效的所有索引,這兩個(gè)數(shù)組將包含相同的值。對(duì)于副本中有效但不是原始索引的任何索引,副本將包含null。
當(dāng)且僅當(dāng)指定的長(zhǎng)度大于原始數(shù)組的長(zhǎng)度時(shí),此類索引才會(huì)存在。// 生成的數(shù)組與原始數(shù)組的類完全相同。
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}很明顯調(diào)用了另一個(gè)copyOf方法,該方法有三個(gè)參數(shù),最后一個(gè)參數(shù)指明要轉(zhuǎn)換的數(shù)據(jù)的類型,其源碼 如下:
// newType類
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength]: (T[])
Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}這里可以很明顯地看出,該方法實(shí)際上是在其內(nèi)部又創(chuàng)建了一個(gè)長(zhǎng)度為newlength的數(shù)組,調(diào)用
System.arrayCopy()方法,將原來數(shù)組中的元素復(fù)制到了新的數(shù)組中。
下面來看System.arrayCopy()方法。該方法被標(biāo)記了native,調(diào)用了系統(tǒng)的C/C++代碼,在JDK中是看不到的,但在openJDK中可以看到其源碼。該函數(shù)實(shí)際上最終調(diào)用了C語(yǔ)言的memmove()函數(shù),因此它可以保證同一個(gè)數(shù)組內(nèi)元素的正確復(fù)制和移動(dòng),比一般的復(fù)制方法的實(shí)現(xiàn)效率要高很多,很適合用來批量處理數(shù)組。
Java強(qiáng)烈推薦在復(fù)制大量數(shù)組元素時(shí)用該方法,以取得更高的效率。
ArrayList基于數(shù)組實(shí)現(xiàn),可以通過下標(biāo)索引直接查找到指定位置的元素,因此查找效率高,但每次插入或刪除元素,就要大量地移動(dòng)元素,插入刪除元素的效率低。
在查找給定元素索引值等的方法中,源碼都將該元素的值分為null和不為null兩種情況處理, ArrayList中允許元素為null。
到此這篇關(guān)于Java中ArrayList實(shí)現(xiàn)原理及基本方法的文章就介紹到這了,更多相關(guān)ArrayList實(shí)現(xiàn)原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot 驗(yàn)證碼框架 CAPTCHA詳解
這篇文章主要介紹了Spring Boot 驗(yàn)證碼框架 CAPTCHA詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03
Java實(shí)現(xiàn)二叉樹的示例代碼(遞歸&迭代)
二叉樹(Binary?tree)是樹形結(jié)構(gòu)的一個(gè)重要類型。本文將利用Java語(yǔ)言實(shí)現(xiàn)二叉樹,文中的示例代碼講解詳細(xì),需要的同學(xué)可以參考一下2022-03-03
Java?SpringBoot?@Async實(shí)現(xiàn)異步任務(wù)的流程分析
這篇文章主要介紹了Java?SpringBoot?@Async實(shí)現(xiàn)異步任務(wù),主要包括@Async?異步任務(wù)-無返回值,@Async?異步任務(wù)-有返回值,@Async?+?自定義線程池的操作代碼,需要的朋友可以參考下2022-12-12
Java?SpringMVC實(shí)現(xiàn)自定義攔截器
這篇文章主要為大家詳細(xì)介紹了SpringMVC實(shí)現(xiàn)自定義攔截器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-03-03
SpringMVC接收與響應(yīng)json數(shù)據(jù)的幾種方式
這篇文章主要給大家介紹了關(guān)于SpringMVC接收與響應(yīng)json數(shù)據(jù)的幾種方式,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用springmvc具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03

