Java collection series 2, a hundred secrets and a sparse Vector

1. Vector overview

When introducing Vector, people often say:

底层实现与 ArrayList 类似,不过Vector 是线程安全的,而ArrayList 不是。

So is this definition correct? Let's analyze it in combination with the previous article:

Java Collections Series 1, ArrayList

Vector dependencies

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

Vector is a vector queue, and its dependencies are the same as ArrayList , so it has the following functions:

  • 1. Serializable : Supports object serialization. Although the member variable is not modified with the transient keyword, Vector still implements the writeObject() method for serialization.
  • 2. Cloneable : Override the clone() method to copy the array through Arrays.copyOf().
  • 3. RandomAccess : Provides the random access function, we can quickly obtain the element object through the serial number of the element.
  • 4. AbstractList : inherits AbstractList, indicating that it is a list with corresponding functions such as adding, deleting, checking, and changing.
  • 5. List : leave a question, why do you need to implement the List interface after inheriting AbstractList?

*Extended thinking : Why does the serialization of Vector only override the writeObject() method?

Careful friends, if you look at the source code of vector, you can find that there is such a statement in the comments of writeObject():

This method performs synchronization to ensure the consistency
    of the serialized data.

After reading the comments, you may have a sudden realization. Isn't the core idea of ​​Vector thread safety? Then the serialization process must be locked to operate, in order to say that it is thread-safe. Therefore, even if there is no elementData not decorated with transient, you still need to override writeObject().

*Extended thinking : Same as ArrayLit and most of the collection classes, why do you need to implement the List interface if you inherit AbstractList?

There are two theories, you can refer to:

1. In StackOverFlow: Portal The answerer with the most voted answer said that he asked Josh Bloch who wrote this code and learned that it was a spelling error.

2. The getInterfaces of the Class class can obtain the implemented interface, but cannot obtain the implementation interface of the parent class, but this operation is meaningless.

2. Vector member variable

    /**
        与 ArrayList 中一致,elementData 是用于存储数据的。
     */
    protected Object[] elementData;

    /**
     * The number of valid components in this {@code Vector} object.
      与ArrayList 中的size 一样,保存数据的个数
     */
    protected int elementCount;

    /**
     * 设置Vector 的增长系数,如果为空,默认每次扩容2倍。
     *
     * @serial
     */
    protected int capacityIncrement;
    
     // 数组最大值
     private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

Compared with member variables in ArrayList, Vector has two less empty array objects: EMPTY_ELEMENTDATA and DEFAULTCAPACITY_EMPTY_ELEMENTDATA

Therefore, the first difference between Vector and ArrayList is that the member variables are inconsistent .

3. Vector constructor

Vector provides four constructors:

  • Vector() : default constructor
  • Vector(int initialCapacity) : capacity is the default capacity size of Vector. When the capacity increases due to adding data, the capacity is doubled each time.
  • Vector(int initialCapacity, int capacityIncrement) : capacity is the default capacity size of the Vector, and capacityIncrement is the incremental value each time the Vector's capacity increases.
  • Vector(Collection<? extends E> c) : Create a Vector containing the collection

At first glance, the constructors provided in Vector are as rich as those in ArrayList. But after analyzing the constructor of ArrayList in the previous section , and looking at the constructor of Vector, you will feel a dull feeling.

    //默认构造函数
    public Vector() {
        this(10);
    }
    
    //带初始容量构造函数
    public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }
    
    //带初始容量和增长系数的构造函数
    public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }

The code doesn't seem to have many problems. It's the same as the code we usually write, but it lacks a charm compared to the constructor in ArrayList. Interested students can take a look at the constructor implementation in ArrayList.

    public Vector(Collection<? extends E> c) {
        elementData = c.toArray();
        elementCount = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
    }

After JDK 1.2, a constructor to convert Collection to Vector was proposed. The actual operation is to copy the contents of a Collection array to a Vector object through Arrays.copyOf(). There may be a NullPointerException thrown here .

Contrast on the constructor: Vector's constructor is designed to be inferior to ArrayList.

4. Add method (Add)

Vector has one more method than ArrayList in the method of adding elements. The add methods supported by Vector are:

  • add(E)
  • addElement(E)
  • add(int i , E element)
  • addAll(Collection<? extends E> c)
  • addAll(int index, Collection<? extends E> c)

4.1 addElement(E)

Let's take a look at what's special about this extra addElement(E) method:

    /**
     * Adds the specified component to the end of this vector,
     * increasing its size by one. The capacity of this vector is
     * increased if its size becomes greater than its capacity.
     *
     * <p>This method is identical in functionality to the
     * {@link #add(Object) add(E)}
     * method (which is part of the {@link List} interface).
     *
     * @param   obj   the component to be added
     */
    public synchronized void addElement(E obj) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = obj;
    }

From the comments above, this method has the same function as the add(E) method. Therefore, there is nothing special except that the returned data is different.

We follow the above code to analyze the addition method in Vector. It can be seen that Vector has locked the entire add method (added synchronized modification). In fact, we can understand that the process of adding elements mainly includes the following operations:

  • ensureCapacityHelper(): Confirm container size
  • grow(): grow the container if needed
  • elementData[elementCount++] = obj:设值

In order to avoid multi-threading, when the capacity of ensureCapacityHelper does not need to be expanded, other threads just fill up the array, and ArrayIndexOutOfBoundsException will occur at this time , so locking the entire method can avoid this situation.

