Java集合Collection

来源:互联网 发布:java接口怎么写 编辑:程序博客网 时间:2024/06/08 12:13

集合,就是存放数据对象的容器,类比于之前学过的一种容器–数组,集合有着比较高的优越性。就拿存放的数据来说,数组存放的数据类型是单一的,即仅能存放某一种类型的数据,而且数组一旦定义以后,其长度大小都不可再更改,而Collection集合可以存放任意类型的数据,集合的大小可以随着存储数据量的多少自动增容。(当然集合和数组中存放的都是对象的引用而非对象本身)

Collection接口类:最顶层的集合接口类:其中定义了集合的通用性方法,包含基本的操作CRUD
—–List接口:有序,可重复性
—–Set接口:无序,不可重复
上述两类都是单列集合

Collection接口的共性方法:

  • 增加:

    1:add() 将指定对象存储到容器中              add 方法的参数类型是Object 便于接收任意对象2:addAll() 将指定集合中的元素添加到调用该方法和集合中
  • 删除:

    3:remove() 将指定的对象从集合中删除4:removeAll() 将指定集合中的元素删除
  • 修改

    5:clear() 清空集合中的所有元素
  • 判断

    6:isEmpty() 判断集合是否为空7:contains() 判断集合何中是否包含指定对象 所以使用contains方法进行判断的时候,可能会需要重写equals方法,因为判断包含的标准可能不一样8:containsAll() 判断集合中是否包含指定集合 使用equals()判断两个对象是否相等  
  • 获取:

    9:int size()    返回集合容器的大小
  • 转成数组

    10: toArray()   集合转换数组

List接口:有序,可重复性的元素;对于List类的集合,会涉及到对于某个索引的操作,允许在指定位置插入元素,通过索引来访问某个元素

List接口特有的方法:

  • 1:增加

    void add(int index, E element) 指定位置添加元素            boolean addAll(int index, Collection c) 指定位置添加集合  
  • 2:删除

    E remove(int index) 删除指定位置元素
  • 3:修改

    E set(int index, E element)    返回的是需要替换的集合中的元素
  • 4:查找:

    E get(int index)             注意: IndexOutOfBoundsExceptionint indexOf(Object o)         // 找不到返回-1lastIndexOf(Object o) 
  • 5:求子集合

    List<E> subList(int fromIndex, int toIndex) // 不包含toIndex   

List接口的具体实现类:
——-ArrayList:数组集合

实现原理:ArrayList底层是维护了一个Object数组实现的,利用ArrayList无参构造函数的时候,默认的数组长度是10,当然可以通过ArrayList的构造函数new ArrayList(20)来显示指定初始容量。当目前的容量不够存储对象时,会自动增容为原来的1.5倍。
特点:查询速度快,增删慢。
原因:因为ArrayList底层维护的是一个Object数组,我们都知道数组的空间内存地址是连续的,所以访问某个对象的时候,只需要通过索引便可以直接获取到。但是当我们向ArrayList对象添加对象的时候,首先判断目前的容量是否足够,如果足够,这种情况还好,直接添加元素就可以;如果不够,会创建一个新的ArrayList对象,容量为久对象容量的1.5倍,同时将旧数组中的所有元素拷贝到新数组中,如果数据量大,效率低得可怕。对于删除,假设有一个长度100的数组,假如删除了索引位置2处的一个元素,由于存储的元素是内存空间连续,所以说需要将索引3以后的全部元素拷贝到索引2开始的位置,其中也会有数据量的拷贝,所以效率也低下。
下面写一个小程序:去除ArrayList中重复的元素
思路:新建一个ArrayList对象,遍历旧集合,如果元素不包含在新的ArrayList集合中,则将该元素加入到新集合对象中

