Java集合框架篇Collection—List&Set

目录

Collection

  • 集合类特点
  • 一、只用于存储对象的引用(地址)
  • 二、存储长度可变
  • 三、存储不同类型的对象

在生活中我们有着各种各样的容器,有玻璃的,有陶瓷的,有带把手的,有带隔板的,这些容器有着共同的特性就是一个容器,同时也有着不同的功能差异。在Java中也是如此有着各种各样的集合,我们可以把它这个大家族称之为集合框架。每一个容器对数据的存储方式都有不同,这些存储方式我们称之为:数据结构。常用集合基本图如下:
基本集合框架图

public interface Collection<E> extends Iterable<E>
Collection 层次结构 中的根接口。
Collection 表示一组对象,这些对象也称为 collection 的元素。
一些 collection 允许有重复的元素,而另一些则不允许。
一些 collection 是有序的,而另一些则是无序的。
JDK 不提供此接口的任何直接 实现:它提供更具体的子接口(如 Set 和 List)实现。此接口通常用来传递 collection,并在需要最大普遍性的地方操作这些 collection。
—— jdk api

List

  • List特点:
  • 元素都是有序的
  • 元素可重复
  • 该集合体系有索引
  • 持有特有的ListIterator迭代器

所以凡是操作角标的集合方法都是List特有的方法。

public interface List<E> extends Collection<E>
有序的 collection(也称为序列)。
此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。
与 set 不同,列表通常允许重复的元素。
—— jdk api

ArrayList

  • 该列表特点:
  • 底层数据结构为数组结构。因此查改快,增删较慢
  • 非同步操作
  • ArrayList()初始容量为10的空列表,每次new +50%增长,数据Copy

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, SerializableList
接口的大小可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。(此类大致上等同于 Vector 类,除了此类是不同步的。)
—— jdk api

  • ArrayList 基本用法
import java.util.*;

class ListDemo{
    public static void main(String[] args){
        ArrayList a1 = new ArrayList();
        a1.add("01");
        a1.add("02");
        a1.add("03");
        print(a1);
        // for (Iterator it = a1.iterator();it.hasNext() ; ) {
        //  Object obj = it.next();
        //  if (obj.equals("02")) {
        //      // a1.add("04") 
        //      //操作元素的方式不能同时用集合对象和迭代器 
                //会发生并发修改异常
        //      //ConcurrentModificationException
        //      it.remove();
        //  }
        // }

        //List集合特有的迭代器ListIterator是Iterator的子接口
        //该接口只能通过List的集合的ListIterator获取

        ListIterator li = a1.listIterator();
        for (;li.hasNext() ; ) {
        //迭代次数是固定的并不会因为在迭代过程中添加或删除元素就改变
        // 变化的是集合而不是迭代器
            if (li.next().equals("03")) {
                li.set("003");
                li.add("04");
                //注意:如果顺序颠倒会报异常
            }
            print(1);
        }
        print(a1);
        //[01, 02, 03]
        //[01, 02, 003, 04]
        print(li.hasNext()+" "+li.hasPrevious());
        //false true
    }

    public static void method(){
        ArrayList a1 = new ArrayList();
        a1.add("01");
        a1.add(02);
        a1.add(03);
        print(a1+""+a1.size());
        //[01, 2, 3]3
        a1.add(1,"08");
        print(a1);
        //[01, 08, 2, 3]
        a1.remove(2);
        print(a1);
        //[01, 08, 3]
        a1.set(2,"set008");
        print(a1);
        //[01, 08, set008]
        print(a1.get(1));
        //08
        for (int x=0; x<a1.size(); x++) {
            print("for-get:"+a1.get(x));
        }
        for (Iterator it = a1.iterator();it.hasNext() ; ) {
            print("Iterator:"+it.next());
        }
        /*
        for-get:01
        for-get:08
        for-get:set008
        Iterator:01
        Iterator:08
        Iterator:set008
        */

        print("index="+a1.indexOf("08"));
        //index=1
        List sub = a1.subList(0,2);
        print("sub"+sub);
        //sub[01, 08]

    }

    public static void print(Object obj){
        System.out.println(obj);
    }
}
  • ArrayList 基本元素存储去重实现
import java.util.*;

//ArrayList 去重
//在迭代时循环中 next 调用一次,就要hasNext判断一次

