• 常用集合源码解析(一),ArrayList


    ArrayList主要用作动态数组,是加强版的Array,可以在添加元素时无需担心容量不够,也支持通过index得到元素。

    ArrayList定义

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

    可以得出ArrayList支持泛型。

    先来分析ArrayList的父类和接口:

    1. AbstractList:提供List接口的默认实现,个别为抽象方法;

    2.List定义List(即列表)必须实现的方法,我们通常定义一个ArrayList是这样:List list = new ArrayList(),而非这样ArrayList list = new ArrayList(),也正是由于这点,这样还可以实现多态,来降低耦合度;

    3.RandomAccess:这是一个标记接口,具体使用请参照RandomAccess接口的使用,不过值得关注的是这段话:

    JDK中推荐的是对List集合尽量要实现RandomAccess接口
    如果集合类是RandomAccess的实现,则尽量用for(int i = 0; i < size; i++) 来遍历而不要用Iterator迭代器来遍历,在效率上要差一些。反过来,如果List是Sequence List,则最好用迭代器来进行迭代。

    4.Cloneable:这也是一个标记接口,表示实现该接口的类,可以通过Object的clone方法得到一个该对象的浅拷贝(仅仅拷贝该对象中的基本类型与该对象,而该对象中所有的对其他对象的引用仍然指向原来的对象,没有拷贝),例子:

    public class Person implements Cloneable {
    
        
        String name;
        int age;
        User user;
        
        
        
        public Person(){}
        
        public Person(String name,int age,User user){
            this.name = name;
            this.age = age;    
            this.user = user;
        }
        
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
        
        public User getUser() {
            return user;
        }
    
        public void setUser(User user) {
            this.user = user;
        }
    
        public String toString()
        {
            return "this is person:"+"name:"+this.name+"——age:"+this.age;
        }
    
        @Override
        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
        
        
    }
    
    public class User {
    
        
        String name;
        String password;
        int age;
        
        public User() {
            
        }
    
        public User(String name,String password,int age)
        {
            this.name = name;
            this.password = password;
            this.age = age;
        }
        
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public String toString()
        {
            return "this is user:"+"name:"+this.name+"——password:"+this.password+"——age:"+this.age;
        }
    }
    
    public class CloneTest {
        
        
        public static void main(String[] args) throws Exception {
            User user = new User("user","kk",20);
            Person a = new Person("person",20, user);
            Person b = (Person) a.clone();
            
            System.out.println("Person b:" + b.name + " Person b:user:" + b.user.name);
            System.out.println("Person a:" + a.name + " Person a:user:" + a.user.name);
            
            b.name = "personnamechange";
            b.user.name = "usernamechange";
            
            System.out.println("Person b:" + b.name + " Person b:user:" + b.user.name);
            System.out.println("Person a:" + a.name + " Person a:user:" + a.user.name);
        }
    
    }

    输出为:(可以看到将b中的user的name修改后a中user的name也会改变,可得两者中的user引用的同一个对象)

    Person b:person Person b:user:user
    Person a:person Person a:user:user
    Person b:personnamechange Person b:user:usernamechange
    Person a:person Person a:user:usernamechange

    5.Serializable:标记接口,实现该接口表示该类的对象可进行序列化。Java的对象序列化是指将那些实现了Serializable接口的对象转换成一个字符序列,并能够在以后将这个字节序列完全恢复为原来的对象。例子:

    import java.io.Serializable;
    
    public class User implements Serializable {
    
        
        private String name;
        private String password;
        private int age;
        
        public User() {
            
        }
    
        public User(String name,String password,int age)
        {
            this.name = name;
            this.password = password;
            this.age = age;
        }
        
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public String toString()
        {
            return "this is user:"+"name:"+this.name+"——password:"+this.password+"——age:"+this.age;
        }
    }
    
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    
    public class SerialTest {
    
    public static void main(String[] args) {
            
            User p1 = (User) deSerialByte(serialByte(new User("user","1234",15)));
            
            
            System.out.println("p1:"+p1.toString());
            
        }
        
        //序列化一个对象(可以存储到一个文件也可以存储到字节数组)这里存储到字节数组
        public static byte[] serialByte(Object obj)
        {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos;
            try {
                oos = new ObjectOutputStream(baos);
                oos.writeObject(obj);
                oos.close();
                return baos.toByteArray();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
        
        //反序列化一个对象
        public static Object deSerialByte(byte[] by)
        {
            ObjectInputStream ois;
            try {
                ois = new ObjectInputStream(new ByteArrayInputStream(by));
                return ois.readObject();
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage());
            }
        }
    }

    若User未实现Serializable接口,那么writeObject时将会报NotSerializableException

    成员变量:

    private static final long serialVersionUID = 8683452581122892189L;
    
        /**
         * The array buffer into which the elements of the ArrayList are stored.
         * The capacity of the ArrayList is the length of this array buffer.
         */
        private transient Object[] elementData;
    
        /**
         * The size of the ArrayList (the number of elements it contains).
         *
         * @serial
         */
        private int size;

    1.serialVersionUID:序列化ID,用于序列化

    2.elementData:对象数组,存储ArrayList中的元素,至于transient表示该成员变量在序列化时将不被纳入序列化范围内,那么这个数组该如何被序列化呢,可以看到ArrayList自身实现了对elementData的序列化与反序列化

    /**
         * Save the state of the <tt>ArrayList</tt> instance to a stream (that
         * is, serialize it).
         *
         * @serialData The length of the array backing the <tt>ArrayList</tt>
         *             instance is emitted (int), followed by all of its elements
         *             (each an <tt>Object</tt>) in the proper order.
         */
        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 array length
            s.writeInt(elementData.length);
    
            // 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();
            }
    
        }
    
        /**
         * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
         * deserialize it).
         */
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            // Read in size, and any hidden stuff
            s.defaultReadObject();
    
            // Read in array length and allocate array
            int arrayLength = s.readInt();
            Object[] a = elementData = new Object[arrayLength];
    
            // Read in all elements in the proper order.
            for (int i=0; i<size; i++)
                a[i] = s.readObject();
        }

    3.size:ArrayList包含元素的数量

    构造方法:

    /**
         * Constructs an empty list with the specified initial capacity.
         *
         * @param  initialCapacity  the initial capacity of the list
         * @throws IllegalArgumentException if the specified initial capacity
         *         is negative
         */
        public ArrayList(int initialCapacity) {
            super();
            if (initialCapacity < 0)
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
            this.elementData = new Object[initialCapacity];
        }
    
        /**
         * Constructs an empty list with an initial capacity of ten.
         */
        public ArrayList() {
            this(10);
        }
    
        /**
         * Constructs a list containing the elements of the specified
         * collection, in the order they are returned by the collection's
         * iterator.
         *
         * @param c the collection whose elements are to be placed into this list
         * @throws NullPointerException if the specified collection is null
         */
        public ArrayList(Collection<? extends E> c) {
            elementData = c.toArray();
            size = elementData.length;
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        }

    该三个构造方法第一个可通过参数指定elementData的容量大小,第二个无参方法可以看到若未指定容量那么elementData初始大小为10,第三个可将其他集合转成数组返回给elementData(返回若不是Object[]将调用Arrays.copyOf方法将其转为Object[])。

    成员方法:列举常用

    add方法

    /**
         * Appends the specified element to the end of this list.
         *
         * @param e element to be appended to this list
         * @return <tt>true</tt> (as specified by {@link Collection#add})
         */
        public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
    
        /**
         * Inserts the specified element at the specified position in this
         * list. Shifts the element currently at that position (if any) and
         * any subsequent elements to the right (adds one to their indices).
         *
         * @param index index at which the specified element is to be inserted
         * @param element element to be inserted
         * @throws IndexOutOfBoundsException {@inheritDoc}
         */
        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++;
        }
    private void ensureCapacityInternal(int minCapacity) {
            modCount++;
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
    
    private void grow(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            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);
        }

    第一个add方法:直接在尾部添加元素,并在添加前确保容量足够(ensureCapacityInternal),若不够重新指定容量后,使用Arrays.copyOf重新创建数组,并将原来的elementData拷贝进去。

    第二个add方法:可指定下标进行元素添加,首先判断指定位置index是否超出elementData的界限,再确保容量足够,并使用System.arraycopy将index后的元素整体后移一位,将index位置空出,之后插入元素。

    这里有一个变量modCount需要注意,

    The number of times this list has been structurally modified.

        这是对modCount的解释,意为记录list结构被改变的次数(观察源码可以发现每次调用ensureCapacoty方法,modCount的值都将增加,但未必数组结构会改变,所以感觉对modCount的解释不是很到位)。

    addAll方法

    /**
         * Appends all of the elements in the specified collection to the end of
         * this list, in the order that they are returned by the
         * specified collection's Iterator.  The behavior of this operation is
         * undefined if the specified collection is modified while the operation
         * is in progress.  (This implies that the behavior of this call is
         * undefined if the specified collection is this list, and this
         * list is nonempty.)
         *
         * @param c collection containing elements to be added to this list
         * @return <tt>true</tt> if this list changed as a result of the call
         * @throws NullPointerException if the specified collection is null
         */
        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;
        }
    
        /**
         * Inserts all of the elements in the specified collection into this
         * list, starting at the specified position.  Shifts the element
         * currently at that position (if any) and any subsequent elements to
         * the right (increases their indices).  The new elements will appear
         * in the list in the order that they are returned by the
         * specified collection's iterator.
         *
         * @param index index at which to insert the first element from the
         *              specified collection
         * @param c collection containing elements to be added to this list
         * @return <tt>true</tt> if this list changed as a result of the call
         * @throws IndexOutOfBoundsException {@inheritDoc}
         * @throws NullPointerException if the specified collection is null
         */
        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;
        }

    第一个方法:将一个集合转成数组,确保容量足够,添加到ArrayList尾部,只要集合c的大小不为空,即转换后的数组长度不为0则返回true,此处有一疑问,即我们在构造方法中知道在使用toArray方法还需对返回值实际类型是否是Object数组进行判断,若不是则要将实际类型转为Object数组,而在addAll方法却没有此操作,为什么?

    第二个方法:将一个集合转成数组,确保容量足够,与add类似通过System.arraycopy将index后的元素后移c.size位,确保这个集合中元素能插入当中。

    clear方法:

    /**
         * Removes all of the elements from this list.  The list will
         * be empty after this call returns.
         */
        public void clear() {
            modCount++;
    
            // clear to let GC do its work
            for (int i = 0; i < size; i++)
                elementData[i] = null;
    
            size = 0;
        }

    将所有元素置成null,并未对数组容量进行释放。

    indexOf、lastIndexOf方法:

    /**
         * Returns the index of the first occurrence of the specified element
         * in this list, or -1 if this list does not contain the element.
         * More formally, returns the lowest index <tt>i</tt> such that
         * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
         * or -1 if there is no such index.
         */
        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;
        }
    
        /**
         * Returns the index of the last occurrence of the specified element
         * in this list, or -1 if this list does not contain the element.
         * More formally, returns the highest index <tt>i</tt> such that
         * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
         * or -1 if there is no such index.
         */
        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;
        }

    可以看出这两个方法都是遍历elementData数组,一个正向遍历一个反向遍历,indexOf返回正向查找到的第一个元素的下标,lastIndexOf返回反向查找到的第一个元素的下标。

    get、set方法:

    /**
         * Returns the element at the specified position in this list.
         *
         * @param  index index of the element to return
         * @return the element at the specified position in this list
         * @throws IndexOutOfBoundsException {@inheritDoc}
         */
        public E get(int index) {
            rangeCheck(index);
    
            return elementData(index);
        }
    
        /**
         * Replaces the element at the specified position in this list with
         * the specified element.
         *
         * @param index index of the element to replace
         * @param element element to be stored at the specified position
         * @return the element previously at the specified position
         * @throws IndexOutOfBoundsException {@inheritDoc}
         */
        public E set(int index, E element) {
            rangeCheck(index);
    
            E oldValue = elementData(index);
            elementData[index] = element;
            return oldValue;
        }
    
    /**
         * Checks if the given index is in range.  If not, throws an appropriate
         * runtime exception.  This method does *not* check if the index is
         * negative: It is always used immediately prior to an array access,
         * which throws an ArrayIndexOutOfBoundsException if index is negative.
         */
        private void rangeCheck(int index) {
            if (index >= size)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }

    get方法先检查查找的index是否越界,再直接通过下标返回元素;

    set方法同样先检查index是否越界,在直接通过赋值将之前的元素替换成指定元素,并返回之前元素。

    isEmpty方法

    无脑判断size是否为0,并返回。

    remove方法

    /**
         * Removes the element at the specified position in this list.
         * Shifts any subsequent elements to the left (subtracts one from their
         * indices).
         *
         * @param index the index of the element to be removed
         * @return the element that was removed from the list
         * @throws IndexOutOfBoundsException {@inheritDoc}
         */
        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;
        }
    
        /**
         * Removes the first occurrence of the specified element from this list,
         * if it is present.  If the list does not contain the element, it is
         * unchanged.  More formally, removes the element with the lowest index
         * <tt>i</tt> such that
         * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
         * (if such an element exists).  Returns <tt>true</tt> if this list
         * contained the specified element (or equivalently, if this list
         * changed as a result of the call).
         *
         * @param o element to be removed from this list, if present
         * @return <tt>true</tt> if this list contained the specified element
         */
        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 remove method that skips bounds checking and does not
         * return the value removed.
         */
        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
        }

    第一个根据下标进行元素删除,首先检查index是否越界,再将该元素取出,并将elementData中在index后的元素整体前移一位,返回该元素;

    第二个是根据元素进行删除,先正向遍历elementData数组,若有相同元素,得到该元素下标,进行如第一个方法的删除操作,返回true,若未遍历到该元素则返回false,值得注意的是,这个remove只删除正向遍历找到的第一个相同元素,而不是删除所有与参数相同的元素。

    trimToSize方法

    /**
         * Trims the capacity of this <tt>ArrayList</tt> instance to be the
         * list's current size.  An application can use this operation to minimize
         * the storage of an <tt>ArrayList</tt> instance.
         */
        public void trimToSize() {
            modCount++;
            if (size < elementData.length) {
                elementData = (size == 0)
                  ? EMPTY_ELEMENTDATA
                  : Arrays.copyOf(elementData, size);
            }
        }

    这个方法用来释放elementData多余的容量,使用Arrays.copyOf将elementData容量重置为size,正好可以存下当前ArrayList中的元素。

    clone方法

     /**
         * Returns a shallow copy of this <tt>ArrayList</tt> instance.  (The
         * elements themselves are not copied.)
         *
         * @return a clone of this <tt>ArrayList</tt> instance
         */
        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);
            }
        }

    该方法返回此 ArrayList 实例的浅表副本。(不复制这些元素本身,而是直接引用这些元素),由此也可以得出Array.copyOf方法返回的数组中元素是原数组元素的引用。

    疑问解决:在addAll方法那里,我留下了一个疑问,在此给出解答,首先解答为何在构造方法中进行类型判断并强转,注释上写了这么一句话

    // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);

    c.toArray方法可能不是返回的Object方法,于是我进行了一个测试:

    @Test
        public void test(){
            List<String> strList = Arrays.asList("test", "hello");
            Object[] a = strList.toArray();
            System.out.println(a.getClass());
        }

    控制台打出:

    那么为何要进行实际类型转换呢?在Java中,允许子类向上转型(Object是所有类的父类),所以可以这样使用:Object[] a = strList.toArray();

    但是如果进行如下操作:

    @Test
        public void test(){
            List<String> strList = Arrays.asList("test", "hello");
            Object[] a = strList.toArray();
            
            a[0] = new Object();
            System.out.println(a[0]);
        }

    会抛出异常:

    java.lang.ArrayStoreException: java.lang.Object;

    这也就是说假如我们有1个Object[]数组,并不代表着我们可以将Object对象存进去,这取决于数组中元素实际的类型,那么为了将Object存进去,就要对a进行实际类型转换,如下

    @Test
        public void test(){
            List<String> strList = Arrays.asList("test", "hello");
            Object[] a = strList.toArray();
            System.out.println(a.getClass());
            a = Arrays.copyOf(a, strList.size(), Object[].class);
            
            a[0] = new Object();
            System.out.println(a.getClass());
        }

    控制台打出:

    强转之后将Object对象存入便不会报错了。因为a的实际类型已是Object[],此时将任意类型的对象存入该数组都不再报错,如此转成Object[]就达到了ArrayList的目的,在未指定具体元素类型时,任何类型都可以存入其中:例子:

    @Test
        public void test(){
            List<String> strList = Arrays.asList("test", "hello");
            Object[] a = strList.toArray();
            System.out.println(a.getClass());
            a = Arrays.copyOf(a, strList.size(), Object[].class);
            
            a[0] = new Person();
            a[1] = new User();
            System.out.println(a[0].getClass() + " " + a[1].getClass());
        }

    控制台打出:

    那么为何addAll没有对c进行Object[]强转呢?ArrayList可没有保证这个c也要像ArrayList一样,在未指定元素具体类型时什么类型都能存,ArrayList只需保证自己有这个厉害的特质就行了,其他的就原封不动的装进去就好

    ArrayList的三个构造方法都有elementData = new Object[]这一操作,他们已经让elementData引用了一个Object[](已是Object[]),所以之后在add操作上,由于允许子类向上转型,于是所有的类型便都可以存入其中了。

     测试一下:

    @Test
        public void test2(){
            List list = new ArrayList();
            list.add("hello");
            list.add(new User());
            List<Integer> objList = Arrays.asList(1,2,3);
            list.addAll(objList);
            System.out.println(list);
        }

    控制台:

    收工

  • 相关阅读:
    Charles网络工具
    查找最长子串的长度(不重复字符)
    KMP算法
    java并发编程实战:第十四章----构建自定义的同步工具
    java并发编程实战:第十二章---并发程序的测试
    java并发编程实战:第二章----线程安全性
    java并发编程实战:第三章----对象的共享
    java并发编程实战:第四章----对象的组合
    java并发编程实战:第五章----基础构建模块
    java并发编程实战:第六章----任务执行
  • 原文地址:https://www.cnblogs.com/tz346125264/p/7642186.html
Copyright © 2020-2023  润新知