• 由Contains开始的


    今天看到一道题目,感觉挺简单的,顺便看下作者的答案,如下。

    去除重复字符并排序

    运行时间限制:无限制
    内容限制:       无限制
    输入:              字符串
    输出:              去除重复字符并排序的字符串
    样例输入:       aabcdefff
    样例输出:       abcdef

    答案

     1 public void deleteAndSort(String str){
     2       int len=str.length();
     3        ArrayList<Character> list=new ArrayList<Character>();
     4         for(int i=0;i<len;i++){
     5           char ele=str.charAt(i);
     6           if(!list.contains(ele)){
     7             list.add(ele);
     8           }
     9        }
    10        Character[] arr=(Character[])list.toArray(new Character[0]);
    11        int size=arr.length;
    12        for(int i=0;i<size;i++){
    13            for(int j=0;j<size-i-1;j++){
    14                   if(arr[j]>arr[j+1]){
    15                       char tmp=arr[j];
    16                       arr[j]=arr[j+1];
    17                       arr[j+1]=tmp;
    18                   }
    19               }
    20        }
    21        for(char c : arr){
    22          System.out.print(c);
    23        }
    24     }

    其中对第六行的一句话list.contains(ele)感到很奇怪,按理说这个List里面都是对象,即使内容相同,但是它们引用(内存地址)和hashCode应该不是一样的,而contains一般都是通过hashCode 来判断是否包含的,这样的话,通过这句话能够知道是否包含吗?于是去查资料。

    ArrayList里面contains的源码为:

     1 public boolean contains(Object o) {
     2     return indexOf(o) >= 0;
     3     }
     4 
     5 
     6  public int indexOf(Object o) {
     7     if (o == null) {
     8         for (int i = 0; i < size; i++)
     9         if (elementData[i]==null)
    10             return i;
    11     } else {
    12         for (int i = 0; i < size; i++)
    13         if (o.equals(elementData[i]))
    14             return i;
    15     }
    16     return -1;
    17     }

    这里面是通过equals来比较是否包含的。

    那么equals是通过什么方式进行比较的呢?它与==有什么区别呢?

    总的来说有以下两点

    1)对于==,如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等;

        如果作用于引用类型的变量,则比较的是所指向的对象的地址

    2)对于equals方法,注意:equals方法不能作用于基本数据类型的变量

        如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;

        诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。

    由于java对八种基本类型byte int short long float double boolean char 都设计了包装类,并且重写了这些包装类的equals方法和hashCode方法,(这里包括Date和String)这样就可以实现上面程序中通过equals来判断是否包含某个字符。

    例如String的equals方法的实现:

     public boolean equals(Object anObject) { 
    if (this == anObject) { 
        return true; 
    } 
    if (anObject instanceof String) { 
        String anotherString = (String)anObject; 
        int n = count; 
        if (n == anotherString.count) { 
    char v1[] = value; 
    char v2[] = anotherString.value; 
    int i = offset; 
    int j = anotherString.offset; 
    while (n-- != 0) { 
        if (v1[i++] != v2[j++]) 
    return false; 
    } 
    return true; 
        } 
    } 
    return false; 
    } 

    这里已经不是对地址进行比较了,而是直接对内容中的字符一个一个的比较了。

    其次是hashCode().

    根据java官方文档的定义,我们可以抽出成以下几个关键点:

    1、hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;(hashCode的作用)

    2、如果两个对象相同,就是适用于equals(java.lang.Object) 方法,那么这两个对象的hashCode一定要相同

    这句话可以理解成只要equals相同,hashCode必定相同,但是反过来hashCode相同,equals不一定相同。

    3、如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第2点;

    4、两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”

     例如String的hashCode()

     public int hashCode() { 
    int h = hash; 
    if (h == 0) { 
        int off = offset; 
        char val[] = value; 
        int len = count; 
    
                for (int i = 0; i < len; i++) { 
                    h = 31*h + val[off++]; 
                } 
                hash = h; 
            } 
            return h; 
    } 

    一般情况下进行判断都是使用equals函数,假如一个类没有重写equals函数和hashCode函数那么在进行判断的时候就会使native 的hashCode进行判断。

    例如HashSet中不能有重复的对象,它在添加对象的时候先进行hashCode的判断,假如两个对象的hashCode不同,则直接断定这两个对象不同;假如hashCode相同

    ,则会跳转到判断是否equals。(注意:hashCode相同但是equals未必是true

        String s1=new String("abc"); 
        String s2=new String("abc");
    
        Set hashset=new HashSet(); 
        hashset.add(s1); 
        hashset.add(s2);  
        System.out.println(s1==s2);//false 
        System.out.println(s1.equals(s2));//true      Iterator it
    =hashset.iterator(); while(it.hasNext()) { System.out.println(it.next()); }

    输出:

    false

    true

    abc

    假如添加的是自定义的User类,并且这个User类没有重写equals和hashCode方法,结果就是另一个样子

    class User
    {   String name;   
    int age;   public User(String name,int age)   {       this.name=name;     this.age=age;   }  
      public void toString()
      {
        return name;
      
    }
    }
        User s1=new User(1,"abc");
        User s2=new
    User(1,"abc");
        Set hashset=new HashSet(); 
        hashset.add(s1); 
        hashset.add(s2);  
        System.out.println(s1==s2);//false 
        System.out.println(s1.equals(s2));//false      Iterator it=hashset.iterator(); while(it.hasNext()) { System.out.println(it.next()); }
     

    输出:

    false

    false

    abc

    abc

    这里第二个输出的false  因为User类没有重写equals方法,这里equals方法判断的是他们的地址,所以这里是不等的。而abc打印两次,他们的hashCode都不是一样的,肯定打印两次。

    说到这里hashCode与内存地址有什么关系呢,假如hashCode是由内存地址产生的,那由对象的一致性原则,hashCode一样,那么它们的内存地址也是一样的,这样他们equals肯定是true,但是事实并不是这样的,事实是equals为true肯定可以推出hashCode相同,但是hashCode相同未必能够推出equals为true。这样就是说hashCode与内存关系不大,参考:hashCode返回的并不一定是对象的(虚拟)内存地址,具体取决于运行时库和JVM的具体实现。

    附:

    1、List,Map,Set三者的contains的实现

    List的contains的实现如上。

    Set的Contains的实现:

    public boolean contains(Object o) {
        return map.containsKey(o);
        }

    其内部使用的是HashTable

    而HashMap也是使用同样的办法。

    2、标准的基本类型只要值相等,哈希值就相同;

    Integer a=10;

    Integer b=10;

    那么a和b的哈希值就相同。类似的还有Short、Long、Byte、Boolean、String等等

    3、同一个对象,与何时运行该程序无关;

    哈希值算法中,对象的内存地址不参与运算。因此只要是同一个对象,那么该对象的哈希值就不会改变。

    4、关于容器的哈希值

    java中常用的容器有List、Map、Set。那么它们的哈希值又有什么特点呢?

    假设有如下两个List:

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

    list1.add("item1");

    list1.add("item2");

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

    list2.add("item2");

    list2.add("item1");

    这两个List的哈希值是不一样的。对于List来讲,每一个元素都有它的顺序。如果被添加的顺序不同,最后的哈希值必然不同。

    假如有如下两个Map:

    Map<String, String> map1= new HashMap<String, String>();

    map1.put("a", "1");

    map1.put("b", "2");

    map1.put("c", "3");

    Map<String, String> map2= new HashMap<String, String>();

    map2.put("b", "2");

    map2.put("a", "1");

    map2.put("c", "3");

    这两个Map虽然元素添加的顺序不一样,但是每一个元素的Key-Value值一样。Map是一种无序的存储结构,因此它的哈希值与元素添加顺序无关,这两个Map的哈希值相同。

    假如有如下两个Set:

    Set<String> set1= new HashSet<String>();

    set1.add("a");

    set1.add("b");

    set1.add("c");

    Set<String> set2= new HashSet<String>();

    set2.add("b");

    set2.add("a");

    set2.add("c");

    类似的,由于Set也是一种无序的存储结构,两个Set虽然添加元素的顺序不一样,但是总体来说元素的个数和内容是一样的。因此这两个Set的哈希值也相同。

    其实,Set的实现是基于Map的。我们可以看到,如果将Set中的元素当做Map中的Key,把Map中的value始终设置为null,那么它就变成了一个Set。

    Set<String> set1= new HashSet<String>();

    set1.add("a");

    set1.add("b");

    set1.add("c");

    Map<String, String> map1= new HashMap<String, String>();

    map1.put("a", null);

    map1.put("b", null);

    map1.put("c", null);

    通过实验我最后得到了印证,set1与map1的哈希值相同。

  • 相关阅读:
    [转载]linux下svn命令使用大全
    【转载】Js获取当前日期时间及其它操作
    【转载】checkbox复选框的一些深入研究与理解
    IDEA13中配置struts错误:ClassNotFoundException: org...dispatcher.ng.filter.StrutsPrepareAndExecuteFilter +找不到java程序包 解决办法
    【转】jQuery.extend 函数详解
    【转载】Highcharts使用指南
    [转载]几个开源Javascript图形库
    sqlserver数据库创建快照发布遇到的错误:对路径“XXXX”访问被拒绝
    服务器安装ubuntu 14.04 server,开机启动屏幕不停滚动错误WRITE SAME failed. Manually zeroing
    NAT 模式下虚拟机安装的centos7 ping主机显示connect: Network is unreachable
  • 原文地址:https://www.cnblogs.com/maydow/p/4579104.html
Copyright © 2020-2023  润新知