目录
一方面,面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储。另一方面,使用Array存储对象方面具有一些弊端,而Java 集合就像一种容器,可以动态地把多个对象的引用放入容器中。
集合框架,设计好了大量的好用的数据结构:
线性表:数组、栈、队列、链表,哈希表,树,图:多维结构,矩阵
集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中)
数组在内存存储方面的特点:
数组初始化以后,长度就确定了。
数组声明的类型,就决定了进行元素初始化时的类型
数组在存储数据方面的弊端:
数组初始化以后,长度就不可变了,不便于扩展
数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高。 同时无法直接获取存储元素的个数
数组存储的数据是有序的、可以重复的。====>存储数据的特点单一
Java 集合类可以用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数组。解决数组存储数据方面的弊端。
集合的使用场景
应用在各种应用的数据列表中,如下图,等等
在Android客户端,将JSON对象或JSON数组 转换为Java对象或Java对 象构成的List
在服务器端,将Java对象或Java对象构 成的List转换为JSON对象 或JSON数组,
在数据库端拿到的是List对象
集合貌似看起来比较强大,它啥时用呢?当当对象多的时候,先进行存储。
集合本身是一个工具,它存放在java.util包中。
JDK最早的1.0版本中。提供的集合容器很少。升级到1.2版,为了更多的需求,出现了集合框架。有了更多的容器。可以完成不同的需求。
这些容器怎么区分?区分的方式:每一个容器的数据结构(数据存储的一种方式)不一样。
Java 集合框架图:
从上面的集合框架图可以看到,Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射。Collection 接口又有 3 种子类型,List、Set 和 Queue,再下面是一些抽象类,最后是具体实现类,常用的有ArrayList 、LinkedList、HashSet、LinkedHashSet、HashMap 、LinkedHashMap 等等。
集合框架是一个用来代表和操纵集合的统一架构。所有的集合框架都包含如下内容:
接口:是代表集合的抽象数据类型。例如 Collection、List、Set、Map 等。之所以定义多个接口,是为了以不同的方式操作集合对象
实现(类):是集合接口的具体实现。从本质上讲,它们是可重复使用的数据结构,例如:ArrayList、LinkedList、HashSet、HashMap。
算法:是实现集合接口的对象里的方法执行的一些有用的计算,例如:搜索和排序。这些算法被称为多态,那是因为相同的方法可以在相似的接口上有着不同的实现。
除了集合,该框架也定义了几个 Map 接口和类。Map 里存储的是键/值对。尽管 Map 不是集合,但是它们完全整合在集合中。
集合框架体系如图所示:
不同的线性容器进行不断的向上抽取,最后形成了一个集合框架,这个框架就是Collection接口。在 Collection接口定义着集合框架中最最共性的内容。在学习时:我们需要看最顶层怎么用, 创建底层对象即可。因为底层继承了父类中的所有功能。
Collection接口继承树:
Map接口继承树
Collection 接口是 List、Set 和 Queue 接口的父接口,该接口里定义的方法既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。
JDK不提供此接口的任何直接实现,而是提供更具体的子接口(如:Set和List) 实现。
Collection接口:单列数据,定义了存取一组对象的方法的集合
List:元素有序、可重复的集合,“动态”数组 --- 线性表
ArrayList、LinkedList、Vector
Queue:非连续、非顺序的存储结构 --- 队列
Set:元素无序、不可重复的集合 --- 哈希表
HashSet、TreeSet、LinkedHashSet
在 JDK 5.0 之前,Java 集合会丢失容器中所有对象的数据类型,把所有对象都当成 Object 类型处理;从 JDK 5.0 增加了泛型以后,Java 集合可以记住容器中对象的数据类型。
1、添加
2、获取有效元素的个数
3、清空集合
4、是否是空集合
5、是否包含某个元素
6、删除
7、取两个集合的交集
8、集合是否相等
9、转成对象数组
10、获取集合对象的哈希值
11、遍历
- package day19.exer;
-
- import java.util.*;
-
- public class CollectionTest {
- // 压制警告
- @SuppressWarnings({"rawtypes", "unchecked"})
- public static void main(String[] args) {
- // Collection是一个接口,不能创建实例
- Collection coll = new ArrayList();
- Collection coll1 = new ArrayList(2);
- coll1.add(12);
- coll1.add(21);
- // 添加一个元素
- coll.add("AA");
- coll.add(1000); // 自动装箱
- coll.add(new Date());
- // 添加另一个集合的元素
- coll.addAll(coll1);
- System.out.println(coll); // [AA, 123, Fri Aug 05 22:41:50 GMT+08:00 2022, 12, 21]
- // 获取有效元素的个数
- System.out.println(coll.size()); // 5
- // 清空集合
- coll1.clear();
- System.out.println(coll1); // []
- // 是否是空集合
- System.out.println(coll1.isEmpty()); // true
- // 是否包含某个元素
- System.out.println(coll.contains(new String("AA")));
- // true, 这里包含的原因是调用String中的equals, 比较两个字符串的值是否相等
- coll.add(new Person("小王", 12));
- System.out.println(coll.contains(new Person("小王", 12)));
- // false, 这里不包含的原因是调用元素的equals方法,比较的两个的内存地址
- System.out.println(coll.containsAll(coll1)); // true, 空集合默认包含在另一个集合中
- coll1.add(111);
- System.out.println(coll.containsAll(coll1)); // false
- // 删除元素
- coll.add("AA");
- System.out.println(coll); // [AA, 1000, Fri Aug 05 23:16:39 GMT+08:00 2022, 12, 21, day19.exer.Person@16795f1, AA]
- coll.remove("AA");
- System.out.println(coll); // [1000, Fri Aug 05 23:16:39 GMT+08:00 2022, 12, 21, day19.exer.Person@16795f1, AA]
- Collection coll2 = new ArrayList(2);
- coll2.add(1000);
- coll2.add("AA");
- coll.removeAll(coll2);
- System.out.println(coll); // [Fri Aug 05 23:16:39 GMT+08:00 2022, 12, 21, day19.exer.Person@16795f1]
- // 取两个集合的交集
- coll.add(111);
- System.out.println(coll); // [Fri Aug 05 23:24:44 GMT+08:00 2022, 12, 21, day19.exer.Person@16795f1, 111]
- coll1.add(222);
- System.out.println(coll1); // [111, 222]
- System.out.println(coll.retainAll(coll1)); // true
- System.out.println(coll); // [111], retainAll把交集的结果存在当前集合coll中,不影响coll1
- System.out.println(coll1); // [111, 222]
- // 集合是否相等
- System.out.println(coll.equals(coll1)); // false
- coll.add(222);
- System.out.println(coll); // [111, 222]
- System.out.println(coll.equals(coll1)); // true
- // 转成对象数组
- Object[] objArr = coll2.toArray();
- System.out.println(Arrays.toString(objArr)); // [1000, AA]
- // 获取集合对象的哈希值
- System.out.println(coll.hashCode()); // 4624
- // 遍历
- // foreach
- for (Object o : coll) {
- System.out.print(o + ", "); // 111, 222,
- }
- // 迭代器遍历迭代数据,foreach底层就是使用迭代器实现的
- Iterator iterator = coll.iterator();
- while (iterator.hasNext()) {
- System.out.print(iterator.next() + ", "); // 111, 222,
- }
- }
- }
-
- class Person {
- public String name;
- public int age;
-
- public Person(String name, int age) {
- this.name = name;
- this.age = age;
- }
-
- public Person() {
-
- }
-
- @Override
- public boolean equals(Object o) {
- System.out.println(o + " 调用Person对象的equals方法");
- return this == o;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(name, age);
- }
- }
集合的使用细节:
集合中,不能保存基本数据类型!如果一定要使用基本数据类型,但是jdk1.5以后可以这么写,则需要使用它们的包装类,所以存储的还是对象(基本数据类型包装类对象)。
集合中存储其实都是对象的地址。
删除基本数据类型,需要手动装箱,否则,int默认是下标,而不是数据本书
向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals()
存储时提升了Object。取出时要使用元素的特有内容,必须向下转型。
Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素
GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元 素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。类似于“公 交车上的售票员”、“火车上的乘务员”、“空姐”。
Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,那么所 有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了 Iterator接口的对象。
Iterator 仅用于遍历集合,Iterator 本身并不提供承装对象的能力。如果需要创建 Iterator 对象,则必须有一个被迭代的集合。
集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合 的第一个元素之前。
Iterator迭代器接口中的方法:
boolean hasNext():判断Iterator中是否存在下一个元素
E next():返回Iterator中的下一个元素
void remove():从基础集合中移除迭代器返回的最后一个元素(可选操作)。
- //hasNext():判断是否还有下一个元素
- while(iterator.hasNext()){
- //next():①指针下移 ②将下移以后集合位置上的元素返回
- System.out.println(iterator.next());
- }
在调用it.next()方法之前必须要调用it.hasNext()进行检测。若不调用,且下一条记录无效,直接调用it.next()会抛出NoSuchElementException异常。
注意:
Iterator可以删除集合的元素,但是是遍历过程中通过迭代器对象的remove方 法,不是集合对象的remove方法。
如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法, 再调用remove都会报IllegalStateException。
Java 5.0 提供了 foreach 循环迭代访问 Collection和数组。
遍历操作不需获取Collection或数组的长度,无需使用索引访问元素。
遍历集合的底层调用Iterator完成操作。
foreach还可以用来遍历数组。
语法格式:元素类型
- // for (要遍历的元素类型 自定义的元素名称 : 要遍历的结构对象名称)
- for (Object o : coll) {
- System.out.print(o);
- }
鉴于Java中数组用来存储数据的局限性,我们通常使用List替代数组
List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引
List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
JDK API中List接口的实现类常用的有:ArrayList、LinkedList和Vector
List除了从Collection集合继承的方法外,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 lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
Object remove(int index):移除指定index位置的元素,并返回此元素
Object set(int index, Object ele):设置指定index位置的元素为ele
List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex 位置的子集合
- public class ListText {
- public static void main(String[] args) {
-
- List l1 = new LinkedList();
- List l2 = new Vector();
- List l3 = new ArrayList();
-
- l3.add(",happy ");
- l3.add("new ");
- l3.add("years !");
- l3.add(0,"马克");
- Iterator it1 = l3.iterator();
- while (it1.hasNext()){
- Object obj1 = it1.next();
- System.out.print(obj1);
- }
- System.out.println();
- System.out.println(l3.get(0)+"?");
- l3.add(",happy ");
- System.out.println(l3.indexOf(",happy "));
- System.out.println(l3.lastIndexOf(",happy "));
- l3.remove(4); //删除下标为4的元素
- Iterator it2 = l3.iterator();
- while (it2.hasNext()){
- Object obj2 = it2.next();
- System.out.print(obj2);
- }
- System.out.println();
- l3.set(3,"years!!");
- Iterator it3 = l3.iterator();
- while (it3.hasNext()){
- Object obj3 = it3.next();
- System.out.print(obj3);
- }
- }
- }
ArrayList 类是一个可以动态修改的数组,与普通数组的区别就是它是没有固定大小的限制,我们可以添加或删除元素。ArrayList 继承了 AbstractList ,并实现了 List 接口。
ArrayList 类位于 java.util 包中,使用前需要引入它,语法格式如下:
- import java.util.ArrayList; // 引入 ArrayList 类
-
- ArrayList
objectName =new ArrayList<>(); // 初始化
ArrayList 是 List 接口的典型实现类、主要实现类;是线程不安全的,效率高;底层使用Object[] elementData存储
本质上,ArrayList是对象引用的一个”变长”数组
ArrayList的JDK1.8之前与之后的实现区别?
JDK1.7:ArrayList像饿汉式,直接创建一个初始容量为10的数组
JDK1.8:ArrayList像懒汉式,一开始创建一个长度为0的数组,当添加第一个元 素时再创建一个始容量为10的数组
Arrays.asList(…) 方法返回的 List 集合,既不是 ArrayList 实例,也不是 Vector 实例。
Arrays.asList(…) 返回值是一个固定长度的 List 集合
List与数组间的转换:
- // 集合 --->数组:toArray()
- Object[] arr = coll.toArray();
- for(int i = 0;i < arr.length;i++){
- System.out.println(arr[i]);
- }
-
- // 拓展:数组 --->集合:调用Arrays类的静态方法asList(T ... t)
- List
list = Arrays.asList(new String[]{"AA", "BB", "CC"}); - System.out.println(list);
-
- List arr1 = Arrays.asList(new int[]{123, 456});
- System.out.println(arr1.size());//1
-
- List arr2 = Arrays.asList(new Integer[]{123, 456});
- System.out.println(arr2.size());//2
常用方法:
增:add(Object obj)
删:remove(int index) / remove(Object obj)
改:set(int index, Object ele)
查:get(int index)
插:add(int index, Object ele)
长度:size()
遍历:① Iterator迭代器方式;② 增强for循环;③ 普通的循环
- List list = new ArrayList();
- list.add("AA");
- list.add(123); // 存储基本数据时,会自动装箱
- list.add(1);
- list.add(2);
- list.add(0, "BB");
- list.add(1, "呵呵呵");
- System.out.println(list);
- System.out.println(list.get(0));
- System.out.println(list.get(1));
- System.out.println(list.remove("AA"));
- System.out.println(list);
- // 更新值
- System.out.println(list.set(0, "test"));
- System.out.println(list);
-
- // 注意:删除基本数据类型,需要手动装箱
- // 否则,int默认是下标,而不是数据本书
- System.out.println(list.remove(Integer.valueOf(2)));
- System.out.println(list);
-
- System.out.println(list.contains("呵呵呵"));
-
- // 使用泛型,表示nums只能存储字符串
- List
nums = new ArrayList<>(); - nums.add("张三");
- nums.add("李四");
- nums.add("王五");
- nums.add("赵六");
- // System.out.println(nums.get(0));
-
- // 遍历list中的数据
- // for (int i = 0; i < nums.size(); i++) {
- // System.out.println(nums.get(i));
- // }
-
- // foreach
- // for (String str : nums) {
- // System.out.println(str);
- // }
-
- // 迭代器遍历迭代数据,foreach底层就是使用迭代器实现的
- // Iterator
it = nums.iterator(); - // while(it.hasNext()) {
- // System.out.println(it.next());
- // }
链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的地址。
对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高,底层使用双向链表存储
void addFirst(Object obj)
void addLast(Object obj)
Object getFirst()
Object getLast()
Object removeFirst()
Object rem oveLast()
LinkedList:双向链表,内部没有声明数组,而是定义了Node类型的first和last, 用于记录首末元素。同时,定义内部类Node,作为LinkedList中保存数据的基本结构。Node除了保存数据,还定义了两个变量:
prev变量记录前一个元素的位置
next变量记录下一个元素的位置
- private static class Node
{ - E item;
- Node
next; - Node
prev; -
- Node(Node
prev, E element, Node next) { - this.item = element;
- this.next = next;
- this.prev = prev;
- }
- }
Vector 是一个古老的集合,JDK1.0就有了。大多数操作与ArrayList 相同,区别之处在于Vector是线程安全的。
在各种list中,最好把ArrayList作为缺省选择。当插入、删除频繁时, 使用LinkedList;Vector总是比ArrayList慢,所以尽量避免使用。
void addElement(Object obj)
void insertElementAt(Object obj,int index)
void setElementAt(Object obj,int index)
void removeElement(Object obj)
void removeAllElements()
面试题:请问ArrayList/LinkedList/Vector的异同?谈谈你的理解?ArrayList底层 是什么?扩容机制?Vector和ArrayList的最大区别?
二者都线程不安全,相对线程安全的Vector,执行效率高。 此外,ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。对于 随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。对于新增 和删除操作add(特指插入)和remove,LinkedList比较占优势,因为ArrayList要移动数据。
Vector和ArrayList几乎是完全相同的,唯一的区别在于Vector是同步类(synchronized),属于 强同步类。因此开销就比ArrayList要大,访问要慢。正常情况下,大多数的Java程序员使用 ArrayList而不是Vector,因为同步完全可以由程序员自己来控制。Vector每次扩容请求其大 小的2倍空间,而ArrayList是1.5倍。Vector还有一个子类Stack。