Java集合框架之Collections接口及实现类

集合框架(collections framework)

现实生活中:不少相同事物凑在一块儿,好比人群
数学中的集合:具备共同属性的事物的整体
java中的集合框架:是一种工具类,就像是容器,储存任意数量的具备共同属性的对象html

其实说白了,能够把一个集合当作一个微型数据库,操做不外乎“增删改查”四种操做,咱们在学习使用一个具体的集合类时,须要把这四个操做的时空复杂度弄清楚了,基本上就能够说掌握这个类了。java

两大基类Collection与Map

在集合框架的类继承体系中,最顶层有两个接口:node

  • Collection表示一组纯数据
  • Map表示一组key-value对

通常继承自Collection或Map的集合类,会提供两个“标准”的构造函数:面试

  • 没有参数的构造函数,建立一个空的集合类
  • 有一个类型与基类(Collection或Map)相同的构造函数,建立一个与给定参数具备相同元素的新集合类

由于接口中不能包含构造函数,因此上面这两个构造函数的约定并非强制性的,可是在目前的集合框架中,全部继承自Collection或Map的子类都遵循这一约定。算法

Collection

输入图片说明

如上图所示,Collection类主要有三个接口:数据库

  • Set表示不容许有重复元素的集合(A collection that contains no duplicate elements)
  • List表示容许有重复元素的集合(An ordered collection (also known as a sequence))
  • Queue JDK1.5新增,与上面两个集合类主要是的区分在于Queue主要用于存储数据,而不是处理数据。(A collection designed for holding elements prior to processing.)

Collection接口部分源码数组

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

    int size();
    boolean isEmpty();
    boolean contains(Object o);
    boolean add(E e);
    boolean remove(Object o);
    boolean containsAll(Collection<?> c);
    boolean addAll(Collection<? extends E> c);
    boolean removeAll(Collection<?> c);
    void clear();

    Iterator<E> iterator();
    Object[] toArray();
}

以上是Collection接口的经常使用方法,由于3个子接口都继承了这个接口,所以在它们各自特有方法外,都会实现以上方法。安全

Map

输入图片说明

Java 中有四种常见的Map实现——HashMap, TreeMap, Hashtable和LinkedHashMap:session

  • HashMap就是一张hash表,键和值都没有排序。
  • TreeMap以红黑树结构为基础,键值能够设置按某种顺序排列。
  • LinkedHashMap保存了插入时的顺序。
  • Hashtable是同步的(而HashMap是不一样步的)。因此若是在线程安全的环境下应该多使用HashMap,而不是Hashtable,由于Hashtable对同步有额外的开销,不过JDK 5以后的版本可使用conncurrentHashMao代替HashTable。

说到Map接口的话你们也许在熟悉不过了。Map接口实现的是一组Key-Value的键值对的组合。 Map中的每一个成员方法由一个关键字(key)和一个值(value)构成。Map接口不直接继承于Collection接口(须要注意啦),由于它包装的是一组成对的“键-值”对象的集合,并且在Map接口的集合中也不能有重复的key出现,由于每一个键只能与一个成员元素相对应。多线程

另外,Set接口的底层是基于Map接口实现的。Set中存储的值,其实就是Map中的key,它们都是不容许重复的。

Map接口部分源码

public interface Map<K,V> {

    int size();
    boolean isEmpty();
    boolean containsKey(Object key);
    boolean containsValue(Object value);
    V get(Object key);
    V put(K key, V value);
    void putAll(Map<? extends K, ? extends V> m);
    V remove(Object key);
    void clear();

    Collection<V> values();
    Set<K> keySet();
    Set<Map.Entry<K, V>> entrySet();
    interface Entry<K,V> {
        K getKey();
        V getValue();
        V setValue(V value);

    }
}

源码中的方法你们也比较熟悉。在遍历Map时,咱们能够经过keySet()方法获取到全部key,它返回的是一个Set对象,遍历Set,经过key获取value。也能够经过entrySet()的方式获取Entry的Set,遍历Set,经过Entry的getValue()getKey()方法获取值和对象,这些在后面会详细讲到。

List接口

List接口对Collection进行了简单的扩充,所以它继承了Collection接口。其中大部分方法和其继承的Collection相同,至于不一样之处也不太经常使用,你们能够参考源码。

特色:

List中存储的元素是有序的,并且能够重复的存储相关元素。

ArrayList

特色:

ArrayList的底层使用数组实现,当元素的数量超过数组长度时,经过新建更大容量数组,将原数组内容拷贝一份,而后新增元素的方式存储元素。

优势:

相似数组的形式进行存储,所以它的随机访问速度极快。

缺点:

不适合于在线性表中间须要频繁进行插入和删除操做。由于每次插入和删除都须要移动数组中的元素。

能够这样理解ArrayList就是基于数组的一个线性表,只不过数组的长度能够动态改变而已。

