一、容器简介
开发和学习中需要时刻和数据打交道,如何组织这些数据是编程中重要的内容。我们一般通过“容器”来容纳和管理数据。 事实上,数组就是一种容器,可以在其中放置对象或基本类型数据。
数组的优势:是一种简单的线性序列,可以快速地访问数组元素,效率高。如果从效率和类型检查的角度讲,数组是最好的。
数组的劣势:不灵活。容量需要事先定义好,不能随着需求的变化而扩容。比如:在一个用户管理系统中,要把今天注册的所有用户取出来,那么这样的用户有多少个?在写程序时是无法确定的。因此,在这里就不能使用数组。
基于数组并不能满足我们对于“管理和组织数据的需求”,所以我们需要一种更强大、更灵活、容量随时可扩的容器来装载我们的对象。 这就是我们今天要学习的容器。容器(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);
}
}
}
运行结果:
系列文章: