java集合之Collection接口

一、集合框架的概述

1.集合、数组都是对多个数据进行存储操作的结构,简称java容器。此时的存储,主要是指内存层面的存储,不涉及到持久化存储(如.txt,.jpg)

2.数组在存储多个数据方面的缺点:

  • 一旦初始化,其长度就不可改变。
  • 数组中提供的方法非常有限,对于增加、删除、插入数据等操作,非常不便,同时效率不高。
  • 获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用。
  • 数组存储数据的特点:有序、可重复。对于无序、不可重复的需求不能满足。

 3.集合的概念图:

 

二、集合框架

1.Collection接口:单列集合,用来存储一个一个对象。

  1.1list接口:存储有序、可重复的数据(动态数组)

    具体实现类:ArrayList、LinkedList、Vector(并列关系)

常用方法:

①contains(Object obj):判断当前集合中是否包含obj,判断的原理其实是调用obj对象所在类的equals(),所有如果obj的是String,因为String重写了equals,所有new相同的内容返回的比较结果是true。但是如果是比较一个自定义类,那么调用的便是Object中的equals,比较的就是地址,返回结果就是false。

②containsAll(Collection coll):判断形参coll中的所有元素是否都存在当前集合中。

③remove(Object obj):移除前需要比较是否相等,所有也要重写equals

④removeAll(Collection coll):从当前集合中移除coll中所有的元素。差集

⑤retainAll(Collection coll):获取当前集合和coll集合的交集,返回当前集合

⑥equals(Object obj):要向返回true,需要当前集合和形参集合的元素都相同。

⑦hashcode():返回当前对象的哈希值

⑧集合——>数组:toArray()

⑨数组——>集合:调用Arrays类的静态方法asList()

Arrays.asList(new int【】{123,456});//这个时候,会转换为一个集合元素

Arrays.asList(new Integer【】{123,456});//这个时候,才会转换为二个集合

⑩iterator():返回Iterator接口的实例,用于遍历集合元素。

使用方式一:

一个个输出,但是不清除有多少个元素,容易报异常:

System.out.println(iterator.next());。。。。

如果超出集合中元素的个数,就会报异常:NoSuchElementException

使用方式二:

用for循环来打印,这样不会手抖多打印,而且简单方便

使用方式三:推荐方式:

while(iterator.hasNext()){
System.out.println(iterator.next());
}

错误使用方式一:

while(coll.iterator().hasNext()){
System.out.println(coll.iterator.next());//结果会第一个元素无限循环,因为每调用一次iterator都会生成一个新的集合
}

错误使用方式二:

while((iterator.next())!=null){
System.out.println(iterator.next());//返回不全且报异常,因为判断上调用了next指针下移,输出再次下移,等于输出一个数,指针移动两次
}

 

删除操作remove():

可以在遍历时,删除集合中的元素,此方法不同于集合中的remove()。

Iterator iter = coll.iterator();//回到起点
while(iter.hasNext()){
Object obj = iter.next();
if(obj.equals(“Tom”)){
iter.remove();
}
}

注意:如果还未调用next()或在上一次调用next方法之后已经调用了remove方法,再调用remove会报illegalStateException。

 

JDK5,0新增了foreach循环,用于遍历集合、数组,使用方法:

for(Object obj : coll){//集合元素的类型 局部变量 : 集合对象
System.out.println(obj);//内部其实还是iterator迭代器,也就是把coll里面的元素放入obj中
}

 

---ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object【】 elementData存储。

ArrayList源码分析:

①在JDK7.0的时候:

ArrayList list = new ArrayList();//底层创建了一个长度为10的Object【】数组elementData。
list.add(123);//elementData【0】 = new Integer(123);
。。。
list.add(11);//如果这次的添加导致底层elementData数组容量不够,则扩容。默认情况下,扩容为原来的1.5倍,同时需要将原有数组中的数据复制到新的数组中。

结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)

②在JDK8.0中的变化:

