Java基礎系列-ArrayList


原創文章,轉載請標注出處:《Java基礎系列-ArrayList》

一、概述

ArrayList底層使用的是數組。是List的可變數組實現,這里的可變是針對List而言,而不是底層數組。

數組有自身的特點,不變性,一旦數組被初始化,那么其長度就固定了,不可被改變。這就導致了ArrayList中的一個重要特性:擴容。

二、源碼解析

2.1 聲明

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{/*...*/}

可以看到ArrayList類實現了四個接口:

還繼承自AbstractList 抽象類,這個抽象類是List 的抽象實現,實現了一些List中的公共方法。

2.2 字段解析

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    // 默認的初始容量
    private static final int DEFAULT_CAPACITY = 10;
    // 共享使用的空實例,這個空實例是沒有容量的空實例
    private static final Object[] EMPTY_ELEMENTDATA = {};
    // 共享使用的空實例,這個空實例可被擴容到初始容量(10)
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    // ArrayList中保存元素的緩沖數組,DEFAULTCAPACITY_EMPTY_ELEMENTDATA標識的空數組在第一個添加元素時會被擴容到10個大小。
    transient Object[] elementData; // non-private to simplify nested class access
    // ArrayList集合中包含的元素數量
    private int size;
    // 集合的容量最大值為Integer的最大值-8,這里為什么減去8呢?主要是因為一些虛擬機會在數組中保存一些頭信息,這些信息是區別於使用者添加的元素之外的存在,如果最大為Integer的最大,當頭信息添加之后,再添加元素就有可能會造成內存溢出。
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
}

字段中有幾個需要注意的點:

  1. EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA的區別:前者表示的是一個空數組,后者表示的也是一個空數組,但是不同在於后者是可以擴容的,當往進添加首個元素的時候就會觸發擴容機制,容量會擴容到10個長度。
  2. elementData字段是保存元素的緩沖數組,被transient修飾表示它不會被序列化,這意味着集合對象保存的元素不會被自動序列化,所以后面添加了writeObject和readObject方法,用來序列化和反序列化數組中的元素。

2.3 構造器解析

ArrayList有三個構造器:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    // 構建一個初始容量是10的ArrayList
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    // 根據給定的初始容量initialCapacity構建一個ArrayList
    // 如果initialCapacity>0則直接直接創建容量為initialCapacity的ArrayList
    // 如果initialCapacity=0則直接使用EMPTY_ELEMENTDATA空集合
    // 如果initialCapacity<0,則出錯。
    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);
        }
    }
    // 將給定的集合轉換為ArrayList
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }    
}

我們最常使用的其實是第一種,但是在我們實際編程時,如果可以預估到集合的最大容量,那么可以使用第二種方式,這樣可以減少擴容的時間和內存消耗,一次性到位。

2.4 添加

2.4.1 添加指定元素

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
}

ensureCapacityInternal方法主要用於校驗當前List的容量是否已經達到極限,如果達到極限需要進行擴容。具體參照2.11中擴容解析。

剩下的就是添加新元素的邏輯,簡單至極,直接將新元素到添加到底層數組elementData的下一下標位size++即可。

2.4.2 添加指定元素到指定位置

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    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++;
    }
}

首先校驗給定的添加位置index,index必須小於size的值,並大於等於0。

然后同樣校驗容量是否達到極限,達到極限需要擴容。

之后執行一個本地方法,System.arraycopy方法用於將指定位置及其后面的所有元素整個通過復制遷移到從index+1開始的位置,即整體后移一位,將index位空出來用於保存新元素。

最后將新元素添加到空出的index位置。

2.4.3 將指定集合中的元素添加到List末尾

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }
}

首先將給定的集合轉換為數組Object[]。

然后以目標List的size+給定集合轉化的數組的容量為總容量進行容量校驗,若容量不足,執行擴容操作。

再然后通過本地方法執行數組復制操作將給定集合轉換數組的元素復制到目標List的底層數組的尾部。

最后不要忘記將size增加。

2.4.4 將指定集合中的元素添加到List指定位置

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments 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;
    }
}

首先校驗下標index,index必須小於size,大於等於0。

然后將給定集合轉換為數組Object[],再執行容量校驗,擴容操作。

通過本地方法數組復制操作將給定位置開始的所有元素整體后移一定的距離,具體的距離為給定集合轉換后數組的容量大小,這樣就能空出容量大小的空位來存放給定的集合元素。

