1、类集概述
类集设置的目的(常用类库的重点):
对象数组有那些问题?普通的对象数组的最大问题在于数组中的元素个数是固定的,不能动态的扩充大小,所以最早的时候可以通过链表实现一个动态对象数组。但是这样做毕竟太复杂了,所以在Java中为了方便用户操作各个数据结构,所以引入了类集的概念,有时候就可以把类集称为java对数据结构的实现。
在整个类集中的,这个概念是从JDK12 (Java2) 之后才正式引入的,最早也提供了很多的操作类,但是并没有完整的提出类集的完整概念。
类集中最大的几个操作接口: Collection、 Map、Iterator, 这三个接口为以后要使用的最重点的接口。 所有的类集操作的接口或类都在javautil包中。
Java类集结构图:.
- Collection:用来存储单值
- Map:用来存储双值
- Iterator:迭代器可以用最优的方式来存取数据
2、链表
数组的动态扩容很费内存。主要表现在数组存储中不利于删除数据,删除数据后数据不连续,操作前移费内存。但链表就解决了这个问题。
链表 [Linked List]:链表是由一组不必相连(不必相连:可以连续也可以不连续) 的内存结构(节点) ,按特定的顺序链接在一起的抽象数据类型。
补充:
抽象数据类型(Abstract Data Type [ADT]):表示数学中抽象出来的一些操作的集合。
内存结构:内存中的结构,如: struct、特殊内存块…等等之类;
数组和链表的区别和优缺点:
数组是一种连续存储线性结构,元素类型相同,大小相等。
数组的优点: 存取速度快
数组的缺点:
事先必须知道数组的长度
插入删除元素很慢
空间通常是有限制的
需要大块连续的内存块
插入删除元素的效率很低
链表是离散存储线性结构 n 个节点离散分配,彼此通过指针相连,每个节点只有一个前驱节点,每个节点只有一个后续节点,首节点没有前驱节点,尾节点没有后续节点。
链表优点:空间没有限制 插入删除元素很快
链表缺点:存取速度很慢
删除指针:后一个指针向前移!
2、链表共分几类?
链表常用的有 3 类: 单链表、双向链表、循环链表。
单链表:只知道下一数据的下标
双链表:既知道上一个数据的下标,也知道下一个数据的下标
循环链表:首尾相连
链表的核心操作集有 3 种:插入、删除、查找(遍历)
单链表
//链表节点
class Node{
Object data;
Node next;
}
单链表 [Linked List]:由各个内存结构通过一个 Next 指针链接在一起组成,每一个内存结构都存在后继内存结构(链尾除外) ,内存结构由数据域和 Next 指针域组成。
单链表实现图示:
解析:
Data 数据 + Next 指针,组成一个单链表的内存结构 ;
第一个内存结构称为 链头,最后一个内存结构称为 链尾;
链尾的 Next 指针设置为 NULL [指向空];
单链表的遍历方向单一(只能从链头一直遍历到链尾)
单链表操作集:
3、二叉树
二叉树是树的一种,每个节点最多可具有两个子树,即结点的度最大为 2(结点度:结点拥有的子树数)。
//二叉树节点
class Node{
Object data;
Node left;
Node right;
}
先序遍历(根-左-右):1-2-4-8-9-5-10-3-6-7
中序遍历:(左-根-右):8-4-9-2-10-5-1-6-3-7
后序遍历(左-右-根):8-9-4-10-5-2-6-7-3-1
4、Collection接口(重点)
Collection 接口是在整个 Java 类集中保存单值的最大操作父接口, 里面每次操作的时候都只能保存一个对象的数据。
此接口定义在 java.util 包中。
本接口中一共定义了 15 个方法, 那么此接口的全部子类或子接口就将全部继承以上接口中的方法。
但是, 在开发中不会直接使用 Collection 接口。 而使用其操作的子接口: List、 Set。
之所以有这样的明文规定, 也是在 JDK 1.2 之后才有的。 一开始在 EJB 中的最早模型中全部都是使用 Collection 操作的, 所以很早之前开发代码都是以 Collection 为准, 但是后来为了更加清楚的区分, 集合中是否允许有重复元素所以, SUN在其开源项目 —— PetShop(宠物商店) 中就开始推广 List (允许重复)和 Set (不允许重复)的使用。
5、List接口(重点)
在整个集合中 List 是 Collection 的子接口, 里面的所有内容都是允许重复的。
List 子接口的定义:
public interface List<E> extends Collection<E>
此接口上依然使用了泛型技术。 此接口对于 Collection 接口来讲有如下的扩充方法:
在 List 接口中有以上 10 个方法是对已有的 Collection 接口进行的扩充。
所以, 证明, List 接口拥有比 Collection 接口更多的操作方法。
了解了 List 接口之后, 那么该如何使用该接口呢? 需要找到此接口的实现类, 常用的实现类有如下几个:
· ArrayList(95%) 、 Vector(4%) 、 LinkedList(1%)
5.1、ArrayList(重点)
ArrayList 是 List 接口的子类, 此类的定义如下:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable
此类继承了 AbstractList 类。 AbstractList 是 List 接口的子类。 AbstractList 是个抽象类, 适配器设计模式。
范例: 增加及取得元素
package org.listdemo.arraylistdemo;
import java.util.ArrayList;
import java.util.List;
public class ArrayListDemo01 {
public static void main(String[] args) {
List<String> all = new ArrayList<String>(); // 实例化List对象, 并指定泛型类型
all.add("hello "); // 增加内容, 此方法从Collection接口继承而来
all.add(0, "LAMP ");// 增加内容, 此方法是List接口单独定义的
all.add("world"); // 增加内容, 此方法从Collection接口继承而来
System.out.println(all); // 打印all对象调用toString()方法
}
}
ArrayList的构造方法
构造器 | 描述 |
---|---|
ArrayList() | 构造一个初始容量为10的空列表。 |
ArrayList(int initialCapacity) | 构造具有指定初始容量的空列表。 |
ArrayList(Collection<? extends E> c) | 按照集合的迭代器返回的顺序构造一个包含指定集合元素的列表。 |
当用ArrayList() 创建一个长度为10的数组,当存储到11个数据时,会扩容1.5倍,知道扩容到足够位置,如果存储大量的数据,便会导致大量的内存空间被浪费,因此初次创建时需要创建大量的数据时,因使用ArrayList(int initialCapacity)的一参构造方法,指定存储的数量。
ArrayList通过无参构造器构造的集合在开始时长度是多少?
为0.,然后当存入数据后,会比较发现存不下,在将容量扩容到10
package com.java.arrayListDemo;
import java.util.ArrayList;
public class Demo1 {
public static void main(String[] args) {
ArrayList<Integer> arr = new ArrayList();
arr.add(100);
}
}
进入add方法的中:
public boolean add(E e) {
modCount++;
add(e, elementData, size);//add(要往集合中添加的内容,存数据的数组,数组的长度)
return true;//只要调用add方法,一定返回True,必定为true
}
再看下add算法
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)//目前存的数据跟数据长度一致,说明已经存满了
elementData = grow();//存满的话,调用扩容算法,扩容后将新的数组赋值给elementData
elementData[s] = e;//正常存
size = s + 1;//存完size(数组长度)+1
}
再看下add算法下的grow()算法
private Object[] grow() {
return grow(size + 1);//目前已存的数据量跟数组总长度是一致的,已经存满了,最少需要加一个
}
再看grow()算法的方法
//minCapacity已经加1了
private Object[] grow(int minCapacity) {
//将一个旧数组和新长度产生(copy)一个新数组
return elementData = Arrays.copyOf(elementData,//旧数组
newCapacity(minCapacity));//新长度
//旧数组的数据会给到新数组
}
再看下新长度如何产生
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;//旧数组的长度
//旧数组的长度+其右移一位(二进制,旧长度的0.5倍)
//新的 = 1.5倍的旧 = 1倍的旧 + 0.5倍的旧
int newCapacity = oldCapacity + (oldCapacity >> 1);
//扩容后的长度-加一后的长度<=0(当旧的=0、1、一组时)
if (newCapacity - minCapacity <= 0) {
//扩容后的长度小于数组加一,说明新扩容后的长度不够用
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
//如果 数组=默认数组(即只有第一次增加数组时)
// private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
return Math.max(DEFAULT_CAPACITY, minCapacity);//返回最大值
//private static final int DEFAULT_CAPACITY = 10;
if (minCapacity < 0) // overflow内存溢出,符号改变
throw new OutOfMemoryError();
return minCapacity;//需要多少,给多少
}//没进入if,新扩容后的长度够用
//新的长度<int类型的最大值-8
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE)//在这8个数的范围之中
//private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
? Integer.MAX_VALUE//返回int类型最大值,
: MAX_ARRAY_SIZE;//返回int类型的最大值-8
}
add存,get取
package com.java.arrayListDemo;
import java.util.ArrayList;
public class Demo1 {
public static void main(String[] args) {
ArrayList<Integer> arr = new ArrayList();
arr.add(100);
arr.add(200);
System.out.println(arr.get(0));
System.out.println(arr.get(1));
System.out.println(arr.get(2));
}
}
运行结果:
5.2、Vector(重点)
与 ArrayList 一样, Vector 本身也属于 List 接口的子类, 此类的定义如下:
public class Vector<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable
此类与 ArrayList 类一样, 都是 AbstractList 的子类。 所以, 此时的操作只要是 List 接口的子类就都按照 List 进行操作。
package org.listdemo.vectordemo;
import java.util.List;
import java.util.Vector;
public class VectorDemo01 {
public static void main(String[] args) {
List<String> all = new Vector<String>(); // 实例化List对象, 并指定泛型类型
all.add("hello "); // 增加内容, 此方法从Collection接口继承而来
all.add(0, "LAMP ");// 增加内容, 此方法是List接口单独定义的
all.add("world"); // 增加内容, 此方法从Collection接口继承而来
all.remove(1); // 根据索引删除内容, 此方法是List接口单独定义的
all.remove("world");// 删除指定的对象
System.out.print("集合中的内容是: ");
for (int x = 0; x < all.size(); x++) {
// size()方法从Collection接口继承而来
System.out.print(all.get(x) + "、 "); // 此方法是List接口单独定义的
}
}
}
构造器 | 描述 |
---|---|
Vector() | 构造一个空向量,使其内部数据数组的大小为 10 ,其标准容量增量为零。(ArrayLisy每次扩容1.5倍) |
Vector(int initialCapacity) | 构造一个具有指定初始容量且容量增量等于零的空向量。 |
Vector(int initialCapacity, int capacityIncrement) | 构造具有指定初始容量和容量增量的空向量。 |
Vector(Collection<? extends E> c) | 按照集合的迭代器返回的顺序构造一个包含指定集合元素的向量。 |
package com.java.arrayListDemo;
import java.util.Vector;
public class VectorDemo {
public static void main(String[] args) {
Vector<Integer> v = new Vector<Integer>();
//Vector :使用的是数组结构,对于增加删除慢,查找快.
v.add(10);
v.add(20);
System.out.println(v.get(1));
}
}
5.3、(重点)LinkedList
LinkedList可以当成栈和队列。
package com.java.arrayListDemo;
import java.util.LinkedList;
public class LinkListDemo {
public static void main(String[] args) {
//LinkedList :使用的是双向链表结构,对于增加删除快,查找慢.
LinkedList<Integer> data = new LinkedList<Integer>();
data.add(100);
data.add(200);
data.add(300);
data.add(400);
Integer num1 = data.removeFirst();//删除并返回第一个元素
System.out.println(num1);
Integer num2 = data.removeLast();//删除并返回最后一个元素
System.out.println(num2);
int num3 = data.size();//返回此列表中的元素数
System.out.println(num3);//2个
Object[] arr = data.toArray();//以适当的顺序(从第一个元素到最后一个元素)返回包含此列表中所有元素的数组。
for(int i=0;i<arr.length;i++){
System.out.println(arr[i]);
}
}
}
输出结果:
100
400
2
200
300
package com.java.arrayListDemo;
import java.util.LinkedList;
public class LinkListDemo2 {
public static void main(String[] args) {
//LinkedList :使用的是双向链表结构,对于增加删除快,查找慢.
LinkedList<Integer> data = new LinkedList<Integer>();
//压栈
data.push(100);
data.push(200);
data.push(300);
//将元素推送到此列表所表示的堆栈上。
//弹栈
Integer num1 = data.pop();//弹出此列表所代表的堆栈中的元素。换句话说,删除并返回此列表的第一个元素。
System.out.println(num1);
}
}
输出结果:300
6、迭代器Iterator与ListIterator
package com.java.arrayListDemo;
import java.util.ArrayList;
import java.util.Iterator;
public class IteratorDemo {
public static void main(String[] args) {
ArrayList<Integer> data =new ArrayList<>();
data.add(1);
data.add(2);
data.add(3);
data.add(4);
data.add(5);
data.add(6);
Iterator<Integer> iterator = data.iterator();
while(iterator.hasNext()){
Integer i = iterator.next();//返回迭代中的下一个元素。
System.out.println(i);
}
}
}
1
2
3
4
5
6
package com.java.arrayListDemo;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
public class ListIteratorDemo {
public static void main(String[] args) {
ArrayList<Integer> data =new ArrayList<>();
data.add(1);
data.add(2);
data.add(3);
data.add(4);
data.add(5);
data.add(6);
ListIterator<Integer> iterator = data.listIterator();
iterator.next();
iterator.next();//返回列表中的下一个元素并前进光标位置。
iterator.set(25);//指定的元素替换 next()或 previous()返回的最后一个元素(可选操作)。
iterator.previous();//返回列表中的上一个元素并向后移动光标位置。
iterator.previous();//返回两次回到光标为0
iterator.remove();//从列表中删除 next()或 previous() (可选操作)返回的最后一个元素。删除了0光标位置数组
while(iterator.hasNext()){
Integer num = iterator.next();
System.out.println(num);
}
}
}
25
3
4
5
6
7、forEach(增强for循环)
迭代数组或集合(collection)
package com.java.arrayListDemo;
import java.util.ArrayList;
public class ForEachDemo {
public static void main(String[] args) {
int arr[] = {
1,2,5,8,10};
/*for(int i=0;i<arr.length;i++){
System.out.println(arr[i]);
}*/
//ctrl+shift+/:注释
//注释玩用shift+enter换行
//forEach :增强For循环 ,最早出现在C#中 .
//用于迭代数组或集合(collection)
//语法:
//for(数据类型 变量名:集合或数组名){}
for(int data:arr){
System.out.println(data);
}
System.out.println("-----------------------");
ArrayList<String> data = new ArrayList<>();
data.add("锄禾日当午");
data.add("汗滴禾下土");
for (String s:data) {
System.out.println(s);
}
}
}
1
2
5
8
10
-----------------------
锄禾日当午
汗滴禾下土
8、Set
包含重复元素的集合。 更正式地说,集合不包含元素对e1和e2 ,使得e1.equals(e2)和最多一个null元素。 正如其名称所暗示的,此接口模拟数学 集 抽象。
该Set接口放置额外的约定,超过从继承Collection接口,所有构造函数的合同,而位于该合同add , equals和hashCode方法。 为方便起见,此处还包括其他继承方法的声明。 (这些声明附带的规范是针对Set接口定制的,但它们不包含任何其他规定。)
对构造函数的额外规定,毫不奇怪,所有构造函数必须创建一个不包含重复元素的集合(如上所定义)。
注意:如果将可变对象用作set元素,则必须非常小心。 如果在对象是集合中的元素时以影响equals比较的方式更改对象的值,则不指定集合的行为。 这种禁令的一个特例是,不允许将一个集合作为一个元素包含在内。
8.1、HashSet
此类实现Set接口,由哈希表(实际上是HashMap实例)支持
package com.java.arrayListDemo;
import java.util.HashSet;
import java.util.Iterator;
import java.util.ListIterator;
public class HashSetDemo {
public static void main(String[] args) {
//HashSet:散列存放(哈希表在学习HashMap集合时讲解)
HashSet<String> set = new HashSet<>();
boolean flag1 = set.add("锄禾日当午");
set.add("汗滴禾下土");
set.add("谁知盘中餐");
set.add("粒粒皆辛苦");
boolean flag2 = set.add("锄禾日当午");
System.out.println(flag1);
System.out.println(flag2);//不能添加重复元素1,所以返回值为添加失败
Iterator<String> iterator = set.iterator();
/* while(iterator.hasNext()){
String str = iterator.next();
System.out.println(str);
}*/
for (String s:set) {
System.out.println(s);
}
}
}
true
false
汗滴禾下土
谁知盘中餐
锄禾日当午
粒粒皆辛苦
因为HasSet是散列存放的,所以看到输出将结果就是无序的。
8.2、TreeSet与Comparable
SortedSet s = Collections.synchronizedSortedSet(new TreeSet(…)); 此类的iterator方法返回的迭代器是快速失败的:如果在创建迭代器之后的任何时间修改集合,除了通过迭代器自己的remove方法之外,迭代器将抛出ConcurrentModificationException 。 因此,在并发修改的情况下,迭代器快速而干净地失败,而不是在未来的未确定时间冒任意,非确定性行为的风险。
请注意,迭代器的快速失败行为无法得到保证,因为一般来说,在存在不同步的并发修改时,不可能做出任何硬性保证。 失败快速迭代器在尽力而为的基础上抛出ConcurrentModificationException。 因此,编写依赖于此异常的程序以确保其正确性是错误的: 迭代器的快速失败行为应该仅用于检测错误。
此类是Java Collections Framework的成员。
package com.java.arrayListDemo;
import java.util.TreeSet;
public class TreeSetDemo {
//TreeSet:
public static void main(String[] args) {
TreeSet<String> data = new TreeSet<>();
data.add("B");
data.add("C");
data.add("A");
data.add("D");
for (String s:data) {
System.out.println(s);
}
}
}
A
B
C
D
这里的有序是按系统顺序排的
package com.java.arrayListDemo;
import java.util.Objects;
import java.util.TreeSet;
public class TreeSetDemo2 {
public static void main(String[] args) {
TreeSet<Person> data = new TreeSet<>();
Person p1 = new Person("张三",18);
Person p2 = new Person("李四",19);
Person p3 = new Person("王五",19);//一样大的话就存储失败
data.add(p1);
data.add(p2);
data.add(p3);
for (Person p:data) {
System.out.println(p);
}
}
}
//实现Comparable接口进行自定义排序
class Person implements Comparable<Person>{
private String name;
private int age;
@Override
public int compareTo(Person o) {
//this 与0比较
//返回的数据:负数this小/零一样大/正数this大
if(this.age>o.age){
return 1 ;
}else if(this.age==o.age){
return 0;
}
return -1 ;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
9、Map
set集合使用map集合的单值存储,因此只用了map集合键值对中的键来存储数据。
将键映射到值的对象。 MAP不能包含重复的键; 每个键最多可以映射一个值。
此接口取代了Dictionary类,它是一个完全抽象的类而不是接口。
Map:Mapping映射
哈希桶中的数据量大于8时,从链表转换为红黑二叉树。
当哈希桶中的数据量减少到6时,从红黑二叉树转换为链表。
哈希桶中的数据量为7时,减少到6时,要从红黑二叉树转换为链表吗?
答:要分情况如果,如果存储情况本来就是链表,则不用
初始桶数量16,散列因子0. 75,如果大于散列因子0. 75就会扩容到32,存储十九会变为对32取余运算。
9、Map集合各子类区别分析
HashMap、Hashtable、ConcurrentHashMap的区别在于多线程,线程安全与否
HashMap :线程不安全,效率高。同时操作
Hashtable:线程安全,效率低。队列机制
ConcurrentHashMap:采用分段锁机制,保证线程安全,效率又比较高。操作时锁定集合内部的操作的哈希桶。
散列因子给的越高,存储的数据越多,越节省空间, 查询效率就越低
散列因子给的越低,存储的数据越少,越浪费空间, 查询效率就越高