ArrayList 1、ArrayList的底层 – 数组
数组结构的优缺点:
数组查询块,根据地址和索引直接获取元素
数组增删改慢,每次都需要创建新数组。且移动元素位置
2、ArrayList继承关系 1 2 3 4 public class ArrayList <E> extends AbstractList <E> implements List <E>, RandomAccess, Cloneable, java.io.Serializable { }
2.1、Serializable标记性接口 1、介绍
类的序列化有实现java.io.Serializable接口的类启用。不实现此接口的类将不会使任何状态序列化或反序列化。可序列化类的所有子类型都是可序列化的。序列化接口没有方法或字段,仅用于标识可串行化的语义。
序列化:将对象的数据写入到文件。
反序列化:将文件中对象的数据读取出来。
2、java.io.Serializable源码:
1 2 public interface Serializable { }
3、 Serializable的使用:
创建一个Student类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Student implements java .io.Serializable{ private String name; private Integer age; public Student () { } public Student (String name, Integer age) { this .name = name; this .age = age; } @Override public String toString () { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}' ; } }
一个测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public static void main (String[] args) throws IOException, ClassNotFoundException { writeObject(); readObject(); }private static void writeObject () throws IOException { ObjectOutputStream objectOutputStream = new ObjectOutputStream (new FileOutputStream ("obj.txt" )); Student student1 = new Student ("j" , 32 ); objectOutputStream.writeObject(student1); objectOutputStream.close(); }private static void readObject () throws IOException,ClassNotFoundException{ ObjectInput objectInput = new ObjectInputStream (new FileInputStream ("obj.txt" )); Student o = (Student)objectInput.readObject(); objectInput.close(); System.out.println(o); }
结果:
在控制台中打印出对象
我们可以测试,如果Student对象不继承Serializable接口,结果会怎样。
明显看出:NotSerializableException,没有序列化接口
2.2、Cloneable标记性接口 1、介绍
一个类实现Cloneable接口来指示Object.clone()方法。该方法对于该类的实例进行字段的复制时合法的。
在不实现Cloneable接口的实例上调用对象的克隆方法会导致异常CloneNotSupportedException。
克隆就是依据已经有的数据,创造一份新的完全一样的数据拷贝
2、Cloneable源码:
1 2 public interface Cloneable { }
3、克隆的前提条件
被克隆对象所在的类必须实现Cloneable接口
必须重写clone方法
4、clone的基本使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static void main (String[] args) { ArrayList<String> strings1 = new ArrayList <>(); strings1.add("1" ); strings1.add("2" ); strings1.add("3" ); strings1.add("4" ); strings1.add("5" ); Object strings2 = strings1.clone(); System.out.println(strings1 == strings2); System.out.println(strings1); System.out.println(strings2); }
5、clone底层实现
在ArrayList中克隆函数的重写:
1 2 3 4 5 6 7 8 9 10 11 public Object clone () { try { ArrayList<?> v = (ArrayList<?>) super .clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0 ; return v; } catch (CloneNotSupportedException e) { throw new InternalError (e); } }
ArrayList继承AbstractList,但super.clone();并不是AbstractList类中的,他是Object中的克隆方法,而这个方法的底层时通过C来写的
1 protected native Object clone () throws CloneNotSupportedException;
下面这句是克隆的主体:copyof是Arrays中的方法:
1 v.elementData = Arrays.copyOf(elementData, size);
我们具体来看copyof()方法:它返回了一个数组,调用重载函数
1 2 3 public static <T> T[] copyOf(T[] original, int newLength) { return (T[]) copyOf(original, newLength, original.getClass()); }
copyof()的重载函数:
如果(Object)newType == (Object)Object[].class 为 true,直接创建一个数组,如果不为真,调用
Array.newInstance()方法创建具有指定组件类型和尺寸的新数组。
System.arraycopy()方法:将指定源数组中的数组从指定位置复制到目标数组位置
1 2 3 4 5 6 7 8 9 public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T []> newType) { @SuppressWarnings("unchecked") T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object [newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); System.arraycopy(original, 0 , copy, 0 , Math.min(original.length, newLength)); return copy; }
6、案例:学生1姓名A,年龄18,将该对象的数据复制到另一个对象学生2中,并且此后学生1,2两个对象的数据不会互相影响。
方法一:浅拷贝:
在Student类中重写clone方法
注意:需要将权限修饰符改为public,将返回对象改为Student
1 2 3 4 @Override public Student clone () throws CloneNotSupportedException { return (Student) super .clone(); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public static void main (String[] args) throws CloneNotSupportedException { Student student = new Student ("A" ,18 ); Student student1 = student.clone(); System.out.println(student.hashCode() == student1.hashCode()); System.out.println(student); System.out.println(student1); student1.setName("C" ); System.out.println(student); System.out.println(student1); }
浅拷贝的局限性:
基本数据类型可以达到完全复制,而引用数据类型不可以
原因:在学生对象student被克隆的时候,引用数据类型仅仅是拷贝了一份引用,因此当克隆对象引用数据类型的值发生改变时,被克隆对象student的引用数据类型的值也会跟随改变。
也就是说,浅拷贝只是克隆了引用数据类型的地址。
我们新写一个类:Book类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Book { private String bookName; public Book (String bookName) { this .bookName = bookName; } public String getBookName () { return bookName; } public void setBookName (String bookName) { this .bookName = bookName; } @Override public String toString () { return "Book{" + "bookName='" + bookName + '\'' + '}' ; } }
在Student类中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class Student implements Cloneable { private String name; private Integer age; private Book book; public Student () { } public Student (String name, Integer age, Book book) { this .name = name; this .age = age; this .book = book; } @Override public String toString () { return "Student{" + "name='" + name + '\'' + ", age=" + age + ", book=" + book + '}' ; } @Override public Student clone () throws CloneNotSupportedException { return (Student) super .clone(); }
测试:我们发现修改Book的名称,两个学生的Book数据都会改变
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public static void main (String[] args) throws CloneNotSupportedException { Book chineseBook = new Book ("Chinese" ); Student student = new Student ("A" ,18 ,chineseBook); Student student1 = student.clone(); System.out.println(student.hashCode() == student1.hashCode()); System.out.println(student); System.out.println(student1); chineseBook.setBookName("Chinese1" ); System.out.println(student); System.out.println(student1); }
方法二:深拷贝:重写clone()方法
在Book中继承Cloneable接口:在Book类中重写clone方法:
1 2 3 4 @Override protected Object clone () throws CloneNotSupportedException { return super .clone(); }
在Student类中重写Clone方法
1 2 3 4 5 6 7 8 9 10 11 12 13 @Override public Student clone () throws CloneNotSupportedException { Student stu = (Student) super .clone(); Book book = (Book)this .book.clone(); stu.setBook(book); return stu; }
测试:同样一段代码:student的Book数据改变,但student1没有改变
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public static void main (String[] args) throws CloneNotSupportedException { Book chineseBook = new Book ("Chinese" ); Student student = new Student ("A" ,18 ,chineseBook); Student student1 = student.clone(); System.out.println(student.hashCode() == student1.hashCode()); System.out.println(student); System.out.println(student1); chineseBook.setBookName("Chinese1" ); System.out.println(student); System.out.println(student1); }
2.3、RandomAccess标记接口 标记接口有List实现,以表明它们支持快速(通常为恒定时间)随机访问。
此接口的主要目的时允许通用算法更改其行为,以便应用于随机访问列表或顺序访问列表时提供良好的性能。
2.4、AbstractList类 该类提供的骨干实现的List接口以最小化来实现该接口由一个“随机访问”数据存储备份所需的工作。
3、ArrayList源码 3.1、构造函数 1、空参构造
1 2 3 4 5 public ArrayList () { this .elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
2、构造具有指定初始容量的空列表
1 2 3 4 5 6 7 8 9 10 11 12 13 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); } }
3、构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public ArrayList (Collection<? extends E> c) { Object[] a = c.toArray(); if ((size = a.length) != 0 ) { if (c.getClass() == ArrayList.class) { elementData = a; } else { elementData = Arrays.copyOf(a, size, Object[].class); } } else { elementData = EMPTY_ELEMENTDATA; } }
3.2、add方法 1、将指定的元素追加到此列表的末尾
1 2 ArrayList<String> str = new ArrayList <>(5 ); str.add("A" );
源码:
1 2 3 4 5 6 public boolean add (E e) { ensureCapacityInternal(size + 1 ); elementData[size++] = e; return true ; }
1 2 3 4 5 6 private void ensureCapacityInternal (int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); }
1 2 3 4 5 6 7 8 private static int calculateCapacity (Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; }
1 2 3 4 5 6 7 8 private void ensureExplicitCapacity (int minCapacity) { modCount++; if (minCapacity - elementData.length > 0 ) grow(minCapacity); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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); }
我们在回到add()方法的源码中。
1 2 3 4 5 6 7 8 9 public boolean add (E e) { ensureCapacityInternal(size + 1 ); elementData[size++] = e; return true ; }
2、在此列表的指定位置插入指定元素
1 2 3 4 5 6 7 ArrayList<String> str = new ArrayList <>(); str.add("A" ); str.add("B" ); str.add("C" ); str.add(1 ,"D" );
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 public void add (int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1 ); System.arraycopy(elementData, index, elementData, index + 1 , size - index); elementData[index] = element; size++; }
1 2 3 4 private void rangeCheckForAdd (int index) { if (index > size || index < 0 ) throw new IndexOutOfBoundsException (outOfBoundsMsg(index)); }
关于System.arraycopy()方法,我们无法读到源码,但我们可以写一些例子来了解这个方法:
1 2 3 4 5 6 int [] ints = {1 ,2 ,3 ,0 ,0 ,0 }; System.arraycopy(ints,1 ,ints,2 ,2 );for (int anInt : ints) { System.out.print(anInt + "\t" ); }
3、按指定集合的Iterator返回的顺序将指定集合中的元素追加到此列表的末尾
1 2 3 4 5 6 7 8 9 10 11 12 ArrayList<String> str = new ArrayList <>(); ArrayList<String> str1 = new ArrayList <>(); str.add("A" ); str.add("B" ); str.add("C" ); str1.add("D" ); str1.add("E" ); str1.add("F" ); str.addAll(str1);
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 public boolean addAll (Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); System.arraycopy(a, 0 , elementData, size, numNew); size += numNew; return numNew != 0 ; }
4、将指定集合中的元素插入此列表中,从指定的位置开始。
1 2 3 4 5 6 7 8 9 10 11 12 ArrayList<String> str = new ArrayList <>(); ArrayList<String> str1 = new ArrayList <>(); str.add("A" ); str.add("B" ); str.add("C" ); str1.add("D" ); str1.add("E" ); str1.add("F" ); str.addAll(1 ,str1);
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public boolean addAll (int index, Collection<? extends E> c) { rangeCheckForAdd(index); Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); int numMoved = size - index; if (numMoved > 0 ) System.arraycopy(elementData, index, elementData, index + numNew, numMoved); System.arraycopy(a, 0 , elementData, index, numNew); size += numNew; return numNew != 0 ; }
3.3、set方法 1 2 3 4 5 6 7 ArrayList<String> str = new ArrayList <>(); str.add("A" ); str.add("B" ); str.add("C" ); str.set(1 ,"D" );
源码:
1 2 3 4 5 6 7 8 9 10 11 public E set (int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; }
3.4、get方法 1 2 3 4 5 6 7 ArrayList<String> str = new ArrayList <>(); str.add("A" ); str.add("B" ); str.add("C" ); str.get(1 );
源码:
1 2 3 4 5 public E get (int index) { rangeCheck(index); return elementData(index); }
3.5、toString方法 1 2 3 4 5 6 7 ArrayList<String> str = new ArrayList <>(); str.add("A" ); str.add("B" ); str.add("C" ); str.toString();
toString()方法是AbstractCollection类中的方法,它是AbstractList是父类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public String toString () { Iterator<E> it = iterator(); if (! it.hasNext()) return "[]" ; StringBuilder sb = new StringBuilder (); sb.append('[' ); for (;;) { E e = it.next(); sb.append(e == this ? "(this Collection)" : e); if (! it.hasNext()) return sb.append(']' ).toString(); sb.append(',' ).append(' ' ); } }
3.6、迭代器 1 2 3 4 5 6 7 8 9 10 11 ArrayList<String> str = new ArrayList <>(); str.add("A" ); str.add("B" ); str.add("C" ); Iterator<String> it = str.iterator();while (it.hasNext()){ System.out.println(it.next()); }
源码:
1 2 3 4 public Iterator<E> iterator () { return new Itr (); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 private class Itr implements Iterator <E> { int cursor; int lastRet = -1 ; int expectedModCount = modCount; Itr() {} 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 ; return (E) elementData[lastRet = i]; }
在迭代器中checkForComodification();方法有什么用呢?
下面举一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ArrayList<String> str = new ArrayList <>(); str.add("A" ); str.add("B" ); str.add("C" ); Iterator<String> it = str.iterator();while (it.hasNext()){ String s = it.next(); if (s.equals("A" )){ str.remove("A" ); } } System.out.println(str);
运行结果:
我们在api文档中查看这个异常:
当不允许这样的修改时,可以通过检测到对象的并发修改的方法来抛出此异常。
1 2 3 4 5 6 7 final void checkForComodification () { if (modCount != expectedModCount) throw new ConcurrentModificationException (); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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 ; }
1 2 3 4 5 6 7 8 9 10 11 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 ; }
但是:值得注意的是,如果我们删除的是倒数第二个元素:不会抛出异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ArrayList<String> str = new ArrayList <>(); str.add("A" ); str.add("B" ); str.add("C" ); Iterator<String> it = str.iterator();while (it.hasNext()){ String s = it.next(); if (s.equals("B" )){ str.remove("B" ); } } System.out.println(str);
这是因为在删除完后,arrayList中的长度变为了:2,在进行下一次迭代时,需要判断size和光标的大小:
1 2 3 4 public boolean hasNext () { return cursor != size; }
那么如果删除元素?
迭代器为我们提供了一个方法:remove():
从底层集合删除此迭代器返回的最后一个元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 (); } }
3.7、clear方法 1 2 3 4 5 6 7 ArrayList<String> str = new ArrayList <>(); str.add("A" ); str.add("B" ); str.add("C" ); str.clear();
源码:
1 2 3 4 5 6 7 8 9 10 11 12 public void clear () { modCount++; for (int i = 0 ; i < size; i++) elementData[i] = null ; size = 0 ; }
3.8、contains方法 如果此列表包含指定元素,则返回true
1 2 3 4 5 6 7 ArrayList<String> str = new ArrayList <>(); str.add("A" ); str.add("B" ); str.add("C" ); str.contains("A" );
源码:
1 2 3 4 public boolean contains (Object o) { return indexOf(o) >= 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 public int indexOf (Object o) { if (o == null ) { for (int i = 0 ; i < size; i++) if (elementData[i]==null ) return i; } else { for (int i = 0 ; i < size; i++) if (o.equals(elementData[i])) return i; } return -1 ; }
3.9、isEmpty方法 如果此列表不包含元素,返回true
1 2 3 4 5 6 7 ArrayList<String> str = new ArrayList <>(); str.add("A" ); str.add("B" ); str.add("C" ); str.isEmpty();
源码:
1 2 3 4 public boolean isEmpty () { return size == 0 ; }