java集合小结

java 集合简介

Java集合大致可以分为Set、List、Queue和Map四种体系。

其中Set代表无序、不可重复的集合;List代表有序、重复的集合;而Map则代表具有映射关系的集合。Java 5 又增加了Queue体系集合,代表一种队列集合实现。

Java集合就像一种容器,可以把多个对象(实际上是对象的引用,但习惯上都称对象)“丢进”该容器中。从Java 5 增加了泛型以后,Java集合可以记住容器中对象的数据类型,使得编码更加简洁、健壮。

1.Java集合和数组的区别:

①.数组长度在初始化时指定,意味着只能保存定长的数据。而集合可以保存数量不确定的数据。同时可以保存具有映射关系的数据(即关联数组,键值对 key-value)。

②.数组元素即可以是基本类型的值,也可以是对象。集合里只能保存对象(实际上只是保存对象的引用变量),基本数据类型的变量要转换成对应的包装类才能放入集合类中。

2.Java集合类之间的继承关系:

Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口。

这里写图片描述

图中,ArrayList,HashSet,LinkedList,TreeSet是我们经常会有用到的已实现的集合类。

Map实现类用于保存具有映射关系的数据。Map保存的每项数据都是key-value对,也就是由key和value两个值组成。Map里的key是不可重复的,key用户标识集合里的每项数据。

这里写图片描述

Collection接口:


Collection接口是Set,Queue,List的父接口。Collection接口中定义了多种方法可供其子类进行实现,以实现数据操作。由于方法比较多,就偷个懒,直接把JDK文档上的内容搬过来。

1.1.接口中定义的方法

这里写图片描述

可以看出Collection用法有:添加元素,删除元素,返回Collection集合的个数以及清空集合等。 其中重点介绍iterator()方法,该方法的返回值是Iterator。

1.2.使用Iterator遍历集合元素

Iterator接口经常被称作迭代器,它是Collection接口的父接口。但Iterator主要用于遍历集合中的元素。 Iterator接口中主要定义了2个方法:
这里写图片描述

下面程序简单示范了通过Iterator对象逐个获取元素的逻辑。

public class IteratorExample {
    public static void main(String[] args){
        //创建集合,添加元素  
        Collection<Day> days = new ArrayList<Day>();
        for(int i =0;i<10;i++){
            Day day = new Day(i,i*60,i*3600);
            days.add(day);
        }
        //获取days集合的迭代器
        Iterator<Day> iterator = days.iterator();
        while(iterator.hasNext()){//判断是否有下一个元素
            Day next = iterator.next();//取出该元素
            //逐个遍历,取得元素后进行后续操作
            .....
        }
    }

}

