Collection接口
无序 允许重复
public interface Collection<E> extends Iterable<E>
方法
- add新增
- remove删除
- contains判断集合中是否有指定元素
- clear清空集合
- size元素个数
- iterator获取迭代器,通过迭代器遍历集合中的所有元素
- toArray转换为等长的数组
如何判断删除的元素相等
@Override
public boolean equals(Object obj) {
// 用户自定义的比较规则
if (this == obj)
return true;
if (obj == null) //当前对象不可能为null,否则空指针异常
return false;
if (getClass() != obj.getClass()) // 类型判断。一个类只能加载一次
return false;
A1 other = (A1) obj;
//调用Objects工具类中的方法进行相等判断
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
return Objects.equals(id, other.id) && Objects.equals(name, other.name);
}
==和equals
==比较的是对象的引用值
equals用户自定义比较规则。如果没有自定义equals方法,则从Object类中继承得到equals方法
public boolean equals(Object obj) {
return (this == obj);
}
package com.yan1;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
public class Test1 {
public static void main(String[] args) {
Collection cc = new ArrayList();
A1 aa = new A1();
cc.add(aa);
cc.add(aa);
System.out.println(cc.size());
System.out.println(cc.remove(new A1()));
System.out.println(cc.size());
}
}
class A1 {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
@Override
public boolean equals(Object obj) {
// 用户自定义的比较规则
if (this == obj)
return true;
if (obj == null) //当前对象不可能为null,否则空指针异常
return false;
if (getClass() != obj.getClass()) // 类型判断。一个类只能加载一次
return false;
A1 other = (A1) obj;
//调用Objects工具类中的方法进行相等判断
/* public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
*/
return Objects.equals(id, other.id) && Objects.equals(name, other.name);
}
}
List接口
有序 允许重复
public interface List<E> extends Collection<E>
继承自Collection接口的方法
- 继承自Collection接口的方法
- boolean add(E e);向集合末尾追加元素e对象
- boolean remove(Object obj)删除第一个和obj相等的元素,如果没有和obj相等元素,则报异常indexoutofboundsexception
List接口中的特殊方法
- void add(int index, E element); 向指定索引位置index上添加元素element,原始数据自动后移
- E get(int index); 获取指定索引号对应的元素,index应该在[0,size-1]
- E set(int index, E element); 用于给指定索引位置上进行赋值,这个位置上必须有对应的数据(已经赋过值),这里实际上是修改操作,否则indexoutofboundsexception
- E remove(int index);删除指定位置的元素,可以返回原始位置上存储的元素
- int indexOf(Object o); 查找从左向右第一个o元素的下标索引,如果元素不存在返回-1
- int lastIndexOf(Object o);从右向左查找
对象相等判定使用的是equals方法
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray(); //将List集合对象转换为数组
Arrays.sort(a, (Comparator) c); //调用Arrays工具类中的排序方法对数组进行排序
ListIterator<E> i = this.listIterator(); //获取List集合对象中特殊的Iterator迭代器对象
for (Object e : a) { //foreach结构遍历数组中的所有元素
i.next();
i.set((E) e); //修改集合List中迭代器指定的当前位置上的元素
}
}
list.sort(new Comparator<A2>() {
public int compare(A2 o1, A2 o2) {
// 自定义比较规则,按照id从小到大,如果id相等则按照name从大到小
int res = o1.getId().compareto(o2.getId());
if (res == 0)
res = (o1.getName().compareto(o2.getName())) * -1;
return res;
}
});
对应的实现类
-
ArrayList底层实现为数组,线程不安全
-
Vector底层实现为数组,线程安全 synchronized
-
LinkedList底层实现为链表,线程不安全
Set集合
无序 不允许重复
public interface Set<E> extends Collection<E>
没有新方法
如果进行对象相等比较:
- 按照hashCode值进行比较
- 潜规则:
- java要求当两个对象的equals为true时,要求两个对象的hashCode值相等。
- hashCode值相等并不一定equals为true
实现类:
- HashSet
- TreeSet
- LinkedHashSet
package com.yan3;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
public class Test1 {
public static void main(String[] args) {
Set set = new HashSet();
set.add(new A1(99L, "zhangsan"));
set.add(new A1(88L, "lisi"));
set.add(new A1(99L, "wangwu"));
set.forEach(System.out::println);
}
}
class A1 {
private Long id;
private String name;
@Override
public int hashCode() {
System.out.println(this+"::hashcode()");
return id.hashCode();
}
//比较规则为:按照id进行比较,如果id相等则对象相等
public boolean equals(Object obj) {
System.out.println(this+"::equals()");
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
A1 other = (A1) obj;
return Objects.equals(id, other.id);
}
public A1(Long id, String name) {
super();
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "A1 [id=" + id + ", name=" + name + "]";
}
}
常见的List接口的实现类
ArrayList的使用和实现
List list=new ArrayList();
public class ArrayList<E> extends AbstractList<E>
通过继承抽象类可以共享所有公共方法
-
implements List, 实现List接口
-
RandomAccess, 实现随机访问接口
-
Cloneable, 实现克隆接口
-
java.io.Serializable 实现序列化接口
-
transient Object[] elementData; 真是存放数据的数组
-
private int size; 当前集合中存储的元素个数
构造器
public ArrayList() {//针对存储数据的数组进行初始化操作
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
//常量定义为DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};空数组
//如果使用无参构造器,则不会直接创建数组,而是采用空数组,当第一次添加元素时才创建数组
//使用无参构造器时ArrayList会构建一个空数组用于未来存放数据,这里是一种对内存消耗的优化处理
}
带参构造器
List list=new ArrayList(18)
18就是初始化容积,这里的初始化参数值必须为[0,int的最大值)
public ArrayList(int initialCapacity) { //参数为初始化容积
if (initialCapacity > 0) { 如果初始化容积大于0,则按照指定的容积创建数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA; 空数组,长度为0的数组
} else { //初始化容积值小于0则报异常
throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
}
}
add方法的定义[重点]
protected transient int modCount = 0;
public boolean add(E e) {
modCount++; 用于统计当前集合对象的修改次数
add(e, elementData, size); 参数1:新增元素;参数2:存储数据的数组;参数3:当前集合中存储的元素个数
return true; 返回true表示添加成功
}
private void add(E e, Object[] elementData, int s) {
//如果数组已经存满了,则首先进行扩容处理
if (s == elementData.length) 判断当前集合中存储的元素个数是否和数组长度相等
elementData = grow(); 如果相等则进行扩容处理
elementData[s] = e; 在数组的指定位置存储元素
size = s + 1; 集合中所存储的元素个数
private Object[] grow(int minCapacity) {
//调用Arrays工具类中的方法进行数组的拷贝,同时调用newCapacity方法计算所需要的新容积值
return elementData = Arrays.copyOf(elementData,newCapacity(minCapacity));
}
}
private Object[] grow() { //扩容处理方法
return grow(size + 1); //继续调用其它扩容方法,参数为 当前存储个元素个数+1---最小容积
}
private Object[] grow(int minCapacity) {
//调用Arrays工具类中的方法进行数组的拷贝,同时调用newCapacity方法计算所需要的新容积值
return elementData = Arrays.copyOf(elementData,newCapacity(minCapacity));
}
private static final int DEFAULT_CAPACITY = 10;
private int newCapacity(int minCapacity) { 计算所需要的新容积值
int oldCapacity = elementData.length; 计算原来数组的长度
//计算新容积值
int newCapacity = oldCapacity + (oldCapacity >> 1); 相当于是老容积增加50%,这就是扩容比
//计算出的新容积值是否满足所需要的最小容积值
if (newCapacity - minCapacity <= 0) {
//如果存储数据的数组为初始化时的空数组,则计算默认初始化容积10和所需要最小容积值则最大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // OOM内存溢出
throw new OutOfMemoryError();
return minCapacity;
}
//如果计算出的新容积值大于所需要的最小容积值
return(newCapacity - MAX_ARRAY_SIZE <= 0)?newCapacity:hugeCapacity(minCapacity);
//如果计算出的新容积值小于最大允许的容积值,则返回计算出的新容积
(minCapacity > MAX_ARRAY_SIZE)? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
//如果计算出的新容积值大于最大允许的容积值并且minCapacity大于MAX_ARRAY_SIZE,则返回最大整数值,否则返回最大允许的容积值
}
//数组长度的上限值
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
结论:
- 如果直接初始ArrayList,则采用延迟初始化处理【初始化数组长度为0】的方式以节约内存,当添加数据时,才进行数组的初始化,默认初始化容积为10
- 添加数据时当存储数据的数组长度不足时,数组会自动变长,变长的比例为原容积的50%
删除元素
public E remove(int index) {
Objects.checkIndex(index, size); //检查所需要删除位置下标参数是否在合理的范围内[0,size-1],如果超出范围则报异常
final Object[] es = elementData; 获取所存储的所有元素数组
E oldValue = (E) es[index]; 获取指定位置上的元素
fastRemove(es, index); 利用System.arraycopy的方法使用覆盖的方式删除指定位置上的元素
return oldValue; 返回删除掉的元素
//没有缩容处理
}
private void fastRemove(Object[] es, int i) {
modCount++; 修改次数+1
final int newSize;
if ((newSize = size - 1) > i) //size - 1就是可以删除元素的最大下标
System.arraycopy(es, i + 1, es, i, newSize - i); 通过数组拷贝的方式覆盖掉指定位置的元素
es[size = newSize] = null; //最后位置赋null值
{1,2,3,4,5} 执行arraycopy方法需要覆盖下标为2的元素{1,2,4,5,5}
}
结论:
get方法的实现
public E get(int index) {
Objects.checkIndex(index, size);针对索引下标进行合法性验证
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
迭代器的实现类中定义【内部类】
private void checkForComodification() {
if (modCount != expectedModCount) {
throw new ConcurrentModificationException(); 并发修改异常
}
}
结论:
- 首先要求index应该在[0,size-1]的范围内,否则异常
- 如果index正确则按照下标从数组中获取元素
- 如果多个线程同时操作一个ArrayList,可以通过ConcurrentModificationException解决修改出现的线程安全问题
LinkedList的使用和实现
双向链表实现,增删快,查询慢 (线程不安全)
属性:
- transient int size = 0; 存储的元素个数
- transient Node first; 头节点
- transient Node last; 尾节点
节点的定义
private static class Node<E> { 静态内部类
E item; 节点上存储的具体数据
Node<E> next; 指向下一个节点
Node<E> prev; 指向上一个节点
//创建节点对象的参数
* prev前一个节点的引用 element具体存储的数据 next下一个节点引用
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
构造器方法
没有指定容积的构造器,理论上来说,链表实际上没有长度限制,但是int size属性要求元素个数必须在[0,Integer.max-value]范围内
public LinkedList() {
}
add方法的定义:在双向链表的末尾新增元素
- add(E e)
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last; 缓存尾节点
final Node<E> newNode = new Node<>(l, e, null); 创建新的节点,新节点的下一个节点为null
last = newNode; 将尾指针执行新创建的节点
if (l == null) 如果原来的尾节点为null,则新增的节点应该是链表的第一个节点
first = newNode; 将头指针指向新创建的节点
else
l.next = newNode; 如果原来的尾节点不为null,则表示已经有元素了,将原来的尾节点指向新创建的节点,这样就将新节点加入到链表中
size++; 链表中元素个数加1
modCount++; 修改次数加1
}
- add(int,Object) 在指定位置新增元素,原来位置上的元素后移
public void add(int index, E element) {
checkPositionIndex(index); 检查index的合法性,要求index应该是在[0,size]的范围内
index >= 0 && index <= size;如果不合法则抛出异常indexoutofboundsexception
if (index == size) 如果索引值index等于size,则在链表默认添加元素
linkLast(element);
else 在指定下标位置之前添加元素
linkBefore(element, node(index));
}
//获取指定位置上的节点对象
Node<E> node(int index) {
if (index < (size >> 1)) { 如果序号值小于size/2则从头指针开始遍历链表,查找指定索引值的元素Node
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next; 从前向后遍历
return x;
} else { 如果序号值大于等于size/2则从尾指针开始遍历链表,查找指定索引值的元素Node
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev; 从后向前遍历
return x;
}
}
//在指定节点对象之前添加e数据
void linkBefore(E e, Node<E> succ) {
final Node<E> pred = succ.prev; 获取指定位置的前一个节点
final Node<E> newNode = new Node<>(pred, e, succ); 创建节点对象,参数1是原始节点的前一个节点,参数2是具体存储的数据,参数3是原始位置上的节点
succ.prev = newNode; 设置原始位置上的node对象的前一个节点为新创建的节点【双向链表】
if (pred == null) 如果指定位置的节点的前一个节点为null,则插入的新节点应该就是头节点
first = newNode; 使头指针指向新创建的节点
else 设置前一个节点的下一个节点指针指向新创建的节点
pred.next = newNode;
size++; 元素个数加1
modCount++; 修改次数加1
}
删除和参数相等的第一个元素,删除成功返回true,否则返回false
public boolean remove(Object o) {
if (o == null) { 删除值为null的元素,使用==判断
for (Node<E> x = first; x != null; x = x.next) {从头指针开始执行遍历,查找第一个值为null的节点
if (x.item == null) { //判断当前节点的值是否为null
unlink(x); 删除指定的Node节点,并且返回Node中存储的数据
return true;
}
}
} else { 删除值非null的元素,判断使用equals方法
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) { 判断当前需要删除的数据和节点中的数据是否相等
unlink(x); 删除指定的Node节点,并且返回Node中存储的数据
return true;
}
}
}
return false;
}
删除指定的Node节点,并且返回Node中存储的数据
E unlink(Node<E> x) {
final E element = x.item; 获取节点中的数据
final Node<E> next = x.next; 获取节点的下一个节点对象的引用
final Node<E> prev = x.prev; 获取上一个节点
if (prev == null) { 如果当前节点的前一个节点为null,则表示当前节点为头节点
first = next; 使头指针指向当前节点的下一个节点
} else { 如果不是头节点
prev.next = next; 上一个节点的下一个节点为当前节点的下一个节点,将当前节点从链表中剔除出来
x.prev = null; 当前节点的上一个节点赋null值
}
if (next == null) {如果当前节点的下一个节点为null,则表示当前节点为尾节点
last = prev; 使尾指针指向当前节点的上一个节点
} else { 如果不是尾节点
next.prev = prev; 下一个节点的上一个节点引用值为当前节点的上一个节点,将当前节点从链表中剔除出来
x.next = null; 当前节点的下一个节点赋null值
}
x.item = null; 将当前节点的数据赋null值
size--; 链表中的元素个数-1
modCount++; 修改次数+1
return element; 返回当前节点原来的数据
}
按照指定的索引序号删除对应的元素,同时返回删除元素的具体数据值
public E remove(int index) {
checkElementIndex(index); 检查序号值是否合法,不合法抛出异常indexoutofboundsexception index >= 0 && index < size
return unlink(node(index)); 首先查找指定索引序号对应的节点对象,然后删除对应的节点,返回原来节点上存储的具体数据
}
修改操作
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item; //获取原始节点存储的数据
x.item = element; //修改节点上的数据为新值
return oldVal; //返回原始存储的数据
}
查找操作,如果找到则返回索引序号,如果没有找到则返回-1
public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
Vector的使用和实现
@since 1.0 从JDK1.0开始提供的实现类
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
属性:
- protected Object[] elementData; 内部的数据存储采用的是Object[]
- protected int elementCount; 存储的元素个数
- protected int capacityIncrement;容积增长的步长值
构造器
public Vector() {
this(10);
}
public Vector(int initialCapacity) { 参数为初始化容积
this(initialCapacity, 0);
}
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0) 初始化容积不能小于0
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity]; 初始化数组,不是ArrayList中的延迟初始化
this.capacityIncrement = capacityIncrement;
}
成员方法:
public synchronized boolean add(E e) { 方法上有synchronized,基本上所有的修改方法上都有synchronized关键字,所以线程安全的
modCount++; 修改次数+1
add(e, elementData, elementCount); 参数1为需要添加的元素,参数2为具体存储数据的数组,参数3为元素个数
return true;
}
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length) 如果当前存储的元素个数和数据相等则先进行扩容处理
elementData = grow();
elementData[s] = e;
elementCount = s + 1;
}
private Object[] grow() {
return grow(elementCount + 1);
}
//具体的扩容处理,调用newCapacity方法获取新容积
private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}
private int newCapacity(int minCapacity) {
int oldCapacity = elementData.length; 获取原始容积值,也就是存储数据的数组长度
//新容积值的算法:
* 如果设置了容积增长的步长值,则新容积为原始容积+步长值
* 如果没有设置容积增长的步长值,则扩容增长100%
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity <= 0) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
return (newCapacity-MAX_ARRAY_SIZE <= 0)? newCapacity: hugeCapacity(minCapacity);
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
总结List的三种实现类
ArrayList | LinkedList | Vector | |
---|---|---|---|
实现方式 | 数组,按照索引下标访问速度快O(1),但是当删除添加元素时会导致元素的移动,速度慢O(n) | 双向链表,按照索引下标访问速度慢O(n),但是删除添加元素速度快O(1) | 数组,按照索引下标访问速度快O(1),但是当删除添加元素时会导致元素的移动,速度慢O(n) |
是否同步 | 不同步,线程不安全,但是并发高,访问效率高 | 不同步,线程不安全,但是并发高,访问效率高 o | 同步,所以线程安全,但是并发低,访问效率低 |
如何选择 | 经常需要快速访问,较少在中间增加删除元素时使用;如果多线程访问,则需要自行编程解决线程安全问题 | 经常需要在内部增删元素,但是很少需要通过索引快速访问时使用;如果多线程访问,则需要自行编程解决线程安全问题 | 一般不使用,如果在多线程访问时可以考虑使用 |