class ArrayListTest{
    public static void main(String[] args){
        ArrayList al = new ArrayList();
        al.add("001");
        al.add("002");
        al.add("002");
        al.add("001");
        print(al);
        al = singleElement(al);
        print(al);
    }

    public static void print(Object obj){
        System.out.println(obj);
    }

    public static ArrayList singleElement(ArrayList al){
        ArrayList newAl = new ArrayList();

        for (Iterator it = al.iterator();it.hasNext() ; ) {
            Object obj = it.next();
            if (!newAl.contains(obj)) {
            //contains的底层原理就是用equals比较
                newAl.add(obj);
            }
        }

        return newAl;

    }
}
  • ArrayList 对象存储去重实现
import java.util.*;
/*
当ArrayList中是对象时,需要对Object的equals进行规则重写
因为在List中数据的比较操作都是通过equals来判断的
*/
class ArrayListTest2{
    public static void main(String[] args){
        ArrayList al = new ArrayList();
        al.add(new Person("wanger1",32));
        al.add(new Person("wanger2",52));
        al.add(new Person("wanger3",33));
        al.add(new Person("wanger4",31));
        al.add(new Person("wanger4",31));

        al = singleElement(al);

        for (Iterator it = al.iterator();it.hasNext() ; ) {
            Person p = (Person)it.next();
            print(p.getName()+":"+p.getAge());  
        }
    }

    public static void print(Object obj){
        System.out.println(obj);
    }

    public static ArrayList singleElement(ArrayList al){
        ArrayList newAl = new ArrayList();
        for (Iterator it = al.iterator();it.hasNext() ; ) {
            Object obj = it.next();
            if (!newAl.contains(obj)) {
                newAl.add(obj);
            }
        }

        return newAl;

    }
}

class Person{
    private String name;
    private int age;

    Person(String name,int age){
        this.name = name;
        this.age = age;
    }

    public String getName(){
        return name;
    }

    public int getAge(){
        return age;
    }

    public boolean equals(Object obj){
        if(!(obj instanceof Person))
            return false;
            //instanceof进行类型检查规则是:
            //你属于该类吗?或者你属于该类的派生类吗?
        Person p = (Person)obj;
        System.out.println(this.name+"..."+p.name);
        return this.name.equals(p.name) && this.age == p.age;
    }
}

LinkedList

  • 该列表特点:
  • 底层数据结构为链表结构。因此查改慢,增删快
  • 也是非同步的
  • LinkedList() 构造的是一个空列表

为了更好理解链表示意图如下:
LinkedList操作示意图

由此可以看出LinkedList的增删都对列表的改动不大,而查改则需要诶个数据进行询问就很麻烦。

  • LinkedList 基础用法
import java.util.*;

/*
addFirst();
addLast();

getFirst();
getLast();

removeFirst();
removeLast();
有返回值并删除当前返回值,没有元素会出现NoSuchElementException

JDK1.6替代方法

offerFiest();
offerLast();

peekFirst();
peeklast();

pollFirst();
pollLast();
以上 获取元素,没有时会返回null

*/

class LinkedListDemo{
    public static void main(String[] args){
        LinkedList link = new LinkedList();

        link.addFirst("001");
        link.addFirst("002");
        link.addFirst("003");
        link.addFirst("004");

        print(link);
        //[004, 003, 002, 001]
        print(link.getLast());
        //001
        print(link.getFirst());
        //004
        print(link.removeFirst());
        //001
    }

    public static void print(Object obj){
        System.out.println(obj);
    }
}
  • 用LinkedList 实现队列或者堆栈
import java.util.*;

//堆栈 : 先进后出 First In Last Out FILO
//队列 : 先进先出 First In First Out FIFO

class LinkedListTest{
    public static void main(String[] args){
        Queue q = new Queue();
        q.myAdd("001");
        q.myAdd("002");
        q.myAdd("003");

        while(!q.isNull()){
            print(q.myGet());
        }
    }

    public static void print(Object obj){
        System.out.println(obj);
    }
}

class Queue{
    private LinkedList link;

    Queue(){
        link = new LinkedList();
    }

    public void myAdd(Object obj){
        link.addFirst(obj);
    }

    public Object myGet(){
        return link.removeLast();
    }
    public boolean isNull(){
        return link.isEmpty();
    }
}

总结

  • 在List中还有一种容器结构叫做Vector,功能与ArrayList是一样的,但是它是同步操作,特有取出方式是枚举(与Iterator功能一致但名字太长被抛弃),每次增长是100%。
  • 当我们判断不定用ArrayList还是LinkedList的时候,我们可以看看数据是否庞大并且需要大量的增删,若是就用LinkedList,否就用ArrayList吧,因为平时一般大量的都是查看。