public class Demo6 {    public static void main(String[] args) {        ArrayList arr = new ArrayList();        Person p1 = new Person("jack", 20);        Person p2 = new Person("rose", 18);        Person p3 = new Person("rose", 18);        arr.add(p1);        arr.add(p2);        arr.add(p3);        System.out.println(arr);        ArrayList arr2 = new ArrayList();        for (int i = 0; i < arr.size(); i++) {            Object obj = arr.get(i);            Person p = (Person) obj;            if (!(arr2.contains(p))) {                arr2.add(p);            }        }        //利用Iterator        /**        Iterator it = arr.iterator();            Person p = (Person)it.next();            if(!arr2.contains(p)){                arr2.add(p);            }        }        */        System.out.println(arr2);    }}class Person {    private String name;    private int age;    public Person() {    }    public Person(String name, int age) {        this.name = name;        this.age = age;    }    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;    }    @Override    public int hashCode() {        return this.name.hashCode() + age * 37;    }    @Override    public boolean equals(Object obj) {        if (!(obj instanceof Person)) {            return false;        }        Person p = (Person) obj;        return this.name.equals(p.name) && this.age == p.age;    }    @Override    public String toString() {        return "Person@name:" + this.name + " age:" + this.age;    }}

在实际开发中,ArrayList是使用率最高的一个集合。

List接口的具体实现类:
——-LinkedList:链表集合

实现原理:底层是采用链表实现的,内存地址是不连续的。
特点:查找慢,增删快。
原因:因为底层是双向链表实现,每个节点存储了对象的引用和下一个节点的内存地址。内存地址不连续,所以说如果想要检索集合中的某个元素,必须通过遍历该集合,依次取出每个元素来进行比对判断是否是所要检索的元素,所以查找慢;但是增加元素的时候,链表在插入新元素的时候,只需要让前一个元素记住新元素,让新元素记住下一个元素就可以了,所以插入很快;删除元素的时候,只需要让前一个元素记住后一个元素,后一个元素记住前一个元素就可以了,所以删除也很快。
特有方法:

  • 1:方法介绍

    addFirst(E e) addLast(E e) getFirst() getLast() removeFirst() removeLast() 

    如果集合中没有元素,获取或者删除元
    素抛:NoSuchElementException

  • 2:数据结构

            1:栈             先进后出            push()             pop()        2:队列            先进先出            offer()            poll()
  • 3:返回逆序的迭代器对象

    descendingIterator()   返回逆序的迭代器对象

很多方法的功能无需解释,看方法就能猜个差不多,其中push()和addLast()实现的功能是一样的,都是在后面增加一个元素;pop()和removeLast()实现的功能也是一样的,都是移除最后一个元素;offer()和addLast()实现的功能是一样的,都是在后面增加一个元素;poll()和removeFirst()实现的功能是一样的,都是移除第一个元素。奇怪了?为什么设计这么多方法实现了相同的功能?
这是为了模拟堆栈结构和队列结构,下面的程序利用LinkedList集合模拟了堆栈结构和队列结构

import java.util.LinkedList;/** * Created by Dream on 2017/10/24. * StackList模拟的堆栈结构 * QueueList模拟的队列结构 */class StackList{    LinkedList list;    public StackList(){        list = new LinkedList();    }    public void add(Object o){        list.push(o);    }    public Object pop(){        return list.pop();    }    public int size(){        return list.size();    }}class QueueList{    LinkedList list;    public QueueList(){        list = new LinkedList();    }    public void add(Object o){        list.offer(o);    }    public Object remove(){        return list.poll();    }    public int size(){        return list.size();    }}public class LinkedListPrac {    public static void main(String[] args){        StackList stack = new StackList();        stack.add("Dream11");        stack.add("Thia22");        stack.add("Sam33");        int size1 = stack.size();        for(int i=0;i<size1;i++){            System.out.print(stack.pop()+"   ");        }        System.out.println();        QueueList queue = new QueueList();        queue.add("Dream11");        queue.add("Thia22");        queue.add("Sam33");        int size2 = queue.size();        for(int i=0;i<size2;i++){            System.out.print(queue.remove()+"   ");        }    }}

下面再利用LinkedList集合实现洗牌的功能

import java.util.LinkedList;import java.util.Random;/** * Created by Dream on 2017/10/24. * 所有的花色和点数分别存放在String数组中,然后通过循环首先创建扑克牌 * 然后通过随机生成0~51范围的整数作为索引值,交换两个索引值的元素 * 如此往复100次,也可以其他次数,即可得到洗牌后的结果 */class Poker{    String color;    String num;    public Poker(String color,String num){        this.color = color;        this.num = num;    }    public String toString(){        return "["+color+num+"]";    }}public class PokerLinkedList {    public static void main(String[] args){        LinkedList pokers = createPoker();        shufflePokers(pokers);        showPokers(pokers);    }    public static LinkedList createPoker(){        LinkedList pokers = new LinkedList();        String[] colors = {"黑桃","梅花","方片","红桃"};        String[] nums = {"A","2","3","4","5","6","7","8","9","10","J","Q","K"};        for(int i=0;i<colors.length;i++){            for(int j=0;j<nums.length;j++){                Poker poker = new Poker(colors[i],nums[j]);                pokers.add(poker);            }        }        return pokers;    }    public static void shufflePokers(LinkedList pokers){        Random random = new Random();        for(int i=0;i<100;i++){            int index1 = random.nextInt(52);            int index2 = random.nextInt(52);            Poker p1 = (Poker) pokers.get(index1);            Poker p2 = (Poker)pokers.get(index2);            pokers.set(index1,p2);            pokers.set(index2,p1);        }    }    public static void showPokers(LinkedList pokers){        for(int i=0;i<pokers.size();i++){            System.out.print(pokers.get(i));            if(i%10 == 9)                System.out.println();        }    }}

List接口的具体实现类:
——-Vector:描述的是线程安全的ArrayList集合

实现原理:底层也是维护了一个Object数组实现的,实现原理与ArrayList是一样的。但是Vector线程安全,操作效率低
???ArrayList和Vector的区别
!!!相同点:底层都是通过维护一个Object数组实现的
不同点:1.ArrayList是线程不同步的,操作效率高;Vector是线程同步的,操作效率低。
2.ArrayList是JDK1.2出现的,Vector是JDK1.0就出现了

迭代方法并不是Iterator,而是通过Enumeration 接口

  • boolean hasMoreElements()

      测试此枚举是否包含更多的元素。 
  • E nextElement()

      如果此枚举对象至少还有一个可提供的元素,则返回此枚举的下一个元素。
Vector v = new Vector();// 遍历Vector遍历Enumeration ens = v.elements();while ( ens.hasMoreElements() ){    System.out.println( ens.nextElement() );}

迭代器:专门取出集合中的对象,通俗来讲,就是通过迭代器能够遍历集合中的对象。但是该对象是比较特殊的,不能通过new来创建,因为该类是以内部类的形式存在于集合内部,是依赖于集合而存在的
Collection接口类中定义了获取迭代器的方法iterator(),所有Collection集合体系的均可以获得自身集合的迭代器,只不过每个子类都进行了重写,因为底层存储数据的实现方式是不一样的
对于List集合,可以通过get方法或者Iterator获取元素,而Set集合没有get方法,只能通过Iterator获取

Collection集合的通用迭代器—Iterator接口该接口是集合的迭代器接口类,定义了常见的迭代方法

  • 1:boolean hasNext()

    判断集合中是否有元素,如果有元素可以迭代,就返回true。
  • 2: E next()

    返回迭代的下一个元素,注意: 如果没有下一个元素时,调用

    next元素会抛出NoSuchElementException

  • 3: void remove()

    从迭代器指向的集合中移除迭代器返回的最后一个元素(可选操作)。

下面来分析一下迭代器的原理:

ListIterator it = list.listIterator();  //1        while(it.hasNext()){            //2            System.out.print(it.next()+" "); //3}

1.返回一个迭代器,获取迭代器的时候,迭代器中的指针指向了第一个元素
2.hasNext()判断迭代器当前指针是否有指向元素
3.next()方法获取指针当前指向的元素同时将指针地址+1,使其指向下一个元素
注意:在对集合进行迭代的过程中,是不允许迭代器以外的方式对元素进行操作,因为这样会产生安全隐患,Java会抛出 java.util.ConcurrentModificationException异常,普通迭代器只支持在迭代器中删除操作。
如下列代码就会报上述异常:

List list = new ArrayList();list.add("A1");list.add("B2");list.add("C3");list.add("D4");Iterator it = list.iterator();while(it.hasNext()){    System.out.println(it.next());    list.add("aaa");}

ListIterator:List特有的迭代器,除了有通用的迭代器方法,该迭代器还支持添加元素,逆序遍历元素

  • add(E e)

    将指定的元素插入列表(可选操作)。该元素直接插入到 next 返回的下一个元素的前面(如果有)
  • void set(E o)

