一、集合框架——set

 

1、set体系特点

:元素无序,(指的是存入和取出的顺序不一定一致)不可以重复

集合的功能和collection是一致的。

   常见的子类

    Set

底层数据结构是哈希表

      |---TreeSet

集合的功能和Collection是相似的。

2、HashSet介绍

) HashSet特点

      线程不安全,存取速度快。

hashCode和equals来完成保证元素唯一性。如果元素的HashCode值相同,才会判断equals是否为true。如果元素的hashCode值不同,不会调用equals

HashSet对于判断元素是否存在,以及删除等操作,依赖的方法是元素的hashCode和equals方法。

2)哈希表知识

     如果一个集合中有很多元素,比如有一万个,并没有包含要查找的对象时,则意味着你的程序需要从该集合中取出一万个元素进行逐一比较才得到结论。有人发明了一种哈希算法来提高从集合中查找元素的效率。这种方式将集合分成若干个存储区域,每个对象可以计算出一个哈希码,可以将哈希码分组,每组分别对应某个存储区域,根据一个对象的哈希码就可以确定对象存储在哪一个区域。

     哈希表存的就是一些哈希值的表,存的顺序不是按照你存储的顺序定义的。它的顺序是按照哈希值大小顺序来存的。取的时候,是按照表的顺序来取的。

哈希值示例 Demo@c17164  Demo@1fb8ee3  前面是类型名,@后面就是哈希地址值

那如果哈希值相同呢

我们复写下hashSet的hashCode()方法,只要建立对象,哈希值都为3c

那么hashSet会怎么做呢?首先,地址不同,一定不是重复的

如果地址相同,会判断存储的对象是否相同,用equals方法。

如果地址相同,但不是同一个对象,这时,它会在该地址下顺延。

就是采用哈希算法存取对象的集合,它内部采用对某个数组n进行取余的方式对哈希码进行分组和划分对象的存储区域,object定义了一个hashcode方法来返回每个java对象的哈希码。

当hashset集合中查找某个对象时,java系统首先调用对象的hashcode方法获取该对象的哈希码,然后根据哈希码找到相应区域,最后取出该存储区域中的每个元素与该对象进行equals方法比较,这样不用遍历集合中的所有元素就可以得到结论。可见hashset集合具有很好的对象检索功能。

提示:

如果两个对象的equals比较相等的话,也要让他们的hashcode也相等,但反之则不成立。即equals方法比较结果不想等的对象可以拥有相同的哈希码,或者说哈希码相同的两个对象equals方法比较的结果可以不等,

当一个对象被存储进hashset集合中后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进hashset集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为参数去hashset集合中检索对象,也将返回找不到对象的结果,这也会导致无法从hashset集合中单独删除当前对象,从而造成内存泄露。

3) HashSet存储自定义对象   

需求:往hashSet集合中存入自定义对象——Person类对象

思路:

判断重复元素,保证唯一性。

     姓名和年龄相同为同一个人,重复元素。

但怎么才能做到无序呢?重写equals方法,但是发现并没有调用equals方法来判断元素是否相同。

所以,添加的元素还是不能做到无序。

怎么办呢?回到哈希表中来HashSet存入的是哈希地址值。只要地址值不同,就视为不同对象,已经存进去了,就不会再去调用equals方法去判断了。

那现在我们要干什么呢?

II我们需要在person类中重写hashSet中的hashCode()方法。

那怎么生成哈希值呢?

判断条件是什么,就根据判断条件来建立哈希值。

set化组件架构_映射关系

 

set化组件架构_比较器_02

 

说明:在哈希表中是根据哈希值来存储的,当哈希值都相同时,在哈希表中,就会在同一个地址值下顺延对象。当哈希值相同,添加元素时就会判断与同地址值中所存储对象是否相同。如果相同,就不会存进去。

就会出现上述打印结果。

如果我们想知道什么时候调用hashCode,可以在

public int hashCode()
 {
”.....hashCode”);
   Return 60; 
}

 

set化组件架构_映射关系_03

 

 

看结果,这样比较效率很低,a1和a3明显就不是一个对象,为什么还要比较呢?比较次数太多。怎么办呢?

可以在hashcode()判断一下,按条件设置哈希值

 

public int hashCode()
 {
”.....hashCode”);
字符串本身都实现了生成哈希值,再加上年龄,就唯一了。
}

结论:

HashSet是如何保证元素唯一性的呢?

hashcode和equals来完成

如果元素的hashcode值相同,才会判断equals是否为相同

如果元素的hashcode值不同,不会调用equals。

所以,我们要自定义对象时,需要复写hashcode和equals方法来判断是否相同。

附:有一种情况必须注意,Return name.hashcode()+age;这条语句:如果存  Zhangsan  20  Lisi      40

