List集合存储元素的特点:有序可重复
有序:List集合中的元素有下标。从零开始,以1递增。
可重复:存储一个1,还可以再存储
List继承了Collection,也就会有自己自带的方法
List中特有的常用的方法:
void add(int index, Object element) 往列表指定位置添加元素
使用不多,对ArraysList来说效率太低(涉及到元素的移动)
//创建list类型的集合 List myList = new ArrayList(); //添加元素 myList.add("A");//默认都是往集合末尾添加元素 myList.add("B"); myList.add("C"); myList.add("D"); //在列表指定位置添加元素 myList.add(1,"Queen"); //迭代 Iterator it = myList.iterator(); while (it.hasNext()){ System.out.println(it.next()); }Object get(int index) 根据下标获取元素
因为有下标,所以List有其独有的遍历方式 Set没有
for (int i = 0; i < myList.size(); i++) { System.out.println(myList.get(i)); } Object o = myList.get(1); System.out.println(o);int indexOf(Object o) 获取指定对象第一次出现处的索引
int lastIndexOf(Object o)获取指定对象最后一次出现出的索引
Object remove(int index)根据下标删除元素
Object set(int index, Object element) 根据下标修改指定位置的元素值
增:add,save new
删:delete、drop、remove
改:update、set、modify
查:find、get、query、select
默认初始化容量为10 (底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量变成10)
ArrayList数组底层是Object类型的数组Object[]
构造方法
new ArrayList() 或者 new ArrayList(20)
//默认初始化容量为10 List list1 = new ArrayList(); //集合的size()方法是获取当前集合中的元素的个数,不是获取集合的容量。 System.out.println(list1.size());//0 //指定初始化容量为20 List list2 = new ArrayList(20); //集合的size()方法是获取当前集合中的元素的个数,不是获取集合的容量。 System.out.println(list2.size());//0扩容
当ArrayList容量满了以后,会在原容量的基础上扩容1/2 原容量的1.5倍 (初始化容量为10 扩容后为15) 如果扩容的太小 会把容量变为minCapacity,如果扩容的太大了,会把MAX_ARRAY_SIZE=Integer.MAX_SIZE - 8;作为新的容量
优化:尽可能少的扩容。因为数组的扩容效率比较低,建议在使用ArrayList集合的时候预估元素的个数,给定一个初始化容量(每次扩容都会创建一个大容量的ArrayList,然后把所有的元素复制到新的ArrayList)
优点:检索效率比较高(每个元素占用空间大小相同,内存地址是连续的,知道首元素内存地址,知道下标,通过数学表达式计算出元素的内存地址,所以检索效率最高)
缺点:随机增删效率比较低,线程不安全,数组无法存储大数据量(很难找到一块非常巨大的连续的内存空间)
注意:在数组末尾添加元素效率比较高
面试题:这么多的集合中,你用那个集合比较多?
ArrayList,因为在实际的使用过程中,用户用来查询的操作会比较多 增删会比较少,大多数场景下会往数组末尾添加元素,效率不会受到影响,如果需要很多的增删,可以使用LinkedList,需要线程安全,那就使用Vector
链表优点:随机增删元素效率较高。(因为增删元素不涉及到大量元素位移)
链表缺点:查询效率较低,每一次查找某个元素的时候都需要从头节点开始往下遍历,不能通过数学表达式的计算来找到被查找元素的内存地址
/* 单链表中的节点。 节点是单向链表中的中基本的单元 每一个节点Node都有两个属性: 一个存储元素,一个存储下一个节点的内存地址 */ public class Node { //存储的数据 Object element; //下一个节点的内存地址 Node next; public Node() { } public Node(Object element, Node next) { this.element = element; this.next = next; } } public class Link { //创建头节点 Node header = new Node(null,null); Node linked = header; int size = 0; //增加元素的方法 public void add(Object obj){ Node node = new Node(obj, null); if(header.next == null){ header.next = node; }else { linked.next = node; } linked = node; size++; } //删除元素的方法 public void remove(Object obj){ if (size == 0){ System.out.println("没有元素"); return; } Node flag = address(obj); if (flag == null){ System.out.println("没有这个元素"); return; } while (flag != null){ if(flag.next.next == null){ flag.next = null; size--; }else { flag.next = flag.next.next; size--; } flag = address(obj); } } //修改元素的方法 public void set(Object obj1,Object obj2){ if (size == 0){ System.out.println("没有元素"); return; } Node flag = address(obj1); if (flag == null){ System.out.println("没有这个元素"); return; } while (flag != null){ flag.next.element = obj2; flag = address(obj1); } System.out.println("修改成功"); } //查询元素的方法 public void select(Object obj){ if (size == 0){ System.out.println("没有元素"); return; } Node flag = header.next; if(header.next.element == obj){ System.out.println(1); }else { flag = header.next.next; int i = 2; while (true){ if(flag.element != obj && flag.next != null){ flag = flag.next; i++; }else if (flag.element == obj){ System.out.println(i); break; }else { System.out.println("没有这个元素"); break; } } } } //寻找元素地址的方法,返回的是找到元素的前一个node的地址 public Node address(Object obj){ Node flag = header; if (flag.next == null){ return null; } while (true){ if(flag.next.element != obj && flag.next.next != null){ flag = flag.next; }else if (flag.next.element == obj){ return flag; }else { return null; } } } //打印链表的方法 public void out() { if (size == 0){ System.out.println("没有元素"); return; } Node flag = header.next; System.out.print("["); System.out.print(" " + flag.element); while (flag.next != null ){ System.out.print(", " + flag.next.element); flag = flag.next; } System.out.print(" ]"); } } //测试类 public class Test { public static void main(String[] args) { Link link = new Link(); loop: while(true){ System.out.println("请选择功能: \n1: 添加元素\n2: 删除元素\n3: 修改元素\n4: " + "查询元素\n5: 输出链表\n0: 退出系统"); Scanner sc = new Scanner(System.in); int a = sc.nextInt(); Scanner sc2 = new Scanner(System.in); switch (a){ case 1: System.out.println("请输入要添加元素的值:"); int x = sc2.nextInt(); link.add(x); break; case 2: System.out.println("请输入要删除元素的值:"); int z = sc2.nextInt(); link.remove(z); break ; case 3: System.out.println("请输入要修改的元素"); int v1 = sc2.nextInt(); System.out.println("将这个元素修改为:"); int v2 = sc2.nextInt(); link.set(v1,v2); break ; case 4: System.out.println("请输入要查找元素的值:"); int y = sc2.nextInt(); link.select(y); break; case 5: link.out(); break ; case 0:break loop; } } } }ArrayList:把检索发挥到极致(末尾添加元素的效率还是很高的)
LinkedList:把随机增删发挥到极致。
实际使用过程中,加元素都是往末尾添加,所以ArrayList用的比LinkedList多
内存图
//头插法 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; else f.prev = newNode; size++; modCount++; } //尾插法 void linkLast(E e) { //先获取尾 final Node<E> l = last; //创建新节点 让它指向尾节点 final Node<E> newNode = new Node<>(l, e, null); //让尾节点指向新创建的节点 last = newNode; if (l == null) first = newNode; else l.next = newNode; size++; modCount++; }无初始化容量
底层是数组
初始化容量为10
每次扩容为原来容量的2倍 (ArrayList为1.5倍)
Vector中所有的方法都带synchronized关键字,是线程安全的。效率较低使用较少
将一个线程不安全的ArrayList集合转换成线程安全的
使用集合工具类
java.util.Collection 集合接口
java.util.Collections 集合工具类
//创建一个ArrayList List myList = new ArrayList(); //编程线程安全的 List list = Collections.synchronizedCollection(myList); //myList就变成了线程安全的了 mylist.add(11); mylist.add(12); mylist.add(288);不管是LinkedList还是ArrayList,写代码并不需要关心是哪种集合
面向接口编程,调用的方法都是接口中的方法
//List list = new ArrayList();//这样写表示代码底层你使用了数组 List list = new LinkedList();//这样写表示代码底层你使用了双向链表 //以下这些方法面向的都是接口编程 list.add("123"); list.add("456"); list.add("789"); for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); }