注意: 当使用Iterator对集合元素进行迭代时,把集合元素的值传给了迭代变量(就如同参数传递是值传递,基本数据类型传递的是值,引用类型传递的仅仅是对象的引用变量。

下面的程序演示了这一点:

public class IteratorExample {
  public static void main(String[] args) {
            List<MyObject> list = new ArrayList<>();
            for (int i = 0; i < 10; i++) {
                list.add(new MyObject(i));
            }

            System.out.println(list.toString());

            Iterator<MyObject> iterator = list.iterator();//集合元素的值传给了迭代变量,仅仅传递了对象引用。保存的仅仅是指向对象内存空间的地址
            while (iterator.hasNext()) {
                MyObject next = iterator.next();
                next.num = 99;
            }

            System.out.println(list.toString());
    }
    static class MyObject {
        int num;

        MyObject(int num) {
            this.num = num;
        }

        @Override
        public String toString() {
            return String.valueOf(num);
        }
    }
}

输出结果如下:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

[99, 99, 99, 99, 99, 99, 99, 99, 99, 99]

下面具体介绍Collection接口的三个子接口Set,List,Queue。

set简介

Set集合与Collection集合基本相同,没有提供任何额外的方法。实际上Set就是Collection,只是行为略有不同(Set不允许包含重复元素)。

Set集合不允许包含相同的元素,如果试图把两个相同的元素加入同一个Set集合中,则添加操作失败,add()方法返回false,且新元素不会被加入。

3.List集合

3.1.简介

List集合代表一个元素有序、可重复的集合,集合中每个元素都有其对应的顺序索引。List集合允许使用重复元素,可以通过索引来访问指定位置的集合元素 。List集合默认按元素的添加顺序设置元素的索引,例如第一个添加的元素索引为0,第二个添加的元素索引为1……

List作为Collection接口的子接口,可以使用Collection接口里的全部方法。而且由于List是有序集合,因此List集合里增加了一些根据索引来操作集合元素的方法。

3.2.接口中定义的方法

void add(int index, Object element): 在列表的指定位置插入指定元素(可选操作)。

boolean addAll(int index, Collection< E> c) : 将集合c
中的所有元素都插入到列表中的指定位置index处。

Object get(index): 返回列表中指定位置的元素。

int indexOf(Object o): 返回此列表中第一次出现的指定元素的索引;如果此列表不包含该元素,则返回 -1。

int lastIndexOf(Object o): 返回此列表中最后出现的指定元素的索引;如果列表不包含此元素,则返回 -1。

Object remove(int index): 移除列表中指定位置的元素。

Object set(int index, Object element): 用指定元素替换列表中指定位置的元素。

List subList(int fromIndex, int toIndex): 返回列表中指定的 fromIndex(包括 )和
toIndex(不包括)之间的所有集合元素组成的子集。

Object[] toArray(): 返回按适当顺序包含列表中的所有元素的数组(从第一个元素到最后一个元素)。 除此之外,Java
8还为List接口添加了如下两个默认方法。

void replaceAll(UnaryOperator operator):
根据operator指定的计算规则重新设置List集合的所有元素。

void sort(Comparator c): 根据Comparator参数对List集合的元素排序。

4.Queue集合

4.1.简介

Queue用户模拟队列这种数据结构,队列通常是指“先进先出”(FIFO,first-in-first-out)的容器。队列的头部是在队列中存放时间最长的元素,队列的尾部是保存在队列中存放时间最短的元素。新元素插入(offer)到队列的尾部,访问元素(poll)操作会返回队列头部的元素。通常,队列不允许随机访问队列中的元素。

这里写图片描述

三、Map集合


1.简介

Map用户保存具有映射关系的数据,因此Map集合里保存着两组数,一组值用户保存Map里的key,另一组值用户保存Map里的value,key和value都可以是任何引用类型的数据。Map的key不允许重复,即同一个Map对象的任何两个key通过equals方法比较总是返回false。

如下图所描述,key和value之间存在单向一对一关系,即通过指定的key,总能找到唯一的、确定的value。从Map中取出数据时,只要给出指定的key,就可以取出对应的value。

这里写图片描述

2.Map集合与Set集合、List集合的关系

①.与Set集合的关系

如果 把Map里的所有key放在一起看,它们就组成了一个Set集合(所有的key没有顺序,key与key之间不能重复),实际上Map确实包含了一个keySet()方法,用户返回Map里所有key组成的Set集合。

②.与List集合的关系

如果把Map里的所有value放在一起来看,它们又非常类似于一个List:元素与元素之间可以重复,每个元素可以根据索引来查找,只是Map中索引不再使用整数值,而是以另外一个对象作为索引。

这里写图片描述
Map中还包括一个内部类Entry,该类封装了一个key-value对。Entry包含如下三个方法:
这里写图片描述
Map集合最典型的用法就是成对地添加、删除key-value对,然后就是判断该Map中是否包含指定key,是否包含指定value,也可以通过Map提供的keySet()方法获取所有key组成的集合,然后使用foreach循环来遍历Map的所有key,根据key即可遍历所有的value。下面程序代码示范Map的一些基本功能:

public class MapTest {
    public static void main(String[] args){
        Day day1 = new Day(1, 2, 3);
        Day day2 = new Day(2, 3, 4);
        Map<String,Day> map = new HashMap<String,Day>();
        //成对放入key-value对
        map.put("第一个", day1);
        map.put("第二个", day2);
        //判断是否包含指定的key
        System.out.println(map.containsKey("第一个"));
        //判断是否包含指定的value
        System.out.println(map.containsValue(day1));
        //循环遍历
        //1.获得Map中所有key组成的set集合
        Set<String> keySet = map.keySet();
        //2.使用foreach进行遍历
        for (String key : keySet) {
            //根据key获得指定的value
            System.out.println(map.get(key));
        }
        //根据key来移除key-value对
        map.remove("第一个");
        System.out.println(map);
    }

}

结果如下:
true

true

Day [hour=2, minute=3, second=4]

Day [hour=1, minute=2, second=3]

{第二个=Day [hour=2, minute=3, second=4]}

Set


  1. SortedSet:

    1) 顾名思义就是有序的Set,但是它的有序和LinkedHashSet不一样,LinkedHashSet维护的是插入时的顺序,而SortedSet维护的是元素之间大小关系的顺序(比如升序、降序等,是根据大小关系来维护顺序的);

    2) 这种维护是时刻维护的(就跟堆维护堆序一样),每次插入元素的时候就会根据其大小放入合适的位置,删除一个元素后也会调整剩余元素的位置使之符合升序/降序状态;

    3) SortedSet的实现类不多,基本只会用TreeSet这个实现类,之所以叫TreeSet,是因为它使用红黑树实现的,因此内部是一个树的结构;

2. TreeSet常用方法:都是其对象方法,里面的E表示模板类型,可以先理解为Object类型

1) 因为它是维护大小顺序的,因此其方法基本上都是与大小顺序有关的;

2) 找首尾元素:E first | last();

3) 找指定元素之前/之后的第一个元素:E lower | higher(E e);

     i. 这个之前之后不是指大小,而是在序列中的位置;

     ii. 如果是升序排列那么前后对应小大,如果是降序排列,那前后对应大小!!

     iii. 指定的参数可以是不属于集合中的元素,但只要能和集合中的元素比较大小即可!例如集合:1, 2, 4, 5,那么lower(3) = 2

4) 获取子集:区间端点所代表的元素同样可以不属于集合中的元素

     i. 区间子集:SortedSet<E> subSet(E fromElement, E toElement); // 获取[fromElement, toElement)区间内的子集(由于集合中元素是可比的,因此可以用区间表示)

     ii. 头子集:SortedSet<E> headSet(E toElement);  // 获取(-∞, toElement)之间的子集

     iii. 尾子集:SortedSet<E> tailSet(E fromElement); // 获取[fromElement, +∞)之间的子集

!!Java所有API中的区间都是左闭右开的!!

  1. 如何设定排序:

    1) 既然是有序的(大小顺序)那么应该有方法设定如何排序(升序还是降序),总公共有两种方法来设置排序,一种是自然排序,另一种是定值排序;

!!排序是自动的,但是排序的依据是大小的比较,因此排序设置也是围绕着大小比较进行的;

2) 自然排序:

     i. 集合直接在底层(隐式)调用集合元素自定义的compareTo方法所设定的大小顺序进行排序;

     ii. compareTo是一个接口方法,它位于Comparable接口中:
public interface Comparable<T> {
    public int compareTo(T o);
}

!这也是一个标志方法,很多需要比较大小的API都只认这个接口;
!!实现这个接口就必须实现compareTo方法才行!该方法就是拿自己和其它的o进行比较;

!!和C语言cmp的实现原理完全相同,前 - 后表示升序,后 - 前表示降序;

      iii. 很多Java基础类像String、Date、BigDecimal、Integer等包装器类都已经实现了Comparable接口,所以可以放心使用;

!!特别的Boolean也实现了大小比较,规定true大于false;

String类下compareTo()方法
比较两个字符串,依次对比两个字符串中字符的ASC码,两个字符的ASC码相等则继续比较下两个字符直至比较出不同的两个字符跳出方法.方法返回值类型为整型.
例如
String a = “2345”;
String b = “2322”;
则a.compareTo(b)返回2
b.compareTo(a)返回-2
如果两个字符串相等,则返回值为0
这里写图片描述

3) 定制排序:
i. SortedSet的一个构造器版本中可以指定一个Comparator接口:TreeSet(Comparator comparator);

      ii. Comparator顾名思义就是比较器的意思,就是指该TreeSet中元素的顺序由该比较起来规定!

      iii. 其实SortedSet中有一个成员就是comparator,如果是自然排序(使用无参的构造器)就会默认把元素自己的compareTo赋给它,如果是使用的是上面的这个构造器就会把指定的比较器赋给comparator成员,因此如果同时使用自然排序和定制排序,那么定制排序将会覆盖自然排序(即只有定制排序起到作用!);

      iv. Comparator是一个函数式接口:
public interface Comparator<T> {
    int compare(T o1, T o2);
}

!里面也是一个比较方法,比较原理和compareTo完全相同;
v. 示例:定制一个降序排列的TreeSet:TreeSet ts = new TreeSet((o1, o2) -> ((A).o2).a - ((A)o1).a); // 后 - 前就是降序

!!这里假设元素是A类型的,A类中只包含一个int a成员;

5. SortedSet(TreeSet)判断元素重复的标准:

1) 由于SortedSet也是Set的一种,因此也不允许元素重复!

2) SortedSet判断元素重复的标准还是由compareTo(自然排序)/Comparator(定制排序)规定的,返回0时表示相等;

3) 注意!并不是由元素的equals方法决定!但是设计类的时候一定要让equals和compare保持一致,否则会产生很多逻辑上的错误!!养成这个良好的习惯!
  1. SortedSet注意事项1——必须实现compare:

    1) 这里的实现compare是指compareTo或者Comparator二者之一(必须实现自然排序或者定值排序);

    2) 虽然会有一些小的例外:如果两者都不实现,当你添加第一个元素的时候没有问题,但是添加第二个元素的时候就会抛出异常!因为添加第一个元素的时候不用和任何元素比较,但是添加第二个元素的时候需要和第一个元素比较大小来决定插入到什么位置,而此时发现没有compare方法提供比较服务,因此报错了!

    3) 但是不要抱着这样的侥幸,好像如果集合中只有一个元素就不需要实现compare了!这是非常不好的习惯,必须要实现compare!

  2. SortedSet注意事项2——类型问题:

    1) 由于在实现compare方法(compareTo/Comparator时)中,接口API提供的接口方法要么是模板类型T或者是Object类型,因此在比较方法体中都要先进行类型转换成元素本身的类型之后再进行比较;

    2) 正是由于上面的这个原因,SortedSet中应该只能存放类型相同的元素,如果两者类型不同,则在进入比较方法后类型转换将会失败!比如同时存放String和Date,那么在比较两者大小的时候不是调用String的compare就是调用Date的compare,两者的compare中都会将另一个参数的类型转化成自己的类型,但显然这两种类型是不兼容的,转换是必然会抛出异常!

    3) 因此一句话总结就是:为了不发生错误,SortedSet中最好只放相同类型的元素,除非你的所有元素都是Object类型的!养成好习惯!

  3. Collection保存的是引用——注意事项3(重申)最好不要保存可变数据:

    1) 这里需要指出的是不管是Set还是Map,即所有的Collection保存的全都是引用(只能保存对象类型数据,即使直接存放基本类型,也会被自动隐式地包装成相应的包装器类型);

    2) 因此像很多返回查找到的元素的方法(first、last等),返回的都是集合重元素的引用,如果你通过该引用修改了那个元素,那么就意味着集合中相应的元素也被修改了!

    3) 正式因为这个问题,集合中最好不要保存可变数据,因为你在集合外的修改会影响到集合内;

    4) 最致命的就是这样的修改会破坏集合本身的规则:由于修改可能会导致集合元素的hashCode、equals、compareTo/Comparator等结果的改变,但是这种改变并不会调整元素在数据结构中的位置,这可能会导致严重的错误!

    5) 这之前讲过,在这里(SorteSet中)例子就是:集合[1, 2, 3, 4],如果你通过查找的方式获取了3的引用,并在集合外修改成了5,那么原集合就变成了[1, 2, 5, 4],并不会因为这种修改而更新排序,这就违背了SortedSet排序的原则,可能会在后面的逻辑中出现非常致命的错误!

    6) 因此,如果你强行想修改元素同时也不想破坏集合原有的结构规则,那通用的方法就是:

    i. 先获取想修改的元素;
    
    ii. 删除该元素;
    
    iii. 将修改后的元素再重新加入集合;
    

!!按照这个顺序就可以保持原有的结构规则了!即“拿出来”修改,然后再“放回去”;

排序实例演示:http://www.importnew.com/18280.html

TreeSet集合的遍历

    List<String> list = new ArrayList<String>();

        list.add("a");
        list.add("c");
        list.add("b");
        Collections.sort(list);// 默认进行升序

        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i));
        }
        输出结果abc

set集合遍历
    SortedSet<String> mySet = new TreeSet<String>();
        mySet.add("a");
        mySet.add("c");
        mySet.add("b");
        // 迭代遍历
        Iterator<String> it = mySet.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }

        // for循环
        for (String s : mySet) {
            System.out.println(s);
        }
        结果是abc

在java的集合中,判断两个对象是否相等的规则是:

1)、判断两个对象的hashCode是否相等 。
如果不相等,认为两个对象也不相等,完毕
如果相等,转入2)
(这一点只是为了提高存储效率而要求的,其实理论上没有也可以,但如果没有,实际使用时效率会大大降低,所以我们这里将其做为必需的。后面会重点讲到这个问题。)
2)、判断两个对象用equals运算是否相等 。
如果不相等,认为两个对象也不相等
如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键)

对于一般类的对象(除String等封装类型对象外):