Zhangsan的哈希值是40+20=60

Lisi的哈希值是20+40=60

容易下面,要判断zhansan,25在不在呢?

System.out.println(“zhangsan:”+hs.contains(new Person(“zhansan”,25)));导致哈希值相同,还需要判断equals
return name.hashcode()+age*一个数;

结论

hashcode和equals方法。

 

3、TreeSet介绍

)特点

         Set

数据结构是哈希表,线程是非同步的。对于数据结构不同,保证数据唯一性的方法也不同,         hashset依赖的是Hashcode和equals方法

可以对set集合中的元素进行排序

                    底层数据结构是二叉树

保证元素唯一性的依据是:compareTo方法返回0

为0,就是表示是同一元素,无法存储。因为set是不可以重复的。

TreeSet排序的第一种方式,让元素自身具备比较性。

元素需要实现Comparable接口,覆盖compareTo方法。

Set是无序的,而它的子类TreeSet就弥补这一缺点。

)方法演示

     

set化组件架构_数据结构_04

 

3)TreeSet存储自定义对象——按自然排序

TreeSet集合中存储自定义对象学生

      按照学生的年龄进行排序

 

set化组件架构_set化组件架构_05

编译失败

异常:classCastException:student can not cast to java.lang.Comparable

类型转换出错语句  Ts.add(new Student(“lisi007”,20));

处理:

首先,把这条语句,包括下面三条添加语句都注释掉

运行,正常

再存一个,运行,挂了。

问题就在这

让Student对象具有自然顺序的比较性

我们查阅api文档 java.lang.comparable

发现comparable是一个接口。

public interface Comparable<T>

此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,

到这里,我们就明白原因,为什么存一个元素就可以,存两个就出错

TreeSet集合按照什么方式排序,我们并没有告诉它。因为我们的student类对象根本就不具备比较性。

TreeSet的要求:必须让元素具备比较性,才能排序

怎么才能具备比较性?

实现接口。

set化组件架构_映射关系_06

 

看起来正常了,可是还有一个问题

假如

TreeSet ts=new TreeSet();
“lisi02”,22));
“lisi007”,20));
“lisi09”,19));
“lisi01”,19));

结果呢?

Lisi09::19
Lisi007::20
Lisi02::22

少了一个。01没存进来。

再看api文档,compareTo方法

年龄相等,return0;就是代表此对象等于指定对象,视为同一对象了。

所以这个对象就没存进去

在我们生活中,年龄相同,姓名也相同,才被视为同一对象。

当主要条件相同时,要按照次要条件排序

当年龄相同时,要按照姓名排序

查阅api文档,string类,里面已经复写了compareTo方法,说明字符串已经具备比较性。

set化组件架构_映射关系_07

 

总结

排序时,当主要条件相同时,一定要判断下次要条件

4)二叉树知识

 当需要比较的时候,如果所存元素越多,比较次数越多,效率越低

那么TreeSet底层的数据结构到底是什么,能提高效率呢?二叉树。

根据所学二叉树特性,会大大减少比较次数,提高了效率。但二叉树,如果元素多了,是不是也慢啊,

所以,实际中,会取一个折中值。

需求:上例中的元素我们怎么存进去的,就怎么取出来。

思路:

TreeSet只看compareTo比较的结果,不看怎么实现的。只要返回1,就是大于,返回0,就是等于,返回-1,就是小于

所以,只要compareTo的返回值一直为1

 

在内存中,二叉树的形状就是一直存在右边的单叉树中

 

set化组件架构_set化组件架构_08

 

 

 

  

每存进去一个元素,排序的结果都是比上一个大,所以,就形成了单叉树——右

如果想让倒序取出,就使compareTo的返回值是-1.即可

5)用比较器Comparator方式排序

比较器的由来

当元素自身不具备比较性,或者具备的比较性不是所需要的。这时需要让容器自身具备比较性。定义了比较器,将比较器对象作为参数传递给TreeSet集合的构造函数。

TreeSet集合的元素都需要具备比较性,才能进行排序

问题:  那要是万一,元素真的不具备比较性呢? 或者,本身具备比较性,可是现在需求变了,需要按照姓名排序呢?就有了TreeSet集合的第二种排序方式:比较器排序

   当元素自身不具备比较性时,或者具备的比较性不是所需要的,这时就需要让集合自身具备比较性。

  可以比较一共有两种,一是元素,第二就是集合。

就好像,容器里的东西没有比较性,就让容器进行相比。

在集合一初始化时就有了比较方式。参与构造函数。

TreeSet(Comparator<? super E> comparator) 

TreeSet,它根据指定比较器进行排序。

comparator 比较器

  

set化组件架构_set化组件架构_09

 

如果又加入一个元素  

Lisi007.。。29呢

结果,没存进去

姓名相同,年龄不同,可以存在。

set化组件架构_比较器_10

 

还有一种简便方法

我们看,age是不是一个整数,整数也是一个对象,也实现了comparable接口。

int compareTo(Integer anotherInteger)

Integer 对象。 

class MyCompare implements Comparator
 {
    public int compare(Object o1,Object o2)
    {
       Student s1=(Student)o1;
       Student s2=(Student)o2;
      int  num=s1.getName().compareTo(s2.getName())
      if(num==0)
       {
         return  new Integer(s1.getAge()).compareTo(s2.getAge());
        }
    return num;    }
 }

总结:

当两种排序都存在时,以比较器为主。

定义一个类,实现comparator接口,覆盖compare方法。

练习

往treeSet集合存入字符串,按照字符串长度排序。

set化组件架构_映射关系_11

 

二、Map集合

1、概述

和Collection相似,都是集合框架中的顶层接口。他们外观没有联系,但内部有一定的联系。

查阅api文档

Map<K,V>

类型参数:

此映射所维护的键的类型

映射值的类型

将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。 

说明K和V的关系是映射关系。

Map集合:该集合存储键值对,一对一对往里存,而且要保证键的唯一性。

Map集合的应用

比如:我们知道ArrayList是能给元素加索引,而Map能自定给元素起名字都可以。

所以Map是经常被运用的。

Collection集合叫做单列集合,而Map集合叫做双列集合。

2、体系特点

       Map

底层是哈希表数据结构,不可以存入Null键或Null值。该集合是线程同步的   JDK1.0

底层是哈希表数据结构,允许使用Null键或null值。是不同步的。JDK1.2

底层是二叉树结构。线程不同步。可以用于给Map集合中的键排序。

Set很像。

其实,Set底层就是使用了Map集合。

HashTable:用作键的对象必须实现hashcode方法和equals方法。

 

、方法

)增加

将指定的值与此映射中的指定键关联

从指定映射中将所有映射关系复制到此映射中

)删除

清空

如果存在一个键的映射关系,则将其从此映射中移除。根据键,删除

)判断
            boolean containsKey(Object key) 
            boolean containsValue(Object value) 
            boolean isEmpty() 
)获取
            V get(Object key) 
            int size()

返回此映射中包含的值的 Collection 视图。 

返回此映射中包含的映射关系的 Set 视图。 

返回此映射中包含的键的 Set 视图。

、共性方法演示

集合的两种取出方式:

1,Set<k>  keySet:将map中所有的键存入到Set集合,因为set具备迭代器,所以可以通过迭代方式取出所有的键,再根据get方法获取每一个键对应的值。

集合的取出原理:将map集合转成set集合,在通过迭代器取出。

2,Set<Map.Entry<k,v>>entrySet:将map集合中的映射关系存入到了set集合。而这个关系的数据类型就是:Map.Entry.

演示:

 

set化组件架构_数据结构_12

 

注意:添加元素时,相同的键,那么后添加的value值会覆盖原来的值。

2)遍历集合演示:

:它是一个接口,是定义在Map内部的。就好像迭代器的原理一样.它代表是映射关系的类型。就好像夫妻的结婚证书一样,有了这个类型,就可以找到双方。但是不是任何一方。

Map.Entry:其实,Entry也是一个接口,它是Map接口中的一个内部接口。

interface Map
 {
    public   static  interface Entry
  {
      public abstact Object getKey();
      public abstact Object getValue();  }
 }
 class HashMap implements Map//实现Map
 {
     class   haha  implements  Map.Entry//内部类来实现Entry
    public Object getKey()
    {
    }
   public Object getValue()
    {
    }   
 }

  

set化组件架构_映射关系_13

为什么把Entry定义在Map集合的内部呢?是不是现有Map集合,才有的映射关系。影身关系是Map集合内部的事物。而且这个关系是在直接访问Map集合中的成员,定义在内部最好。

再回来看Map集合的方法摘要,发现,里面有一个嵌套类摘要

Static  interface  Map.Entry<K,V>      映射项(键-值对)。 

5、Map扩展

)Map集合的使用:Map集合被使用是因为具备映射关系

2)实例需求:

“yureban ” “01” “zhangsan”
“yureban””02” “lisi”
“jiuyeban” “01””wangwu”
“jiuyeban””02” “zhangliu”

有两个映射关系,而且嵌套。

问题,怎么存呢?

都是yureban,键重复,添加的时候,不就覆盖了吗?

以前是一对一映射,现在是一对多映射。

 

set化组件架构_set化组件架构_14

set化组件架构_映射关系_15