Java集合Collection

集合,就是存放数据对象的容器,类比于之前学过的一种容器–数组,集合有着比较高的优越性。就拿存放的数据来说,数组存放的数据类型是单一的,即仅能存放某一种类型的数据,而且数组一旦定义以后,其长度大小都不可再更改,而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)             注意: IndexOutOfBoundsException
    int indexOf(Object o)         // 找不到返回-1
    lastIndexOf(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.如果对应上的字符都一样,比较的就是字符串的长度

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值