若普通类没有重写hashcode()和equals()方法,,那么其对象在比较时,是继承的object类中的hashcode()方法,object类中的hashcode()方法是一个本地方法,对该方法的返回值进行比较时,比较的是对象的地址(引用地址),使用new方法创建内容相同的对象,两次生成的当然是不同的对象。除非重写hashcode()方法。在object类中定义的equals()方法也是对对象地址的比较。一句话总结:若不重写普通类的hashcode()和equals()方法,在Set集合中对象引用地址不一样,对象即不重复。

对于String等对象(String、Integer、Double····等等):

由于这些封装类本身已经重写了hashcode()方法,并且重写的方法的返回值跟对象的内容相关,而不是跟引用地址相关。这些封装类中的equals()方法同样进行了重写,比较的是对象的内容,而非引用地址。一句话总结:String等类的对象在集合中均比较他们的内容,内容相同则覆盖已存在的对象。

compareTo与compare(o1,o2)方法工作原理

compareTo(Object o)方法是java.lang.Comparable接口中的方法,当需要对某个类的对象进行排序时,该类需要实现Comparable接口的,必须重写public int compareTo(T o)方法,比如MapReduce中Map函数和Reduce函数处理的 (key,value),其中需要根据key对键值对进行排序,所以,key实现了WritableComparable接口,实现这个接口可同时用于序列化和反序列化。WritableComparable接口(用于序列化和反序列化)是Writable接口和Comparable接口的组合;
compare(Object o1,Object o2)方法是java.util.Comparator接口的方法,它实际上用的是待比较对象的compareTo(Object o)方法。

HashSet简介


HashSet是基于HashMap来实现的,操作很简单,更像是对HashMap做了一次“封装”,而且只使用了HashMap的key来实现各种特性,我们先来感性的认识一下这个结构:

HashSet<String> set = new HashSet<String>();
set.add("语文");
set.add("数学");

其大致的结构是这样的:

这里写图片描述
map是整个HashSet的核心,而PRESENT则是用来造一个假的value来用的。

(1) 为啥要用HahSet?
假如我们现在想要在一大堆数据中查找X数据。LinkedList的数据结构就不说了,查找效率低的可怕。ArrayList哪,如果我们不知道X的位置序号,还是一样要全部遍历一次直到查到结果,效率一样可怕。HashSet天生就是为了提高查找效率的。

(2) hashCode 散列码
散列码是由对象导出的一个整数值。在Object中有一个hashCode方法来得到散列码。基本上,每一个对象都有一个默认的散列码,其值就是对象的内存地址。但也有一些对象的散列码不同,比如String对象,它的散列码是对内容的计算结果:

//String对象的散列码计算

String str="hello"; 
int hash=0; 
for(int i=0;i<length();i++) 
   hash=31*hash+charAt(i); 

这里写图片描述

那么下面散列码的结果不同也就好解释了。s和t都还是String对象,散列码由内容获得,结果一样。sb和tb是StringBuffer对象,自身没有hashCode方法,只能继承Object的默认方法,散列码是对象地址,当然不一样了。

String s=new String(“OK”);//散列码: 3030
String t=”Ok”; /散列码: 3030
StringBuffer sb=new StringBuffer(s); //散列码:20526976
StringBuffer tb=new StringBuffer(t); //散列码:20527144
(3) HashSet 散列表的内部结构

这里写图片描述

4) HashSet 如何add机制

假如我们有一个数据(散列码76268),而此时的HashSet有128个散列单元,那么这个数据将有可能插入到数组的第108个链表中(76268%128=108)。但这只是有可能,如果在第108号链表中发现有一个老数据与新数据equals()=true的话,这个新数据将被视为已经加入,而不再重复丢入链表。

HashSet的散列单元大小如何指定?

Java默认的散列单元大小全部都是2的幂,初始值为16(2的4次幂)。假如16条链表中的75%链接有数据的时候,则认为加载因子达到默认的0.75。HahSet开始重新散列,也就是将原来的散列结构全部抛弃,重新开辟一个散列单元大小为32(2的5次幂)的散列结果,并重新计算各个数据的存储位置。以此类推下去…..

(5) 为什么HashSet查找效率提高了。

知道了HashSet的add机制后,查找的道理一样。直接根据数据的散列码和散列表的数组大小计算除余后,就得到了所在数组的位置,然后再查找链表中是否有这个数据即可。

查找的代价也就是在链表中,但是真正一条链表中的数据很少,有的甚至没有。几乎没有什么迭代的代价可言了。所以散列表的查找效率建立在散列单元所指向的链表中的数据要少 。
(6) hashCode方法必须与equals方法必须兼容

