调集概念的由来
调集是为了便利存储和操作一组方针而规划,例如在实际开发中,咱们常常需求处理一组数据,例如存储用户信息、商品信息等,而调集类提供了一种便利的办法来办理这些数据。
调集与数组比较不同之处
- 动态扩展:调集类能够依据需求动态扩展巨细,而数组的巨细是固定的。 当然数组也是能够完成主动扩容,但需求手动进行完成。数组的默许巨细是依据你创立数组时分进行,指定数组长度,例如int[] arr = new int[10],能够存放10个整数,假如不指定数组长度是不可进行运用。
- 更多的功用:调集类提供了许多实用的功用,例如迭代器、排序、查找等,能够大大提高开发功率
- 更高效的内存运用:调集类能够依据实际需求分配内存,而数组需求一次性分配一切内存,可能会构成内存糟蹋。
调集全家福
调集家庭成员分类
Collection调集是指一组单个数据元素的调集,这些元素能够是任何类型的方针,例如字符串、数字、自界说方针等。Collection调集包括List、Set和Queue三种类型。
- List:是一种有序的调集,能够存储重复的元素。List调集中的元素能够经过索引拜访和修正,例如ArrayList、LinkedList等。
- Set:是一种不答应重复元素的调集,元素的次序是不确定的。Set调集中的元素不能够经过索引拜访和修正,例如HashSet、TreeSet等。
- Queue:是一种先进先出(FIFO)的调集,一般用于完成队列。Queue调集中的元素不能够经过索引拜访和修正,例如LinkedList、PriorityQueue等。
Map调集是指一组键/值对映射联系的调集,每个键对应一个值。Map调集中的键是仅有的,可是值能够重复。Map调集包括HashMap、TreeMap、LinkedHashMap等类型。常用的完成有:
- HashMap:根据哈希表完成,无序的,答应键和值。
- TreeMap:根据红黑树完成,有序的,不答应键,但答应值。
- LinkedHashMap:根据哈希表和双向链表完成,有序的,答应键和值。
本章只写关于Collection调集成员家庭,Map家庭能够在后续章节进行弥补
Collection调集家庭成员介绍
Iterable接口介绍:
Iterable接口是Java调集结构中的一个接口,它是一切调集类的父接口,界说了一种迭代器的行为,使得调集类能够被迭代。Iterable接口中只要一个办法iterator(),该办法返回一个Iterator方针,用于遍历调集中的元素。
简略运用小事例
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Python");
list.add("C++");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println(element);
}
上述代码中,经过List调集的iterator()办法获取一个Iterator方针,然后运用while循环和next()办法遍历调集中的元素并输出。
Iterable 源码解读
Java中的Iterable接口是一个调集结构中的根本接口,Iterable接口的作用是提供一种统一的遍历调集元素的办法,使得不同的调集类能够运用相同的遍历办法,从而便利程序员进行调集操作。
Iterable 接口包括一个办法 iterator(),该办法返回一个 Iterator 方针,该方针能够用于遍历调集中的元素。
forEach
办法是用来对调集中的每个元素履行指定的操作,这个操作由传入的Consumer
函数式接口完成。默许完成是经过迭代器遍历调集中的元素,然后对每个元素履行操作。
spliterator
办法是用来创立一个Spliterator
方针,该方针能够对调集中的元素进行切割、遍历和处理。
1. Collection调集
Collection调集规划解读
Collection 是 Java 中的一个接口,它是一个调集结构的根底接口,界说了一些通用的办法,用于操作调集中的元素。它界说了一些通用的对调集操作办法,如增加元素、删去元素、判别元素是否存在、获取调集巨细等。接口答应咱们操作调集时不必关注具体完成, 从而达到“多态”。在面向方针编程语言中,接口一般用来构成标准。其他调集接口和类(Map 除外)都扩展或完成了Collection interface。
List接口特色
- 承继 Collection调集接口
- 有序:有序(元素存入调集的次序和取出的次序共同)。List中每个元素都有索引符号。能够依据元素的索引符号(在List中的方位)拜访元素。
- 可重复:List答应参加重复的元素。更确切地讲,List一般答应满足条件的元素重复参加容器
List接口具体完成
ArrayList
ArrayList实际上是一个动态数组,容量能够动态的增长,其承继了AbstractList,完成了List, RandomAccess(随机拜访), Cloneable, java.io.Serializable这些接口。
RandomAccess的作用是答应在数据结构中随机拜访元素,而不需求依照次序遍历整个数据结构。这种随机拜访能够大大提高数据结构的拜访功率,特别是关于大型数据结构来说。RandomAccess一般用于数组、列表等数据结构中,能够经过索引或下标来拜访元素。在Java中,完成了RandomAccess接口的数据结构能够运用快速随机拜访的算法,而没有完成该接口的数据结构则需求运用迭代器等办法来遍历元素。
简略说便是,假如完成RandomAccess接口就下标遍历,反之迭代器遍历 完成了Cloneable, java.io.Serializable意味着能够被克隆和序列化,LinkedList由于底层采用数据结构时双向链表(内存空间不接连))所以就不支撑快速随机拜访的能力,有必要依次进行遍历。
Cloneable 接口是 Java 中的一个符号接口,用于指示一个类能够被克隆。完成 Cloneable 接口的类能够运用 Object 类中的 clone() 办法来创立一个新的方针,该方针与原始方针具有相同的状态。Cloneable 接口的作用是提供一种简略的办法来完成方针的仿制,而不需求重新创立一个新的方针并将其初始化为原始方针的副本。这在某些情况下能够提高程序的功能和功率。可是需求留意的是,clone() 办法是浅仿制,即只仿制方针的根本类型和引证类型的地址,而不会仿制引证类型所指向的方针。因此,在运用 clone() 办法时需求特别当心,避免出现意外的过错。
浅仿制和深仿制区别
浅仿制只仿制方针的引证,而不是方针本身。也便是说,新方针和原方针共享同一个引证类型的特点,修正其间一个方针的特点会影响另一个方针的特点。
Array.prototype.concat():返回一个新数组,包括原数组和其他数组或值的仿制
深仿制则是将方针及其引证类型的特点悉数仿制一份,新方针和原方针互不影响。
JSON.parseObject(result) 办法属于深仿制
需求留意的是,深仿制可能会由于方针的循环引证而导致栈溢出或死循环
Serializable是Java中的一个接口,用于完成方针的序列化和反序列化。一个类完成了Serializable接口后,就能够将该类的方针转换成字节序列,以便在网络上传输或者保存到本地文件中。一起,也能够将字节序列反序列化成方针,以便在程序中运用。
ArrayList 运用办法
// 初始化一个 String 类型的数组
String[] stringArr = new String[]{"hello", "world", "!"};
// 修正数组元素的值
stringArr[0] = "goodbye"; System.out.println(Arrays.toString(stringArr));
// [goodbye, world, !]
// 删去数组中的元素,需求手动移动后边的元素
for (int i = 0; i < stringArr.length - 1; i++) {
stringArr[i] = stringArr[i + 1];
}
stringArr[stringArr.length - 1] = null;
System.out.println(Arrays.toString(stringArr));
ArrayList优缺陷:
优点:
- 能够动态地增加和删去元素,不需求预先指定数组的巨细;
- 能够存储任何类型的方针,包括根本数据类型的包装类;
- 能够经过索引快速拜访元素;
- 能够运用迭代器遍历调集中的元素;
- 能够运用泛型来确保调集中元素的类型安全。
缺陷:
- 在刺进或删去元素时,需求移动其他元素,功率较低;
- 在很多元素的情况下,可能会占用较大的内存空间;
- 不支撑多线程并发拜访,需求手动进行同步处理;
- 在进行元素查找时,功率较低,由于需求遍历整个调集。
ArrayList源码阅读
List list = new ArrayList<>(); 创立一个 ArrayList一个履行流程是
// 经过结构办法进行初始化
/**
*默许无参结构函数
*DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为0.初始化为10,
也便是说初始其实是空数组 当增加第一个元素的时分数组容量才变成10
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 带初始容量参数的结构函数(用户能够在创立ArrayList方针时自己指定调集的初始巨细)
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
elementData和DEFAULTCAPACITY_EMPTY_ELEMENTDATA解读
private static final long serialVersionUID = 8683452581122892189L;
/**
* 默许初始容量巨细
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 空数组(用于空实例)。
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 空数组(用于空实例)。
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 保存ArrayList数据的数组
*/
transient Object[] elementData;
/**
* 保存ArrayList数据的数组
*/
private int size;
//在这里能够看到咱们不解的 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
//实际上是一个空的方针数组,当增加第一个元素的时分数组容量才变成10
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
增加办法add
```
/**
* 将指定的元素追加到此列表的结尾。
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
/** 在此列表中的指定方位刺进指定的元素。
*先调用 rangeCheckForAdd 对index进行界限查看然后调用 ensureCapacityInternal 办法确保capacity足够大;
*再将从index开端之后的一切成员后移一个方位;将element刺进index方位;最终size加1。
*/
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
// Increments modCount!!
//arraycopy()这个完成数组之间仿制的办法必定要看一下,
//下面就用到了arraycopy()办法完成数组自己仿制自己
//System.arraycopy 是 Java 中的一个办法,
//用于将一个数组的一部分仿制到另一个数组中的指定方位。
//它的作用是快速地将一个数组的部分内容仿制到另一个数组中,
//能够用于数组的仿制、合并、排序等操作,
//其间,src 表明源数组,srcPos 表明源数组的开始方位,
//dest 表明方针数组,destPos 表明方针数组的开始方位,
//length 表明要仿制的元素个数。该办法是一个静态办法,能够直接经过类名调用
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++; }
这个办法便是将一个元素增加到数组的size++方位上。ensureCapacityInternal,它是用来扩容的,准确说是用来进行扩容查看的
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
}
参数 minCapacity 表明需求存储的元素数量的最小值。
calculateCapacity(elementData, minCapacity) 是一个内部办法,用于计算实际需求的容量巨细。
ensureExplicitCapacity() 是另一个内部办法,用于查看当时容量是否足够,假如不够则进行扩容。
因此,ensureCapacityInternal() 的作用便是先计算需求的容量巨细,然后查看当时容量是否足够,假如不够则进行扩容,以确保 ArrayList 内部的容量足够存储指定的元素数量。
grow办法具体完成是先将原数组的容量(oldCapacity)右移一位(相当于除以2),然后将得到的结果加上原数组的容量,得到新的容量(newCapacity)。假如新容量依然小于指定的最小容量,则将新容量设置为指定的最小容量。
假如新容量超过了数组的最大容量(Integer最大值-8),则调用另一个办法(hugeCapacity)来获取一个合适的容量值。这个办法呢判别当时值大于Arrary最大值(Integer最大值-8 )返回Integer最大值否则返回Arrary最大值(Integer-8)
最终,运用Arrays.copyOf办法将原数组的元素仿制到新数组中,并将新数组赋值给原数组变量elementData。
ArrayList支撑两种删去
- remove(int index) 依照下标删去 常用
- remove(Object o) 依照元素删去 会删去和参数匹配的第一个元素
/** 移除list中指定方位的元素 一切后续元素左移 下标减1
*参数是要移除元素的下标
返回值是被移除的元素
*/
public E remove(int index) {
//下标越界查看 rangeCheck(index);
//修正次数统计 modCount++;
//获取这个下标上的值
E oldValue = elementData(index);
//计算出需求移动的元素个数 (由于需求将后续元素左移一位 此处计算出来的是后续元素的位数)
int numMoved = size - index - 1;
//假如这个值大于0 阐明后续有元素需求左移 是0阐明被移除的方针便是最终一位元素
if (numMoved > 0)
//索引index只要的一切元素左移一位 覆盖掉index方位上的元素
System.arraycopy(elementData, index+1, elementData, index,numMoved);
// 将最终一个元素赋值为null 这样就能够被gc回收了
elementData[--size] = null;
//返回index方位上的元素 return oldValue; }
//移除特定元素 public boolean remove(Object o) {
//假如元素是null 遍历数组移除第一个null
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
//遍历找到第一个null元素的下标 调用下标移除元素的办法
fastRemove(index);
return true;
}
} else {
//找到元素对应的下标 调用下标移除元素的办法
for (int index = 0;
index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index); return true;
}
}
return false;
}
//依照下标移除元素
private void fastRemove(int index) {
modCount++; int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null;
}
ArrayList总结
- 底层数组完成,运用默许结构办法初始化容量是10
- 扩容的长度是在原长度根底上加二分之一
- 完成了RandomAccess接口,底层又是数组,读取元素功能很好
- 线程不安全,一切的办法均不是同步办法也没有加锁,因此多线程下慎用
- 次序增加快删去和刺进需求仿制数组 功能很差
赠送一张总结收拾