      用指定元素替换 next 或 previous 返回的最后一个元素
  • hasPrevious()

      逆向遍历列表,列表迭代器有多个元素,则返回 true。
  • previous()

    返回列表中的前一个元素。
 while (it.hasPrevious()){   //1      System.out.print(it.previous()+" ");   //2}

1.hasPrevious判断是否存在上一个元素
2.如果存在上一个元素,首先将指针向上移动一个单位,然后再获取当前指针指向的元素

List list = new ArrayList();list.add("A1");list.add("B2");list.add("C3");list.add("D4");ListIterator it = list.listIterator; while (it.hasNext()){   //1      System.out.print(it.next()+" ");   //2      it.add("aaa");  //3}

1.hasNext判断当前指针是否指向元素
2.获取当前指针指向的元素,同时将指针向下移动一个单位
3.添加元素的时候是插入到next返回的下一个元素前面
剖析一下上述代码执行的过程:
最初集合内元素:A1,B2,C3,D4
1.迭代器指针指向A1元素
2.此时的next返回的元素是A1,next返回的下一个元素是B2,而且此时迭代器指针已经指向B2
3.添加到元素B2之前
一次循环执行结束后集合内元素:A1,aaa,B2,C3,D4
继续执行:
1.迭代器指向B2
2.此时next返回的元素是B2,返回的下一个元素是C3,而且此时迭代器指针已经指向C3
3.添加到C3元素之前
第二次循环结束后集合内的元素:A1,aaa,B2,aaa,C3,D4
以此类推,最终集合内元素为:A1,aaa,B2,aaa,C3,aaa,D4,aaa

Set集合接口:集合中元素无序,且不可重复。存入的数据顺序和我们取出的数据顺序是不一定一致的。其中没有get方法,如果想得到其中存储的元素,只能通过迭代器获得
————HashSet:Set接口具体实现类的一种,底层是通过哈希表实现的。

实现原理:底层通过哈希表存储,在向hashSet集合中添加对象时,首先计算该对象的哈希值,然后根据对象的哈希值得出在哈希表中的存放位置(可以把哈希值简单理解为内存地址)。假设现在要新添加一个对象,计算该对象得到该对象的哈希值,然后就去哈希表里寻找这个位置
1.如果该位置没有存储任何元素,则可以直接将该元素放到此存储位置上
2.如果发现该位置已经存储有数据,那么新对象会和已存储的数据进行equals方法比较,如果相等,则该元素视为相同元素,不再添加到哈希表中,此时是添加新元素失败;如果equals方法返回了false,则表示并不是同一个元素,则将该元素也添加到哈希表的该位置上。咦?奇怪,怎么不是一个萝卜一个坑?这是因为哈希表中每个位置都是一个“桶式结构”,如果有相同哈希值但是不想等的元素就会被放置到同一个“桶”里。总结一下就是,先比较hashCode值,如果相同,再equals比较,这是最后一道防线。
特点:存取速度快,通过对象的哈希值直接可以得出在哈希表中的位置,相当于直接索引,所以存取速度快!

创建一个HashSet对象,每次向元素中添加对象的时候,首先会调用该对象的hashCode()方法,如果hashCode()方法返回的结果是一样的,则会再调用该对象的equals()方法,所以每次添加对象,hashCode方法是必调用,equals只有在hashCode方法返回true时才会被调用。对于自定义的对象,需要根据添加规则来重写hashCode方法和equals方法,否则默认继承Object的方法,有可能是不满足要求的!
写一个小例子:

import java.util.HashSet;/** * Created by Dream on 2017/10/26. * 对于要添加的对象,只要身份证号id一样,无论其名字是什么,视为同一人 */class Person{    int id;    String name;    public Person(int id,String name){        this.name = name;        this.id = id;    }    public String toString(){        return "{id:"+this.id+" name:"+this.name+"}";    }    public int hashCode(){        System.out.println("====hashCode=====");        return this.id;    }    public boolean equals(Object o){        System.out.println("======equals====");        Person p = (Person)o;        return this.id == p.id;    }}public class HashSetPrac {    public static void main(String[] args){        HashSet set = new HashSet();        set.add(new Person(110,"Dream"));        set.add(new Person(440,"Thia"));        set.add(new Person(220,"Marry"));        set.add(new Person(330,"Tom"));        set.add(new Person(110,"耿耿"));        System.out.println(set);    }}

输出结果:
====hashCode=====
====hashCode=====
====hashCode=====
====hashCode=====
====hashCode=====
======equals====
false
[{id:440 name:Thia}, {id:330 name:Tom}, {id:220 name:Marry}, {id:110 name:Dream}]
添加几次元素调用几次HashCode方法,其中最后一个元素添加失败,因为id号为110的元素已经被存储了
注意:String类对象重写了hashCode方法,如果两个String对象的内容是一致的,则其hashCode得到的结果也是一致的!

——–>TreeSet集合:底层是通过红黑树(二叉树)结构实现的。

如果元素具备自然顺序的特性,那么就按照元素具备的自然顺序特性进行排序。一提到自然顺序,可能想到的就是阿拉伯数字和英文字母,确实,如果向TreeSet集合中存储这些数据,得到的都是有序的数据。
但是,如果我们想要存储自定义的对象呢?TreeSet存储的元素是有序的,可想而知肯定是具有某种比较规则,如果是自定义的对象,就需要我们提供一定的比较规则!如果不定义比较规则,就会报错
1.添加的元素不具备自然顺序的特性,则元素所属的类必须实现Comparable接口,把元素的比较规则写在方法compareTo方法中,如果方法返回0则视该元素为重复元素,不再添加。

import java.util.TreeSet;/** * Created by Dream on 2017/10/26. * 人员按照工资多少排序 */class Employee implements Comparable{    int id;    String name;    int salary;    public Employee(int id,String name,int salary){        this.id = id;        this.name = name;        this.salary = salary;    }    public String toString(){        return "{id:"+id+" name:"+name+" salary:"+salary+"}";    }    /*负整数、零或正整数,根据此对象是小于、等于还是大于指定对象*/    public int compareTo(Object o){        Employee e = (Employee)o;        return this.salary-e.salary;    }}public class TreeSetPrac {    public static void main(String[] args){        TreeSet set = new TreeSet();        set.add(new Employee(110,"Dream",300));        set.add(new Employee(220,"Thia",100));        set.add(new Employee(330,"Jerry",200));        set.add(new Employee(440,"Tom",500));        System.out.println(set);    }}

红黑树是一种特殊的二叉树,左大右小,即左节点的值小于其父节点的值,右节点的值大于其父节点的值。
实现原理:底层是通过红黑树结构存储的。添加的第一个元素作为红黑树的根节点,并且根节点自己和自己也会有比较,添加第二个元素的时候,首先与跟元素比较,如果小于则存储为左节点,如果大于,则存储为右节点,同样的道理去添加后续的元素。每次添加完节点后,都要看一下当前的“树结构”是否是二叉树,如果不是,则首先调整为二叉树,然后再添加元素。
2.添加的元素不具备自然顺序的特性,同时对象自身也没有实现Comparable接口,那么创建TreeSet的时候必须传入一个比较器,如果比较方法返回0,则视为重复元素。
自定义比较器格式:

class myClass implements Comparator{    int compare(T t1,T t2){    }}
/*自定义比较器*/class MyComparator implements Comparator{    public int compare(Object o1,Object o2){        Employee e1 = (Employee)o1;        Employee e2 = (Employee)o2;        return e1.id - e2.id;    }}public class TreeSetPrac {    public static void main(String[] args){        MyComparator comparator = new MyComparator();        TreeSet set = new TreeSet(comparator);        set.add(new Employee(110,"Dream",300));        set.add(new Employee(220,"Thia",100));        set.add(new Employee(330,"Jerry",200));        set.add(new Employee(440,"Tom",500));        System.out.println(set);    }}

推荐使用Comparator比较器,因为这个比较器可以应用在很多类上,而Comparable的比较规则只能在一个类中使用。
如果同时传入比较器,而且对象本身也实现了Comparable接口,那么排序规则以比较器为准。
向TreeSet集合中存入字符串对象,是可以对字符串进行比较的,因为String类已经实现了Comparable接口,重写了compareTo方法,那么比较规则是什么?
1.对应位置处的字符不相同,比较的就是对应位置处不同的字符
2.如果对应上的字符都一样,比较的就是字符串的长度