List
概述
-
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) 根据下标修改指定位置的元素值
-
增删改查(english)
增:add,save new
删:delete、drop、remove
改:update、set、modify
查:find、get、query、select
ArrayList
-
默认初始化容量为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
构造方法
//创建一个初始化容量为10的集合
List list1 = new ArrayList();
//创建一个初始化容量为100的集合
List list2 = new ArrayList(100);
//创建一个HashSet
Collection c = new HashSet();
//往HashSet中添加元素
c.add(100);
c.add(300);
c.add(989);
c.add(150);
//通过这个构造方法就可以将HashSet集合转换成List集合。
List list3 = new ArrayList(c);
for (int i = 0; i < list3.size(); i++) {
System.out.println(list3.get(i));
}
LinkedList
单向链表结构
链表优点:随机增删元素效率较高。(因为增删元素不涉及到大量元素位移)
链表缺点:查询效率较低,每一次查找某个元素的时候都需要从头节点开始往下遍历,不能通过数学表达式的计算来找到被查找元素的内存地址
/*
单链表中的节点。
节点是单向链表中的中基本的单元
每一个节点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++;
}
无初始化容量
总结
- LinkedList集合是双向链表
- 对于链表数据接口来说,随机增删效率较高,检索效率较低
- 链表中的元素在空间存储中,内存地址不连续
Vector
-
底层是数组
-
初始化容量为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));
}