ArrayList list = new ArrayList();//底层Object【】 elementData初始化为{},并没有创建长度。
list.add(123);//第一次调用add()时,底层才去通过grow()创建了长度为10的数组,并将数据123添加到elementData中。
...
//后续的添加和扩容操作和7.0没有区别

结论:JDK7.0中的ArrayList的对象的创建类似于单例的饿汉式,而8.0中的创建类似于单例中的懒汉式,延迟了数组的创建,节省内存。

 常用方法:

增:void add(Object obj)

删:remove(int index)/remove(Object obj)

改:set(int index,Object ele)

查:get(int index)

插:add(int index,Object ele)

长度:size()

遍历:1.Iterator迭代器方式

   2.增强for循环

   3.普通循环

---LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储。

源码分析:

LinkedList list = new LinkedList();//内部声明了Node类型的first和last属性,默认值为null
list.add(123);//将123封装到Node中,创建了Node对象。

其中,Node定义为(体现了LinkedList双向链表的说法):

private static class Node<E>{
E item;
Node<E> next;
Node<E> prev;

Node(Node<E> prve,E element,Node<E> next){
this.item = element;
this.next = next;
this.prev = prev;
}
}

 

---Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object【】 elementData。

 源码分析:

JDK7.0和JDK8.0中通过Vector()构造器创建对象时,底层都创建了长度为10的数组,在扩容方面,默认扩容为原来数组长度的2倍。

 

 

 

  1.2set接口:存储无序、不可重复的数据

    具体实现类:HashSet、LinkedHashSet、TreeSet

注意:Set所用的方法都是Collection里面定义的。

   向Set中添加的数据,其所在的类一定要重写hashCode()和equals()

   重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码

   重写两个方法的小技巧:对象中用作equals()比较的Field,都应该用来计算hashCode值。

---HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值

1.无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值。

2.不可重复性:保证添加的元素按照其equals()判断时,不能返回true。即:相同的元素只能添加一个。

3.添加元素的过程:

我们向HashSet中添加元素a,首先调用元素a所在类的hashCode(),计算元素a的哈希值,此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置。(即:索引位置),判断数组此位置上是否已经有元素:

  如果此位置上没有其他元素,则元素a添加成功。——>情况1

  如果此位置上有其他元素b(或以链表形式存在多个元素),,则比较元素a和元素b的hash值:

    如果hash值不相同,则元素a添加成功。——>情况2

    如果hash值相同,进而需要调用元素a所在类的equals():

      equals()返回true,则元素a添加失败。

      equals()返回false,则元素a添加成功。——>情况3

对于添加成功的情况2和情况3而言:元素a与已经存在指定索引位置上数据以链表的方式存储。

JDK7.0:元素a放到数组中,指向原来的元素。

JDK8.0:原来的元素在数组中,指向元素a。(七上八下)HashSet底层:数组+链表结构。

 

---LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历

原理其实同HashSet一样,额外的每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。,优点就是对于频繁的遍历操作,LInkedHashSet效率高于HashSet。原理图解如下:

 

---TreeSet:可以按照添加对象的指定属性,进行排序。

1.向TreeSet中添加的数据,要求是相同类的对象。

2.两种排序方式:自然排序(实现Comparable接口)和定制排序(Comparator)

3.自然排序中,比较两个对象是否相同的标准为:comparaTo()返回0,不再是equals()

4.定制排序中,比较两个对象是否相同的标准为:compara()返回0,不再是equals()

 自然排序代码例子:

TreeSet sa = new TreeSet();
        sa.add(new TisiGui("刘某人",20));
        sa.add(new TisiGui("张某人",34));
        sa.add(new TisiGui("欧某人",30));
        sa.add(new TisiGui("四某人",2));
        sa.add(new TisiGui("色某人",60));

        Iterator iterator = sa.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }

//TisiGui类中
@Override
    public int compareTo(Object o) {
        if (o instanceof TisiGui){
            TisiGui e = (TisiGui)o;
            return this.name.compareTo(e.name);
        }else{
            throw new RuntimeException();
        }
    }

 

posted @ 2019-05-18 14:24  黑暗之魂DarkSouls  阅读(175)  评论(0编辑  收藏  举报