Java通识基础24——集合类1
Collection
综述
集合类的概念——容器类
集合类的对象——容器本身
集合类对象的基本功能——
- 装对象——
add(对象)
- 取出对象——
get
- 删除对象
- 容器大小
- 查看容器是否为空——
empty
- 清空容器——
clear()
- 遍历容器
集合的根接口collection
collection
的三个子接口
Set
子类
- 基本等同于
collection
,但是集合元素不允许重复(不能装两个相同的对象) - 集合元素是无序的
- 实现类
HashSet
(真正无序)- 子类
LinkedHashSet
- 子类
- 接口
SortedSet
(字面意思,有序的Set集合)- 子类
TreeSet
- 子类
- 实现类
List
子类(Set
的反例)
- 集合元素可以重复
- 元素时有序的,元素都有索引
- 基于数组的实现类
ArrayList
- 基于链表的实现类
LinkedList
(既是队列,也是栈)
- 基于数组的实现类
Queue
子类
- 队列——先入先出(后面将解释)
- 提供子接口
Deque
- 该接口的实现类
ArrayDeque
- 继承实现类
LinkedList
(既是队列,也是栈)
- 该接口的实现类
- 提供子接口
三个基础的数据结构
数据结构介绍
数据结构——栈(Stack
)
形象的将栈理解为一个弹夹——后压进去的子弹先被打出来
push
(压栈)——将对象压入栈中
pop
(弹栈)——让对象从栈中弹出
综上所述,栈遵循先入后出的特征(只有一个出入口)
数据结构——队列(Queue
)
可以形象理解为单项轨道,特征是遵循这一个方向
对象先入先出(一个入口一个出口)、
数据结构双端队列(Deque
)
既可以作为栈,也可作为队列
有两个口既可以出也可以入
Collection
根接口的基本用法和基本方法
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
public class CollectionTest {
public static void main(String[] args) {
Collection<String> c=new HashSet<String>();//尖括号内叫泛型,限制该集合只能装指定类型的对象
Collection<String> c1=new HashSet<>();//菱形语法,功能与上一句相同
//该语句实现了向上转型,从HashSet向上转型为Collection
//装对象——add(对象)
c.add("Date");//上面泛型已经声明只能装String
c.add("wyd");
c.add("lsl");
c.add("ggg");
System.out.println(c);
//取出对象——get
//删除对象
c.remove("wyd");
System.out.println(c);
//容器大小
System.out.println(c.size());
//查看容器是否为空——empty
System.out.println(c.isEmpty());
//遍历容器(集合)
for (String g:c){
System.out.println(g);
}//用foreach循环进行遍历
for(Iterator<String> a=c.iterator();a.hasNext();){
String st=a.next();
System.out.println(st);
}//用Iterator遍历器进行遍历
Iterator<String> b=c.iterator();
while (b.hasNext()){
String st=b.next();
System.out.println(st);
}//用Iterator遍历器进行遍历
c.forEach(t-> System.out.println(c));//Lambda表达式遍历
//判断是否包含某一个对象
System.out.println(c.contains("wyd"));
System.out.println(c.contains("lsl"));
//清空容器——clear()
c.clear();
System.out.println(c);
}
}
Set
子接口
HashSet
实现类的特征与功能
特征
基本等同于collection
,所拥有的方法相同
但增加了两个要求
- 元素不可重复
- 元素无序
- 快速存读数据
最大的特征是速度快——Set
最常用的实现类是HashSet
import java.util.*;
public class Set {
public static void main(String[] args) {
HashSet<Integer> sets=new HashSet<>();
sets.add(2);
sets.add(3);
sets.add(6);
sets.add(0);
sets.add(2);//元素不能重复
sets.add(122);
System.out.println(sets);
}
}
/*
[0, 2, 3, 6, 122]顺序打乱并且不显示重复元素
*/
HashSet
判断两个元素是否相等
- 两个对象通过
equals
比较是否相等 - 两个对象的
HashCode()
返回值是否相等
该方法是给HashSet
、HashMap
等需要Hash
算法的程序调用的,作为该对象的标识
hashCode
方法
重写原则
- 所有参与
equals
比较的field
都需要参与HashCode
计算 - 计算出每一个
HashCode
的值。并且将其累加(乘质数,质数个数取决于变量数量)
重写目的——让equals
和hashCode
方法一致
import java.util.HashSet;
public class SetTest2 {
public static void main(String[] args) {
HashSet<Cat> m1=new HashSet<>();
m1.add(new Cat("black",2.2));
m1.add(new Cat("black",2.2));
System.out.println(m1);
}
}
class Cat {
private String color;
private Double weight;
public Cat(String color,double weight){
this.color=color;
this.weight=weight;
}
public void setColor(String color) {
this.color = color;
}
public void setWeight(double weight) {
this.weight = weight;
}
public double getWeight() {
return weight;
}
public String getColor() {
return color;
}
@Override
public String toString(){
return "Cat"+this.color+" "+this.weight;
}
@Override
public boolean equals(Object obj){
if(this==obj){
return true;
}
if (obj!=null&&obj.getClass()==Cat.class){
Cat target=(Cat)obj;
return this.color.equals(target.color)&&(this.weight-target.weight)<1e-6;
}
return false;
}
@Override
public int hashCode(){
int prime=31;//定义一个质数
return this.color.hashCode()*prime+this.weight.hashCode();
//计算出每一个Field的hashCode值并乘质数
//这里有两个变量,第一个乘一个质数(一般31)
}
}
/*
[Catblack 2.2]此时就保留了一个元素
*/
Java系统提供的类只要重写了equals
方法,一般也要重写hashCode
方法
HashSet
的底层实现机制详解
HashSet
的底层是基于Hash
表(本质是数组)实现的HashSet
的方法是基于HashMap
实现的
数组,在所有的数据结构中是最迅捷的——
数组变量保存了数组对象的首地址,这样查找很迅速
数组的第i个元素的地址=首地址+i*元素大小
注意,此处的i
从0开始,避免从1开始有减法参与运算(在计算机中,加法运算永远都比减法运算块),当程序需要取第i
个元素的时候,直接根据上式计算出来的地址取值
HashSet
存元素的步骤:HashSet
会先调用该元素的HashCode()
,该方法返回一个int值HashSet
会根据元素的HashCode()
方法的返回值计算该元素应该保存在HashSet
底层数组的什么位置
具体而言就是拿此元素的HashCode
方法的返回值对于数组长度取余
比如HashCode
值为100,数组长度为8,其余即为4。这个元素将被放在底层数组的第5位HashSet
会到底层数组的该位置去检查该位置是否有已有元素- 如果这个位置没有元素,就把新元素存到数组的该位置
- 如果这个位置有元素,就会形成链(旧元素会被新元素从原有的位置挤出去,新元素会给旧元素抛出橄榄枝手牵手形成链条的逻辑模型)
HashSet
取元素的步骤:HashSet
会先调用该元素的HashCode()
,该方法返回一个int值HashSet
会根据元素的HashCode()
方法的返回值计算该元素应该保存在HashSet
底层数组的什么位置HashSet
会到底层数组的该位置去检查该位置是否有需要的元素- 如果这个位置确实保存着我们需要找的元素,直接取出该元素即可
- 如果该位置的元素不是我们需要找的元素,需要遍历该位置形成的链逐个去找
链越长,查找的性能越差,而底层数组越大,形成链的概率越小
HashSet
的两个性能选项
HashSet(int initialCapacity, float loadFactor)
initialCapacity
——初始大小,也就是底层数组的大小,其默认值是16loadFactor
——负载因子——当元素个数除以数组长度大于等于负载因子的时候,此时HashSet
会将数组长度扩大一倍。比如负载因子是0.5,数组用了一半的时候就会扩展。所以当负载因子越大,数组的利用率越高,形成链的概率越大。反之,数组的利用率越低,形成链的概率越小
默认的负载因子大小为0.75
List
接口和ArrayList
实现类
特征
- 元素可以重复
- 元素有序,按照添加顺序排列,第一个元素的元素索引为0,第二个元素的元素索引为1…..
由于List
的集合元素是有索引(下标)的,因此程序可以根据下标来就行添加。获取,删除
形象的看,List
相当于一个竹子容器,每一节只能装一个对象
Set
集合支持的方法,List
集合也完全支持
import java.util.ArrayList;
import java.util.List;
public class ListTest {
public static void main(String[] args) {
List<String> a=new ArrayList<>();
a.add("233");
a.add("wyd");
a.add("wyd");
System.out.println(a);
//根据索引来添加
a.add(1,"gtn");
a.add(1,"lsl");
//根据索引替换
a.set(3,"wt");
System.out.println(a);
//根据索引获取
System.out.println(a.get(3));
//根据索引删除
a.remove(4);
System.out.println(a);
//获取元素位置
System.out.println(a.indexOf("gtn"));
System.out.println(a.indexOf("kkk"));//不存在将返回-1
//取出子集
List<String> b=a.subList(1,3);//前包后不包
System.out.println(b);
}
}
/*
[233, wyd, wyd]
[233, lsl, gtn, wt, wyd]
wt
[233, lsl, gtn, wt]
2
-1
[lsl, gtn]
*/
可以通过下标来遍历集合,原有的三种遍历方式也是可行的
for (int i=0;i<a.size();i++){
System.out.println(a.get(i));
}
List
集合类同样可以在初始化的时候指定底层数组的长度,默认长度为10
Deque
接口
代表双端队列——既是栈也是队列
ArrayDeque
基于数组实现,性能较好(块!已经代替了stack
)
使用场景
栈——
push(元素)
pop()
peek()
获取最顶端的元素但并不弹出
import java.util.ArrayDeque;
import java.util.Deque;
public class ArrayDequeTest {
public static void main(String[] args) {
Deque<String> stack=new ArrayDeque<>();
stack.push("wyd");
stack.push("sff");
stack.push("zsj");
//压入元素
System.out.println(stack);
System.out.println(stack.pop());
//先压的在下面,先弹最后压进去的“子弹”
System.out.println(stack.peek());
//显示最上面的元素
}
}
/*
[zsj, sff, wyd]
zsj
sff
*/
队列——
offer(元素)
:入队poll()
:出队peek()
:回去队头元素,但不出队
import java.util.ArrayDeque;
import java.util.Deque;
public class ArrayDequeTest {
public static void main(String[] args) {
Deque<String> stack=new ArrayDeque<>();
stack.offer("wyd");
stack.offer("sff");
stack.offer("zsj");
//压入元素
System.out.println(stack);
System.out.println(stack.poll());
//先压的在下面,先弹最后压进去的“子弹”
System.out.println(stack.peek());
//显示最上面的元素
}
}
/*
[wyd, sff, zsj]
wyd
sff
*/
List
有的方法Deque
同样也有
回顾
根接口——collection
Set
子接口——元素内容不可以重复;元素无序
List
子接口——元素内容可以重复;元素有序
ArrayList
实现类:基于数组实现——性能好LinkedList
实现类:功能强大性能差
Queue
子接口:队列;特征——FIFO先入先出
Set
子接口的子接口——SortedSet
- 实现类
HashSet
——基于hash
表,快速存取LinkedHashSet
子类——额外维护一条链的记住元素的添加顺序
Queue
子接口的子接口——Deque
,双端队列——既是栈,也是队列
Interator
接口——遍历器