复习
1.二分搜索法。线性的有序的数列,升序数列。时间复杂度为:T(n) = O(log2n)
2.选择排序:将无序区中最小的元素和无序区中的第一个元素进行不断交换的过程
3.插入排序:将无序区中第一个元素放到有序区中合适的位置。
4.数组:元素有序的:添加的顺序,0序的,随机访问(根据下标)的效率很高。
数组结构的jdk对应的实现类 java.util.ArrayList java.util.Vector
5.栈:元素特点:先入后出。入栈 push、出栈pop。
Jdk提供了栈的对应的实现类。Java.util.Stack
6.链表:元素也是有序的,添加的顺序,0序的。每一个元素称为一个节点 Node。
单向链表一个Node中的内容:数据+后继结点的引用next。只要在单向链表中持有头结点的引用就可以访问到所有的Node。
双向链表:一个Node 包含的内容:数据+后继结点引用next+前驱结点引用prev。
在双向链表中只要持有头结点和尾部结点的引用就可以实现双向的访问。
JDK中提供的链表的对应的实现类:java.util.LinkedList 双向的链表。
7.树:二叉树。Jdk中对于树结构的具体实现类 java.util.TreeSet java.util.TreeMap.
8.哈希表 hashtable jdk对应的实现类 java.util.HashSet java.util.HashMap…
9.队列:元素的特点 先入先出。Jdk中具体的实现类 java.util.LinkedList.
第一节 容器概述
容器出现的必要性:程序如果管理大量的对象,必须使用容器来管理。数组作为一种简单的容器存在有很多的缺点,通常不能满足需求。
1.数组的缺点:
增加删除元素效率低,需要移动大量的元素。
数组元素的类型必须是唯一的。
数组是定长的
数组根据内容查找元素效率低,需要遍历整个数组进行比较。
数组没有封装,对于数组元素的任何的操作,都需要自己通过方法来实现。(把属性和操作属性的方法放到一个类里面,这 也是一个封装的过程)(假如说数组也是个对象,如果添加个元素直接点add是不是更好??如果要删除个元素,直接点remove index是不是更好??,但是数组没有封装,它的任何的操作都要自己写方法来实现。)
还有就是数组的元素是连续的,这种连续可以带来它的随机访问的效率很高,同时也会带来一个小的缺点,因为数组必须在堆内存中连续的分配空间,那么这个时候,比如我申请5亿个元素,可能堆内存中都没有这么大的连续的空间 ,会导致内存溢出。所以它对内存的要求更加苛刻一点儿。
结论:数组存在诸多的缺点,所以jdk提供了一套完整的解决方案,可以满足不同类型数据的要求。
2.Java 的集合框架 collection framework
学习集合框架,其实就是学习各种具有不同特点的容器对象如何使用。(对象又是根据类来创建的,就是学习jdk给我们提供的容器类)本质上就是学习jdk在集合框架部分定义的不同功能的容器类。
3.集合框架部分容器的分类
sequence 序列:元素是有序的,添加的顺序,0序的。元素可以重复。有序不唯一。
Set 集:元素无序的,唯一的。
Map 映射:元素是一对数组组成的。一个key一个value。一个键一个值。键值对对象。可以通过key找到value。不能通过 value 找到key。
第二节 ArrayList
1.java.util.Collection
该接口是容器部分的顶层接口,是List 和 Set 的公共的父接口。对于实现的子类的要求:元素无序、不唯一。
2.java.util.List
List是Collection直接的子接口,对实现子类的要求:元素有序(添加的顺序)、不唯一
3.java.util.ArrayList
该类是数组数据结构的一个具体的实现类。(比数组有优点,首先它是自动扩容的,不需要我们手动写到方法进行扩容,底层给扩容了,封装了。还有一个是对数组增加 很多的封装的方法,对元素所有的操作可以通过方法调用实现了,而不是要自己去写,这就是这个类的向对于数组的优点。)
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* ArrayList的学习
*/
public class TestArrayList {
public static void main(String[] args) {
test();
}
//ArrayList的基本使用
public static void test(){
//创建容器对象
List list=new ArrayList();//父类引用指向子类对象 (这个类可以理解为带封装的数组,自动扩容,和封装了对元素所有的操作的方法即:对元素的增、删、改、查)
//添加元素,尾部追加
list.add("千万里我追寻着你");
list.add("可是你却并不在意");
list.add("你不像是在我的梦里");
list.add("在梦里你是我的唯一");
list.add(1.1);//自动装箱,等价于下面那一句
list.add(Double.valueOf(1.1));
list.add(new Student(19,"学生1",Gender.男));
//插入元素
list.add(0,"千万次的问");//这里面的两个"千万次的问"是在常量池中的,这两个添加的元素指向的同一个地址
list.add(0,"千万次的问");
//删除元素(底层是使用equals()方法来实现的,String和Double都重写了equals()这个方法,所以可以根据内容直接删除)
list.remove(6);//可以根据索引删除元素
list.remove(1.1);//也可以根据元素的内容删除元素
list.remove("千万次的问");//有相同元素的时候,只删除了第一个
/*
删除的时候是怎么删的呢?肯定是要遍历容器中所有的元素,和我们要删除的元素进行比较,通过equals()来进行比较的。
说明:下面这一行里面的new Student(19,"学生1",Gender.男)是一个新创建的对象,是一个实参
很显然它和上面添加元素创建的学生对象不是同一个(尽管属性相同),
为了根据内容相同可以删除元素,所以必须重写Student类的equals()方法
*/
/*看源代码里面比较的时候确实用的是equals()
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
*/
list.remove(new Student(19,"学生1",Gender.男));
//修改元素
list.set(1,"time time again");
list.set(2,"time time again");
list.set(3,"你到底爱不爱我");
//获得元素
Object o = list.get(4);
System.out.println("4 = "+o);
//其他功能
int size=list.size();
System.out.println(size);//5
boolean b=list.contains("你不像是在我的梦里");
System.out.println(b);//false
//list.sort();
//list.isEmpty();
//list.clear();
int index=list.indexOf("你不像是在我的梦里");//返回元素“你不像。。。”这个元素对应的索引,不含这个元素的话返回-1.
System.out.println(index);//-1
//list.lastIndexOf();
//list.subList();
//list.toArray();
//遍历元素
//这里调用了list的toString方法(肯定是重写了toString方法),
// 怎么实现的呢?肯定是让每个元素调用自己的toString方法,String类和8中基本数据类型常用类都重写了toString方法,
//并且我们创建额Student类里面也重写了toString方法,如果不重写会调用父类的toString,打印的是对象的地址。
System.out.println(list);
}
**//4.遍历**
private static void foreach(){
List list=new ArrayList();
list.add("a");
list.add("b");
list.add("_");
list.add("_");
list.add("c");
list.add("d");
list.add("_");
list.add("e");
list.add("null");
//for i 遍历
for (int i = 0; i < list.size(); i++) {
Object o =list.get(i);
if("_".equals(o)){
list.remove(o);
//为了保证如果两个要删除的元素是相邻的,那么后面的元素就不能删除的问题,补偿用的
i--; //就是为了保证所有的元素都可以被获得。
}
}
//for-each
for(Object o:list){
System.out.println(o);
}
//使用迭代器
//得到被遍历的容器对象上的迭代器对象
Iterator iterator=list.iterator();
//使用迭代器对象遍历容器元素
while(iterator.hasNext()){ //迭代器是否还要没有遍历的元素
Object o=iterator.next(); //返回被遍历的元素的对象
System.out.println(o);
}
//这个迭代器iterator不能用它进行第二次的遍历了,迭代器具有一次失效的特点。如果想进行第二次遍历,必须重新获得。
//iterator=list.iterator(); //这叫引用的复用
//还可以用for循环来写
//在初始化这里得到这个迭代器,循环条件是it.hasNext(),自增括号里不用写,是在迭代器调用next()的时候做的(游标向后走)
for (Iterator it=list.iterator();it.hasNext();) {
Object next=it.next();
}
}
}
class Student{
private int age;
private String name;
private Gender gender;
public Student(int age, String name, Gender gender) {
this.age = age;
this.name = name;
this.gender = gender;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
", gender=" + gender +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
if (name != null ? !name.equals(student.name) : student.name != null) return false;
return gender == student.gender;
}
@Override
public int hashCode() {
int result = age;
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + (gender != null ? gender.hashCode() : 0);
return result;
}
}
enum Gender{
男,女
}
这是 iterator()方法的底层实现,是ArrayList类的一个内部类。
说明:Iterator iterator=list.iterator(); 这里Iterator是一个接口,list是一个对象,调用了iterator()这个方法,那么这个方法返回的是个什么呢?返回的是这个接口的一个子类对象,哪个子类对象?这个子类对象在哪?是在ArraList类里面的一个内部类,在不同的实现类里面是他们的内部类,我们只需要知道返回的是一个接口的一个子类对象就可以了。
Iterator的底层实现
public Iterator iterator() { return new Itr();} 这句代码返回的是一个Itr对象 |
---|
private class Itr implements Iterator { } 而Itr 实现了Iterator接口,这个类是ArrayList的一个成员内部类 |
---|
//这是 iterator()方法的底层实现,Iterator是一个接口,ArrayList类的一个内部类Itr继承了这个接口并实现了父类里面的方法。
public Iterator<E> iterator() {
return new Itr();
}
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
int cursor; // index of next element to return这个是游标
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1; //游标每次加1
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
5.迭代器
概念:迭代器是一个对象,一个专门用于遍历容器中元素的对象。
迭代器遍历元素是容器部分专有的遍历的方式,迭代器是为集合而生。是遍历容器部分的标准迭代的方式。
注意:如果使用迭代器遍历我们的容器,如果在遍历的过程中,想要删除某些元素,一定要用迭代器的remove方法。不要用list.remove(),会报错的。
6.泛型
泛型是jdk1.5推出的新的一种语法。针对容器所带来的的好处
1:可以限制容器对象添加元素的类型,避免加入不想加入的元素的类型。
2:获得容器元素对象之后,如过想使用元素的实际类型的方法,不需要再进行向下的类型转换。
3.ArrayList的底层实现是数组
源码 transient Object[] elementData; // non-private to simplify nested class access 说明:ArrayList的底层实现是数组 |
---|
4.在泛型这里,只要是合法标识符就行。但是我们一般使用两个符号,一个是E(代表的元素类型),如果不是元素类型别的呢,那么我们就使用T(type)来代表某种类型。最终我们在用这种带有泛型的类,指出它的泛型的时候,public class ArrayList extends AbstractList,它的实参一定是一个类的名字。
import java.util.ArrayList;
import java.util.List;
/**
* 泛型的学习
*/
public class TestGenerics {
public static void main(String[] args) {
//泛型学习
List<String> list=new ArrayList();
//容器通常管理的是同一种数据类型(虽然可以放不同类型的数据)
//要求:list对象只能放String类型
list.add("123");
list.add("456");
list.add("789");
//不希望其他的类型被添加进来
//list.add(new Object());
Object o=list.get(0);//一定要注意这时候的返回类型是Object,因为ArrayList的底层实现是一个Object[]类型的数组
//如果使用子类特有功能的时候,需要进行强制类型转换
String s=(String)o;
System.out.println(s.charAt(1));
String s1=list.get(0);//使用泛型后,这里返回的类型直接就是String类型的
System.out.println(s1.charAt(1));
}
}
7:ArrayList 源码分析
部分源码
public class ArrayList<E> extends AbstractList<E> //这里类里面所有E出现的地方,这个容器里面即将要存的元素的约定的类型
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
*/
transient Object[] elementData; // 底层的数据结构non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
*/
private int size;
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
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);
}
}
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() { //这个是它的默认无参的构造方法
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; //调用默认无参的构造方法的时候,这里直接的是elementData指向了一个空的数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
说明:1.ArrayList这里有一个泛型,泛型约定的是我这个容器里面要放的元素类型,在这个类里面E出现的地方就代表约定着这个容器里面即将要存的元素的类型,这个E也可以是其他的合法的标识符就行,但是我们一般用元素类型用E(element),非元素类型用T(type)。
2.Object[] elementDate;这个就是底层的数据结构(这个数组缓存区就是用来存放ArrayList元素的)
3.private int size;意思是包含元素的个数
4.这个初始容量,
源码 private static final int DEFAULT_CAPACITY = 10; |
---|
难道我们一new一个ArrayList它的初始容量就是10吗??不是10,是下面这个:
/** Shared empty array instance used for empty instances */ private static final Object[] EMPTY_ELEMENTDATA = {}; |
---|
也就是说在我们刚刚创建一个新的ArrayList对象的时候,是一个空的Object[] 数组,也就是说在饿哦们还没有向ArrayList中添加内容的时候,它是个空的Object[]数组,长度是0。当我们第一次往里面加的时候,它要扩容了,扩容到10。源码里面的注释部分也是这样解释的:
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
下面接着看源码:增加、删除、查找
/**
* 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) {
//这句话是扩容,传入的参数size+1(当前元素个数加1)的意思是当前这个容器它所应该具有的容量最小值,
ensureCapacityInternal(size + 1); // Increments modCount! 这个方法是负责扩容的,传入的参数是当前参数个数加1,这个值是个什么值呢?是这个容器所应该具有的容量的最小值。意思就是:
elementData[size++] = e;//容量够了以后,执行这句,将e存入size所对应的位置,然后size加1
return true; //是否添加成功了。
}
---------------------------------------------------------------------------------------------------------
//这里我们要看一下它这个扩容,我们要理解一下它的扩容规则。
//形式参数minCapacity表示的是容量的最小值
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //这个就是modify count,用来记录修改的次数
// overflow-conscious code
if (minCapacity - elementData.length > 0) //最小容量大于现有容量意思是该扩容了
grow(minCapacity);
}
----------------------------------------------------------------------------------------------------------
//扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length; //把数组原来的容量记录下来。
int newCapacity = oldCapacity + (oldCapacity >> 1);//新的容量,这个就是它的扩容规则,是原有容量的1.5倍
if (newCapacity - minCapacity < 0)
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);//它这种扩容的写法和我们写的栈的实现类的扩容写法是一样的。容器扩容完以后,还是elementDate引用去指向这个新的数组。
}
-----------------------------------------------------------------------------------------------------------
//删除的底层实现
public E remove(int index) { //根据索引进行删除
rangeCheck(index);//这句是做健壮性检测的,比如:你不能给我传负数,不能超过我的容量范围等等....
modCount++;
E oldValue = elementData(index);//获得要删除处索引的元素,
int numMoved = size - index - 1;//删除要进行大量的元素移动,这句就是要移动元素的个数
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,//利用数组复制来实现元素的移动底层还是for
numMoved); //不像我们用写for循环来进行移位。
elementData[--size] = null; // clear to let GC do its work //避免内存泄漏的,我们也写过类似的代码
return oldValue; //z将这个被删除的元素返回了。
}
-----------------------------------------------------------------------------------------------------------
//查找的底层实现
/**
* 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) {
rangeCheck(index);
return elementData(index);//
}
-----------------------------------------------------------------------------------------------------------
//查找的底层实现
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
-----------------------------------------------------------------------------------------------------------
//修改的底层实现
/**
* Replaces the element at the specified position in this list with
* the specified element.
*
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index); //拿到要被修改的原来的值
elementData[index] = element; //把一个新的放在这个位置上
return oldValue; //最后返回原来的值,我们一般在删除和修改的时候会把现有的元素的值返回,避免万一我要再用它一次。
}
//这个类中所有的涉及到索引的方法,都调用了rangeCheck()方法,如果超出它的个数了,抛出一个异常
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
8.总结:
ArrayList底层使用Object[]数组实现。初始容量为空的Object[]数组。当第一次添加元素的时候,(从0)扩容到默认容量为10.
每次扩容行为都会扩容到现有容量的1.5倍。
元素有序不唯一,可以有多个null。
该类是一个线程非安全的,效率比较高。
该类底层使用数组实现,所以优缺点和数组类型,比数组强于有封装,有自动扩容的方法。该类可以理解为带封装的数组容器类。
第三节 Vector
Vector 是List 实现的子类。出现的版本比较早,jdk1.0就出现了Vector类。
元素的特点,有序的(0序,添加的顺序。可重复),可以有多个null。
扩容的规则:每次扩容为现有容量的2倍。
这个类的功能基本上和ArrayList是一致的。Vector是线程安全的,效率比较低。ArrayList 出现在一定程度上是为了在线程安全的情况下使用ArrayList 来替代使用Vector,以提高软件的效率。
ArrayList中有的方法,Vector都有,Vector类出现的版本比较早,所以该类中定义了一些比较早期的方法。和现有的一些方法功能一样,名字不同。
import java.util.*;
/**
* 学习Vector的方法
*/
public class TestVector {
public static void main(String[] args) {
//创建一个Vector对象
Vector<String> v= new Vector<>();
//Vector的所有方法
//添加
v.addElement("a");
v.addElement("b");
v.addElement("c");
//插入
v.insertElementAt("d",3);
v.insertElementAt("e",4);
//修改
v.setElementAt("E",4);
//删除
v.removeElementAt(0);
v.removeElement("E");
//获得
System.out.println(v.elementAt(0));
//迭代 迭代器接口是一个泛型接口,泛型代表了要被遍历的元素的类型
Iterator<String> iterator=v.iterator();//迭代器泛型,它是一个泛型接口,要遍历啥,这里就传啥,返回的就是啥。
while (iterator.hasNext()){
iterator.next();
}
//枚举器用于遍历Vector,只定义了两个方法
Enumeration<String> e=v.elements();
while(e.hasMoreElements()){
String s = e.nextElement();
System.out.println(s);
}
System.out.println(v);
//ArrayList是否可以使用枚举器遍历
List<String> list=new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
//ArrayList可以使用枚举器来遍历,只是需要自己来写实现
Iterator<String> it=list.iterator();
//这是一个方法匿名内部子类
Enumeration<String> enumeration=new Enumeration<String>() {
@Override
public boolean hasMoreElements() {
return it.hasNext();
}
@Override
public String nextElement() {
return (String) it.next();
}
};
//使用枚举类来遍历
while(enumeration.hasMoreElements()){
String s=enumeration.nextElement();
System.out.println(s);
}
}
}
Vector的底层实现:
//Vector的类名
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
//这里省略。。。
}
//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;
/**
* The number of valid components in this {@code Vector} object.
* Components {@code elementData[0]} through
* {@code elementData[elementCount-1]} are the actual items.
*
* @serial
*/
protected int elementCount;
Java.util.Stack (栈)是Vector 的子类。但是这种继承的关系值得商榷。
Stack 的功能应该很单一,就是压栈和弹栈。
第四节LinkedList
1.特点:
该类是双向链表的具体的实现类,该类中包含了头结点和尾部结点的引用。First last。(LinkList是List的实现子类,除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列(队列只能操作两头双端的)或双端队列。)
该容器的元素是有序的(添加的顺序),不唯一,可以重复,可以有多个null。与ArrayList的元素特点一样。
底层是链表,所有元素的特点和链表一致。元素随机访问的效率比较低。根据内容访问元素效率也比较低。遍历元素效率也低,插入元素、删除元素效率比较高,不需要移动大量的元素,但是定位要删除和插入的位置的过程效率比较低。如果删除和插入元素的位置在链表的两头,效率还可以。
2.源码分析
LinkList是链表(双向链表)的java的具体实现类。处了链表还包含了队列和栈的功能, 我们一般情况下只把他当做双向链表的实现类。
我们说这个类是一个双向链表,我们在讲数据结构的时候知道,双向链表只要持有头结点和尾节点,就可以达到双向遍历双向访问。
size:是它的元素个数
Node这是个节点,是个LinkList的内部类。双向链表里面,每一个节点包含数据(数据被封装到Node里面了,数据类型就是E)、指向后继节点的next和指向前驱节点的prev。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
transient int size = 0;//元素的个数
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last; //默认值都是空,不像ArrayList需要先new 一个Object[] 数组出来
/**
* Constructs an empty list.
*/
public LinkedList() { //默认值都是空,我们在new一个时候是个空的,里面没有节点不像数组的底层实现要先new一个Object[]数组出来
}
-----------------------------------------------------------------------------------------------------------
//这是一个内部类(是一个私有的静态的内部类)
private static class Node<E> {//这个就是添加到链表里面的数据,被封装成了一个Node,包括数据,next和prev
E item;
Node<E> next; //也是Node类型
Node<E> prev; //也是Node类型
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
-----------------------------------------------------------------------------------------------------------
//尾部添加
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);//添加到末尾
else
linkBefore(element, node(index));
}
-----------------------------------------------------------------------------------------------------------
//尾部追加
/**
* Links e as last element.
*/
void linkLast(E e) {
final Node<E> l = last; //备份了last,为什么要备份,是因为后面改last了,所以要将原last备份
final Node<E> newNode = new Node<>(l, e, null); //new了一个Node,传入了三个参数,分别是prev、element、和 next。新节点的前驱节点指向last(即l),元素是e,后继节点指向null
last = newNode;//last指向了最后的一个节点(即新添加进来的)
/*
我们在尾部添加的时候,有几种情况:第一种是链表为空,我们添加一个数据的时候,这个数据要先被封装成Node,所以要先new一个Node出来生成一个Node对象,然后first要指向这个Node,last也要指向这个Node;第二种链表不为空,这个时候我还要添加一个数据进来,第一步肯定还是要创建一个Node出来,然后让原来的last的next指向这个新Node,新节点的pre指向原来的尾节点,然后再让last这个引用指向这个新节点。
*/
if (l == null) //这个链表为空
first = newNode;//让first也指向newNode,也就是说让first和last都指向newNode
else
l.next = newNode;//不为空的时候,让原有的last(即l)的next指向newNode
size++;
modCount++;
}
//在最前面追加
/**
* 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++;
}
----------------------------------------------------------------------------------------------------------
//获得指定索引的元素(这个链表也是有序号的,0序的,这个序号是人为的加上去的(数组是本身就有的,叫索引))
/**
* 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) {//获得指定序号的元素,只能通过头结点或者是尾节点通过不断的next或者previous进行找;
checkElementIndex(index);//这句是做安全性的检测的
return node(index).item; //返回的是节点的数据,即node(index).item
}
/**
* Returns the (non-null) Node at the specified element index.
*/
Node<E> node(int index) { //返回的是index这个位置上索引对应的Node节点
// assert isElementIndex(index);
//根据index的位置判断是从前往后找,还是从后往前找
if (index < (size >> 1)) { //(如果index小于size右移一位)意思是要找的节点在链表的前半部分
Node<E> x = first; //定义了一个局部变量等于first
for (int i = 0; i < index; i++)
x = x.next; //这一句的意思就是first节点进行不断的.next
return x;
} else {
Node<E> x = last; //从后往前找
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
-----------------------------------------------------------------------------------------------------------
//插入
/**
* 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));//否则,就是在元素node(index)(它返回的是index位置上的node节点)前面插入element。所以要把element封装到node中,插入到node(index)z这个位置,那么node(index)就要后移。
}
----------------------------------------------------------------------------------------------------------
/**
* Inserts element e before non-null Node succ.
*/
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev; //通过被插入位置的node获得它前驱节点的引用
final Node<E> newNode = new Node<>(pred, e, succ); //
succ.prev = newNode;
if (pred == null)
first = newNode;//如果是空,则新的节点是头结点了
else
pred.next = newNode;//不为空,也就是常规情况
size++;
modCount++;
}
-----------------------------------------------------------------------------------------------------------
//删除
/**
* 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));//这里面有一个node(index)返回的是序号位置的node
}
-----------------------------------------------------------------------------------------------------------
//删除
/**
* 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 效率测试
结论:
1.ArrayList 随机访问的效率远远的高于LinkedList
2.ArrayList 插入的效率和LinkedList基本一致。
3.如果一堆数据只操作数据的头和尾可以选择使用 LinkedList,其他的就用ArrayList。
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
* 效率测试
*/
public class TestEfficient {
public static void main(String[] args) {
test1();
test2();
}
//插入效率测试
static void test2(){
final int COUNT = 50000;
List<String> list0 = new ArrayList<>();
List<String> list1 = new LinkedList<>();
for (int i = 0; i <COUNT ; i++) {
list0.add("a");
list1.add("a");
}
long time = System.currentTimeMillis();
for (int i = 0; i <COUNT ; i++) {
list0.add(COUNT>>1,"a");
}
long cost = System.currentTimeMillis()-time;
System.out.println("array cost = "+cost);//623ms
time = System.currentTimeMillis();
for (int i = 0; i <COUNT ; i++) {
list1.add(COUNT>>1,"a");
}
cost = System.currentTimeMillis()-time;
System.out.println("linked cost = "+cost);//2248ms
}
//随机访问效率测试
static void test1(){
final int COUNT = 50000;
List<String> list0 = new ArrayList<>();
List<String> list1 = new LinkedList<>();
for (int i = 0; i <COUNT ; i++) {
list0.add("a");
list1.add("a");
}
long time = System.currentTimeMillis();
for (int i = 0; i <COUNT ; i++) {
list0.get(COUNT>>1);
}
long cost = System.currentTimeMillis()-time;
System.out.println("array cost = "+cost);//3ms
time = System.currentTimeMillis();
for (int i = 0; i <COUNT ; i++) {
list1.get(COUNT>>1);
}
cost = System.currentTimeMillis()-time;
System.out.println("linked cost = "+cost);//2334ms
}
}
今日练习:
1:实现:自定义ArrayList 容器类。底层使用数组实现。扩容规则150%。
功能:
add(Object o) 尾部追加
add(int index, Object o) 插入,不允许中间有空元素的插入。
remove(Object o) 根据内容删除
remove(int index) 根据索引删除
set(int index ,Object o):修改index位置的元素
get(int index) 获得指定位置的元素。
size(); 元素个数。
clear() 清空元素
import java.util.Arrays;
//2:实现:自定义ArrayList 容器类。底层使用数组实现。扩容规则150%。
// 功能:
// add(Object o) 尾部追加
// add(int index, Object o) 插入,不允许中间有空元素的插入。
// remove(Object o) 根据内容删除
// remove(int index) 根据索引删除
// set(int index ,Object o):修改index位置的元素
// get(int index) 获得指定位置的元素。
// size(); 元素个数。
// clear() 清空元素
public class MyArrayList {
//容器的初始化容量
public static final int DEFAULT_CAPACITY = 10;
private int size;//元素的个数
//底层使用的数据结构,数组
private Object[] elementsData;
public MyArrayList(){
size = 0;
elementsData = new Object[DEFAULT_CAPACITY];
}
//尾部添加元素
public void add(Object o){
if(isFull()){
int newLength = size * 3 >> 1;
elementsData = Arrays.copyOf(elementsData,newLength);
}
//将o放到size指向的元素位置。size++
elementsData[size++] = o;
}
/**
* 将o插入到index 的位置。元素的添加必须是连续的。
* @param index
* @param o
*/
public void add(int index,Object o){
checkIndex(index);
if(index == size){//尾部添加
add(o);
}else{//插入
//所有的index 后的元素整体后移一位元素的位置。
System.arraycopy(elementsData,index,elementsData,index + 1,size-index);
//将o赋值给 index位置的元素
elementsData[index] = o;
size ++;
}
}
/**
* 检查index是否满足需求
* @param index
*/
private void checkIndex(int index)throws IllegalArgumentException{
if(index < 0 || index > size) {
IllegalArgumentException e = new IllegalArgumentException("传入了非法的参数:" + index);
throw e;
}
}
/**
* 根据索引删除元素
* @param index
* @return
*/
public Object remove(int index)throws IllegalArgumentException{
if(index < 0 || index > size-1) {
IllegalArgumentException e = new IllegalArgumentException("传入了非法的参数:" + index);
throw e;
}
//先备份要被删除的元素
Object o = elementsData[index];
//移动元素的个数
int removeCount = size - index -1;
System.arraycopy(elementsData,index + 1, elementsData,index,removeCount);
size --;
elementsData[size] = null;
return o;
}
public Object remove(Object o){
//获得o 在 容器中的索引位置。
int index = getIndex(o);
if(index >= 0){
return remove(index);
}
return null;
}
/**
* 获得指定对象在容器中的索引位置
* @param o 查找的对象。
* @return o在容器中的索引 如果不存在返回 -1
*/
private int getIndex(Object o){
for (int i = 0; i < size; i++) {
if(o.equals(elementsData[i])){
return i;
}
}
return -1;
}
/**
* 设置指定的位置的元素的值。
* @param index
* @param o
*/
public void set(int index, Object o){
checkIndex(index);
if(index == size){
add(o);
}else{
elementsData[index] = o;
}
}
/**
* 获得index位置的元素的值。
* @param index
* @return
*/
public Object get(int index){
if(index < 0 || index > size-1) {
IllegalArgumentException e = new IllegalArgumentException("传入了非法的参数:" + index);
throw e;
}
return elementsData[index];
}
public boolean isFull(){
return size == elementsData.length;
}
public boolean isEmpty(){
return size == 0;
}
public String toString(){
return Arrays.toString(Arrays.copyOf(elementsData,size));
}
public int size(){
return size;
}
//清空容器
public void clear(){
for (int i = 0; i < size; i++) {
elementsData[i] = null;
}
size = 0;
}
}