Compared with ArrayList, in the step of confirming the container size, the step of ArrayList#ensureCapacityInternal is missing, mainly because the Vector is constructed and the default array size is created, so that the array will not be empty.

Next in the grow() method:

    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        //区别与ArrayList 中的位运算,这里支持自定义增长系数
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

Vector supports custom growth coefficients, which is one of the few highlights in the add() method.

4.2 add(int index, E element)

This part of the code is not much different from that in ArrayList, mainly because the exception thrown is different, what is thrown in ArrayList is IndexOutOfBoundsException. Here, ArrayIndexOutOfBoundsException is thrown. As for why you need to extract the operation into the insertElementAt() method? Children's shoes can think about it.

    /**
     * @throws ArrayIndexOutOfBoundsException if the index is out of range
     *         ({@code index < 0 || index > size()})
     * @since 1.2
     */
    public void add(int index, E element) {
        insertElementAt(element, index);
    }
    
    public synchronized void insertElementAt(E obj, int index) {
        modCount++;
        if (index > elementCount) {
            throw new ArrayIndexOutOfBoundsException(index
                                                     + " > " + elementCount);
        }
        ensureCapacityHelper(elementCount + 1);
        System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
        elementData[index] = obj;
        elementCount++;
    }
    

In the addition method, Vector is similar to ArrayList. Vector has a weird addElement(E).

5. Remove method (Remove)

Vecotr provides a lot of deletion methods, but as long as you look at the source code, you can find that most of them are the same method.

  • remove(int location)
  • remove(Object object)
  • removeAll(Collection<?> collection)
  • removeAllElements()
  • removeElement(Object object)
  • removeElementAt(int location)
  • removeRange(int fromIndex, int toIndex)
  • clear()

5.1、remove(int location) & removeElementAt(int location)

Compare remove(int location) and removeElementAt(int location)

public synchronized void removeElementAt(int index) {
        modCount++;
        if (index >= elementCount) {
            throw new ArrayIndexOutOfBoundsException(index + " >= " +
                                                     elementCount);
        }
        else if (index < 0) {
            throw new ArrayIndexOutOfBoundsException(index);
        }
        int j = elementCount - index - 1;
        if (j > 0) {
            System.arraycopy(elementData, index + 1, elementData, index, j);
        }
        elementCount--;
        elementData[elementCount] = null; /* to let gc do its work */
    }


public synchronized E remove(int index) {
        modCount++;
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);
        E oldValue = elementData(index);

        int numMoved = elementCount - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--elementCount] = null; // Let gc do its work

        return oldValue;
    }

Except for the different data types returned, other internal operations are actually the same. remove is an operation that overrides the parent class, and removeElement is a custom method in Vector. The fastRemove() method is provided in ArrayList, which has the same effect, but the scope of removeElement is public.

5.2、remove(Object object) & removeElement(Object object)

    public boolean remove(Object o) {
        return removeElement(o);
    }
    
    public synchronized boolean removeElement(Object obj) {
        modCount++;
        int i = indexOf(obj);
        if (i >= 0) {
            removeElementAt(i);
            return true;
        }
        return false;
    }
    
    

remove(Object object) actually calls removeElement(Object object) internally. The delete operation first finds the index of the object (same as remove(E) in ArrayList), and then calls removeElementAt(i) (fastRemove() method in ArrayList) to delete.

The rest of the deletion operations are similar to ArrayList, and will not be analyzed in detail here. Generally speaking, in the deletion method, Vector and ArrayList are similar.

6. Thread-safe Vector?

Expanding our thinking, we often say that Vector is a thread-safe array list, so is it thread-safe all the time? There is such a question in StackOverFlow:

StackOverFlow Portal

Is there any danger, if im using one Vector(java.util.Vector) on my server program when im accessing it from multiple threads only for reading? (myvector .size() .get() ...) For writing im using synchronized methods. Thank you.

There is a more detailed analysis of the answer:

Each individual method in Vector is thread-safe because it is decorated with synchronized. However, if some complex operations are encountered, and multiple threads need to rely on the vector for related judgments, then this time is not thread-safe.

if (vector.size() > 0) {
    System.out.println(vector.get(0));
}

As shown in the above code, after Vector judges that size()>0, if another thread empties the vector object at the same time, an exception will occur at this time. Therefore, Vector is not thread-safe in the case of compound operations.

Summarize

The title of this article is: Vector, the reason is that if we don't know Vector in detail, or in interviews, we often think that Vector is thread-safe. But actually Vector is thread-safe only on every single method operation.

To summarize the differences with ArrayList:

  • 1. Constructor, ArrayList is slightly deeper than Vector. The default array length of Vector is 10, and creation is a setting.
  • 2. The expansion method grow(), ArrayList is expanded by bit operation, and Vector is expanded by the growth coefficient (creation is set, if it is too empty, it will double)
  • 3. Vector method calls are thread-safe.
  • 4. Member variables are different

References:

  • http://www.cnblogs.com/skywang12345/p/3308833.html
  • https://juejin.im/post/5aec1863518825671c0e6c75
  • https://stackoverflow.com/questions/23246059/java-vector-thread-safety?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
  • https://blog.csdn.net/ns_code/article/details/35793865

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325377767&siteId=291194637