对于ArrayList的详细使用信息以及建立的过程能够查看jdk中ArrayList的源码,这里不作过多的讲解。

对于使用ArrayList的开发者而言,下面几点内容必定要注意啦,尤为找工做面试的时候常常会被问到。做者去年面试的时候,都已经被问烦了

注意啦!!!!!!!!

一、关于扩容问题:

默认ArrayListde的默认构造函数ArrayList()会构造一个长度为10的数组。
ArrayList(int initialCapacity)构造函数会构造一个指定长度的数组。
添加元素时,若是超出了长度,则以每次旧长度的3/2倍增加。

例:

new ArrayList(20);扩容几回?
答案: 0次,由于直接产生了一个长度20的数组

二、ArrayList是线程不安全的,在多线程的状况下不要使用。

若是必定在多线程使用List的,你可使用Vector,由于Vector和ArrayList基本一致,区别在于Vector中的绝大部分方法都使用了同步关键字修饰,这样在多线程的状况下不会出现并发错误哦,还有就是它们的扩容方案不一样,ArrayList是经过原始容量*3/2,而Vector是容许设置默认的增加长度,Vector的默认扩容方式为原来的2倍(能够经过构造函数设置,如设置为2,扩容后长度为旧长度+2)。

切记Vector是ArrayList的多线程的一个替代品。

三、ArrayList实现遍历的几种方法

ArrayList<String> list = new ArrayList();
        list.add("hello");
        list.add(",");
        list.add("world");

        // 第一种遍历方式使用foreach遍历List,编译器编译时,会将这种方式转化为迭代器方式
        for (String str : list) {
            System.out.print(str);
        }
        System.out.println();

        // 第二种遍历方式使用for循环依次获得元素
        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i));
        }
        System.out.println();

        // 第三种遍历方式使用迭代器遍历
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()) {
            System.out.print(iterator.next());
        }
        System.out.println();

LinkedList

特色:

LinkedList的底层用双向链表实现。另外建议阅读其源码,并不难,会让你有醍醐灌顶的感受。

优势:

链表相对于实现ArrayList的数组来讲,其存储空间是散列的而不是连续的,所以在链表中间插入和删除元素时,无需移动后面的元素,只须要改变3个节点的关联便可。

缺点:

由于LinkedList不是空间连续的,所以随机读取时,须要从头至尾的读取,所以不如ArrayList来得快。另外,在使用双向链表实现时,须要额外提供空间供记录前驱节点和后继节点的地址,消耗了额外空间。

对于使用LinkedList而言,下面几点内容必定要注意啦

注意啦!!!!!!!!

一、LinkedList和ArrayList的区别和联系

主要从底层实现、优缺点(随机读取、新增、删除)等方面总结,详见以前总结的特色、优缺点,再也不赘述。

二、LinkedList的内部实现

强烈建议你们去看看源码,其内部使用双链表实现,若是你实在不想看,下面的代码提供了其节点结构,和最经常使用的add()remove()方法。

// 长度
    transient int size = 0;
    
    // 节点的表示
    private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
    
    // 此段不是源码,仅供理解思想
    private void add(Node<E> node, E data) {
        Node<E> newNode = new Node<E>(node.prev, data, node);
        newNode.prev.next = newNode;
        node.prev = newNode;
        size++;
    }

    // 此段不是源码,仅供理解思想
    private E remove(Node<E> node) {
        node.next.prev = node.prev;
        node.prev.next = node.next;
        size--;
        return node.item;
    }

三、LinkedList不是线程安全的

注意LinkedList和ArrayList同样也不是线程安全的,若是在对线程下面访问能够本身重写LinkedList

而后在须要同步的方法上面加上同步关键字synchronized

四、LinkedList的遍历方法

同ArrayList

LinkedList<String> list = new LinkedList();
        list.add("hello");
        list.add(",");
        list.add("world");

        // 第一种遍历方式使用foreach遍历List
        for (String str : list) {
            System.out.print(str);
        }
        System.out.println();

        // 第二种遍历方式使用for循环依次获得元素,这种方式用到了随机读取,特别特别不推荐
        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i));
        }
        System.out.println();

        // 第三种遍历方式使用迭代器遍历
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()) {
            System.out.print(iterator.next());
        }
        System.out.println();
    }

五、LinkedList能够被当作堆栈来使用

因为LinkedList实现了接口Dueue,因此LinkedList能够被当作堆栈来使用,这个你本身研究吧。

Vector

Vector和ArrayList不论在实现,仍是使用上,都大同小异。所以也就不细说,他们主要的不一样就是Vector是线程安全的,它在一些方法上加了synchronized关键字。

划重点了!!!!!!!!

一、Arraylist与Vector的区别

  • Vector是线程安全的,ArrayList不是线程安全的。
  • ArrayList在底层数组不够用时在原来的基础上扩展0.5倍(3/2),Vector是扩展1倍。