Set

  • Set特点:
  • 元素排列是无序的
  • 不可重复
  • 无索引

public interface Set<E>extends Collection<E>
一个不包含重复元素的 collection。
更确切地讲,set 不包含满足 e1.equals(e2) 的元素对 e1 和 e2,并且最多包含一个 null 元素。
正如其名称所暗示的,此接口模仿了数学上的 set 抽象。
—— jdk api

HashSet

  • HashSet特点:
  • 底层数据结构是Hash表(存放一堆Hash值的表)
  • 保证元素唯一性原理:判断元素hashCode值是否相同,若相同再用equals比较是否为同一个对象
  • 非同步

我们知道每一个存储在集合中的对象都会有一个来至Object的hashCode()方法,这个方法就为我们生成Hash值,所以有时当我们每次用 对象 OR对象.toString() 打印时就会出现类似 Person@xxx @前面的是该对象实例化的类名 后面部分是hashCode值,注:hashCode值与内存地址值可能有关系但不是必然的,只是一种约定,并不强制。

hashCode
public int hashCode()
返回该对象的哈希码值。支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。
hashCode 的常规协定是:
一、在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
二、如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
三、如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。

toString
public String toString()返回该对象的字符串表示。
通常,toString方法会返回一个“以文本方式表示”此对象的字符串。
结果应是一个简明但易于读懂的信息表达式。建议所有子类都重写此方法。 Object 类的 toString 方法返回一个字符串,该字符串由类名(对象是该类的一个实例)、at标记符“@”和此对象哈希码的无符号十六进制表示组成。
换句话说,该方法返回一个字符串,它的值等于:
getClass().getName() + '@' + Integer.toHexString(hashCode())
—— jdk api Object

  • HashSet 中实现存储自定义类去重
import java.util.*;

class HashSetTest{
    public static void main(String[] args){
        HashSet hs = new HashSet();
        hs.add(new Person("w1",12));
        hs.add(new Person("w2",13));
        hs.add(new Person("w3",14));
        hs.add(new Person("w1",12));

        for (Iterator it = hs.iterator();it.hasNext() ; ) {
            Person p = (Person)it.next();
            print(p.getName()+":"+p.getAge());
        }
    }

    public static void print(Object obj){
        System.out.println(obj);
    }
}

class Person{
    private String name;
    private int age;

    Person(String name,int age){
        this.name = name;
        this.age = age;
    }

    public int hashCode(){
        System.out.println(this.name+"...hashCode...");
        return this.name.hashCode() + this.age*37;
        //对象间比较必须重写hashCode方法。默认每个new对象是必然不同的
        //*37是为了减小偶然性,注意不要越界了。
    }

    public String getName(){
        return name;
    }

    public int getAge(){
        return age;
    }

    public boolean equals(Object obj){
        if(!(obj instanceof Person))
            return false;
        Person p = (Person)obj;
        System.out.println(this.name+"::"+p.name);
        return this.name.equals(p.name) && this.age == p.age;
    }
}

TreeSet

  • TreeSet特点:
  • 可以对Set集合中元素进行排序(默认ASCII码大小排序又称自然排序)
  • 底层用了一个平衡二叉树(红黑树)的数据结构
  • 非同步

public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, Serializable
基于 TreeMap 的 NavigableSet 实现。使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序,具体取决于使用的构造方法。

TreeSet底层图解

  • TreeSet 在查找元素的时候就会从根节点开始比较,就会减少一半边的工作量,但是当元素多了的时候,就会在根节点中取折中值再比较。
  • 既然说到了比较那么就会引入它的比较原理了。

