Java中的集合(上)

1.什么是集合

集合类是Java数据结构的实现。Java的集合类是java.util包中的重要内容,它允许以各种方式将元素分组,并定义了各种使这些元素更容易操作的方法。Java集合类是Java将一些基本的和使用频率极高的基础类进行封装和增强后再以一个类的形式提供。集合类是可以往里面保存多个对象的类,存放的是对象,不同的集合类有不同的功能和特点,适合不同的场合,用以解决一些实际问题。

2.走进集合

在没有集合类之前,实际上在Java语言里已经有一种方法可以存储对象,那就是数组。数组不仅可以存放基本数据类型也可以容纳属于同一种类型的对象。数组的操作是高效率的,但也有缺点。比如数组的长度是不可以变的,数组只能存放同一种类型的对象(或者说对象的引用)。
另外,在程序设计过程中,程序员肯定会经常构建一些特殊的数据结构以正确的描述或者表达现实情况。比如描述火车进站出站,他们会用到“栈”这个数据结构,常用的数据结构还有:队列、链接表、树和散列表等等。这些数据结构几乎在每一段程序设计过程中都会使用到,但是如果每次编程都要重新构建这些数据结构显然违背了软件组件化的思想。因此Java的设计者考虑把这些通用的数据结构做成API供程序员调用。
基于以上几点必须解决的问题。Java提供了对象的数种保存方式,除了内置的数组以外,其余的称为集合类。为了使程序方便地存储和操纵数目不固定的一组数据,JDK中提供了Java集合类,所有Java集合类都位于Java.util包中,与Java数组不同,Java集合不能存放基本数据类型数据,而只能存放对象的引用。

3.集合的分类

Java中的集合类可以分为两大类:一类是实现Collection接口;另一类是实现Map接口。
首先我们先来看一下Collection。Collection是一个基本的集合接口,Collection中可以容纳一组集合元素(Element)。

public interface Collection<E> extends Iterable<E> {
    int size();
    boolean isEmpty();
    boolean contains(Object o);
    Iterator<E> iterator();
 Object[] toArray();
    boolean add(E e);
    boolean remove(Object o);
    boolean containsAll(Collection<?> c);
    boolean removeAll(Collection<?> c);
   default boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        boolean removed = false;
        final Iterator<E> each = iterator();
        while (each.hasNext()) {
            if (filter.test(each.next())) {
                each.remove();
                removed = true;
            }
        }
        return removed;
    }

     boolean retainAll(Collection<?> c);
     void clear();
     boolean equals(Object o);
     int hashCode();
        @Override
    default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, 0);
    }
        default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }
        default Stream<E> parallelStream() {
        return StreamSupport.stream(spliterator(), true);
    }
}

以上便是Collection接口中的方法 想必大家应该多多少少都用过。会发现Collection接口继承了Iterable这个玩意儿 那我们下一步我们继续看Iterable

public interface Iterable<T> {
    Iterator<T> iterator();
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

我们会发现这个接口没有继承和实现任何接口,那么这就是顶级接口了,那我们就一起看这个接口下的方法

Iterator<T> iterator():这个方法是返回一个内部元素为T类型的迭代器(自行百度)
default void forEach(Consumer<? super T> action) 这个东西就是我们常用的ForEach遍历了吧
default Spliterator<T> spliterator() 创建并返回一个可分割迭代器

Collection 的老父亲我们已经拜访完了,现在我呢去安排一下他的儿子们

Collection有两个重要的子接口List和Set。List表达一个有序的集合,List中的每个元素都有索引,使用此接口能够准确的控制每个元素插入的位置。用户也能够使用索引来访问List中的元素,List类似于Java的数组。Set接口的特点是不能包含重复的元素。对Set中任意的两个元素element1和element2都有elementl.equals(element2)= false。另外,Set最多有一个null元素。此接口模仿了数学上的集合概念。

Collection接口、List接口、Set接口以及相关类的关系如图1所示。

java 对象集合中的某个值拼字符串 java一种集合_ci


现在我们看一下List

public interface List<E> extends Collection<E> {

我们看到List接口继承了Collection接口 那么Collection接口中的方法List也一并拥有。我们接下来看一下List对数据元素的操作

boolean add(E e); //像List集合中添加元素 我们在先不看底层实现
boolean remove(Object o);//这个就是删除了
boolean addAll(Collection<? extends E> c);//这个方法就是添加一个集合
........总之方法很多 我们在这就不一一列举..............

我们在上边看到List有两个子类 分别是ArrayList LinkedList。
我们现在看一下ArrayList

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

我们发现ArrayList 继承了AbstactList抽象类的时候 同时又实现了List RandomAccess Cloneable
Serializable 接口。现在我们先看一下源码

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;

    /**
     * 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) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * 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();
        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;
        }
    }

现在我们开始进入ArrayList

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

当我们创建这个ArrayList的时候 构造方法就会执行 它会把将一个空数组{}赋值给了elementData,这个时候集合的长度size为默认长度0;很明显我们可以看出该底层是一个数组 那么它的特点就会很明显:查询速度快,增加速度慢。而且该集合是线程不安全的。当数据需要频繁的查询,而增加删除较少的时候,建议使用ArrayList数组存储数据。
我们看完特点,我们继续熟悉一下常见的API

ArrayList list = new ArrayList();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵六");

add() 方法:

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

通过 ensureCapacityInternal(size + 1) 来保证底层Object[]数组有足够的空间存放添加的数据,然后将添加的数据存放到数组对应的位置上,我们看一下是怎么保证数组有足够的空间。继续往下看源码`

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

确定了Object[]足够存放添加数据的最小容量,然后通过 grow(int minCapacity) 来进行数组扩容
扩容规则为“数组当前足够的最小容量 + (数组当前足够的最小容量 / 2)”,即数组当前足够的最小容量 * 1.5,当然有最大值的限制。

rivate 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);
}

size() : 获取集合长度,通过定义在ArrayList中的私有变量size得到
isEmpty():是否为空,通过定义在ArrayList中的私有变量size得到
contains(Object o):是否包含某个元素,通过遍历底层数组elementData,通过equals或==进行判断
clear():集合清空,通过遍历底层数组elementData,设置为null.
我们就简单看了一下ArrayList 其余API方法可以查看源码。加油奥利给
现在我们开始进入LinkedList

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

java 对象集合中的某个值拼字符串 java一种集合_ci_02


LinkedList是一个继承于AbstractSequentialList的双向链表。它也可以被当做堆栈、队列或双端队列进行使用。

LinkedList实现List接口,能让它进行队列操作。

LinkedList实现Deque接口,即能将LinkedList当做双端队列使用。

LinkedList实现Cloneable,即覆盖了函数clone(),能被克隆。

LinkedList实现了java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。

LinkedList中的操作不是线程安全的。`在这里插入代码片

ransient int size = 0;
transient Node<E> first;
transient Node<E> last;
`transient关键字:将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会被序列化。
public LinkedList() {
}
创建LinkedList的时候 如果没有参数会调用这个方法。如果有参数会调用下面这个方法
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}``
首先会调用无参数的构造方法,然后调用addAll方法将集合内元素全部加入到链表中,addAll方法我们后面会详细的分析。
俩个构造方法可以看出LinkedList是一个无界链表,不存在容量不足的问题。对LinkedList的操作实质就是对双向链表的操作。详情移步数据结构