关于JAVA集合的那点事

(一) Vector   ArrayList   LinkedList

Vestor,ArrayList,LinkedList这三个类都实现了java.util.List接口;

Vector和ArrayList使用Objec的数组形式来存储,可直接按序号索引元素,故搜索速度较快,但在数组中间插入新元素时要设计数组元素的内存移动,导致速度较慢;

LinkedList则是采用了双向链表的存储方式,插入新元素时只要指定该元素的前后项即可,速度较快,但是搜索时要向前或向后遍历,速度较慢

Vector是线程安全的,他的很多方法都实现了线程同步(synchronized),但是性能上来讲,Vector会低于ArrayList

 

(二)HashSet

HashSet 是 Set 接口的常用实现类,构造函数如下:

 public HashSet() {
       map = new HashMap<E,Object>();
 }

从构造函数我们可以看出,实际上HashSet是通过HashMap来实现的,因此HashSet也是采用Hash存储机制进行数据的存储。

我们都知道HashSet不允许插入重复的值或对象,但是对于两个内容相同的对象呢?

运行下面的代码我便能知晓一二:

public class Test {

 //定义一个内部类

 private static class Demo{

  private int value;

  public Demo(int value){

   this.value=value;

  }  

  //重写toString方法,便于打印输出

  public String toString(){

   return ("value="+value);

  }

  //重写equals方法

   public boolean equals(Object o) {

              Demo demo = (Demo) o;

              return (demo.value == value) ? true : false;

      }

   //重写hashCode方法

   public int hashCode(){

    return value;

   }

 }

 

 public static void main(String[] args) {

  HashSet<Demo> set=new HashSet<Demo>();

  set.add(new Demo(1));

  System.out.println(set);

  set.add(new Demo(1));

  System.out.println(set);

 } 

}

运行结果为:

[value=1]
[value=1]

但是当我们把Demo的equals方法或hashCode方法注释掉一个或者全部注释掉时我们会发现运行结果变为:

[value=1]
[value=1, value=1]

如果不重写对象的hashCode方法和equals方法的情况下,HashSet依旧会将内容相同的两个对象一起存入

那么为什么会如此呢?

由于HashSet是依靠HashMap实现的,所以要解答这个问题我们必须搞清楚HashMap内部的存储机制

通过另一篇文章——深入讲解HashMap,我们可以发现,键值对在存入HashMap中定义的entry数组时,会先根据键(key)对象的hashCode计算出要存储在数组的那个下标值下,然后在遍历该下标值的entry链,如果找到了key与要插入的key相同的entry对象则替换该对象的值,否则,则将此新键值对插入到该entry链的头部;

而对于HashSet<E>事实上就相当于HashMap<E,Object>

如果不重写hashCode方法,那么我们知道Object默认的hashCode返回的是一个与内存地址相关的数值,两个不同对象的hashCode返回值一般是不相同的(当然也有很小的几率相同),哪怕是这两个对象的内容完全一致,所以在插入entry数组的时候,这两个对象存储的下标值也有很大的概率不在同一个下标值内,自然就两个对象都会被存储在entry数组中;

那么即使我们重写了hashCode也还是不够,因为在entry链中,会根据对象的equals方法判断新添加进来的key时候已经存在,我们知道Object默认的equals方法返回的是该对象的内存地址,自然的,两个不同对象用equals进行比较时返回的就是false,也就是此时这两个对象都会被存储再同一个entry链中。

明白了上面的机制后,我们可以看出,如果我们要实现在HashSet(或者是HashMap的key)中不会被插入内容相同的对象时,我们就必须重写要插入的对象的hashCode和equals方法,而且这两个方法要根据该对象的内容来重写,确保两个内容相同的对象的hashCode返回值相同,并使用equals比较时返回true

 

(三)TreeSet  TreeMap  HashSet  HashMap

TreeMap它内部是一个树形结构存储结构,使用二叉树对数据进行存储;

TreeMap是有序的,自然要求元素是可以比较大小的,如果构造函数指定Comparator的话,就使用这个Comparator比较大小

