在Java的类集里面(java.util)包中,提供了两个最为核心的接口:Collection、Map接口。
其中Collection接口的操作形式与之前编写链表的操作形式类似,每一次进行数据操作的时候只能够对单个对象进行处理。
List
ArrayList和LinkedList和Vector的区别和实现原理:
ArrayList和Vector只能按照顺序存储元素(从下标位0的位置开始),删除元素的时候,需要移位并置空,默认初识容量都是10。
ArrayList和Vector都是基于数组实现的,LinkedList基于双向链表实现的(含有头结点)。
- 线程安全:ArrayList和LinkedList不具有线程安全性,用在单线程环境中。如果要在并发环境下使用他们,可以调用Collections类中的静态方法synchronizedList()对ArrayList和LinkedList进行调用即可。Vector已经说过了。
- 扩容机制:从内部实现来讲,ArrayList和Vector都是使用Object的数组来存储的。当你向这两种类型中添加元素的时候,如果容量不够,需要进行扩容。ArrayList扩容后的容量是之前的1.5倍,然后把之间的数据拷贝到新建的数组中。Vector默认情况下扩容后的容量是之前的2倍。Vector可以设置容量增量,而ArrayList不可以。在Vector中有capacityIncrement:向量的大小大于其容量时,容量自动增加的量。如果在创建Vector时,指定了capacityIncrement的大小,则每次当Vector中动态数组容量需要增加时,如果容量的增量大于零,增加的大小都是capacityIncrement。如果容量的增量小于等于零,则每次需要增大容量时,向量的容量将增大为之前的2倍。
- 可变长度数组的原理:当元素个数超出数组的长度时,会产生一个新数组,将原数组的数据复制到新数组,再将新的元素添加到新数组中。
- 增删改查的效率:ArrayList和Vector中,从指定的位置(用index)检索一个对象,或在集合的末尾插入、删除一个对象的时间是一样的,可表示为O(1)。但是,如果在集合的其他位置增加或移除元素那么花费的时间是O(n)。LinkedList中,在插入、删除集合中任何位置的元素所花费的时间都是一样的O(1),但他在索引一个元素的时候比较慢,O(n)。
- 所以,如果只是查找特定位置的元素或只在集合的末端增加、移除元素,那么使用Vector或者ArrayList都可以。如果是对其他指定位置的插入、删除操作,最好选择LinkedList。
Set
Set接口与List接口最大的不同在于Set接口中的内容是不允许重复的。同时需要注意的是,Set接口并没有对Collection接口进行扩充,而List对Collection进行了扩充。因此,在Set接口中没有get()方法。
在Set子接口中有两个常用的子类:HashSet(无序存储)、TreeSet(有序存储)。
HashSet的实现原理
对于HashSet而言,它是基于HashMap实现的,HashSet底层使用HashMap来保存所有元素,因此HashSet的实现方式比较简单,相关HashSet的操作,基本上都是直接调用HashMap的方法来完成。HashSet的元素都放在HashMap的key上面,而Value中的值都是统一的一个private static final Object PRESENT = new Object();
//底层使用HashMap来保存HashSet中的所有元素
private transient HashMap<E,Object> map;
//定义一个虚拟的Object对象作为HashMap的value,将此对象定义为static final
private static final Object PRESENT = new Object();
/*
*默认的无惨构造器,构造一个空的HashSet。
*实际底层会初始化一个空的HashMap,并使用默认初识容量为16和加载因子0.75
*/
public HashSet(){
map = new HashMap<E,Object>;
}
public boolean add(E e){
return map.put(e,PRESENT) == null;
}
TreeSet使用的是升序排列的模式完成的
TreeSet排序分析
既然TreeSet子类可以进行排序,所以我们可以利用TreeSet实现数据的排列处理操作。此时要想进行排序实际上是针对于对象数组进行的排序处理,而如果要进行对象数组的排序 ,对象所在的类一定要实现Comparable接口并且覆写compareTo()方法,只有通过此方法才能知道大小关系。
需要提醒的是如果现在是在用Comparable接口进行大小关系匹配,所有属性必须全部进行比较操作。
实际使用中使用TreeSet太过于麻烦了。而在项目中,简单Java类都是根据数据表设计得来的,如果一个类的属性很多,那么比较起来就很麻烦了。所以我们一般使用HashSet。
Comparable接口与Comparator接口
Comparable(内部排序接口)简介
Comparable是排序接口。
如果一个类实现了Comparable接口,就意味着该类支持排序。既然实现Comparable接口的类支持排序,假设现在存在“实现Comparable接口的类的对象的List列表(或数组)”,则该List列表(或数组)可以通过Collections.sort(或者Arrays.sort)进行排序。
此外,“实现Comparable接口的类的对象”可以用作 "有序映射(如TreeMap)"中的键或 " 有序集合(TreeSet)"中的元素,而不需要指定比较器。
Comparable定义
public interface Comparable<T> {
public int compareTo(T o);
}
关于返回值:
可以看出compareTo方法返回一个int值,该值有三种返回值:
- 返回负数:表示当前对象小于比较对象
- 返回0:表示当前对象等于目标对象
- 返回正数:表示当前对象大于目标对象
Comparator(外部排序接口)简介
Comparator是比较器接口。
我们若需要控制某个类的次序,而该类本身不支持排序(即没有实现Comparable接口);那么,我们可以建立一个"该类的比较器"来进行排序。这个"比较器"只需要实现Comparator接口即可。
也就是说,我们可以通过 "实现Comparator类来新建一个比较器",然后通过该比较器类进行排序。
Comparator定义
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
}
仅仅只包含两个函数。
int compare(T o1, T o2)是比较o1和o2的大小。
- 返回 < 0,意味着o1小于o2;
- 返回 = 0,意味着o1和o2相等;
- 返回 > 0,意味着o1大于o2
Comparator和Comparable比较
Comparable是排序接口;若一个类实现了Comparable接口,就意味着该类支持排序。
而Comparator是比较器;我们若需要控制某个类的次序,可以建立一个该类的比较器来进行排序。
重复元素判断(hashCode与equals方法)
在使用TreeSet子类进行数据保存的时候,重复元素的判断依靠Comparable接口完成的。但是这并不是全部Set接口判断重复元素的方式,因为如果使用的是HashSet子类,由于其跟Comparable接口没有任何关系,所以它判断重复元素的方式依靠的是Object类中的两个方法:
- hash码 :public native int hashCode();
- 对象比较:public boolean equals(Object obj)
equals()的作用是用来判断两个对象是否相等,在Object里面的定义是
public boolean equals(Object obj){
return (this == obj);
}
这说明在我们实现自己的equals方法之前,equals等价于==,而==运算符是判断两个对象是不是同一个对象,即他们的地址是否相等。而覆写equals更多的是追求两个对象在逻辑上的相等,你可以说是值相等,也可说是内容相等。
覆写equals的准则
- 自反性:对于任何非空引用值x,x.equals(x)都应返回true
- 对称性:对于任何非空引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)才应返回true
- 传递性:对于任何非空引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)返回true,那么x.equals(z)应返回true
- 一致性:对于任何非空引用值x和y,多次调用x.equals(y)始终返回true或始终返回false,前提是对象上equals比较中所用的信息没有被修改
- 非空性:对于任何非空引用值x,x.equals(null)都应该返回false。
hashCode用于返回对象的hash值,主要用于查找的快捷性,因为hashCode也是在Object对象中就有的,所以所有Java对象都有hashCode,在HashTable和HashMap这一类的散列表结构中,都是通过hashCode来查找在散列表中的位置。
在Java中进行对象比较的操作有两步:第一步要通过一个对象的唯一编码找到一个对象的信息,当编码匹配之后,再调用equals()方法进行内容的比较。
如果两个对象equals,那么它们的hashCode必然相等,但是hashCode相等,equals不一定相等。
对象判断必须两个方法equals()、hashCode()返回值都相同菜判断为相同。
个人建议:
- 保存自定义对象的时候使用List接口
- 保存系统类信息的时候使用Set接口(避免重复)。