TreeSet
public TreeSet()构造一个新的空 set,该 set 根据其元素的自然顺序进行排序。
插入该 set 的所有元素都必须实现 Comparable 接口
另外,所有这些元素都必须是可互相比较的:对于 set 中的任意两个元素 e1 和 e2,执行 e1.compareTo(e2) 都不得抛出 ClassCastException。如果用户试图将违反此约束的元素添加到 set(例如,用户试图将字符串元素添加到其元素为整数的set中,则add调用将抛出ClassCastException(类型转换异常)。

从它的构造方法我们可以得知很重要的信息就是添加的数据间必须具备可比较性

public interface Comparable<T>此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的 compareTo 方法被称为它的自然比较方法。
int compareTo(T o)比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。

所以当我们需要用TreeSet中添加自定义对象时,一定要记着让对象具备比较性。

import java.util.*;

class TreeSetTest{
    public static void main(String[] args){
        TreeSet hs = new TreeSet(new myCompare());
        // TreeSet hs = new TreeSet();
        hs.add(new Student("w1",15));
        hs.add(new Student("w2",13));
        hs.add(new Student("w4",14));
        hs.add(new Student("w3",14));
        hs.add(new Student("w3",14));

        for (Iterator it = hs.iterator();it.hasNext() ; ) {
            Student p = (Student)it.next();
            print(p.getName()+":"+p.getAge());
        }
    }

    public static void print(Object obj){
        System.out.println(obj);
    }
}

class Student implements Comparable{
    private String name;
    private int age;

    Student(String name,int age){
        this.name = name;
        this.age = age;
    }

    public String getName(){
        return name;
    }

    public int getAge(){
        return age;
    }

    public int compareTo(Object obj){
        if(! (obj instanceof Student))
            throw new RuntimeException("Not Student Object");
        Student s = (Student)obj;
        if (this.age == s.age) {
            return this.name.compareTo(s.name);
        }
        return this.age - s.age;
        //返回值为0,是会认为相同对象,就不会存进去
        //String类本身就实现了Comparable
    }
}

class myCompare implements Comparator{
    public int compare(Object o1,Object o2){
        if(!(o1 instanceof Student) || !(o2 instanceof Student))
            throw new RuntimeException("Not Student Object");

        Student s1 = (Student)o1;
        Student s2 = (Student)o2;
        int num = s1.getName().compareTo(s2.getName());
        if(num == 0){
            return new Integer(s1.getAge()).compareTo(new Integer(s2.getAge()));
        }
        return num;
    }
}

在上述代码示例中,我同时使用了另一种方法让元素具备了比较性,那就是在实例化TreeSet()类时传入一个构造器。

public TreeSet(Comparator<? super E> comparator)
构造一个新的,空的树集,根据指定的比较器进行排序
插入到集合中的所有元素必须由指定的比较器相互比较 : comparator.compare(e1, e2)不能为ClassCastException中的任何元素e1和e2 。
如果用户尝试向该集合添加一个违反此约束的元素,则add调用将抛出ClassCastException 。
参数 comparator - 将用于对该集合进行排序的比较器。

  • Comparator让集合自身具备比较性,在集合初始化时,就有了比较方式。

  • 可能有人会问这个比较器Comparator和另一个实现Comparable接口,应该在哪里用?

  • 解答:当我们在项目中写代码时,若是你不能改其他人的在自定义类中的已经实现了Comparable接口覆盖compareTo方法,但是你却需要用其他的比较方式,这个时候就可以写一个比较器Comparator覆盖compare方法,当你使用比较器时,它会具有优先性。其次当你使用的元素不具备比较性或者不具备所需要的比较性的时候,这个时候只需使用比较器让容器自身具备比较性。
/*
练习:按照字符串长度排序
字符串本身具备比较性,但是它的比较方式不是所需要的
这时就只能使用比较器
*/

import java.util.*;

class TreeSetTest2{
    public static void main(String[] args){
        TreeSet hs = new TreeSet(new strLenComparator());

        hs.add("w1");
        hs.add("w2");
        hs.add("w333");
        hs.add("w222");
        hs.add("w333");


        for (Iterator it = hs.iterator();it.hasNext() ; ) {
            print((String)it.next());
        }
    }
    public static void print(Object obj){
        System.out.println(obj);
    }
}

class strLenComparator implements Comparator{
    public int compare(Object o1,Object o2){
        if(!(o1 instanceof String) || !(o2 instanceof String))
            throw new RuntimeException("Not String Object");

        String s1 = (String)o1;
        String s2 = (String)o2;
        int num = new Integer(s1.length()).compareTo(s2.length());
        if(num == 0)
            return s1.compareTo(s2);
        return num;
    }
}

总结

  • HashSet 允许放入一个null
  • TreeSet 不允许放入null
  • HashSet 无序
  • TreeSet 有序
  • Hash是用空间换时间
  • Tree是为了有序
  • HashSet 底层是 HashMap
  • TreeSet 底层是TreeMap

猜你喜欢

转载自blog.csdn.net/bfinwr/article/details/79357411