JAVA容器

(1)Collection接口和Collections类的区别

Collection是个java.util下的接口,它是各种集合结构的父接口。

Collections是个java.util下的类,它包含有各种有关集合操作的静态方法。

Collection 层次结构中的根接口。Collection 表示一组对象,这些对象也称为 collection元素。一些 collection 允许有重复的元素,而另一些则不允许。一些 collection 是有序的,而另一些则是无序的。JDK 不提供此接口的任何直接 实现:它提供更具体的子接口(如 Set List)实现。此接口通常用来传递 collection,并在需要最大普遍性的地方操作这些 collection

Collection Collections 的区别。

Collection是集合类的上级接口,继承与他的接口主要有Set List.

Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作

(2)实现比较的Comparable接口和Comparator

ü  Comparable接口(里面只定义了一个compareTo方法)

public interface Comparable<T> {

    public int compareTo(T o);

}

ü  Comparator

public interface Comparator<T> {

    int compare(T o1, T o2);

    boolean equals(Object obj);

}

(3)ListMapSet三个接口,存取元素时,各有什么特点

ü  ListSet具有相似性,都是单列元素的集合,因此他们都有一个共同的父接口Collection

ü  List表示先后顺序的集合,因此可以插入相等的元素,而Set里面不允许有重复的元素,即不能有两个相等的对象,这个相等取决于对象的equals方法;

ü  下面我们来深入讨论底层中HashSetadd方法的实现:

public class HashSet<E>

    extends AbstractSet<E>

    implements Set<E>, Cloneable, java.io.Serializable

{

    private transient HashMap<E,Object> map;

    // Dummy value to associate with an Object in the backing Map

    private static final Object PRESENT = new Object();

    public HashSet() {

        map = new HashMap<E,Object>();

    }

    public boolean contains(Object o) {

        return map.containsKey(o);

    }

    public boolean add(E e) {

        return map.put(e, PRESENT)==null;

    }

}

惊讶地发现,HashSet元素的唯一性是借助HashMapKey的唯一性来实现的。因此现在再深入看看HashMap中是如何保证Key的唯一性的:

public V put(K key, V value) {

    if (key == null)

       return putForNullKey(value);

    int hash = hash(key.hashCode());

    int i = indexFor(hash, table.length);

for (Entry<K,V> e = table[i]; e != null; e = e.next)

{

       Object k;

        if (e.hash == hash && ((k = e.key) == key

                                      || key.equals(k))) {

                V oldValue = e.value;

                e.value = value;

                e.recordAccess(this);

                return oldValue;

      }

}

从这段代码中可以看出,HashMap中的Key是根据对象的hashCode() euqals()来判断是否唯一的。

结论:为了保证HashSet中的对象不会出现重复值,在被存放元素的类中必须要重写hashCode()equals()这两个方法。

心得:以前写贪食蛇程序的时候就曾经遇到过用重写HashMapequals方法来判断添加的元素是否一样,后来也知道了还需要重写hashCode()方法,但是却没想到去看源代码,可见做研究的必须得做深入了,这样才能真正巩固和学习到新知识。

扩展:综上所述,不难推断出:HashMap中的Key完全可以使用自定义的类,而关键之处就在于要同时重写equals方法和hashCode方法,比如说有个User类,其中有个id属性,要求其id属性相等就认为Key相等,故不能重新添加,要实现这样的功能,就必须去重写User类的equalshashCode方法了。下面是网上找的一个实例:

Ø  Dummy.java

public class Dummy {
    private int id;

    public Dummy(final int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(final int id) {
        this.id = id;
    }

    @Override
    public int hashCode() {
        return id;
    }

@Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o instanceof Dummy) {
            Dummy anotherDummy = (Dummy) o;
            return this.getId() == anotherDummy.getId();
        }
        return false;
    }

}

Ø  HashCode.java

public class HashCode {

    public static void main(String[] args) {

        Map<Dummy, String> map = new HashMap<Dummy, String>();

        map.put(new Dummy(1), "old value whose key's value is 1");

        Dummy dummy = new Dummy(1);

        map.put(dummy, "new value whose key's value is 1");

        map.put(new Dummy(2), "old value whose key's value is 2");

        for (Map.Entry entry : map.entrySet()) {

            System.out.println("key: " + entry.getKey() + " value: " + entry.getValue());

        }

    }

}

输出:

key: com.pdm.Dummy@1 value: new value whose key's value is 1
key: com.pdm.Dummy@2 value: old value whose key's value is 2

下面再深入JDKObject类中的equalshashCode方法:

equals hashcode 方法来自于Object类,实现分别如下:

public boolean equals(Object obj) {
         return (this == obj);
}

public native int hashCode();

通过以上两个方法的实现,对object的这两个方法(如果我们的自定义类不overwiter这两个方法,就是如上默认实现),我们可以得出如下结论:

(1)  equals方法默认是对象存放地址的比较

(2)  hashcode是于存放内存地址相关的一个整形值,因为和内存地址相关,所以用到了native关键字,该关键字表明该方法需要调用非java的接口来实现,如c,c#

继续深入:

equals():

用于两个对象的比较,在Object类中已经实现了这个方法,是对对象内部地址的比较,即如果两个对象的内部地址是一样的则是相等的。如果要按照对象内容的进行比较,就需要重载这两个方法。Java语言对equals()的要求如下,这些要求是必须遵循的:

对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。

反射性:x.equals(x)必须返回是“true”。

类推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。

一致性:如果x.equals(y)返回是“true”,只要xy内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。

    任何情况下,x.equals(null)永远返回是“false”;x.equals(x不同类型的对象)永远返回是“false”。

hashCode():

    这个方法返回的是一个用来进行hash操作的整型数,可用于在Collection对象中定位特定对象的位置。Object中默认的实现是把对象内部地址转化为整数作为hashCode

    hashCode()的返回值和equals()的关系如下:

如果x.equals(y)返回“true”,那么xyhashCode()必须相等。

如果x.equals(y)返回“false”,那么xyhashCode()有可能相等,也有可能不等。

    这里有必要说明一下HashMap的原理。为了优化查找对象的性能,在HashMap中按照键值对象的hash值放了若干个箱子,当有一个键值对象加入进来时,调用键值对象的hashCode()方法,根据计算出的hash值把对象放入对应的箱子。当对键值对象进行查找时,首先计算对象的hash值,找到对应的箱子,然后调用equals()与箱子中的对象逐个比较,直到找出相等的对象或者遍历了一遍。

    如果x.equals(y)返回“true”,而x.hashCode() != y.hashCode()会有什么后果呢?假设把xy方法HashMap,即hashMap.put(x, xValue)hashMap.put(y, yValue),在hashMap中会存在两个元素xy,而不是一个。原因很好理解,因为二者的hashCode不同,在hashMap中会放入不用的箱子,从而会被认为是两个对象。

    使用hashCode的目的是为了把对象散列在不同的地方,从而提高检索对象的性能,所以编写一个好的hashCode算法相当重要。在Effective Java 2 edition 中给出了一个简单的hashCode算法:

l  保存一些常量非0值,例如17,在一个intresult变量中。这个值可以是任意的,但是最好不是0,会增加冲突的可能。

l  对象每一个重要的域f(就是equals方法使用到那些field)做以下操作:

    a)对域f计算一个int的哈希码c

i.如果fieldboolean型的,计算(f ? 1 : 0)

ii.如果fieldbyte,char,int之类的,计算 (int)f

iii.如果fieldlong, 计算 (int) (f^(f>>32))

iv.如果fieldfloat,计算 Float.floatToIntBits(f)

v.如果fielddouble,计算Double.doubleToLongBits(f),然后使用2.a.iii的方法计算

hash code

vi.如果field是一个对象的引用,并且这个类的此域比较的equals方法使用了递归的调用equals,直接递归的调用hashCode方法。如果一个更复杂的比较是必须的,对此域计算”canonical representation(规范的表示)”并且在”规范的表示”上调用hashCode(invoke hashCode on the canonical representation)。如果此fieldnull,返回0

vii.如果fieldarray,将数组中的每个元素作为一个单独的field,用上述规则来计算,并使用2.b中的方式合并这些值。如果每个元素都是significant,你可以使用1.5版本添加的Arrays.hasCode方法。

    b)合并2.a中哈希码c的值到result使用下面的方法:

                   result=31*result+c;

l  返回result

l  当你完成hashCode函数之后,自我检验一下相等的实例是否有相等的hash code。编写单元测试去检验你所想的结果。如果相等实例有不同的hash code,找出原因并解决。

说了那么多,应该可以明白了。不过上面还有一个细节,那就是在上面那个实例输出结果中有个@后面跟了个整数,其实就是hashCode的值,上面由于重写了DummyhashCode方法,因此返回的是id的值。

public String toString() {

return getClass().getName() + "@" + Integer.toHexString(hashCode());

}

ü  List集合可以使用get(int index)方法取得具体指定的元素ddeCode可以明白了。不过上面还有一个细节,那就是在输出的,但是Set取元素时,没法取得第几个,只能以Iterator接口取得所有的元素,再逐一遍历各个元素;

ü  MapListSet不同,他是双列的集合,其中有put方法,定义如下:put(obj key, obj value),每次存储时,要存储一对key/value,不能存储重复的key,这个重复的定义是按照equalshashCode方法来决定的;

(4)ArrayListVector的异同

这两个类都实现了List接口(List接口继承了Collection接口),他们都是有序的集

合,即存储在这两个集合中的元素的位置都是有顺序的,相当于一种动态的数组,我们可以按位置索引出某个元素,并且其中的数据允许重复,区别主要包括:

ü  同步性:Vector是线程安全的,也就是他的方法之间是线程同步的:

public synchronized void ensureCapacity(int minCapacity) {

modCount++;

    ensureCapacityHelper(minCapacity);

}

private void ensureCapacityHelper(int minCapacity) {

    int oldCapacity = elementData.length;

    if (minCapacity > oldCapacity) {

        Object[] oldData = elementData;

        int newCapacity = (capacityIncrement > 0) ?

       (oldCapacity + capacityIncrement) : (oldCapacity * 2);

        if (newCapacity < minCapacity) {

       newCapacity = minCapacity;

        }

            elementData = Arrays.copyOf(elementData, newCapacity);

    }

}

public synchronized void setSize(int newSize) {

    modCount++;

    if (newSize > elementCount) {

        ensureCapacityHelper(newSize);

    } else {

        for (int i = newSize ; i < elementCount ; i++) {

       elementData[i] = null;

        }

    }

    elementCount = newSize;

}

ArrayList是线程不安全的,他的方法之间线程是不同步的。如果只有一个线程会访问到集合,那最好是使用ArrayList,因为他不考虑线程安全,效率会高一些;如果有多个线程会访问到集合,那最好是使用Vector,因为不需要我们自己再去考虑和编写线程安全的代码;

备注:对于VectorArrayListHashtableHashMap,要记住线程安全的问题,记住VectorHashtable是旧的,是java一开始就提供了的,他们是线程安全的,ArrayListHashMapjava2时才提供的,他们是线程不安全的。

ü  数据增长:ArrayListVector都有一个初始容量大小(默认为10),当存储他们里面的

元素个数超过了容量时,就需要增加ArrayListVector的存储空间,每次要增加存储空间时,不是只增加一个存储单元,而是增加多个存储单元:

l  对于Vector: int newCapacity = (capacityIncrement > 0) ?

       (oldCapacity + capacityIncrement) : (oldCapacity * 2);

l  对于ArrayList: int newCapacity = (oldCapacity * 3)/2 + 1;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值