Java集合List接口详解——含源码分析
@TOC
集合
在Java编程中,可以使用数组来保存多个对象,但数组长度不可变化,一旦在初始化数组时指定了数组长度,这个数组长度就是不可变的。如果需要保存数量变化的数据,数组就有点无能为力了。而且数组无法保存具有映射关系的数据,比如5000---iphone
,4000----小米
,这种俩个数据中存在关联关系的
集合又分为:
- 接口
- 子接口
- 实现类
具体我们看下面俩张图:
Collection集合的常用方法
- 增加:add(E e) addAll(Collection<? extends E>c)
- 删除:clear() remove(Object o)
- 修改
- 查看:iterator() size()
- 判断:contains(Object o) equals(Object o) isEmpty()
import java.util.ArrayList;
import java.util.Collection;
public class jj {
public static void main(String[] args) {
/*
*Collection接口的常用方法
* 增加:add(E e) addAll(Collection<? extends E> c)
* 删除:clear()remove(object o)
*修改
* 判断:contains(Object o) equals(Object o) isEmpty()
*/
//创建对象,接口不能创建对象,利用它的实现类来创建对象
Collection l1 = new ArrayList();
l1.add(18);
//只能存储对象,这里的18实际上是进行了一个装箱的操作
l1.add(19);
System.out.println(l1);
//toString(),就是实现类中的toString
//l1.clear();
System.out.println(l1.size());
System.out.println(l1.isEmpty());
l1.remove(1);
System.out.println(l1);
}
Collection集合的遍历方式:
我们知道常用的遍历数组有俩种方式,普通for循环和增强for循环
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Objects;
public class traverse {
public static void main(String[] args) {
//对集合进行遍历
Collection a = new ArrayList();
a.add(19);
a.add(18);
a.add(20);
a.add(21);
//方式一:普通for循环
// for(int i = 0; i<a.size(); i++){
// col.
// }
//方式二:增强for
for (Object o : a){
System.out.println(o);
}
//方式三:iterator
Iterator it = a.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
Collection集合是不支持普通for的,但是支持增强for和迭代器iterator,迭代器我们后面再说
Collection子接口list
通过Javaapi也不难看出这是一个子接口
方法比较多,我们主要来说一下Collection没有的方法
/*
*list 接口常用方法
* 增加:add(int index,E element)
* 删除:remove(int index) remove(Object o)
* 修改:set(int index,E element)
* 查看:get(int index)
* 判断
*/
package connector;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class list1 {
public static void main(String[] args) {
List list = new ArrayList();
//子接口要写实现类
list.add(196);
list.add(111);
list.add(222);
System.out.println(list);
list.add(3,333);
System.out.println(list);
list.set(3,33);
System.out.println(list);
list.remove(2);//下标为2的元素
//存入的是Integer类型的数据,删除的是下标为2的元素
System.out.println(list);
list.add("adad");
list.remove("adad");
System.out.println(list);
Object o = list.get(0);
System.out.println(o);
//list集合遍历:
for(int i = 0; i<list.size(); i++){
System.out.println(list.get(i));
}
for(Object object : list){
System.out.println(object);
}
Iterator it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
我们来试想一下,为啥List作为Collection的子接口就能用普通for循环遍历了?
首先我们要明白要用普通for循环,就要有索引方法,来获取元素,而List继承Collection,多出的get方法,使得我们可以获取索引,继而使用普通for循环
源码(均来自JDK1.8)
ArrayList实现类
Array是数组的意思,我们见名知意,可以猜出它的底层应该是用数组来实现的,这个我们后面看源码的时候再说,首先我们看一下,ArrayList中有没有前面没有见过的方法:
貌似都用过,只有后面三个没用过,其中sort排序方法可以参考我的这篇博客,详述Java中sort排序函数
源码来自JDK1.8
Object类型的数组,和size(数组中的有效长度)
transient Object[] elementData; // non-private to simplify nested class access
private int size;
- 构造一个ArrayList类型的数组
public class arraylist {
public static void main(String[] args) {
ArrayList a1 = new ArrayList();
}
}
//new ArrayList的源码
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;//就是给elementData进行了一个赋值操作
}
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//继续,发现它是给了一个空数组
在jdk1.8中,ArrayList初始化为一个空数组{}
- add方法
package connector;
import java.util.ArrayList;
public class arraylist {
public static void main(String[] args) {
ArrayList a1 = new ArrayList();
a1.add("1");
}
}
public boolean add(E e) {
ensureCapacityInternal(size + 1);
//1. 调用ensureCapacityInternal方法,并且传入size+1,现在数组是空,传入的0+1 = 1
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
//2.传入elementData, minCapacity
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//3.传入的为elementData和1
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//4.进行比对,发现还是个空数组
return Math.max(DEFAULT_CAPACITY, minCapacity);
//5. 取比较大的值,发现它变为了10
}
return minCapacity;
}
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
private static final int DEFAULT_CAPACITY = 10;
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
//6. 10-0,必然会进入if语句中
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
//7. 数组长度变为10
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
//8. 数组变化完成,将newCapacity赋值给elementData
}
不难看出,初始数组为0,只有当你添加对象的时候,才重新赋值为新的数组,且初始赋值为长度为10的数组
Vector实现类源码
首先我们来说一下区别,再看源码,
- 底层Object数组,int类型表示数组中的有效长度
- 调用构造器的时候,初始化了一个长度为10 的数组
- add添加元素的时候,底层扩容数组长度为2倍
//调用构造器
public Vector() {
this(10);
}
//add
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
//传入0+1
elementData[elementCount++] = e;
return true;
}
private void ensureCapacityHelper(int minCapacity) {
if (minCapacity - elementData.length > 0)
grow(minCapacity);
//grow
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
//扩容长度为2倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
//基本和上面的ArrayList的add源码一样
对比ArrayList线程安全
synchronized
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
联系:
- 底层都是数组的扩容
- ArrayList是1.5倍扩容,但是不安全
- Vector是2倍扩容,但是安全
数组的优缺点:
数据可重复,查询快,删改慢,这个可以参考,数据结构相关书籍
LinkList实现类
不难看出add方法中,LinkList多了addFirst,addLast,下面我们用crud来总结一下:
增加:addFirst(E e) addLast(E e)
删除:poll()
pollFirst() pollLast()
removeFirst() removeLast()
修改:
查看:element()
getFirst() getLast()
indexof(Object o) LastIndexOf(Object o)
peek()
peekFirst() peekLast()
判断:
代码实现:
import java.util.HashMap;
import java.util.LinkedList;
public class Main {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("addd");
list.add("bddd");
list.add("cddd");
list.add("addd");
System.out.println(list);//LinkedList可以添加重复数据
list.addLast("hh");
list.addFirst("gg");
list.offer("kk");//添加元素到尾端
System.out.println(list);
//删除
System.out.println(list.poll());//删除头上的元素,并且返回头上元素
System.out.println(list);
System.out.println(list.removeFirst());//删除头尾元素
System.out.println(list.removeLast());
list.clear();//清空集合
System.out.println(list);
System.out.println(list.pollFirst());//可以
System.out.println(list.removeFirst());//报错
}
}
poll和remove的区别:
如果为空,poll不报错,返回null,但是remove报错
LinkedList的遍历
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
public class Main {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("addd");
list.add("bddd");
list.add("cddd");
list.add("addd");
//集合的遍历
System.out.println("---------------");
//普通for循环
for(int i = 0;i<list.size(); i++){
System.out.println(list.get(i));
}
//增强for
for(String s : list){
System.out.println(s);
}
//迭代器
Iterator<String >iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
LinkedList源码(JDK17)
为啥突然换成jdk17了?前几天电脑崩了,脑子一糊涂,c盘删了10个g的文件,被迫用笔记本来看源码了,hh
LinkedList数据结构:
线性表(链表)——>双向链表
下面,用Java语言来描述一下:
//节点类
public class Node{
//上一个元素的地址:
private Node pre;
//当前存入的元素:
private Object obj;
//下一个元素的地址:
private Node next;
public Node getPre() {
return pre;
}
public void setPre(Node pre) {
this.pre = pre;
}
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
@Override
public String toString() {
return "Node{" +
"pre=" + pre +
", obj=" + obj +
", next=" + next +
'}';
}
}
//链表实现类
public class MyLinkedList {
//链表中一定有一个首节点,一定有一个尾节点
//当为空链的时候俩个节点都为null
Main.Node first;
Main.Node last;
//计数器
int count = 0;
//构造器:
public MyLinkedList(){
}
//add
public void add(Object obj){
if(first == null){
//add(第一个元素)
Main.Node n = new Main.Node();
n.setPre(null);
n.setObj(obj);
n.setNext(null);
//节点变化
first = n;
last = n;
} else {
//常规情况
Main.Node node = new Main.Node();
node.setPre(last);//node的上一个节点一定是当前链中的最后一个节点
node.setObj(obj);
node.setNext(null);
//当前链中的最后一个节点的下一个元素 要指向node
last.setNext(node);
//最后一个节点变成node
last = node;
}
//计数器加一
count++;
}
public int getCount(){
return count;
}
public Object get(int index){
Main.Node n = first;//表头
for(int i = 0; i<index; i++){
n = n.getNext();
}
return n.getObj();
}
}
class Text{
public static void main(String[] args) {
MyLinkedList ml = new MyLinkedList();
ml.add("aa");
ml.add("bb");
ml.add("cc");
System.out.println(ml.getCount());
System.out.println(ml.get(2));
}
}
下面我们来看jdk17当中LinkedList的源码是怎么写的吧,
E就是一个泛型,具体的类型要在实例化的时候才会最终确定,具体可以看一下,我前几天写的这篇文章:
Java泛型——帮助你更好的读懂源码<dog>
int size就是集合中元素的数量
first,last
,就是我们上面手写link的First,Last
空构造器
add方法
//add方法
public void addFirst(E e) {
linkFirst(e);
}
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;//如果是第一个节点,将链表的first节点指向为新节点
else
f.prev = newNode;//将l的下一个指向为新节点
size++;
modCount++;
}
鉴于篇幅原因,剩下的方法源码分析,还请读者自行解决,或者可以私信我!
- 点赞
- 收藏
- 关注作者
评论(0)