参见 https://zhuanlan.zhihu.com/p/28241176

Set接口

Set接口也是Collection接口的一个经常使用子接口,它区别于List接口的特色在于:

Set中的元素实现了不重复,有点象集合的概念,无序,不容许有重复的元素,最多容许有一个null元素对象。

须要注意的是:虽然Set中元素没有顺序,可是元素在set中的位置是有由该元素的HashCode决定的,其具体位置实际上是固定的。

此外须要说明一点,在set接口中的不重复是由特殊要求的。

举一个例子:对象A和对象B,原本是不一样的两个对象,正常状况下它们是可以放入到Set里面的

可是

若是对象A和B的都重写了hashcode和equals方法,而且重写后的hashcode和equals方法是相同的话。那么A和B是不能同时放入到Set集合中去的

也就是Set集合中的去重和hashcode与equals方法直接相关。

Set接口的常见实现类有HashSet,LinedHashSet和TreeSet这三个,如今依次介绍这三个类:

HashSet

HashSet是Set接口的最多见的实现类了。其最底层是经过Hash表(一个元素为链表的数组)实现的。另外,Hash表底层依赖的两个方法hashcode与equals方法,想存入HashSet的元素须要复写这两个方法。

为何说其最底层呢?请先看下这段源码:

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable {
    
    private transient HashMap<E,Object> map;
    
    public HashSet() {
        map = new HashMap<>();
    }
}

显而易见,HashSet的内部是基于HashMap实现的,咱们都知道在HashMap中的key是不容许重复的,你换个角度看看,那不就是说Set集合吗?

咱们只须要用一个固定值值代替Map中的value,

private static final Object PRESENT = new Object();    

    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

jdk中用了一个静态的Object对象代替了value。那Set中的元素作key即可以保证元素不重复。

下面讲解一下HashSet使用和理解中容易出现的误区:

一、HashSet中存放null值

HashSet中时容许出入null值的,可是在HashSet中仅仅可以存入一个null值哦。

二、 HashSet中存储元素的位置是固定的

HashSet中存储的元素的是无序的,这个没什么好说的,可是因为HashSet底层是基于Hash算法实现的,使用了hashcode,因此HashSet中相应的元素的位置是固定的哦。

三、 遍历HashSet的几种方法

HashSet<String> hashSet = new HashSet<String>();
        hashSet.add("hello,");
        hashSet.add("hello,");
        hashSet.add("world");

        // 第一种遍历方式,使用foreach遍历
        for (String str : hashSet) {
            System.out.print(str);
        }
        System.out.println();

        // 第二种遍历方式,使用迭代器遍历
        Iterator<String> iterator = hashSet.iterator();
        while (iterator.hasNext()) {
            System.out.print(iterator.next());
        }

LinkedHashSet

LinkedHashSet不只是Set接口的子接口并且仍是上面HashSet接口的子接口,和HashSet由HashMap实现同样,LinkedHashSet的底部由LinkedHashMap实现。

Set<String> set = new HashSet<String>();
        set.add("hello");
        set.add("world");

        for (String str : set) {
            System.out.print(str);
        }
        System.out.println();

        set = new LinkedHashSet<String>();
        set.add("hello");
        set.add("world");
        for (String str : set) {
            System.out.print(str);
        }

上面的程序输出结果以下:

输入图片说明

可见,LinkedHashSet集合一样是根据元素的hashCode值来决定元素的存储位置,可是它同时使用链表维护元素的次序。这样使得元素看起来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。

由于它底层由LinkedHashMap实现,因此更多细节参加LinkedHashMap。

TreeSet

TreeSet是SortedSet接口的惟一实现类,TreeSet能够确保集合元素处于排序状态。

TreeSet支持两种排序方式,天然排序 和定制排序,其中天然排序为默认的排序方式。

向TreeSet中加入的应该是同一个类的对象。TreeSet判断两个对象不相等的方式是两个对象经过equals方法返回false,或者经过CompareTo方法比较没有返回0

天然排序

天然排序使用要排序元素的CompareTo(Object obj)方法来比较元素之间大小关系,而后将元素按照升序排列。

Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现了该接口的对象就能够比较大小。obj1.compareTo(obj2)方法若是返回0,则说明被比较的两个对象相等,若是返回一个正数,则代表obj1大于obj2,若是是 负数,则代表obj1小于obj2。

定制排序

天然排序是根据集合元素的大小,以升序排列,若是要定制排序,应该使用Comparator接口,实现 int compare(T o1,T o2)方法

因为水平和时间有限,Map相关内容暂不总结

https://www.cnblogs.com/xiohao/p/4309462.html

https://zhuanlan.zhihu.com/p/24338517?utm_source=wechat_session&utm_medium=social

相关文章
相关标签/搜索