文章目录
基合框架库就是java对数据结构的实现。下面介绍集合中的几个接口。
一、Collection接口
Collectoion接口是Java集合框架库中最基本的接口,它存储的是单个对象(元素),包含 List、Set、Queue 三个子接口。
- List:存储一组 有序(允许重复)的对象。
- Set:存储一组 无序(不允许重复)的对象。
- Queue:首先要遵循队列先进先出的特点,提供了一个接口,提供了一些具体的实现类用来操作。(多使用优先级处理)
Collection的简单使用
- 定义一个collection 的引用
Collection<Integer> coll = new ArrayList<>();
(所有的类都基于Collection接口来使用) - 在集合中添加元素
coll.add(10);
- 移除一个元素
coll.remove(10)
,返回true
或者false
。 - 判断集合当中是否包含某一个元素
coll.contains(10);
- 判断集合是否为空
coll.isEmpty();
- 返回当前集合中有效元素个数
coll.size();
- 添加当前集合
coll.addAll();
- 返回当前迭代器对象
coll.iterator();
,使用迭代器对象遍历当前集合,不需要了解底层的数据结构 (这也是迭代器存在的意义)。
迭代器使用
while (iterator.hasNext()){
//判断当前迭代对象ArrayList集合是否有下一个可迭代的元素
Integer next = iterator.next();//获取下一个给可迭代的元素
System.out.println(next);
iterator.remove();//删除返回的元素
}
1. List
List接口存储的元素是有序的(插入顺序与获取顺序是一样的),允许重复的,数据可以为null。
ArrayList
特点
- 数据插入有序
- 数据可以重复
- 存储的数据可以为null
- 数据结构为数组
- 集合可以自动扩大
使用
- 创建一个空的ArrayList对象,List用来存放String类型的数据
ArrayList<String> list = new ArrayList();
或List<String> list = new ArrayList<>();
- 往list中添加元素
list.add("ce");
- 获取List中的内容
System.out.println(list);
内容能否获取,取决于对象所对应的类是否实现了toString方法(实现了才能打印) - 获取某一个元素的索引位置
list.indexOf("ce");
- 判断List是否为空
list.isEmpty();
- 获取list的大小
list.size();
- 检查list是否包含某一元素
list.contains("hy");
- 获取指定位置的元素
list.get(1);
- 遍历ArrayList中的元素:三种方法
for(String s: list){
System.out.println(s);
}//能够使用foreach循环,是因为Array实现了迭代器接口
//使用迭代器
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
for(int i=0; i<list.size(); i++){
}
- 将ArrayList转换为Array
list.toArray();
实现
- ArrayList 和 数组 的区别?
1)数组一旦创建不能修改大小,ArrayList是一个”动态“的数组,本身会提供一些能够操作的方法。
2)数组在内存中是连续存储的,所以它的索引速度比较快,但是插入/删除时比较麻烦,容易造成内存浪费或溢出。ArrayList可以解决以上问题。 - 代码实现
class MyArrayList<T>{
//属性
private T[] elements ;// 数组->存储元素的容器
private int size; //有效元素个数
private static final int defaultCapactity = 5;
//构造函数
public MyArrayList(){
this(defaultCapactity);
}
//给elements new一个空间
public MyArrayList(int capacity){
this.elements = (T[])new Object[capacity];
}
//添加
public void add(T value){
//判断是否需要扩容
if(size == elements.length){
elements = Arrays.copyOf(elements, elements.length*2);//以2倍的方式扩容
}
//添加元素
elements[size++] = value;
}
//检查位置合法性
public void checkIndex(int index){
if(index < 0 || index > size-1){
throw new UnsupportedOperationException("the index is illegal");//index不合法
}
}
//删除
public boolean remove(int index){
try{
checkIndex(index);
System.arraycopy(elements, index+1, elements, index, size-1-index);
/*数组拷贝(4种方法):
Arrays.copyOf()
System.arraycopy(原数组, 原数组的起始位置,目标数组, 目标数组的起始位置, 拷贝的长度)
clone
for循环 */
elements[--size] = null;
return true;
} catch (UnsupportedOperationException e){
//若存在异常,打印给定信息
System.out.println(e.getMessage());
return false;
}
}
//判断是否包含某一元素
public boolean contains (T target){
for(int i=0; i<size; i++){
if(target.equals(elements[i])){//值相等,存在
return true;
}
}
return false;
}
//给定索引
public T get(int index){
try{
checkIndex(index);
return elements[index];
} catch (UnsupportedOperationException e){
System.out.println(e.getMessage());
return null;
}
}
//修改某一位置的值
public boolean set(int index, T value){
try{
checkIndex(index);
elements[index] = value;
return true;
} catch (UnsupportedOperationException e){
System.out.println(e.getMessage());
return false;
}
}
//把集合当中的有效元素转换为一个String字符串返回
public String toString(){
StringBuilder strs = new StringBuilder();
for(int i=0; i<size; i++){
strs.append(elements[i]+" ");//"+"不高效,故使用StringBuilder
}
return strs.toString();//转换
}
}
源码分析
类的继承关系
- 继承了一个AbstractList,它介于List接口和ArrayList之间一个抽象类的实现,实现了List、RandomAccess(随机访问)、Cloneable、Serializable这四个接口。
- ArrayList和Vector之间的区别和联系?
ArrayList是非线程安全的、Vector是线程安全的
构造函数
- 如果初始化List对象调用无参构造函数,当前第一次调用add方法才会给底层数组初始化,每次调用add
方法都会获取一个最大容量(length, size+1), 判断是否需要扩容,扩容以1.5倍方式进行扩容 - 如果初始化List对象调用带参构造函数,每次调用add方法都会获取一个最大容量(length, size+1),
判断是否需要扩容,扩容以1.5倍方式进行扩容
ArrayList 应用场景
在(数据有序/可重复/可存储null值)的前提下,查询较高的场景。
迭代器实现
- 定义一个返回迭代器对象的方法
- 调用迭代器对象的 hasNext和 next方法去返回当前的迭代对象。
实现 hasNext和next方法 ->在Iterator接口 -> 自定义一个迭代器的类实现 Iteraator接口,重写hasNext和 next方法。
class MyArrayList<T>{
//在上面实现部分的类中实现
//迭代elemennts
public Iterator<T> iterator(){
return new MyItr();//返回迭代器对象
}
class MyItr implements Iterator{
int cursor; //定义一个游标 迭代元素返回位置
@Override
public boolean hasNext() {
//判断是否还有下一个可迭代元素
//elemtents 0~size-1
return cursor < size;//有可迭代的元素
}
@Override
public Object next() {
//返回下一个可迭代元素
T result = elements[cursor];
cursor++;
return result;
}
}
}
快速失败机制
从迭代器源码中modCount了解Java集合的 fast-fail机制
- Java中非线程安全的集合:ArrayList、HashMap,经常可以在一些修改集合结构的操作中看到实例变量modCount++,以此来统计集合的修改次数。
- fast-fail机制:它能够立刻报告任何可能导致失败的错误检测机制。
- 当使用迭代器时(当构造迭代器对象时),起初有一个expectedModCount = modCount,在迭代的过程会判断两者之间的关系,如果modCount与期望值不符合,就说明在迭代的过程中集合结构发生了修改,便会抛出ConcurrentModificationException的异常。
LinkedList
LinkedList底层通过链表实现。
使用
- 创建一个LinkedList 基于双向链表 链表/队列
LinkedList<String> list = new LinkedList<>();
List<String> list = new LinkedList<>;
- 添加到尾节点
list.add("zs");
添加元素到头list.addFirst("ls");
添加元素到尾list.addLast("ww");
- 删除头节点
list.remove();
删除指定元素list.remove("zs");
删除指定位置元素list.remove(0);
- 打印
System.out.println(list);
- 迭代器遍历
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//foreach
for(String s:list){
System.out.println(s);
}
- 修改指定索引位置元素
list.set(0,"xh");
- 获取指定位置的元素
list.get(0);
- 判空
list.isEmpty();
- 获取元素个数
list.size();
- 判断是否包含某元素
list.contains("zs");
实现
class MyLinkedList<T>{
private Node<T> head; //指向头节点
private Node<T> tail; //指向尾节点
private int size; //有效元素的个数
class Node<T>{
private T data; //数据域
private Node<T> prev; //指向当前节点的前一个节点
private Node<T> next; //指向当前节点的后一个节点
//构造函数
public Node(T data, Node<T> prev, Node<T> next){
this.data = data;
this.prev = prev;
this.next = next;
}
}
//尾插法添加一个元素
public void add(T value){
Node<T> newNode = new Node<>(value, tail, null);
//第一次插入要特殊处理:判断是否是空链表
if(head == null){
head = newNode;
}else{
//正常情况下
tail.next = newNode;
}
tail = newNode;//尾巴向后挪
size++;
}
//指定位置添加一个元素
public void add(int index, T value){
if(index < 0 || index > size){
return;
}
if(index == size){//index是尾部
add(value);//调用尾插法
}else{
//根据index找到该位置的节点
Node<T> succ = findNodeByIndex(index);
Node<T> succPrev = succ.prev;
Node<T> newNode = new Node(value, succPrev ,succ);
succ.prev = newNode;
if(succPrev == null){
//将当前节点插到至第一个位置
head = newNode;
}else{
succPrev.next = newNode;
}
}
size++;
}
//获取index位置的节点
public Node<T> findNodeByIndex(int index){
Node<T> tmp = head;//临时引用tmp从头开始
for(int i=0; i<index; i++){
tmp = tmp.next;
}//i=index,跳出循环
return tmp;
}
//删除元素所在的节点
public boolean remove(T value){
Node<T> succ = findNodeByValue(value);
if(succ == null){//没找到
return false;
}
//特殊情况:删除第一个节点和最后一个节点
Node<T> succPrev = succ.prev;
Node<T> succNext = succ.next;
if(succPrev == null){//删除第一个节点
head = succNext;
}else{//正常情况
succPrev.next = succNext;
succ.prev = null;
}
if(succNext == null){ //删除最后一个节点
tail = succPrev;
}else{
//正常情况
succNext.prev = succPrev;
succ.next = null;
}
succ.data = null; //方便垃圾回收
size--;
return true;
}
//通过value获取当前节点
public Node<T> findNodeByValue(T value){
for(Node<T> tmp=head; tmp != null; tmp=tmp.next){
if(tmp.data.equals(value)){//找到节点
return tmp;
}
}
return null;
}
//修改指定位置的元素
public T set(int index, T newValue){
if(index < 0 || index >= size){
return null;
}
Node<T> succ = findNodeByIndex(index);
T result = succ.data;
succ.data = newValue;
return result;
}
//获取某一位置的元素
public T get(int index){
if(index < 0 || index >= size){
return null;
}
return findNodeByIndex(index).data;
}
public String toString(){
StringBuilder strs = new StringBuilder();
Node<T> tmp = head;
for(int i=0; i<size; i++){
strs.append(tmp.data+" ");
tmp = tmp.next;
}
return strs.toString();
}
}
源码
1.类的继承关系
- 相比与ArrayList,多实现了一个类,实现了Deque接口,是双端队列的一个接口,实现它可以将LinkedList当作一个队列来使用。
- 继承了AbstractSequentialList接口以及与ArrayList相同的其他几个接口。
2.类的属性
transient int size = 0;//有效元素个数
transient Node<E> first;//第一个节点
transient Node<E> last;//最后一个节点
3.构造函数
4.数据结构
底层采用双向链表
5. 扩容机制
4.常用方法
- add方法
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);//封装当前节点
last = newNode;//last往后走
if (l == null)//第一次插入
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
//在指定位置添加元素
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)//要往最后一个位置插入
linkLast(element);
else
linkBefore(element, node(index));
}
- remove调用了一个ublink方法
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;
}
- get 方法
应用场景
在(数据有序/可重复/可存储null值)的前提下,在修改比较高的场景下。
LinkedList 也支持 fsat-fail(快速失败机制)。
ArrayList和LinkedList的区别?
不同点 | ArrayList | LinkedList |
---|---|---|
底层实现 | 动态数组 | 双向链表 |
内存上地址空间 | 连续 | 非连续 |
初始化时默认容量 | 10 | null |
扩容 | 1.5倍(需要进行判满操作) | / |
随机访问 | index(效率更高) | next / prev |
随机插入和删除 | 移动目标节点后面的节点(System.arraycopy 方法移动)O(N) | 修改目标节点 next / prev 属性O(1)(效率更高) |
顺序插入和删除 | 不需要移动节点(效率更高) | / |
/ | 存在空间浪费 | / |
vector类
2.Set
HashSet
特点: HashSet底层基于HashMap,当前元素作为key,key不允许重复,具有HashMap所有的特点
如何通过HashMap实现HashSet?
——HashMap中存储key-value键值对,Set接口的实现类都是存储单种类型的元素,Set的实现种使用了HashMap中的Key来存储元素,Value值给定的是一个Object对象填充。
应用场景:对元素去重场景
使用
- HashSet中添加元素也是调用add方法,且只能用这一个方法,set集合不能指定位置添加元素。可以调用size() 方法获取元素个数,remove() 删除元素,isEmpty() 判空,contais() 判断是否存在。
- 没有获取元素和修改元素的方法。(因为底层基于HashMap实现,无法确定位置)
- 遍历可以使用迭代器、foreach循环。
- 在Set中添加和删除不会出现异常报错,增删成功返回 true,失败返回 false。
LinkHashSet
特点:
- LinkedHashSet是基于LinkedHashMap实现的(是set接口的实现,基于哈希表和链表,哈希表用来保证元素的唯一性,链表保证元素的插入顺序)
- 元素是不能重复的
- 可以存储null值
- 数据是有序的(插入和访问有序,相比于HashSet,节点类型多了before、after引用,通过这两个引用决定插入的先后顺序)
应用场景:数据去重且数据有序
HashSet和LinkedHashSet的异同点?
不同点:
- HashSet
继承自 AbstractSet
不能保证元素的排列顺序,顺序有可能发生变化。
不是同步的。
集合元素可以是null,但只能放入一个null。
内部使用HashMap来存储数据,数据存储在HashMap的key中,value都是同一个默认值。 - LinkedHashSet
继承自 HashSet
内部使用LinkHashMap存储数据 -> 元素顺序一定,遍历和插入顺序一致。
相同点:
都不是线程安全的。如果想要保证线程安全,可以使用Collections.synchornizedSet()方法。
TreeSet
也是Set接口的实现,底层基于哈希表和红黑树实现,哈希表用来保证元素的唯一性,红黑树保证元素的有序(自然顺序、比较器接口所对应的顺序)。
3.Queue
二、Map接口
Map接口是哈希表的基本接口,存储的是key-value键值对对象。主要包含下图几个接口。
HashMap
详细介绍在 HashMap总结 中。
LinkedHashMap
特点
- 底层数据结构是哈希表
- 继承自HashMap:具有HashMap的所有特点(数据有序:插入有序、访问有序)
如何做到数据有序?
——维护一个双向的链表接口。
应用场景:数据统计且数据有序(统计数据出现的次数且如果数据次数相等,按照插入顺序)
HashTable
特点
- 底层数据结构是哈希表
- 数据(key)不能重复
- 元素(key和value)都不可以为null
- 数据无序
- 是线程安全的
- 哈希表结构默认大小是11
- 2倍+1的关系进行扩容
HashMap 和 HashTable 和 ConcurrentHashMap 的区别?
HashMap 提供可供应用迭代的键的集合,是快速失败的。
HashMap | HashTable | ConcurrentHashMap |
---|---|---|
数组+链表实现 | 数组+链表实现 | 分段的数组+链表实现 |
继承于AbstractMap | 继承于Dictionary | |
key、value允许为null | 不允许key、value为null | 不允许key、value为null |
线程不安全 | 线程安全 | 线程安全 |
初始 size =16 | 初始 size =11 | 初始 size =16 |
扩容:2n(size为2的n次幂) | 扩容:2n+1 | 扩容因子:0.75 |
不同步 | 同步的 | |
适合单线程环境 | 适合多线程环境 |
WeakHashMap
特点:数据随着实践的推移会消失或减少
Java中的四种引用
- 强引用(strong):所作用的对象在内存不足抛出OOM的问题时都不会被回收
- 软引用(soft):当内存不足时,在发生GC操作时,软引用所作用的对象会倍回收
- 弱引用(weak):只要发生GC操作,无论内存是否充足,弱引用所作用对象会被回收
- 虚引用(phantom):和对象的生命周期无关,主要是来提醒对象被回收
应用场景:在内存紧张情况下,将非关键信息存放在该集合中
TreeMap
特点:
- 数据会按照属性的特征进行排序
- 底层数据结构:使用红黑树
应用场景:数据统计并且数据按照数据大小排序