如果我们自己定义了一个类,想对这个类的大量对象组织成散列表结构便于查找。有一点一定要注意:就是hashCode方法必须与equals方法向兼容。

//hashCode与equals方法的兼容   
public class Employee{   
       public int id;   
       public String name="";   
       //相同id对象具有相同散列码   
       public int hashCode(){    
              return id;   
       }   
       //equals必须比较id   
        public boolean equals(Employee x){   
              if(this.id==x.id) return true;   
              else return false;   
       }   
}  

为什么要这样,因为HashSet不允许相同元素(equals==ture)同时存在在结构中。假如employeeX(1111,“张三”)和employee(1111,”李四”),而Employee.equals比较的是name。这样的话,employeeX和employeeY的equals不相等。它们会根据相同的散列码1111加入到同一个散列单元所指向的列表中。这种情况多了,链表的数据将很庞大,散列冲突将非常严重,查找效率会大幅度的降低。

(6) 总结一下
1、HashSet不能重复存储equals相同的数据 。原因就是equals相同,数据的散列码也就相同(hashCode必须和equals兼容)。大量相同的数据将存放在同一个散列单元所指向的链表中,造成严重的散列冲突,对查找效率是灾难性的。

2、HashSet的存储是无序的 ,没有前后关系,他并不是线性结构的集合。

3、hashCode必须和equals必须兼容, 这也是为了第1点。

HashSet的一个应用实例,笔试题:

对于一个字符串,请设计一个高效算法,找到第一次重复出现的字符。
给定一个字符串(不一定全为字母)A及它的长度n。请返回第一个重复出现的字符。保证字符串中有重复字符,字符串的长度小于等于500。
测试样例:
“qywyer2366tdd”,11
返回:y

import java.util.*;
public class FirstRepeat {
    public static char findFirstRepeat(String A, int n) {

    char[] a=A.toCharArray();
    HashSet hs=new HashSet<>();
    for(int i=0; i<n;i++) 
    {
        if (!hs.add(a[i])) 
        {
            return a[i];
        }
    }
    return 0;
    }

    public static void main(String[] args)
    {
        System.out.println(findFirstRepeat("qywyer2366tdd",11));
    }
}

LinkedHashSet概述:

LinkedHashSet是具有可预知迭代顺序的Set接口的哈希表和链接列表实现。此实现与HashSet的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可为插入顺序或是访问顺序。
注意,此实现不是同步的。如果多个线程同时访问链接的哈希Set,而其中至少一个线程修改了该Set,则它必须保持外部同步。

HashSet,TreeSet和LinkedHashSet的区别

Set接口
Set不允许包含相同的元素,如果试图把两个相同元素加入同一个集合中,add方法返回false。
Set判断两个对象相同不是使用==运算符,而是根据equals方法。也就是说,只要两个对象用equals方法比较返回true,Set就不 会接受这两个对象。

HashSet
HashSet有以下特点
 不能保证元素的排列顺序,顺序有可能发生变化
 不是同步的
 集合元素可以是null,但只能放入一个null
当向HashSet结合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据 hashCode值来决定该对象在HashSet中存储位置。
简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值相 等
注意,如果要把一个对象放入HashSet中,重写该对象对应类的equals方法,也应该重写其hashCode()方法。其规则是如果两个对 象通过equals方法比较返回true时,其hashCode也应该相同。另外,对象中用作equals比较标准的属性,都应该用来计算 hashCode的值。

LinkedHashSet
LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起 来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。
LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。

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。
如果我们将两个对象的equals方法总是返回true,则这两个对象的compareTo方法返回应该返回0
定制排序
自然排序是根据集合元素的大小,以升序排列,如果要定制排序,应该使用Comparator接口,实现 int compare(T o1,T o2)方法

Set集合的总结:

TreeSet、SortedSet的重复标准是

  • 判断元素重复的标准还是由compareTo(自然排序)/Comparator(定制排序)规定的,返回0时表示相等;
    注意!并不是由元素的equals方法决定!但是设计类的时候一定要让equals和compare保持一致,否则会产生很多逻辑上的错误!!养成这个良好的习惯!

HashSet的判断的重复标准是:

  • 判断元素重复的标准是判断equals是否返回ture还有hashcode是否相等
  • 注意HashSet其实就是HashMap的在一次“封装”hashCode必须和equals必须兼容

参考:
http://blog.csdn.net/lirx_tech/article/details/51514248
http://blog.csdn.net/lirx_tech/article/details/51514248

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值