最后再次通過本地數組復制方法將給定的集合轉換的數組元素整體復制到上一步空出來的位置上。

2.5 修改

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
}

修改指定位置的元素為新元素,首先需要校驗給定index的值,index必須大於等於0,小於size,然后將新元素保存到index位置,並將舊元素返回。

2.6 獲取

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
}

獲取指定下標位置的元素值,首先需要校驗給定的下標index,index必須大於等於0,小於size。

2.7 定位

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }
    public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }
}

indexOf是通過正序遍歷的方式搜索給定的元素的下標,lastIndexOf是通過逆序遍歷的方式搜索給定元素的下標,這兩個方法找到的下標都是正序或者逆序該元素首次出現的位置下標。如果o為null,那么將會搜索第一個null值元素的下標。

2.8 移除

2.8.1 移除指定下標的元素

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    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;
    }
}

首先校驗給定的下標值index,index必須小於size,這里的校驗和添加元素的下標校驗有點不同:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
}

前者是此處的index校驗,后者是添加元素的index校驗。

那為何后者比前者要多一個index<0的校驗呢,那是因為在add(int,E)方法中,校驗完成后緊接着就是調用本地方法進行數組復制操作,如果index小於0,那么出錯位置在C代碼中,無法在Java代碼中得以體現,所以提前進行校驗,保證調用本地C代碼之前參數的准確性。前者校驗完成之后,緊接着的是Java代碼獲取指定下標的元素,如果下標小於0,也會出錯但是JVM會拋出異常,不會無聲無息,所以沒有必要校驗是否小於0。

index校驗完成后,通過本地方法數組復制將index+1及其之后的元素整體復制到index位置。

最后將原來的最后一個位置元素置空。

2.8.2 移除指定元素

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    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; // clear to let GC do its work
    }    
}

首先通過循環操作找到首個指定的元素,然后將針對找到的元素執行刪除操作。

刪除操作還是依靠本地的數組復制操作完成的。

2.8.3 清空元素

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }
}

至於清空元素,就是通過循環將List中的每個元素都刪除,將整個List置空。

2.8.4 移除當前List中所有(不)包含在給定集合中的元素

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    // 刪除當前List中所有包含在給定集合中的元素
    public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }
    // 刪除當前List中所有不包含在給定集合中的元素
    public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, true);
    }
    private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }
}

2.9 遍歷

ArrayList的遍歷方式有很多:

2.9.1 ListIterator

ListIterator是繼承自Iterator的,在其基礎上添加了反向遍歷的功能方法。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    // 截取從執行下標開始的元素組成迭代器實例,進行遍歷
    public ListIterator<E> listIterator(int index) {
        if (index < 0 || index > size)
            throw new IndexOutOfBoundsException("Index: "+index);
        return new ListItr(index);
    }
    // 將集合中所有元素組成迭代器實例,進行遍歷
    public ListIterator<E> listIterator() {
        return new ListItr(0);
    }
}

源碼中的ListItr是ListIterator的實現類。

2.9.2 Iterator

Iterator擁有正向遍歷的功能。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    public Iterator<E> iterator() {
        return new Itr();
    }
}

源碼中的Itr就是Iterator的實現類。

2.9.3 Spliterator

Spliiterator是分割迭代器,詳情參見Java集合系列-Spliterator

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    public Spliterator<E> spliterator() {
        return new ArrayListSpliterator<>(this, 0, -1, 0);
    }
}

2.9.4 forEach

forEach方式是java 1.8中新增的方式,接受一個行為作為參數,即接收一個方法引用或者Lambda表達式。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    // action代表接受的行為,是一個函數式接口類型Consumer,表示消費之意,消費就是將資源處理掉,所以有一個入參,無返回值。
    public void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        final int expectedModCount = modCount;
        @SuppressWarnings("unchecked")
        final E[] elementData = (E[]) this.elementData;
        final int size = this.size;
        for (int i=0; modCount == expectedModCount && i < size; i++) {
            action.accept(elementData[i]);// 執行函數式接口行為
        }
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }
}

實例:

public class ArrayListTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.addAll(Arrays.asList("123","444444","2123"));
        ListIterator<String> listIterator = list.listIterator();// 第一種
        listIterator.forEachRemaining(System.out::println);
        System.out.println("-------------");
        ListIterator<String> listIterator1 = list.listIterator(1);// 第二種
        listIterator1.forEachRemaining(System.out::println);
        System.out.println("-------------");
        Iterator<String> iterator = list.iterator();// 第三種
        iterator.forEachRemaining(System.out::println);
        System.out.println("-------------");
        Spliterator<String> spliterator = list.spliterator();// 第四種
        spliterator.forEachRemaining(System.out::println);
        System.out.println("-------------");
        list.forEach(System.out::println);// 第五種
    }
}

