【从0开始学习Java | 第14篇】集合(上)
文章目录
- 数组与集合的对比
- 集合框架
- Collection接口
- 常用方法
- Collection接口遍历元素的方式
- 方式1:使用 iterator(迭代器)
- 方式2 - for循环增强
- 方式3 - 普通for循环
- List接口
- List接口常用方法
- ArrayList 底层结构和源码分析
- 注意事项
- Vector 底层结构和源码剖析
- Vector 与 ArrayList 的比较
- LinkedList 底层结构和源码剖析
- ArrayList 与 LinkedList 比较
数组与集合的对比
数组:
我们使用数组实现增删改查操作:
import java.util.Arrays;public class ArrayUtil {private Object[] elements;private int size;public ArrayUtil() {this(16);}public ArrayUtil(int capacity) {elements = new Object[capacity];}public int size() {return size;}public void add(Object o) {if (size == elements.length) {//扩容当前容器容量的一半int length = elements.length + elements.length >> 1;elements = Arrays.copyOf(elements, length);}elements[size++] = o;}public void delete(Object o){if(o == null){return;}int index = -1;for(int i=0;i<size;i++){if(o.equals(elements[i])){index=i;break;}}System.arraycopy(elements,index+1,elements,index,size-index-1);size--;}public void update(int index,Object o){if(index<0||index>=size){throw new ArrayIndexOutOfBoundsException("下标越界了");}elements[index] = o;}public Object get(int index){if(index<0 || index>=size){throw new ArrayIndexOutOfBoundsException("下标越界了");}return elements[index];}
}
使用数组对元素进行增删改查时,需要我们自己编码实现。而集合是Java平台提供的,也能进行增删改查,已经有了具体的实现。我们不需要再去实现,直接使用Java平台提供的集合即可,这无疑减少了编程的工作量。同时Java平台提供的集合无论是在数据结构还是算法设计上都具有更优的性能
集合:
这就是我们为什么要学习集合,他有诸多数组所不具备的优点
集合框架
从上面的集合框架图可以看到,Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射。
集合框架是一个用来代表和操纵集合的统一架构。所有的集合框架都包含如下内容:
-
接口:是代表集合的抽象数据类型。例如 Collection、List、Set、Map 等。之所以定义多个接口,是为了以不同的方式操作集合对象
-
实现(类):是集合接口的具体实现。从本质上讲,它们是可重复使用的数据结构,例如:ArrayList、LinkedList、HashSet、HashMap。
-
算法:是实现集合接口的对象里的方法执行的一些有用的计算,例如:搜索和排序,这些算法实现了多态,那是因为相同的方法可以在相似的接口上有着不同的实现。
除了集合,该框架也定义了几个 Map 接口和类。Map 里存储的是键/值对。尽管 Map 不是集合,但是它们完全整合在集合中
集合框架 如图所示:
Collection接口
常用方法
方法 | 作用 |
---|---|
int size() | 获取集合的大小 |
boolean isEmpty() | 判断集合是否存有元素 |
boolean contains(Object o) | 判断集合中是否包含给定的元素 |
Iterator iterator() | 获取集合的迭代器 |
Object [ ] toArray() | 将集合转换为数组 |
T [ ] toArray(T [ ] a) | 将集合转换为给定类型的数组并将该数组返回 |
boolean add(E e) | 向集合中添加元素 |
boolean remove(Object o) | 从集合中移除给定的元素 |
void clear() | 清除集合中的元素 |
boolean containsAll(Collection<?> c) | 判断集合中是否包含给定的集合中的所有元素 |
boolean addAll(Collection<? extends E> c) | 将给定的集合的所有元素添加到集合中 |
运用示例
import java.util.ArrayList;
import java.util.List;public class CollectionMethod {@SuppressWarnings("all")public static void main(String[] args) {List list = new ArrayList();list.add("java");list.add(10); // list.add(new Integer(10))list.add(true);System.out.println(list);list.remove(0);list.remove(true);System.out.println(list);System.out.println(list.contains(10));System.out.println(list.size());System.out.println(list.isEmpty());list.clear();System.out.println(list);ArrayList list2 = new ArrayList();list2.add("红楼梦");list2.add("三国演义");list.addAll(list2);System.out.println(list);System.out.println(list.addAll(list2));list.add("聊斋");list.removeAll(list2);System.out.println(list);}
}
运行结果:
[java, 10, true]
[10]
true
1
false
[]
[红楼梦, 三国演义]
true
[聊斋]
Collection接口遍历元素的方式
方式1:使用 iterator(迭代器)
基本介绍:
- Iterator对象称为迭代器,主要用于遍历Collection集合中的元素
- 所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器
- Iterator仅用于遍历集合,它本身并不存放对象
注意:
在调用iterator.next()方法之前必须要调用iterator.hasNext()进行检测。若不调用,且下一条记录无效,直接调用iterator.next()会抛出NoSuchElementException异常
举例:
package com;import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;public class CollectionIterator {public static void main(String[] args){Collection col = new ArrayList();col.add(new Book("三国演义","罗贯中",10.1));col.add(new Book("红楼梦","曹雪芹",34.6));col.add(123);System.out.println(col);Iterator iterator = col.iterator();while(iterator.hasNext()){System.out.println(iterator.next());}}}class Book{private String name;private String author;private double price;public Book(String name, String author, double price) {this.name = name;this.author = author;this.price = price;}
}
运行结果:
[Book{name='三国演义', author='罗贯中', price=10.1}, Book{name='红楼梦', author='曹雪芹', price=34.6}, 123]
Book{name='三国演义', author='罗贯中', price=10.1}
Book{name='红楼梦', author='曹雪芹', price=34.6}
123
方式2 - for循环增强
增强for循环就是简化版的iterator,本质都是一样的
基本语法:
for(元素类型:集合名或数组名){
访问元素
}
例如:
for (Object o : col) {System.out.println(o);
}int[] a = {1, 2, 3, 4, 5};
for (int i : a) {System.out.print(i + " ");
}
方式3 - 普通for循环
for (int i = 0; i < list.size(); i++) {System.out.println(list.get(i));
}
List接口
List 接口是 Collection 接口的子接口
- List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复
- List集合中的每个元素都有其对应的顺序索引,即支持索引
- List容器中的元素都对应一个整数型的序号记载在其容器的位置,可以根据序号存取容器中的元素
- 常用的有:ArrayLIst、LinkedList 和 Vector
List接口常用方法
方法 | 作用 |
---|---|
void add(int index, Object ele) | 在index位置插入ele元素 |
boolean addAll(int index, Collection eles) | 从index位置开始将 eles中的所有元素添加进来 |
Object get(int index) | 获取指定index位置的元素 |
int indexOf(Object obj) | 返回obj在集合中首次出现的位置 |
int lastlndexOf(Object obj) | 返回obj在当前集合中末次出现的位置 |
Object remove(int index) | 移除指定index位置的元素,并返回此元素 |
Object set(int index, Object ele) | 设置指定index位置的元素为ele,相当于是替换. |
List subList(int fromlndex, int tolndex) | 返回从fromlndex到 tolndex位置的子集合 |
方法演示:
package com;import java.util.ArrayList;
import java.util.List;public class List_ {public static void main(String[] args){List list = new ArrayList();list.add("张三丰");list.add("林七夜");// 在index = 1的位置插入一个对象list.add(1,"江梦南");System.out.println(list);List list2 = new ArrayList();list2.add("java");list2.add("cpp");list2.add("java");//从索引为1的位置加入list2的所有元素list.addAll(1,list2);System.out.println(list);System.out.println(list.indexOf("java"));System.out.println(list.lastIndexOf("java"));list.remove(0);System.out.println(list);list.set(1,"玛丽");System.out.println(list);// 返回的元素下标[0,2)的List relist = list.subList(0,2);System.out.println("relist = "+relist);}
}
运行结果:
[张三丰, 江梦南, 林七夜]
[张三丰, java, cpp, java, 江梦南, 林七夜]
1
3
[java, cpp, java, 江梦南, 林七夜]
[java, 玛丽, java, 江梦南, 林七夜]
relist = [java, 玛丽]
ArrayList 底层结构和源码分析
- ArrayList中维护了一个Object类型的数 elementData
- 当创建对象时,如果使用的是无参构造器,则初始elementData容量为0(jdk7是10)
- 当添加元素时: 先判断是否需要扩容,如果需要扩容,则调用grow方法,否则直接添加元素到合适位置
- 如果使用的是无参构造器,如果第一次添加,需要扩容的话,则扩容elementData为10,如果需要再次扩容的话,则扩容elementData为1.5倍
- 如果使用的是指定容量capacity的构造器,则初始elementData容量为capacity
- 如果使用的是指定容量capacity的构造器,如果需要扩容,则直接扩容elementData为1.5倍
注意事项
- ArrayList 是由 数组 来实现数据存储的
- ArrayList 基本等同 Vector,不过 ArrayList 是 线程不安全,所以在多线程情况下,不建议使用ArrayList
Vector 底层结构和源码剖析
- Vector底层也是一个 对象数组、:protected Object [ ] elementData
- Vector 是线程同步的,即 线程安全
Vector 与 ArrayList 的比较
底层结构 | 线程安全(同步)效率 | 扩容倍数 | |
---|---|---|---|
ArrayList | 可变数组 | 不安全,效率高 | 如果有参构造1.5倍 如果是无参构造 1. 第一次10 2. 第二次开始按1.5倍扩容 |
Vector | 可变数组 | 安全,效率不高 | 如果是无参,默认10,满后,就按2倍扩容 如果指定大小,则每次按2倍扩容 |
LinkedList 底层结构和源码剖析
- LinkedList 底层维护了一个 双向链表
- LinkedList 中维护了两个属性 first 和 last,分别只想 首节点 和 尾结点,其中通过 prev 指向前一个,通过 next 指向后一个节点,最终实现双向链表
- 所以LinkedList的元素的添加和删除,不是通过数组来完成的,相对来说效率较高
双向链表图示:
双向链表模拟示例:
package com;public class LinkedList_ {public static void main(String[] args) {Node jack = new Node("jack");Node Tom = new Node("Tom");Node Amy = new Node("Amy");jack.next = Tom;Tom.next = Amy;Amy.prev = Tom;Tom.prev = jack;Node now = jack;while(now != null){System.out.println(now);now = now.next;}System.out.println("-----------");now = Amy;while(now != null){System.out.println(now);now = now.prev;}}
}class Node{public Object item;public Node prev;public Node next;public Node(String name){this.item = name;}public String toString() {return "Node{" +"name='" + item + '\'' +'}';}
}
运行结果:
Node{name='jack'}
Node{name='Tom'}
Node{name='Amy'}
-----------
Node{name='Amy'}
Node{name='Tom'}
Node{name='jack'}
ArrayList 与 LinkedList 比较
底层结构 | 增删的效率 | 改查的效率 | |
---|---|---|---|
ArrayList | 可变数组 | 较低 数组扩容 | 较高 |
LinkedList | 双向链表 | 较高,通过链表追加 | 较低 |
如何选择 ArrayList 和 LinkedList:
- 如果 改查 的操作较多,选择 ArrayList
- 如果 增删 的操作多,选择 LinkedList
- 一般来说,在程序中,80% ~ 90%都是查询,因此大部分情况下选择ArrayList
如果我的内容对你有帮助,请 点赞 , 评论 , 收藏 。创作不易,大家的支持就是我坚持下去的动力!