数据结构和类型2

来源:互联网 发布:sean parker 知乎 编辑:程序博客网 时间:2024/05/02 00:06

set 集合:

元素是无序的,意思是存入和取出不一定一致,元素不可以重复。Set的功能和Collection是一致的因为set 也没有角标。

set 的两个主要应用的集合1 hashset,底层数据结构是哈希表,2 treeset。

哈希表是存放了一堆哈希值的表,哈希值是数据内存地址根据哈希算法得出来的一个值,不同的地址也许有可能得出一样的哈希值,如果两个同样的哈希值都要放在哈希表里的话这两个对象本身就要比较一下,看是不是同一个对象利用的是equals,如果地址值一样但是不是同一个对象就在同样的哈希值下面串接另一个值,也就是说同一个哈希值底下下也许有多个不同引用但是哈希值一样的对象。


应用1:


package SetShow;import java.util.HashSet;import java.util.Iterator;public class HashSetDemo {public static void main(String[] args) {HashSet hs = new HashSet();hs.add("java1");hs.add("java2");hs.add("java3");hs.add("java4");Iterator it = hs.iterator();while(it.hasNext()){sop(it.next());}sop(hs.add("java 01"));sop(hs.add("java 01"));         }public static void sop(Object obj){        System.out.print(obj);        System.out.println();         }}
打印结果

java4
java3
java2
java1
true
false
证明在哈希表中这些元素不是按照进去那样的顺序储存的,在哈希表中如果已经有一个值,如果再存入相同的值就回出现储存false的提示。

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

是通过元素的两个方法,hashcode 和equals来完成的。如果元素的哈希值同,才会去判断equals是否为true。如果哈希值不同,不会调用equals。


应用2:


需求:去除hashset 中的具有相同值的Person类