2.10 校驗

2.10.1 是否為空

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    public boolean isEmpty() {
        return size == 0;
    }
}

size表示的就是List中包含的元素的個數。

2.10.2 是否包含某元素

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }
}

該校驗通過indexOf()方法來實現,如果能找到元素的下標,則存在,否則不存在。

2.11 底層數組擴容

在add和addAll方法中多次出現的ensureCapacityInternal方法就是通向擴容邏輯的通道。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    // 確保底層數組的容量足夠保存當前的元素或元素集,如果容量不足即進行擴容。
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    // 處理首次添加元素時的容量擴容操作,被指定為DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空數組在首次添加元素時需要自動擴容到默認容量10
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    // 校驗是否需要擴容,只有當給定容量值比當前數組的長度要大時,才需要擴容,
    // 因為一般情況下給定容量即為新添加元素后的容量,當前容量達不到這個值是沒有位置保存當前元素的,所以才需要擴容。
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
}

ensureCapacityInternal方法的目的是確保給定的參數指定的容量值。

真正的擴容邏輯位於grow方法中:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);// 擴容為原容量的1.5倍
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 如果最后決定擴容的容量比允許的最大數組容量值要大,那么則進行超限處理
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    // 處理超限問題
    // 如果給定的minCapacity為負數(首位為1)則拋出異常錯誤OutOfMemoryError
    // 如果給定容量大於數組最大容量,則取整數的最大值為容量,否則使用數組的最大容量作為擴容容量
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
}

首先要根據規則計算一個新容量newCapacity,然后將這個新容量值與給定需要的容量值minCapacity進行比較,如果新容量值大於給定容量值,則用新容量值進行擴容,否則使用給定容量值進行擴容。然后進行超限校驗和處理。

最后使用確定好的容量newCapacity來作為新的底層數組容量來進行擴容操作:創建一個新的數組,並遷移元素。

2.12 排序

Java中排序可以通過兩種方式實現:

  • 實現Comparable接口
  • 使用Comparator比較器

具體參見Java基礎系列-Comparable和Comparator

這里很明顯ArrayList的繼承體系中並無Comparable接口,那么只能通過Comparator來實現,這就涉及到了ArrayList中的sort方法:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    public void sort(Comparator<? super E> c) {
        final int expectedModCount = modCount;
        Arrays.sort((E[]) elementData, 0, size, c);
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }
}

使用這種方式來排序需要傳遞一個Comparator比較器作為參數,最簡單的方式就是匿名內部類方式,在Java 1.8之后直接使用Lambda來實現。

public class ComparatorTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.addAll(Arrays.asList("123","45612","7839"));
        list.sort((o1, o2) -> o1.length()-o2.length());
        list.forEach(System.out::println);
    }
}

執行結果為:

123
7839
45612

2.13 克隆

因為ArrayList實現了Cloneable接口,重寫了clone方法,便擁有了對象克隆的功能。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }
}

這是一個淺拷貝的實現。

2.14 序列化/反序列化

由於ArrayList中使用transient修飾了elementData,它代表的是底層的元素數組,序列化的主要內容就是它,或者說是它里面的內容,而它又無法被序列化,因此我們只能通過自定義writeObject方法來手動序列化,定義readObject方法來手動反序列化。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;

        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in capacity
        s.readInt(); // ignored

        if (size > 0) {
            // be like clone(), allocate array based upon size not capacity
            int capacity = calculateCapacity(elementData, size);
            SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
            ensureCapacityInternal(size);

            Object[] a = elementData;
            // Read in all elements in the proper order.
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();
            }
        }
    }    
}

至此,ArrayList的大部分內容都介紹完畢了。

三、總結

最后做一下總結,知識點歸納:

  • ArrayList底層采用數組實現,擁有快速隨機訪問能力,但是非線程安全的集合。
  • ArrayList默認容量為10,擴容規則為當要保存的新元素所需的容量不足時觸發,基本規則為擴容1.5倍。
  • 如果在遍歷的時候發生結構性變化,會觸發ConcurrentModificationException異常。
  • 結構性變化包括:添加新元素,刪除元素。
  • ArrayList支持序列化功能,支持克隆(淺拷貝)功能,排序功能等。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM