【Java集合】List和Set

在Java的类集里面(java.util)包中,提供了两个最为核心的接口:Collection、Map接口。

其中Collection接口的操作形式与之前编写链表的操作形式类似,每一次进行数据操作的时候只能够对单个对象进行处理。

 

List

ArrayList和LinkedList和Vector的区别和实现原理:

      ArrayList和Vector只能按照顺序存储元素(从下标位0的位置开始),删除元素的时候,需要移位并置空,默认初识容量都是10。

      ArrayList和Vector都是基于数组实现的,LinkedList基于双向链表实现的(含有头结点)。

  1. 线程安全:ArrayList和LinkedList不具有线程安全性,用在单线程环境中。如果要在并发环境下使用他们,可以调用Collections类中的静态方法synchronizedList()对ArrayList和LinkedList进行调用即可。Vector已经说过了。
  2. 扩容机制:从内部实现来讲,ArrayList和Vector都是使用Object的数组来存储的。当你向这两种类型中添加元素的时候,如果容量不够,需要进行扩容。ArrayList扩容后的容量是之前的1.5倍,然后把之间的数据拷贝到新建的数组中。Vector默认情况下扩容后的容量是之前的2倍。Vector可以设置容量增量,而ArrayList不可以。在Vector中有capacityIncrement:向量的大小大于其容量时,容量自动增加的量。如果在创建Vector时,指定了capacityIncrement的大小,则每次当Vector中动态数组容量需要增加时,如果容量的增量大于零,增加的大小都是capacityIncrement。如果容量的增量小于等于零,则每次需要增大容量时,向量的容量将增大为之前的2倍。
    1. 可变长度数组的原理:当元素个数超出数组的长度时,会产生一个新数组,将原数组的数据复制到新数组,再将新的元素添加到新数组中。
  3. 增删改查的效率:ArrayList和Vector中,从指定的位置(用index)检索一个对象,或在集合的末尾插入、删除一个对象的时间是一样的,可表示为O(1)。但是,如果在集合的其他位置增加或移除元素那么花费的时间是O(n)。LinkedList中,在插入、删除集合中任何位置的元素所花费的时间都是一样的O(1),但他在索引一个元素的时候比较慢,O(n)。
    1. 所以,如果只是查找特定位置的元素或只在集合的末端增加、移除元素,那么使用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值,该值有三种返回值:

  1. 返回负数:表示当前对象小于比较对象
  2. 返回0:表示当前对象等于目标对象
  3. 返回正数:表示当前对象大于目标对象

 

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的大小。

  1. 返回 < 0,意味着o1小于o2;
  2. 返回 = 0,意味着o1和o2相等;
  3. 返回 > 0,意味着o1大于o2

Comparator和Comparable比较

Comparable是排序接口;若一个类实现了Comparable接口,就意味着该类支持排序。

而Comparator是比较器;我们若需要控制某个类的次序,可以建立一个该类的比较器来进行排序。

 

 

重复元素判断(hashCode与equals方法)

在使用TreeSet子类进行数据保存的时候,重复元素的判断依靠Comparable接口完成的。但是这并不是全部Set接口判断重复元素的方式,因为如果使用的是HashSet子类,由于其跟Comparable接口没有任何关系,所以它判断重复元素的方式依靠的是Object类中的两个方法:

  1. hash码   :public native int hashCode();
  2. 对象比较: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()返回值都相同菜判断为相同。

个人建议:

  1. 保存自定义对象的时候使用List接口
  2. 保存系统类信息的时候使用Set接口(避免重复)。

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值