package SetShow;import java.util.HashSet;import java.util.Iterator;public class HashSetDemo {public static void main(String[] args) {HashSet hs = new HashSet();hs.add(new Person("a1",11));hs.add(new Person("a2",12));hs.add(new Person("a2",12));                                hs.add(new Person("a3",13));      Iterator it = hs.iterator();while(it.hasNext()){Person p = (Person) it.next();sop(p.getName()+" : "+p.getAge());}}public static void sop(Object obj){        System.out.print(obj);        System.out.println();        }}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+" hash code "+name.hashCode()+age);return name.hashCode()+age*27;}public String getName(){return name;}public int getAge(){return age;}public boolean equals(Object obj){if(!(obj instanceof Person))// obj instanceof person:在这里说,obj 是 Person吗return false;Person p = (Person) obj;                System.out.println(this.name+"...equals ?..."+p.name);return this.name.equals(p.name)&& this.age == p.age;}//this equals 在运行contains 的时候被自动调用了,所以如果想比较其他的元素的内容可以自己编写一遍}
输出结果:

a1 hash code 305611
a2 hash code 305712
a2 hash code 305712
a2...equals ?...a2
a3 hash code 305813
a1 : 11
a2 : 12
a3 : 13


从上边的例子可以看出,equals 方法在使用前是调用的hashcode()方法的,名字和年龄经过哈希算法过后就变成了不同的数值,当第二个a2进入哈希表的时候发现他的哈希值名字和以前的那个a2 一样,所以就调了equals 方法进行比较,比较两个person 的name 和 age,具体的操作是:(第二个a2) .equals(第一个a2)。如果发现哈希值一样,内容也一样就判定指向的是同一个对象,则不会把同一个对象存入哈希表两次。所以后来就自动出现了只存入a1,a2,a3的三个情况。

和list 一样hashset 的remove 和 conatins 方法也是依赖equals方法,equals 依靠的是hashcode。


TreeSet:


TreeSet 利用的是比较器的原理存入数据,并且自动给数据按照二叉树的方法排序,出去的时候可以按照系统自带的顺序由小到大取出,或者按照编程者的意愿在覆写完比较器后按照新的比较器的原理来取出。


TreeSet应用1:

Treeset:package SetShow;import java.util.Iterator;import java.util.TreeSet;public class TreeSetDemo {public static void main(String[] args) {TreeSet ts = new TreeSet();ts.add("cba");ts.add("abcd");ts.add("aaa");ts.add("bca");Iterator it = ts.iterator();while(it.hasNext()){sop(it.next());}}public static void sop(Object obj){        System.out.print(obj);        System.out.println();        }}

输出的结果是:

aaa

abcd

bca

cba

Treeset 把进入中的元素按照字母顺序排序了

TreeSet应用2:

学生类有重复的名字和年龄如何去掉重复的。

package SetShow;import java.util.Iterator;import java.util.TreeSet;public class TreeSetDemo {public static void main(String[] args) {TreeSet ts = new TreeSet();ts.add(new Student("02",22));                                ts.add(new Student("007",20));                ts.add(new Student("09",20));ts.add(new Student("09",20));Iterator it = ts.iterator();while(it.hasNext()){Student str = (Student)it.next();sop(str.getName()+": "+str.getAge());}}public static void sop(Object obj){        System.out.print(obj);        System.out.println();    }}       class Student implements Comparable{public String name;public int age;Student(String name,int age){this.name = name;this.age =age;}public String getName(){return this.name;}public int getAge(){return this.age;}@Override public int compareTo(Object obj) {  if(!(obj instanceof Student))                  throw new RuntimeException("不是学生对象");                  Student s = (Student)obj;                  System.out.println(this.name+"...compare to ..."+s.name);                  if(this.age>s.age)                  return 1 ;         if(this.age == s.age  )        {        return this.name.compareTo(s.name);        }                  return -1;}}

输出的是:

02...compare to ...02
007...compare to ...02
09...compare to ...02
09...compare to ...007
09...compare to ...09
007: 20
09: 20
02: 22

第一个进入的先跟自己比较一下,然后第二个进入的跟第一个比较,第三个进入的跟第一个然后和第二个比较一下,最后进入的丛头开始都比较一下,最后发现有相同的名字和相同的年龄,系统判定是同一个对象就没有再被加入到Treeset 中了。

Student 类要复写compare接口因为Treeset 自动为每一个进入的元素都进行了比较然后给出了一个从小到大的排序,override 的目的是让student 在比较完其中的age值之后再去比较其中的名字,比较名字调用的就是jvaa自带的String 的compareto 方法了。


TreeSet取出顺序图:


Treeset 的底层储存元素的结构是二叉树,compareto 比较后大于1 的放在右边,小于的放在左边,0的话就算自己,然后再重新比较名字。调用的时候从小到大被迭代器挨个带出。

当然如果你override 了compareto 方法,如果不论任何情况都返回1的话,元素会从上到下挨个进去总是排在右边,取出的时候会按照输入顺序。


如果override compare to 只返回0 的话就只有第一个学生能储存进去了,因为其他的都被判定成和第一个学生一样的对象了。

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


TreeSet 应用3:

需求:

编写两个比较方法,一个先比较的是名字,一个先比较的是年龄,看看那是名字先排序还是年龄先排序,证明那个比较方法会被系统采用。

package TreeSet;import java.util.Comparator;import java.util.Iterator;import java.util.TreeSet;public class TreeSetDemo1 {public static void main(String[] args) {TreeSet ts = new TreeSet(new MyCompare());ts.add(new Student("02",22));                ts.add(new Student("007",20));ts.add(new Student("09",19));ts.add(new Student("09",18));Iterator it = ts.iterator();while(it.hasNext()){Student str = (Student)it.next();sop(str.getName()+": "+str.getAge());}}public static void sop(Object obj){        System.out.print(obj);        System.out.println();        }}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(new Integer(s2.getAge()));     }     return num ;     }     }class Student implements Comparable//{public String name;public int age;Student(String name,int age){this.name = name;this.age =age;}public String getName(){return this.name;}public int getAge(){return this.age;}@Overridepublic int compareTo(Object obj) {if(!(obj instanceof Student))throw new RuntimeException("不是学生对象");Student s = (Student)obj;if(this.age>s.age  )     return 1;        if(this.age == s.age)        {        return this.name.compareTo(s.name);        }return -1;}}

当两周排序都存在时,以比较器为主,定义一个类,实现Comparator 接口,覆盖compare方法。输出结果为:

007: 20
02: 22
09: 18
09: 19
名字先比较 007 的第二位是0 所以比02的第二位2 要小,所以007 放在最前边,程序中即覆写了compareTo方法,又扩展了比较器

 MyCompare implements Comparator
显然扩展了比较器的那个被系统采用

class Student implements Comparable
在有扩展了Comparator 的时候是没有被优先采用的。

TreeSet应用4


需求:如果想自定义比较器,比如我们想比较名字的长度并且按照有小到大排序的话

