(一) 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); } } }