如果没有指定Comparator的话,就使用自然排序(元素要实现Comparable接口).

二叉树,一般查找时间复杂度为 o(lg(n)),这个效率当然没有HashMap的效率高所以除法是有排序的需求,不然一般都使用HashMap

TreeSet与TreeMap的关系就和HashSet与HashMap的关系一样

由于TreeSet  TreeMap  用的比较少,所以也未作深入的了解,具体以后有需要再进一步分析

 

(四)HashMap  Hashtalbe

Hashtable是继承与Dictionary类,HashMap是Map接口的一个实现(事实上,Hashtable也实现了Map接口);

Hashtable是线程安全,HashMap是Hashtable的轻量级实现(非线程安全实现),所以HashMap的效率会高于Hashtable

HashMap允许将null作为一个entry的key或者value,而Hashtable不允许。

 

(五)关于集合的遍历 

package com;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;

public class 容器类的遍历 {
 
 //有序的集合:数组和arraylist的遍历,可通过下标值实现
  public void bianliArray(String[] a){
   for(int i=0;i<a.length;i++){
    System.out.println(i);
   }
  }
  
  public void bianliArraylist(ArrayList<String> a){
   for(int i=0;i<a.size();i++){
    System.out.println(a.get(i));
   }
  }
  
  //Collection接口定义了一个iterator()方法,该方法返回一个实现了Iterator接口对象(迭代器),然后再使用迭代器进行迭代遍历  
  public void bianliCollection(Collection<String> c){
   Iterator<String> iterator=c.iterator();
   while(iterator.hasNext()){
    System.out.println(iterator.next());
    /*
     * 补充: 值得注意的是,当要在迭代过程中删除某个元素,则必须要用该迭代器的remove方法
     * 而不能使用collection自生的remove方法,这是因为集合进行进入迭代器时会被迭代器锁定,
     * 迭代过程只有迭代器能对此集合进行操作
     */
   }  
  }
  
     //java5之后支持了另一种 遍历方式:foreach方式,这种方式的编写格式简单整洁,但是只能用于只读的情况,
  //因为该方法不能对遍历的元素进行修改和删除操作,分析下面的方法的结果可以发现集合中的字符串没被修改过
  public void bianliForeach(Collection<String> c){
   for(String s: c){
    System.out.println(s);
    s="被修改过了";
   }
   for(String s: c){
    System.out.println(s);
   }
  }
  //但是分析下面这个程序可以发现集合中的u对象却是被修改了,从中可以看出什么么?。。。。
  public void bianliForeach1(Collection<User> c){
   for(User u: c){
    System.out.println(u.getName());
    u.setName("被修改过了");
   }
   for(User u: c){
    System.out.println(u.getName());
   }
  }
  
  /*
   * 对于map的遍历,由于Map接口没有定义和collection一样的iterator方法
   * 不过在Map中定义里entrySet和ketSet方法
   *   entrySet()返回此映射中包含的映射关系的 Set视图,
   *   keySet() 返回此映射中包含的键的 Set 视图。
   *   然后在通过set视图的iterator方法
  */
  
  //用entrySet实现Map的遍历,效率较高
  public  void entrySetOfMap(Map<String, String>  m){
   Iterator iterator=m.entrySet().iterator();
   while(iterator.hasNext()){
    Map.Entry<String, String> entry=(Map.Entry<String, String>)iterator.next();//Map.Entry键值对映射项
    System.out.println("key="+entry.getKey());
    System.out.println("value="+entry.getValue());
   }
  }
  
  //用keySet实现Map的遍历,遍历出key之后还要去map查找对应的value,效率较低
  public void keySetOfMap(Map<String, String> m){
   Iterator iterator=m.keySet().iterator();
   while(iterator.hasNext()){
    Object key=iterator.next();
    String value=(String)m.get(key);
    System.out.println("key="+key);
    System.out.println("value="+value);
   }
  }
 
}

你可能感兴趣的:(关于JAVA集合的那点事)