一、容器简介
开发和学习中需要时刻和数据打交道,如何组织这些数据是编程中重要的内容。我们一般通过“容器”来容纳和管理数据。 事实上,数组就是一种容器,可以在其中放置对象或基本类型数据。
数组的优势:是一种简单的线性序列,可以快速地访问数组元素,效率高。如果从效率和类型检查的角度讲,数组是最好的。
数组的劣势:不灵活。容量需要事先定义好,不能随着需求的变化而扩容。比如:在一个用户管理系统中,要把今天注册的所有用户取出来,那么这样的用户有多少个?在写程序时是无法确定的。因此,在这里就不能使用数组。
基于数组并不能满足我们对于“管理和组织数据的需求”,所以我们需要一种更强大、更灵活、容量随时可扩的容器来装载我们的对象。 这就是我们今天要学习的容器。容器(Collection)也称之为集合。
二、容器的结构
结构图:
单例集合: 将数据一个一个的进行存储。
双例集合: 基于 Key 与 Value 的结构存储数据。
三、单例集合的使用
3.1 Collection 接口介绍
Collection 是单例集合根接口,它是集中、收集的意思。Collection 接口的两个子接口是 List、Set 接口。
3.2 Collection 接口中的抽象方法
方法 | 说明 |
boolean add(Object element) | 增加元素到容器中 |
boolean remove(Object element) | 从容器中移除元素 |
boolean contains(Object element) | 容器中是否包含该元素 |
int size() | 容器中元素的数量 |
boolean isEmpty() | 容器是否为空 |
void clear() | 清空容器中所有元素 |
Iterator iterator() | 获得迭代器,用于遍历所有元素 |
boolean containsAll(Collection c) | 本容器是否包含容器c中的所有的元素 |
boolean addAll(Collection c) | 将容器c中的所有元素增加到本容器中 |
boolean removeAll(Collection c) | 移除本容器和c容器都包含的元素 |
boolean retainAll(Collection c) | 取本容器和c容器都包含的元素,移除非交集元素 |
Object[] toArray() | 转化成Object数组 |
由于 List、Set 是 Collection 的子接口,意味着所有 List、Set 的实现类都有上面的方法。
JDK8 之后,Collection 接口新增的方法:
新增方法 | 说明 |
removeIf | 作用是删除容器中所有满足filter指定条件的元素 |
stream parallelStream | stream和parallelStream 分别返回该容器的Stream视图表示,不同之处在于 parallelStream()返回并行的 Stream,Stream 是 Java 函数式编程的核心类。 |
spliterator | 可分割的迭代器,不同以往的 iterator 需要顺序迭代,Spliterator 可以分割为若干个小的迭代器进行并行操作,可以实现多线程操作提高效率 |
3.3 List接口介绍
List接口的特点:
有序: 有序(元素存入集合的顺序和取出的顺序一致)。List 中每个元素都有索引标记。可以根据元素的索引标记(在 List 中的位置)访问元素,从而精确控制这些元素。
可重复: List 允许加入重复的元素。更确切地讲,List 通常允许满足 e1.equals(e2) 的元素重复加入容器。
List的常用方法:
除了Collection接口中的方法,List多了一些跟顺序(索引)有关的方法:
方法 | 说明 |
void add (int index, Object element) | 在指定位置插入元素,后面的元素全部后移一位 |
Object set (int index,Object element) | 修改指定位置的元素 |
Object get (int index) | 返回指定位置的元素 |
Object remove (int index) | 删除指定位置的元素,后面元素全部前移一位 |
int indexOf (Object o) | 返回第一个匹配元素的索引,如果没有该元素,返回-1 |
int lastIndexOf (Object o) | 返回最后一个匹配元素的索引,如果没有该元素,返回-1 |
3.4 ArrayList容器类
ArrayList 是 List 接口的实现类。是 List 存储特征的具体实现。
ArrayList 底层是用数组实现的存储。 特点:查询效率高,增删效率低,线程不安全。
添加元素:
package cn.pxy.generics; | |
import java.util.ArrayList; | |
import java.util.List; | |
public class ArrayListTest { | |
public static void main(String[] args) { | |
//实例化ArrayList容器 | |
List<String> list=new ArrayList<>(); | |
//添加元素 | |
boolean flag=list.add("pxyxss"); | |
boolean flag=list.add("hhh"); | |
System.out.println(flag); | |
list.add(,"pxy");//索引的数值不能大于元素的个数,会报错 | |
} | |
} |
获取元素:
get | |
E get(int index):返回指定位置的元素。 | |
参数:index:要返回的元素的索引 | |
结果:该索引下的元素 | |
异常:IndexOutOfBoundsException:如果索引超出范围(index<||index>=size()) | |
size | |
int size():返回此列表中的元素数 | |
结果:该列表中的元素数 | |
package cn.pxy.generics; | |
import java.util.ArrayList; | |
import java.util.List; | |
public class ArrayListTest { | |
public static void main(String[] args) { | |
//实例化ArrayList容器 | |
List<String> list=new ArrayList<>(); | |
//添加元素 | |
boolean flag=list.add("pxyxss"); | |
boolean flag=list.add("hhh"); | |
System.out.println(flag); | |
System.out.println("----------"); | |
//通过指定索引位置获取元素 | |
System.out.println(list.get()); | |
System.out.println(list.get()); | |
System.out.println("-----------"); | |
//通过循环获取集合中所用元素 | |
for(int i=;i<list.size();i++) { | |
System.out.println(list.get(i)); | |
} | |
//list.add(,"pxy");//索引的数值不能大于元素的个数,会报错 | |
} | |
} |
运行结果:
删除元素:
1.根据索引删除元素
remove | |
E remove(int index) | |
参数:index:删除元素的索引 | |
结果:返回删除的元素 | |
异常:UnsupportedOperationException:remove操作不支持这个列表 | |
IndexOutOfBoundsException:索引超出范围(index<||index>=size()) | |
String value=list.remove(); | |
System.out.println(value); |
2.删除指定元素
remove | |
boolean remove(Object o):删除第一次出现的指定元素从这个列表,如果包含,返回true | |
异常:ClassCastException:指定的元素的类型不兼容这个列表 | |
NullPointerException:空指针异常 | |
UnsupportedOperationException:remove操作不支持这个列表 | |
boolean flag=list.remove("pxyxss"); | |
System.out.println(flag) |
替换元素:
String val=list.set(,"pxy"); | |
Syetem.out.println(val); |
清空容器:
list.clear(); | |
System.out.println(list.size()); |
判断容器是否为空:
//如果容器为空则返回true,否则返回false | |
boolean flag=list.isEmpty(); | |
System.out.println(flag); |
判断容器中是否包含指定元素:
//如果在容器中包含指定元素则返回true,否则返回false | |
boolean flag=list.contations("pxyxss"); | |
System.out.println(flag); |
查找元素的位置:
查找元素第一次出现的位置:
//indexOf 方法返回的是元素在容器中第一次出现的位置。 | |
//在容器中不存在则返回- | |
int index = list.indexOf("pxyxss"); | |
System.out.println(index); |
查找元素最后一次出现的位置:
//lastIndexOf 方法返回的是元素在容器中最后一次出现的位置,如果元素 在容器中不存在则返回- | |
int lastIndex = list.lastIndexOf("pxy"); | |
System.out.println(lastIndex); |
将单例集合转换成数组:
转换为Object数组:
//将 ArrayList 转换为 Object[]。 但是不能将转换的数组做强制类型转换。 | |
Object[] arr = list.toArray(); | |
for(int i=;i<arr.length;i++){ | |
String str = (String)arr[i]; | |
System.out.println(str); | |
} |
转化为泛型类型的数组:
//可以将单例集合转换为指定类型数组。 但是。类型需要参考泛型中的类型。 | |
String[] arr = list.toArray(new String[list.size()]); | |
for(int i=;i<arr2.length;i++){ | |
System.out.println(arr[i]); | |
} |
容器的并集操作:
//容器的并集操作 | |
List<String> a = new ArrayList<>(); | |
a.add("a"); | |
a.add("b"); | |
a.add("c"); | |
List<String> b = new ArrayList<>(); | |
b.add("b"); b.add("c"); b.add("d"); | |
//a 并 b | |
boolean flag = a.addAll(b); | |
System.out.println(flag); | |
for(String str:a){ | |
System.out.println(str); | |
} |
容器的交集操作:
//容器的交集操作 | |
List<String> a = new ArrayList<>(); | |
a.add("a"); | |
a.add("b"); | |
a.add("c"); | |
List<String> b = new ArrayList<>(); | |
b.add("b"); | |
b.add("c"); | |
b.add("d"); | |
boolean flag = a.retainAll(b1); | |
System.out.println(flag); | |
for(String str :a){ | |
System.out.println(str); | |
} |
容器的差集操作:
//容器的差集操作 | |
List<String> a = new ArrayList<>(); | |
a.add("a"); | |
a.add("b"); | |
a.add("c"); | |
List<String> b = new ArrayList<>(); | |
b.add("b"); | |
b.add("c"); | |
b.add("d"); | |
boolean flag = a.removeAll(b2); | |
System.out.println(flag); | |
for(String str :a){ | |
System.out.println(str); | |
} |
ArrayList 源码分析:
ArrayList 底层存储方式:
//ArrayList 底层是用数组实现的存储。 | |
/** | |
* Default initial capacity. | |
*/ | |
private static final int DEFAULT_CAPACITY =; //初始容量 | |
/** | |
* The array buffer into which the elements of the ArrayList are stored. | |
/** | |
* The array buffer into which the elements of the ArrayList are stored. | |
* The capacity of the ArrayList is the length of this array buffer. Any | |
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA | |
* will be expanded to DEFAULT_CAPACITY when the first element is added. | |
*/ | |
transient Object[] elementData; // non-private to simplify nested class access | |
/** | |
* The size of the ArrayList (the number of elements it contains). | |
* | |
* @serial | |
*/ | |
private int size; |
添加元素:
/** | |
* Appends the specified element to the end of this list. | |
* | |
* @param e element to be appended to this list | |
* @return <tt>true</tt> (as specified by {@link Collection#add}) | |
*/ | |
public boolean add(E e) { | |
ensureCapacityInternal(size +); // Increments modCount!! | |
elementData[size++] = e; | |
return true; | |
} | |
/** | |
* This helper method split out from add(E) to keep method | |
* bytecode size under (the -XX:MaxInlineSize default value), | |
* which helps when add(E) is called in a C-compiled loop. | |
*/ | |
private void add(E e, Object[] elementData, int s) { | |
if (s == elementData.length) | |
elementData = grow(); | |
elementData[s] = e; | |
size = s +; | |
} |
数组扩容:
//容量检查 | |
private void ensureCapacityInternal(int minCapacity) { | |
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); | |
} | |
//容量确认 | |
private void ensureExplicitCapacity(int minCapacity) { | |
modCount++; | |
//判断是否需要扩容,数组中的元素个数-数组长度,如果大于 表明需要扩容 | |
if (minCapacity - elementData.length >) | |
grow(minCapacity); | |
} | |
/** | |
* Increases the capacity to ensure that it can hold at least the | |
* number of elements specified by the minimum capacity argument. | |
* | |
* @param minCapacity the desired minimum capacity | |
*/ | |
private void grow(int minCapacity) { | |
// overflow-conscious code | |
int oldCapacity = elementData.length; //扩容.5 倍 | |
int newCapacity = oldCapacity + (oldCapacity >>); | |
if (newCapacity - minCapacity <) | |
newCapacity = minCapacity; | |
if (newCapacity - MAX_ARRAY_SIZE >) | |
newCapacity = hugeCapacity(minCapacity); | |
// minCapacity is usually close to size, so this is a win: | |
elementData = Arrays.copyOf(elementData, newCapacity); | |
} |
3.5 Vector 容器类
Vector 底层是用数组实现的,相关的方法都加了同步检查,因此“线程安全,效率低”。比如,indexOf 方法就增加了 synchronized 同步标记。
Vector 的使用:
Vector 的使用与 ArrayList 是相同的,因为他们都实现了 List 接口,对 List 接口中的抽象方法做了具体实现。
package cn.pxy.generics; | |
import java.util.List; | |
import java.util.Vector; | |
public class VectorTest { | |
public static void main(String[] args) { | |
//实例化Vector | |
List<String> v=new Vector<>(); | |
v.add("a"); | |
v.add("b"); | |
v.add("c"); | |
for(int i=;i<v.size();i++) { | |
System.out.println(v.get(i)); | |
} | |
System.out.println("***********"); | |
for(String str:v) { | |
System.out.println(str); | |
} | |
} | |
} |
运行结果:
Vector 源码分析:
初始化容器:
/** | |
* The array buffer into which the components of the vector are | |
* stored. The capacity of the vector is the length of this array buffer, | |
* and is at least large enough to contain all the vector's elements. | |
* | |
* <p>Any array elements following the last element in the Vector are null. | |
* | |
* @serial | |
*/ | |
protected Object[] elementData; | |
public Vector() { | |
this(); | |
/** | |
* Constructs an empty vector with the specified initial capacity and | |
* capacity increment. | |
* | |
* @param initialCapacity the initial capacity of the vector | |
* @param capacityIncrement the amount by which the capacity is | |
* increased when the vector overflows | |
* @throws IllegalArgumentException if the specified initial capacity | |
* is negative | |
*/ public Vector(int initialCapacity, int capacityIncrement) { | |
super(); | |
if (initialCapacity <) | |
throw new IllegalArgumentException("Illegal Capacity: "+ | |
initialCapacity); | |
this.elementData = new Object[initialCapacity]; | |
this.capacityIncrement = capacityIncrement; | |
} |
添加元素:
/** | |
* Appends the specified element to the end of this Vector. | |
* | |
* @param e element to be appended to this Vector | |
* @return {@code true} (as specified by {@link Collection#add}) | |
* @since.2 | |
*/ public synchronized boolean add(E e) { | |
modCount++; | |
add(e, elementData, elementCount); | |
return true; | |
} |
数组扩容:
/** | |
* This implements the unsynchronized semantics of ensureCapacity. | |
* Synchronized methods in this class can internally call this | |
* method for ensuring capacity without incurring the cost of an | |
* extra synchronization. | |
* | |
* @see #ensureCapacity(int) | |
*/ | |
private void ensureCapacityHelper(int minCapacity) { | |
// overflow-conscious code | |
//判断是否需要扩容,数组中的元素个数-数组长度,如果大于 表明需要扩容 | |
if (minCapacity - elementData.length >) | |
grow(minCapacity); | |
} | |
private void grow(int minCapacity) { | |
// overflow-conscious code | |
int oldCapacity = elementData.length; | |
//扩容 倍 | |
int newCapacity = oldCapacity + ((capacityIncrement >) ? capacityIncrement : oldCapacity); | |
if (newCapacity - minCapacity <) | |
newCapacity = minCapacity; | |
if (newCapacity - MAX_ARRAY_SIZE >) | |
newCapacity = hugeCapacity(minCapacity); | |
elementData = Arrays.copyOf(elementData, newCapacity); | |
} |
3.6 Stack 容器
Stack 栈容器,是 Vector 的一个子类,它实现了一个标准的后进先出(LIFO:Last In Frist Out)的栈。
Stack 特点是:后进先出。它通过 5 个操作方法对 Vector 进行扩展,允许将向量视为堆栈。
操作栈的方法:
Modifier and Type | Method and Description |
boolean | empty()测试如果这个栈是不是空的 |
E | peek()用于从此Stack中返回顶部元素 |
E | pop()删除这个栈堆的顶部的对象,并返回该对象的值函数 |
E | push(E item)把一个项目推到栈堆的顶部 |
int | search(Object o)传入一个对象,然后从上往下找到第一个该元素并返回该索引。 |
Stack 的使用
package cn.pxy.generics; | |
import java.util.Stack; | |
public class StackTest { | |
public static void main(String[] args) { | |
//实例化栈容器 | |
Stack<String> stack=new Stack<>(); | |
//将元素添加到栈容器,后进先出 | |
stack.push("a"); | |
stack.push("b"); | |
stack.push("c"); | |
//判断栈容器是否为空 | |
System.out.println("栈容器是否为空:"+stack.empty()); | |
//查看栈顶元素 | |
System.out.println(stack.peek()); | |
//返回元素在栈容器中的元素 | |
System.out.println(stack.search("c")); | |
//获取栈容器中的栈顶元素,返回并从栈里删除 | |
String p=stack.pop(); | |
System.out.println(p); | |
String p=stack.pop(); | |
System.out.println(p); | |
String p=stack.pop(); | |
System.out.println(p); | |
System.out.println("栈容器是否为空:"+stack.empty()); | |
} | |
} |
运行结果:
示例:判断元素的对称性String str= “…{…..[….(….)…]….}..(….)..[…]…” ;
package cn.pxy.generics; | |
import java.util.Stack; | |
public class StackTest { | |
public static void main(String[] args) { | |
symmetry(); | |
} | |
//匹配符号的对称性 | |
public static void symmetry() { | |
String str="...{.....[....(....)...]....}..(....)..[...]..."; | |
//实例化Stack | |
Stack<String> stack=new Stack<>(); | |
//假设修正法 | |
boolean flag=true;//假设是匹配的 | |
//拆分字符串获取字符 | |
for(int i=;i<str.length();i++) { | |
char c=str.charAt(i); | |
if(c=='{') { | |
stack.push("}"); | |
} | |
if(c=='[') { | |
stack.push("]"); | |
} | |
if(c=='(') { | |
stack.push(")"); | |
} | |
//判断符号是否匹配 | |
if(c=='}'||c==']'||c==')') { | |
if(stack.empty()) { | |
//修正处理 | |
flag=false; | |
break; | |
} | |
String x=stack.pop(); | |
if(x.charAt()!=c) { | |
//修正处理 | |
flag=false; | |
} | |
System.out.println(flag); | |
} | |
} | |
} | |
} |
运行结果:
运行分析:利用循环将字符赋给c,再判断c的值,如果c是“(”就将它的对称符号“)”推入栈中,如果c是“)”,则将栈顶元素返回赋给x,并将该元素从栈中删除,如果c==x,就说明这个符号对称,同理,如果最后flag=true,说明符号全部对称。
3.7 LinkedList 容器类
LinkedList 底层用双向链表实现的存储。特点:查询效率低,增删效率高,线程不安全。双向链表也叫双链表,是链表的一种,它的每个数据节点中都有两个指针,分别指向前一个节点和后一个节点。 所以,从双向链表中的任意一个节点开始,都可以很方便地找到所有节点。
双向链表介绍
class Node<E>{ | |
E ietm; | |
Node<E> next; | |
Node<E> prev; | |
} |
LinkedList 的使用(List 标准)
LinkedList 实现了 List 接口,所以 LinkedList 是具备 List 的存储特征的(有序,元素有重复)。
package cn.pxy.generics; | |
import java.util.LinkedList; | |
import java.util.List; | |
public class LinkedListTest { | |
public static void main(String[] args) { | |
List<String> list=new LinkedList<>(); | |
//添加元素 | |
list.add("a"); | |
list.add("b"); | |
list.add("c"); | |
list.add("a"); | |
//获取元素 | |
for(int i=;i<list.size();i++) { | |
System.out.print(list.get(i)+""); | |
} | |
System.out.println(); | |
System.out.println("----------"); | |
for(String str:list) { | |
System.out.print(str+""); | |
} | |
} | |
} |
运行结果:
LinkedList 的使用(非 List 标准)
方法 | 说明 |
void addFirst(E e) | 将指定元素插入到开头 |
void addLast(E e) | 将指定元素插入到结尾 |
getFirst() | 返回此列表的第一个元素 |
removeFirst() | 移除此列表中的第一个元素,并返回这个元素 |
removeLast() | 移除此列表中的最后一个元素,并返回这个元素 |
E pop() | 从此列表所表示的堆栈处弹出一个元素,等效于removeFirst |
void push(E e) | 将元素推入此列表所在的栈堆,等效于addFirst(E e) |
boolean isEmpty() | 判断列表是否包含元素,如果不包含元素则返回true |
package cn.pxy.generics; | |
import java.util.LinkedList; | |
public class LinkedListTest { | |
public static void main(String[] args) { | |
LinkedList<String> list=new LinkedList<>(); | |
list.add("a"); | |
list.add("b"); | |
list.add("c"); | |
for(String str:list) { | |
System.out.println(str); | |
} | |
System.out.println("---------------"); | |
System.out.println(list.getFirst()); | |
System.out.println(list.getLast()); | |
System.out.println("------------------"); | |
list.removeFirst(); | |
list.removeLast(); | |
for(String str:list) { | |
System.out.println(str); | |
} | |
System.out.println("----------------"); | |
list.add("c"); | |
list.pop(); | |
for(String str:list) { | |
System.out.println(str); | |
} | |
System.out.println("---------------"); | |
list.push("h"); | |
for(String str:list) { | |
System.out.println(str); | |
} | |
System.out.println(list.isEmpty()); | |
} | |
} |
运行结果:
LinkedList 源码分析
节点类:
private static class Node<E>{ | |
E ietm; | |
Node<E> next; | |
Node<E> prev; | |
Node(Node<E> prev,E element,Node<E> next){ | |
this.ietm=element; | |
this.next=next; | |
this.prev=prev; | |
} | |
} |
成员变量:
transient int size =; | |
/** | |
* Pointer to first node. | |
*/ transient Node<E> first; | |
/** | |
* Pointer to last node. | |
*/ transient Node<E> last; |
添加元素:
/** | |
* Appends the specified element to the end of this list. | |
* | |
* <p>This method is equivalent to {@link #addLast}. | |
* | |
* @param e element to be appended to this list | |
* @return {@code true} (as specified by {@link Collection#add}) | |
*/ public boolean add(E e) { | |
linkLast(e); | |
return true; | |
} | |
/** | |
* Links e as last element. | |
*/ void linkLast(E e) { | |
final Node<E> l = last; | |
final Node<E> newNode = new Node<>(l, e, null); | |
last = newNode; | |
if (l == null) | |
first = newNode; | |
else | |
l.next = newNode; | |
size++; | |
modCount++; | |
} |
头尾添加元素:
addFirst:
/** | |
* Inserts the specified element at the beginning of this list. | |
* | |
* @param e the element to add | |
*/ public void addFirst(E e) { | |
linkFirst(e); | |
} | |
/** | |
* Links e as first element. | |
*/ private void linkFirst(E e) { | |
final Node<E> f = first; | |
final Node<E> newNode = new Node<>(null, e, f); | |
first = newNode; | |
if (f == null) | |
last = newNode; | |
else | |
f.prev = newNode; | |
size++; | |
modCount++; | |
} |
addLast:
/** | |
* Appends the specified element to the end of this list. | |
* | |
* <p>This method is equivalent to {@link #add}. | |
* | |
* @param e the element to add | |
*/ public void addLast(E e) { | |
linkLast(e); | |
} |
在指定位置添加元素:
/** | |
* Inserts the specified element at the specified position in this list. | |
* Shifts the element currently at that position (if any) and any | |
* subsequent elements to the right (adds one to their indices). | |
* | |
* @param index index at which the specified element is to be inserted | |
* @param element element to be inserted | |
* @throws IndexOutOfBoundsException {@inheritDoc} | |
*/ public void add(int index, E element) { | |
checkPositionIndex(index); | |
if (index == size) | |
linkLast(element); | |
else | |
linkBefore(element, node(index)); | |
} | |
private void checkPositionIndex(int index) { | |
if (!isPositionIndex(index)) | |
throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); | |
} | |
/** | |
* Returns the (non-null) Node at the specified element index. | |
*/ Node<E> node(int index) { | |
// assert isElementIndex(index); | |
if (index < (size >>)) { | |
Node<E> x = first; | |
for (int i =; i < index; i++) | |
x = x.next; | |
return x; | |
} else { | |
Node<E> x = last; | |
for (int i = size -; i > index; i--) | |
x = x.prev; | |
return x; | |
} | |
} | |
/** | |
* Inserts element e before non-null Node succ. | |
*/ void linkBefore(E e, Node<E> succ) { | |
// assert succ != null; | |
final Node<E> pred = succ.prev; | |
final Node<E> newNode = new Node<>(pred, e, succ); | |
succ.prev = newNode; | |
if (pred == null) | |
first = newNode; | |
else | |
pred.next = newNode; | |
size++; | |
modCount++; | |
} |
获取元素:
/** | |
* Returns the element at the specified position in this list. | |
* | |
* @param index index of the element to return | |
* @return the element at the specified position in this list | |
* @throws IndexOutOfBoundsException {@inheritDoc} | |
*/ public E get(int index) { | |
checkElementIndex(index); | |
return node(index).item; | |
} | |
private void checkElementIndex(int index) { | |
if (!isElementIndex(index)) | |
throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); | |
} | |
/** | |
* Tells if the argument is the index of an existing element. | |
*/ private boolean isElementIndex(int index) { | |
return index >= && index < size; | |
} |
删除指定位置元素:
/** | |
* Removes the element at the specified position in this list. Shifts any | |
* subsequent elements to the left (subtracts one from their indices). | |
* Returns the element that was removed from the list. | |
* | |
* @param index the index of the element to be removed | |
* @return the element previously at the specified position | |
* @throws IndexOutOfBoundsException {@inheritDoc} | |
*/ public E remove(int index) { | |
checkElementIndex(index); | |
return unlink(node(index)); | |
} | |
/** | |
* Unlinks non-null node x. | |
*/ E unlink(Node<E> x) { | |
// assert x != null; | |
final E element = x.item; | |
final Node<E> next = x.next; | |
final Node<E> prev = x.prev; | |
if (prev == null) { | |
first = next; | |
} else { | |
prev.next = next; | |
x.prev = null; | |
} | |
if (next == null) { | |
last = prev; | |
} else { | |
next.prev = prev; | |
x.next = null; | |
} | |
x.item = null; | |
size--; | |
modCount++; | |
return element; | |
} |
3.8 Set 接口介绍
Set 接口继承自 Collection,Set 接口中没有新增方法,方法和 Collection 保持完全一致。我们在前面通过 List 学习的方法,在 Set 中仍然适用。因此,学习 Set 的使用将没有任何难度。
Set 接口特点: 无序、不可重复。无序指 Set 中的元素没有索引,我们只能遍历查找;不可重复指不允许加入重复的元素。更确切地讲,新元素如果和 Set 中某个元素通过 equals()方法对比为 true,则只能保留一个。Set 常用的实现类有:HashSet、TreeSet 等,我们一般使用 HashSet。
HashSet 容器类:
HashSet 是一个没有重复元素的集合,不保证元素的顺序。而且 HashSet 允许有 null 元素。HashSet 是采用哈希算法实现,底层实际是用 HashMap 实现的(HashSet 本质就是一个简化版的 HashMap),因此,查询效率和增删效率都比较高。
Hash算法 原理(也称为散列算法):
模9运算:把26、15、22、24通过模9存放如容器中,步骤为:26除9余8,所以26放在索引为8的位置;15除9余6,所以15放在索引为6的位置;22除9余4,所以22放在索引为4的位置;24除9,余6,所以24放在索引为6的位置,但是索引为6的位置不为空,所以24向后移,直到找到一个空的位置(7)。
HashSet 的使用:
package cn.pxy.generics; | |
import java.util.HashSet; | |
import java.util.Set; | |
public class HashSetTest { | |
public static void main(String[] args) { | |
//实例化HashSet | |
Set<String> set=new HashSet<>(); | |
//添加元素 | |
set.add("a"); | |
set.add("b"); | |
set.add("c"); | |
set.add("d"); | |
set.add("a"); | |
//获取元素,由于在Set容器中没有索引,所以没有对应的get(int index)方法 | |
for(String str:set) { | |
System.out.println(str); | |
} | |
System.out.println("---------------"); | |
//删除元素 | |
boolean flag=set.remove("c"); | |
System.out.println(flag); | |
for(String str:set) { | |
System.out.println(str); | |
} | |
System.out.println("**********"); | |
int size=set.size(); | |
System.out.println(size); | |
} | |
} |
运行结果:
HashSet 存储特征分析:
HashSet 是一个不保证元素的顺序且没有重复元素的集合,是线程不安全的。HashSet允许有 null 元素。
无序:在 HashSet 中底层是使用 HashMap 存储元素的。HashMap 底层使用的是数组与链表实现元素的存储。元素在数组中存放时,并不是有序存放的也不是随机存放的,而是对元素的哈希值进行运算决定元素在数组中的位置。
不重复:当两个元素的哈希值进行运算后得到相同的在数组中的位置时,会调用元素的 equals()方法判断两个元素是否相同。如果元素相同则不会添加该元素,如果不相同则会使用单向链表保存该元素。
通过 HashSet 存储自定义对象:
创建User对象:
package cn.pxy.generics; | |
public class Users { | |
private String username; | |
private int userage; | |
public Users(String username,int userage) { | |
this.username=username; | |
this.userage=userage; | |
} | |
public Users() { | |
} | |
/** | |
* HashMap存储是不允许有重复值的,equals方法是通过hash取模的值是否 | |
* 相同来判断对象是否相同,为了能够准确的识别对象是否相同,这里重写 | |
* equals方法。 | |
*/@Override | |
public boolean equals(Object obj) { | |
System.out.println("equals...");//测试equals方法有没有执行 | |
if(this==obj) { | |
return true; | |
} | |
if(obj==null||getClass()!=obj.getClass()) { | |
return false; | |
} | |
Users users=(Users)obj; | |
if(userage!=users.userage) { | |
return false; | |
} | |
return username!=null?username.equals(users.username):users.username==null; | |
} | |
@Override | |
public int hashCode() { | |
int result=username!=null?username.hashCode():; | |
result=*result+userage; | |
return result; | |
} | |
public String getUsername() { | |
return username; | |
} | |
public void setUsername(String username) { | |
this.username = username; | |
} | |
public int getUserage() { | |
return userage; | |
} | |
public void setUserage(int userage) { | |
this.userage = userage; | |
} | |
@Override | |
public String toString() { | |
return "Users{"+"username='"+username+'''+",userage="+userage+'}'; | |
} | |
} |
在 HashSet 中存储 Users 对象:
package cn.pxy.generics; | |
import java.util.HashSet; | |
import java.util.Set; | |
public class HashSetTest { | |
public static void main(String[] args) { | |
//实例化HashSet | |
Set<Users> set=new HashSet<>(); | |
Users u=new Users("胖咸鱼",18); | |
Users u=new Users("pxy",18); | |
Users u=new Users("胖咸鱼",18); | |
set.add(u); | |
set.add(u); | |
set.add(u); | |
System.out.println(u.hashCode()); | |
System.out.println(u.hashCode()); | |
System.out.println(u.hashCode()); | |
for(Users users:set) { | |
System.out.println(users); | |
} | |
} | |
} |
运行结果:
HashSet 底层源码分析:
成员变量:
private transient HashMap<E,Object> map; | |
// Dummy value to associate with an Object in the backing Map | |
private static final Object PRESENT = new Object(); |
添加元素:
/** | |
* Adds the specified element to this set if it is not already present. | |
* More formally, adds the specified element {@code e} to this set if | |
* this set contains no element {@code e} such that | |
* {@code Objects.equals(e, e)}. | |
* If this set already contains the element, the call leaves the set | |
* unchanged and returns {@code false}. | |
* | |
* @param e element to be added to this set | |
* @return {@code true} if this set did not already contain the specified | |
* element | |
*/ public boolean add(E e) { | |
return map.put(e, PRESENT)==null; | |
} |
TreeSet 容器类: TreeSet 是一个可以对元素进行排序的容器。底层实际是用 TreeMap 实现的,内部维持了一个简化版的 TreeMap,通过 key 来存储 Set 的元素。 TreeSet 内部需要对存储的元素进行排序,因此,我们需要给定排序规则。
排序规则实现方式:1.通过元素自身实现比较规则;2.通过比较器指定比较规则。
TreeSet 的使用:
package cn.pxy.generics; | |
import java.util.Set; | |
import java.util.TreeSet; | |
public class TreeSetTest { | |
public static void main(String[] args) { | |
//实例化TreeSet | |
Set<String> set=new TreeSet<>(); | |
//添加元素 | |
set.add("c"); | |
set.add("a"); | |
set.add("d"); | |
set.add("b"); | |
set.add("a"); | |
//获取元素 | |
for(String str:set) { | |
System.out.println(str); | |
} | |
} | |
} |
运行结果:
通过元素自身实现比较规则: 在元素自身实现比较规则时,需要实现 Comparable 接口中的 compareTo 方法,该方法中用来定义比较规则。TreeSet 通过调用该方法来完成对元素的排序处理。
创建Users类:
package cn.pxy.test; | |
public class Users implements Comparable<Users>{ | |
private String username; | |
private int userage; | |
public Users(String username,int userage) { | |
this.username=username; | |
this.userage=userage; | |
} | |
public Users() { | |
} | |
@Override | |
public boolean equals(Object o) { | |
System.out.println("equals..."); | |
if(this==o) { | |
return true; | |
} | |
if(o==null||getClass()!=o.getClass()) { | |
return false; | |
} | |
Users users=(Users) o; | |
if(userage!=users.userage) { | |
return false; | |
} | |
return username!=null?username.equals(users.username):users.username==null; | |
} | |
@Override | |
public int hashCode() { | |
int result=username!=null?username.hashCode():; | |
result=*result+userage; | |
return result; | |
} | |
public String getUsername() { | |
return username; | |
} | |
public void setUsername(String username) { | |
this.username = username; | |
} | |
public int getUserage() { | |
return userage; | |
} | |
public void setUserage(int userage) { | |
this.userage = userage; | |
} | |
@Override | |
public String toString() { | |
return "Users{"+"username='"+username+'''+",userage="+userage+'}'; | |
} | |
//定义比较规则 | |
//正数:大;负数:小;:相等 | |
@Override | |
public int compareTo(Users o) { | |
if(this.userage>o.getUserage()) { | |
return; | |
} | |
if(this.userage==o.getUserage()) { | |
return this.username.compareTo(o.getUsername()); | |
} | |
return -; | |
} | |
} |
在TreeSet中存放Users对象:
package cn.pxy.test; | |
import java.util.Set; | |
import java.util.TreeSet; | |
public class Test { | |
public static void main(String[] args) { | |
Set<Users> set=new TreeSet<>(); | |
Users u=new Users("胖咸鱼",18); | |
Users u=new Users("张三",21); | |
Users u=new Users("李四",21); | |
set.add(u); | |
set.add(u); | |
set.add(u); | |
for(Users user:set) { | |
System.out.println(user); | |
} | |
} | |
} |
运行结果:
通过比较器实现比较规则: 通过比较器定义比较规则时,我们需要单独创建一个比较器,比较器需要实现Comparator 接口中的 compare 方法来定义比较规则。在实例化 TreeSet 时将比较器对象交给TreeSet 来完成元素的排序处理。此时元素自身就不需要实现比较规则了。
创建比较器:
package cn.pxy.test; | |
import java.util.Comparator; | |
public class StudentComparator implements Comparator<Student>{ | |
public int compare(Student o,Student o2) { | |
if(o.getAge()>o2.getAge()) { | |
return; | |
} | |
if(o.getAge()==o2.getAge()) { | |
return o.getName().compareTo(o2.getName()); | |
} | |
return -; | |
} | |
} |
创建Student类:
package cn.pxy.test; | |
public class Student { | |
private String name; | |
private int age; | |
public Student(String name,int age) { | |
this.name=name; | |
this.age=age; | |
} | |
public Student() { | |
} | |
@Override | |
public String toString() { | |
return "Student {" + "name='" + name + ''' + ", age=" + age + '}'; | |
} | |
public String getName() { | |
return name; | |
} | |
public void setName(String name) { | |
this.name = name; | |
} | |
public int getAge() { | |
return age; | |
} | |
public void setAge(int age) { | |
this.age = age; | |
} | |
@Override | |
public boolean equals(Object obj) { | |
if(this==obj) { | |
return true; | |
} | |
if(obj==null||getClass()!=obj.getClass()) { | |
return false; | |
} | |
Student student=(Student) obj; | |
if(age!=student.age) { | |
return false; | |
} | |
return name!=null?name.equals(student.name):student.name==null; | |
} | |
@Override | |
public int hashCode() { | |
int result=name!=null?name.hashCode():; | |
result=*result+age; | |
return result; | |
} | |
} |
在TreeSet中存储Student对象:
package cn.pxy.test; | |
import java.util.Set; | |
import java.util.TreeSet; | |
public class Test { | |
public static void main(String[] args) { | |
Set<Student> set=new TreeSet<>(new StudentComparator()); | |
Student s=new Student("胖咸鱼",18); | |
Student s=new Student("胖咸鱼先生",21); | |
Student s=new Student("胖咸鱼吖",21); | |
set.add(s); | |
set.add(s); | |
set.add(s); | |
for(Student student:set) { | |
System.out.println(student); | |
} | |
} | |
} |
运行结果:
TreeSet 底层源码分析:
成员变量:
/** | |
* Constructs a new, empty tree set, sorted according to the | |
* natural ordering of its elements. All elements inserted into | |
* the set must implement the {@link Comparable} interface. | |
* Furthermore, all such elements must be <i>mutually | |
* comparable</i>: {@code e.compareTo(e2)} must not throw a | |
* {@code ClassCastException} for any elements {@code e} and | |
* {@code e} in the set. If the user attempts to add an element | |
* to the set that violates this constraint (for example, the user | |
* attempts to add a string element to a set whose elements are | |
* integers), the {@code add} call will throw a | |
* {@code ClassCastException}. | |
*/ public TreeSet() { | |
this(new TreeMap<>()); | |
} |
添加元素:
/** | |
* Adds the specified element to this set if it is not already present. | |
* More formally, adds the specified element {@code e} to this set if | |
* the set contains no element {@code e} such that | |
* {@code Objects.equals(e, e)}. | |
* If this set already contains the element, the call leaves the set | |
* unchanged and returns {@code false}. | |
* | |
* @param e element to be added to this set | |
* @return {@code true} if this set did not already contain the specified | |
* element | |
* @throws ClassCastException if the specified object cannot be compared | |
* with the elements currently in this set | |
* @throws NullPointerException if the specified element is null | |
* and this set uses natural ordering, or its comparator | |
* does not permit null elements | |
*/ public boolean add(E e) { | |
return m.put(e, PRESENT)==null; | |
} |
3.9 单例集合使用案例
案例:产生【1,10】之间的随机数,将十个不重复的数放入容器中:
package cn.pxy.test; | |
//使用List类型容器实现 | |
import java.util.ArrayList; | |
import java.util.List; | |
public class Test { | |
public static void main(String[] args) { | |
List<Integer> list=new ArrayList<>(); | |
while(true){ | |
//产生随机数 | |
int num=(int) (Math.random()*+1); | |
//判断容器中是否存在该数值 | |
if(!list.contains(num)) { | |
list.add(num); | |
} | |
//结束循环 | |
if(list.size()==) { | |
break; | |
} | |
} | |
for(Integer i:list) { | |
System.out.println(i); | |
} | |
} | |
} |
package cn.pxy.test; | |
//使用Set类型容器实现 | |
import java.util.HashSet; | |
import java.util.Set; | |
public class Test { | |
public static void main(String[] args) { | |
Set<Integer> set=new HashSet<>(); | |
while(true){ | |
//产生随机数 | |
int num=(int) (Math.random()*+1); | |
//将元素添加容器中,由于 Set 类型容器是不允许有重复元素的,所以不需 要判断 | |
set.add(num); | |
//结束循环 | |
if(set.size()==) { | |
break; | |
} | |
} | |
for(Integer i:set) { | |
System.out.println(i); | |
} | |
} | |
} |
运行结果:
系列文章: