List
List继承了Collection,是有序的列表。
- 可以允许重复的对象
- 可以插入多个null元素
- 是一个有序容器,保持了每个元素的插入顺序,输出的顺序就是插入的顺序
常用的实现类有 ArrayList、LinkedList 和 Vector。
①ArrayList是基于数组实现的,是一个数组队列。可以动态的增加容量
②LinkedList是基于链表实现的,是一个双向循环列表。可以被当做堆栈使用
③Vector是基于数组实现的,是一个矢量队列,是线程安全的
Set
Set可以存储多个对象,但并不会记住元素的存储顺序,也不允许集合中有重复元素。
- 不允许重复对象
- 无序容器,你无法保证每个元素的存储顺序,TreeSet通过 Comparator 或者 Comparable 维护了一个排序顺序
- 只允许一个 null 元素
常用的实现类有HashSet、LinkedHashSet、TreeSet。
①HashSet基于HashMap 实现,保证元素唯一性的方式依赖于:hashCode()与equals()方法。元素的存与取的顺序不能保证一致。允许放null值,只能允许一个null
②LinkedHashSet元素唯一不能重复,底层结构是哈希表结构 + 链表结构,元素的存与取的顺序一致
③TreeSet实现了 SortedSet 接口,本质是一个"有序的,并且没有重复元素"的集合,它是二叉树实现的。TreeSet中的元素支持2种排序方式:自然排序或者根据创建TreeSet 时提供的Comparator 进行排序。不允许放入null值
Set去重
HashSet去重
自定义对象不会自动去重需要重写equals和hashcode方法,去重系统类的对象时不用重写。去重:当添加到Set的对象 HashCode码不相同时不会调用equals方法,对象直接存到Set集合中;HashCode相同时才会调用equals方法查看是否是同一个对象。
LinkedHashSet去重
有序的HashSet(按照存入集合的顺序打印)
TreeSet去重
TreeSet存入对象打印时需要实现Comparable接口,重写接口中的 compareTo()方法
JDK1.8中HashSet的数据结构:
/**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}
可见new HashSet()操作实际上是new HashMap<>(),可见底层是以HashMap来实现的。
HashSet.add方法:
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
HashMap.add方法:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
判断插入的key是否存在,要判断两点①hash值是否相同;②对应的值是否相同,前者要看hashCode()方法,后者要看equal()方法。
下面对于基本的数据类型和自定义类类型在计算hashCode和equal的区别:
java.lang.Integer.equals():两个对象对应的值一致则返回true
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
java.lang.String.equals():两个字符串对应的值一致则返回true
public boolean equals(Object anObject) {
if (this == anObject) {//同一个对象,必定是一致的
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {//对比每一个字符
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;//anObject不是String实例,那么返回false
}
java.lang.Object.equals():两个对象的引用是否一致,即两个的对象是否是同一个。
public boolean equals(Object obj) {
return (this == obj);
}
可见对于java.lang.Object.equals()来讲,两个new出来的对象肯定是不一致的,
那么在HashMap数据结构中不会被判定成相同的对象(尽管值相同)。
下面再看看hashCode的源码:
java.lang.Integer.hashCode():
@Override
public int hashCode() {
return Integer.hashCode(value);
}
public static int hashCode(int value) {
return value;
}
java.lang.String.hashCode():
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
java.lang.Object.hashCode():
public native int hashCode();
JDK8的默认hashCode的计算方法是通过和当前线程有关的一个随机数+三个确定值,运用Marsaglia’s xorshift schema随机数算法得到的一个随机数。因此,以上可以看到Integer和String也都是根据具体的value值来计算hashCode,那么尽管两个引用不同但是值相同的对象,依然是想等的,但是Object则不同了。
在阿里巴巴Java开发是手册的集合处理中需要强制遵循如下规则:
- 只要重写equals,就必须重写hashCode
- 因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的对象必须重写这两个方法
- 如果自定义对象做为Map的键,那么必须重写hashCode和equals