数组、ArrayList、链表、LinkedList
数组
数组 | ||||
数组类型 |
不可重复 无序(线性查找) |
可重复(找到第一个即可) 无序(线性查找) |
不可重复 有序(二分查找) |
可重复(找到第一个即可) 有序(二分查找) |
插入 | O(N) |
O(1) |
O(logN+N) | O(logN+N) |
查询 | O(N) | O(N) | O(logN) | O(logN) |
删除(无洞) | O(N) | O(N) | O(lonN+N) | O(logN+N) |
总结 | 可重复无序插入快、下标已知更新查找快;查找删除慢、大小固定 | 查找快;插入删除慢、大小固定 | ||
应用 | 员工表,雇用解雇不经常发生 | |||
java数组 | 无序、可重复;插入快、查询删除慢、大小固定;如果已知下标,更新查找快 | |||
ArrayList | 大小可以扩展;但这是以牺牲效率为代价的 | |||
Vector | 大小可以扩展;但这也是以牺牲效率为代价的 |
java 数组(无序、可重复)
已知下标查找更新快O(1)
String str = strs[1];
strs[1] = "花";
查找慢O(N)
int index = findChar("花", strs);
删除慢O(N)
deleteChar("花", strs);
中部插入慢O(N)
insertCharWithMiddle("兴", 1, strs);
大小固定
public static void main(String[] args) { String[] strs = {"中", "华", "人", "民", "共", "和", "国", null, null, null, null}; print(strs); // 已知下标查找更新快 System.out.println(strs[1]); strs[1] = "花"; print(strs); // 查找慢,需要花费O(N)的时间 int index = findChar("花", strs); if (index == strs.length) { System.out.println("Can't find this char"); } else { System.out.println("Find this char"); } // 删除慢,需要花费O(N)的时间 deleteChar("花", strs); print(strs); // 中部插入慢,需要花费O(N)的时间 insertCharWithMiddle("兴", 1, strs); print(strs); } private static void insertCharWithMiddle(String str, int index, String[] strs) { for (int i = strs.length - 2; i >= index; i--) { strs[i + 1] = strs[i]; } strs[index] = str; } private static void deleteChar(String str, String[] strs) { int index = findChar(str, strs); if (index != strs.length) { for (int i = index; i < strs.length - 2; i++) { strs[i] = strs[i + 1]; } strs[strs.length - 1] = null; } } public static int findChar(String str, String[] strs) { for (int i = 0; i < strs.length; i++) { if (strs[i].equals(str)) { return i; } } return strs.length; } public static void print(String[] strs) { System.out.println(Arrays.asList(strs)); }
ArrayList
末尾插入快,已知下标查找快更新快
一个参数的add("xxx")方法效率高O(1)
get(1)方法效率高O(1)——已知下标查找快
set(1, "xxx")方法效率高O(1)——已知下标更新快
中部插入、查询、删除慢
add(1, "xxx")方法效率低O(N)——中部插入
contains、indexOf方法效率低O(N)——查询慢
remove(1),remove("xxx")方法效率低O(N)——删除慢
数组固定大小,虽然ArrayList可以自动扩展,但是以牺牲效率为代价的。
public static void main(String[] args) { List<String> list = new ArrayList<>(8); // add方法效率高O(1) list.add("中"); list.add("华"); list.add("人"); list.add("民"); list.add("共"); list.add("和"); list.add("国"); System.out.println(list); // get(1),方法效率高O(1),如果知道下标查找快 System.out.println(list.get(1)); // add(1, "xxx")方法效率低O(N),中部插入慢 list.add(1, "花"); System.out.println(list); // 删除慢O(N) list.remove(1); list.remove("人"); System.out.println(list); // contains、indexOf方法都比较慢,需要O(N)的时间 System.out.println(list.contains("中"));; System.out.println(list.indexOf("中")); }
ArrayList容量扩展源码分析
package java.util; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.UnaryOperator; import sun.misc.SharedSecrets; public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { // 默认容量是10 private static final int DEFAULT_CAPACITY = 10; // 默认最大容量是MAX_ARRAY_SIZE,实际可以扩展至Integer的最大值 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; public boolean add(E e) { // ensure /ɪn'ʃʊə/ 确保 Capacity /kəˈpæsəti/ 容量 ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; // 原来数组的元素个数加上新集合的容量 ensureCapacityInternal(size + numNew); // Increments modCount System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; } private void ensureCapacityInternal(int minCapacity) { if (elementData == EMPTY_ELEMENTDATA) { // System.out.println(Math.max(10, 11)); 输出11,比较两个数字,返回大的数字 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { // 父类的一个成员变量,应该是修改次数的记录 modCount++; // 数组容量不够 if (minCapacity - elementData.length > 0) // grow /grəʊ/ 扩大 grow(minCapacity); } private void grow(int minCapacity) { int oldCapacity = elementData.length; // System.out.println(20 >> 1); 结果是20的二分之一,10 // 1、扩展后的数组是原来数组加上原来数组的一半,适用于add(E e)方法 // add(int index, E e)指定的下标越界会报异常,下标必须正确,不存在扩容 int newCapacity = oldCapacity + (oldCapacity >> 1); // 2、扩展后的数组是指定的下标值,比如原有容量是10,addAll一个有8个元素的集合 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 3、扩展后的数组是Integer的最大值,默认的最大值是Integer.MAX_VALUE - 8 // private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 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; } }
链表
除非频繁通过下标访问各个数据,否则都可以使用链表代替数组
链表也可以是有序的无序的,可重复的不可重复的
简单的一个链表定义
class Link { private long id; // data private String name; // data private byte age; // data private String address; // data private Link next; } class LinkList { private Link first; }
最后一个元素的next引用是null
单链表
insertFirst();
deleteFirst();
isEmpty(); // 是否为空
find(); // 遍历,查找指定Link
delete(); // 遍历,删除指定Link
insertAfter(); // 遍历,插入
双端链表
新增对最后一个Link的引用
insertLast();
表头多次插入会颠倒Link插入的顺序;表尾多次插入会保持Link插入的顺序
双端链表也不能提高删除最后一个链接点的效率
链表的效率
表头插入查询删除快,O(1)
中部插入查询删除慢,需要O(N)次比较;在数组中执行这些操作也需要O(N)次比较,但是链表仍然要快一些,因为链表不需要移动元素,只需要比较,而复制时间大于比较时间
有序链表
只有insert()方法与无序链表中的insert()方法不同
效率:插入删除O(N),删除最小值O(1)
双向链表
每个Link多了一个指向前一个元素的引用
第一个元素指向前一个元素的引用是null
双向链表的缺点是每次插入或删除一个Link的时候,要处理四个链接点的引用,而不是两个
双向链表不必是双端链表
deleteLast();
链表迭代器
实现从链接点到链接点步进,以提高效率
java LinkedList
java里的LinkedList是一个双端链表、双向链表。
public static void main(String[] args) { LinkedList<String> strings = new LinkedList<>(); strings.add("1");// 末尾添加 strings.add(1,"2");// 遍历,for循环的i和index比较,在Node里并没有成员变量index strings.addFirst("3"); strings.addLast("4"); strings.contains("5");// 遍历 strings.element();//返回第1项 strings.get(1);// 遍历,for循环的i和index比较 strings.getFirst(); strings.getLast(); strings.indexOf("6");// 遍历,for循环里index递增并返回 strings.offer("7");// 末尾添加 strings.offerFirst("8");// 表头添加 strings.offerLast("9");// 末尾添加 strings.peek();// 返回第1项 strings.peekFirst(); strings.peekLast(); strings.poll(); strings.pollFirst(); strings.pollLast(); strings.pop();// 弹出第1项 strings.push("");// 添加到第1项 strings.remove(); //删除第1项 strings.remove(1); strings.remove("10"); strings.removeFirst(); strings.removeLast(); }
java ArrayList和LinkedList
ArrayList底层是一个无序的可重复的数组,LinkedList底层是一个双端双向链表。
除非频繁地通过下标查询数据,否则都可以使用LinkedList来代替;LinkedList不需要扩容,直接在链表末尾添加元素,如果是添加一个集合,使用for循环。
首尾查询插入删除
ArrayList尾部插入快O(1)
LinkedList首尾插入快O(1)
中部查询插入删除
ArrayList中部插入删除O(N),N
LinkedList中部插入删除O(N),N/2