package TreeSet;import java.util.Comparator;import java.util.Iterator;import java.util.TreeSet;public class TreeSetTest {public static void main(String[] args) {//方法1TreeSet ts = new TreeSet(new StrLenComparator());//方法2:使用匿名内部类如何创建自己的比较器//    TreeSet ts = new TreeSet(new Comparator()//{//public int compare(Object o1, Object o2) {////String s1 = (String)o1;//String s2 = (String)o2;///*从短到长*/int num = new Integer(s1.length()).compareTo(new Integer(s2.length()));////主要条件判断完了之后要判断次要条件如果//if(num ==0 )//{//return s1.compareTo(s2);//}//return num;////}//});                ts.add("abcd");ts.add("cc");ts.add("cba");ts.add("z");ts.add("haha");Iterator it = ts.iterator();while(it.hasNext()){sop(it.next());}     }public static void sop(Object obj){        System.out.print(obj);        System.out.println();    }}class StrLenComparator implements Comparator{public int compare(Object o1, Object o2) {String s1 = (String)o1;String s2 = (String)o2;/*从短到长*/                int num = new Integer(s1.length()).compareTo(new Integer(s2.length()));//主要条件判断完了之后要判断次要条件/*从长到短*///int num = (s1.length()-s2.length());//if( num >0)//{//return -1;//}//if(num<0 )//{//return 1;//}                //如果长度都一样的话就比较内容,调用java的string自带的比较器。                if(num ==0 ){return s1.compareTo(s2);}return num;}}

输出为:

z
cc
cba
abcd
haha

以上给出了两种方法,从长到短和从短到长的排序的比较器写法,和使用匿名内部类以及使用自定义比较器的方法。当然还可以吧s1 和s2 换个位置也能变成从长到短。


泛型:


泛型是jdk1.5 版本以后出现的新特性,用于解决安全问题,是一个安全机制,升级有三部分,高效,安全,简化书写,泛型就是为了安全。

泛型格式:通过<>来定义要操作的引用数据类型。在使用java提供的对象时,什么时候用泛型呢,通常在集合框架中常见,只要见到见到<>就要定义泛型了。Collection 和ArrayList 后边都有<E> 就是提醒程序员,要明确通过集合要操作什么元素。<>为了用来接收类型的,当使用集合时,将集合中要存储的数据类型作为参数传递到其中即可。

比如:ArrayList<String> al = new ArrayList<String>();

定义了一个容器告诉计算机容器内放的是String类型,好处是将运行时期出现的问题转移到了编译的时期,方便程序员解决问题,让运行时期的问题减少,安全。加上了泛型很多时候可以不用强制转换。


泛型应用1:

需求:把名字按照从小到大排序并且输出名字长度,按照泛型的方法。

package GenericDemo;import java.util.Comparator;import java.util.Iterator;import java.util.TreeSet;public class GenericDemo1 {public static void main(String[] args) {TreeSet<String> ts = new TreeSet<String>(new LenComparator());ts.add("abcd");ts.add("cc");ts.add("cba");ts.add("aaa");ts.add("z");ts.add("hahaha");Iterator<String> it = ts.iterator();while(it.hasNext()){String s = it.next();        sop(s+": "+s.length());}    }    public static void sop(Object obj)    {   System.out.print(obj);   System.out.println();    }}class LenComparator implements Comparator<String>{@Overridepublic int compare(String o1, String o2) {int num = new Integer(o1.length()).compareTo(new Integer(o2.length()));if(num ==0 ){return o1.compareTo(o2);}return num;}}

输出为:

z: 1
cc: 2
aaa: 3
cba: 3
abcd: 4
hahaha: 6

比较器本身也能够泛型一下,之后就不用强转o1,o2变成String,代码简单了也安全了。


泛型类:

当类中要操作的引用数据类型不确定的时候,早期定义Object来完成扩展,现在定义泛型类来完成扩展

需求:使用泛型类,这个泛型类可以扩展出学生类或者其他类但是不需要强转。

泛型应用2:

package GenericDemo;public class GenericDemo3 {public static void main(String[] args) {//早期类Tool t = new Tool();t.setObject(new Worker());                        Worker w = (Worker)t.getObject();//泛型类的应用Utils<Worker> u = new Utils<Worker>();u.setObject(new Worker());Worker w1 = u.getObject();}}class Worker{}class Student{}//泛型之前的做法class Tool{private Object obj;public void setObject(Object obj){this.obj =obj;}public Object getObject(){return obj;}}//泛型类,class Utils<QQ>{private QQ q;public void setObject(QQ q){this.q = q;}public QQ getObject(){return q;}}

当定义了泛型类的时候使用时候只要把泛型类后的尖括号内填写要使用的类就能完成了,不容易出错,在编译时期如果添加了和括号内不同的类系统就自动报错了,避免了强转还能提早发现错误。

而早期类需要强转,并且之后当云心的时候才会发现错误。泛型的应用大大提高了安全度,并且降低了程序员犯错误的可能性。



0 0
原创粉丝点击