You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

737 lines
25 KiB

3 years ago
---
title: java容器
author: TianZD
top: true
cover: true
toc: true
mathjax: false
summary: java容器学习笔记,粗略学了一下,没有参考价值
tags:
- java
- 容器
- 学习笔记
categories:
- java
reprintPolicy: cc_by
abbrlink: 17ea147a
date: 2022-04-29 11:00:40
coverImg:
img:
password:
---
[TOC]
# 泛型
JDK1.5(即5.0)后增加,帮助我们建立类型安全的集合
**本质是数据类型的参数化**:
1. 把类型当作是参数一样进行传递;
2. <数据类型>只能是引用类型
好处:
1. 代码可读性更好,不用强制转换
2. 程序更加安全,只要编译时间没有警告,运行时期就不会出现ClassCastException异常;
## 类型擦除
编码时采用泛型写的参数类型,编译器会在编译时去掉,即“类型擦除”,类型参数再编译后会被替换成Object,运行时虚拟机并不知道泛型。
## 定义泛型
可以使用任何字符表示标识符,一般用下面的:
| 泛型标记 | 对应单词 | 说明 |
| -------- | -------- | ------------------------------ |
| E | Element | 在容器中使用,表示容器中的元素 |
| T | Type | 表示普通的java类 |
| K | Key | 表示键,如map中的键key |
| V | Value | 表示值 |
| N | Number | 表示数值类型 |
| ? | | 表示不确定的java类型 |
```java
//定义泛型类
public class Generic<T>{
private T flag;
public void setFlag(T flag){
this.flag = flag;
}
public T getFlag(){
return this.flag;
}
}
```
```java
//泛型方法
public <泛型表示符号> void getName(泛型表示符号 name){
}
public <泛型表示符号> 泛型表示符号 getName(泛型表示符号 name){
}
```
```java
//静态方法定义,静态方法中不能使用类定义的泛型
public static<泛型表示符号> void getName(泛型表示符号 name){
}
public static<泛型表示符号> 泛型表示符号 getName(泛型表示符号 name){
}
```
```java
//通配符?,表示类型不确定的
public void showFlag(Generic<?> generic){
}
//通配符上限限定,如下,表示只能是number或者其子类
public void showFlag(Generic<? extend Number> generic){
}
//通配符下限限定,如下,表示只能是Integer或者其父类
public void showFlag(Generic<? super Integer> generic){
}
```
## 泛型总结
泛型主要用于编译阶段,编译生成的class文件不包含泛型中的类型信息,类型参数再编译后会被替换成Object
1. 基本类型不能用于泛型,但是可以用其对应的包装类
`Test<int> t;错误,可以用Test<Integer>t;`
2. 不能通过类型参数创建对象
T elm = new T();错误
# 容器
用来容纳和管理数据。
## 单例集合
将数据一个一个的进行存储;Collection接口,以单个数据未单位进行存储;
包括:
1. List接口:存储有序,可重复,“动态”数组,实现:ArrayList类、LinkedList类、Vector类
2. Set接口,存储无序,不可重复,数学中的“集合”,HashSet、TreeSet
## 双例集合
基于key与value的结构存储数据,Map接口,数学中的函数y=f(x)
# 单例集合
# Collection
Collection接口时单例集合的根接口,包括两个子接口List、Set接口
抽象方法:
| 方法 | 说明 |
| ----------------------------------- | ----------------------------- |
| boolean add(Object e) | 增加元素 |
| boolean remove(Object e) | 删除 |
| Boolean contains(Object e) | 是否包含 |
| int size() | 元素数量 |
| Boolean isEmpty() | 是否为空 |
| void clear() | 清空所有元素 |
| Iterator iterator() | 获取迭代器,用于遍历所有元素 |
| Boolean containsAll(Collection c) | 判断是否包含C容器中的所有元素 |
| Boolean addAll(Collection c) | 将c中所有元素加到该容器 |
| Boolean removeAll(c) | 移出和c容器中都包含的元素 |
| Boolean retainAll(c) | 移除c中没有的元素 |
| Object[] toArray() | 转化成Object数组 |
# list接口
有序、可重复,有序只是存储有顺序
| 方法 | 说明 |
| ------------------------------ | -------------------------------------------- |
| void add(int index,Object e | 再指定位置插入元素,其余元素后移一位 |
| Object set(int index, Object e | 修改指定位置的元素,原来位置的元素的值返回 |
| Object get(int index) | 返回指定位置的元素 |
| Object remove(int index) | 删除元素,并返回删除的元素 |
| int indexOf(Object o) | 返回第一匹配元素的索引,若无,则返回-1 |
| int lastIndexOf(Object o) | 返回最后一个匹配到的元素索引,若无,则返回-1 |
## ArrayList容器类
LIst接口的实现类,是List存储特征的具体实现,
底层使用数组实现的存储,**特点:查询效率高(使用数组实现),增删效率低(增删后其余元素的索引都要变),线程不安全。**
## 方法使用
```java
package container;
import java.util.ArrayList;
import java.util.List;
/**
* test ArrayList
*/
public class ArrayListTest {
public static void main(String[] args) {
//实例化,通过list引用指向ArrayList对象
List<String> list = new ArrayList< >();
//添加元素
System.out.println(list.add("tian")); //通过collection接口中的方法添加元素,并返回true
list.add(1,"zhen"); //通过List接口方法添加元素
list.add(2,"dong"); //通过List接口方法添加元素
//获取元素
//System.out.println(list.get(0)); //返回第一个元素
for(int i = 0;i< list.size();i++){
System.out.println(list.get(i));
}
//判断是否包含某元素
System.out.println("是否包含tian:"+list.contains("tian"));
//查找元素位置
System.out.println(list.indexOf("tian")); //返回第一次出现tian的位置
System.out.println(list.lastIndexOf("tian")); //返回最后一次出现tian的位置
//删除元素
String str1 = list.remove(0); //删除第一个元素并返回,List定义,删除指定位置
System.out.println("删除的元素:"+ str1);
boolean flag = list.remove("zhen");//删除指定元素,collection方法
//替换元素
String str2 = list.set(0, "Tian");
System.out.println("被替换额元素:"+str2);
//判断是否为空
System.out.println(list.isEmpty());
//清空容器
list.clear();
//判断是否为空
System.out.println(list.isEmpty());
for(String i : list){ //增强for循环
System.out.println(i);
}
}
}
```
```java
//转换为Object[]数组,不能将转换的数组做强制类型转换,只能强制类型转换单个元素,不能是数组
Object[] obj1 = list.toArray();
for(Object str : obj1){
System.out.println(str);
}
//转换泛型类型数组,指定类型的数组
String[] str2 = list.toArray(new String[list.size()]);
for(String i:str2){
System.out.println(i);
}
```
## ArrayList相关
**数组初始化采用延迟初始化,首先创先长度为0的空数组,使用时再分配长度10的。扩容采用1.5倍**
1. 那我们本身就有数组了,为什么要用ArrayList呢?、
**原生的数组会有一个特点:你在使用的时候必须要为它创建大小,而ArrayList不用。**
2. ArrayList是怎么实现的吧,为什么ArrayList就不用创建大小呢?
**其实是这样的,我看过源码。当我们new ArrayList()的时候,默认会有一个空的Object数组,大小为0。当我们第一次add添加数据的时候,会给这个数组初始化一个大小,这个大小默认值为10;**
数组的大小是固定的,而ArrayList的大小是可变的;因为ArrayList是实现了**动态扩容**的,假设我们给定数组的大小是10,要往这个数组里边填充元素,我们只能添加10个元素。而ArrayList不一样,ArrayList我们在使用的时候可以往里边添加20个,30个,甚至更多的元素。
3. ArrayList怎样实现动态扩容的?
使用ArrayList在每一次add的时候,它都会先去计算这个数组够不够空间,如果空间是够的,那直接追加上去就好了。如果不够,那就得扩容。在源码里边,有个**grow**方法,每一次扩原来的**1.5**倍。比如说,初始化的值是10嘛。现在我第11个元素要进来了,发现这个数组的空间不够了,所以会扩到15;空间扩完容之后,会调用**arraycopy**来对数组进行拷贝。
## vector容器类
vector底层也是用数组实现的,相关的方法都加了同步检查,因此是”线程安全,效率低“
相关方法见Arraylist
**初始化采用立即初始化,扩容采用2倍。**
## Stack容器
**Stack栈容器,是vector的一个子类,实现了标准的后进先出**通过5个方法对vector进行扩展,允许将向量视为堆栈。
**栈中元素的位置从上往下,从1开始,不是从0
**
方法:
| 方法 | 用途 |
| -------------------- | -------------------------------------------- |
| Boolean empty() | 测试是否为空 |
| E peek() | 看这个堆栈的顶部的对象,并没有从堆栈中删除它 |
| E pop() | 删除顶部的对象,并返回该对象的值函数 |
| E push() | 把一个项目放到堆栈的顶部 |
| int search(Object o) | 返回元素在栈中的位置,没有则-1 |
```java
package container;
import java.util.Stack;
/**
* test stack
*/
public class StackTest {
public static void main(String[] args) {
//实例化,不能用list引用,因为stack中定义了新的方法
Stack<String > stack = new Stack<>();
//将元素添加到栈容器中,压栈
String a = stack.push("tian");
//System.out.println(a);
stack.push("zhen");
stack.push("dong");
//获取元素,后进先出,只能从栈顶取pop()
String pop = stack.pop();
System.out.println("删除的元素为:"+pop);
//测试是否为空empty()
System.out.println(stack.empty());
//返回栈顶元素
System.out.println("此时栈顶元素为"+stack.peek());
//查看元素的位置search(),注意:栈中元素的位置是从1开始的,不是从0
System.out.println("tian的位置:"+stack.search("tian"));
}
}
```
## LinkedList容器类
**底层用双向链表实现的存储,特点:查询效率低,增删效率高,线程不安全**
有序,可重复的
由于 LinkedList 基于链表实现,存储元素过程中,无需像 ArrayList 那样进行扩容。但有得必有失,LinkedList 存储元素的节点需要额外的空间存储前驱和后继的引用。另一方面,LinkedList 在链表头部和尾部插入效率比较高,但在指定位置进行插入时,效率一般。原因是,在指定位置插入需要定位到该位置处的节点,此操作的时间复杂度为 O(N)。
特有方法:
| 方法 | 说明 |
| ------------------ | -------------------- |
| void addFirst(E e) | 插入到开头 |
| void addLast(E e) | 插入到末尾 |
| getFirst() | 返回第一个元素 |
| getLast() | 返回最后一个元素 |
| removeFirst() | 移除第一个元素并返回 |
| removeLast() | 移除最后一个并返回 |
| E pop() | 等效于removeFirst() |
| void push(E e) | 等效于addFirst(E e) |
| boolean isEmpty() | 判断是否为空 |
```java
LinkedList<String> dataList = new LinkedList<>(); // 创建 LinkedList
dataList.add("test"); // 添加数据
dataList.add(1, "test1"); // 指定位置,添加数据
dataList.addFirst("first"); // 添加数据到头部
dataList.addLast("last"); // 添加数据到尾部
dataList.get(0); // 获取指定位置数据
dataList.getFirst(); // 获取头部数据
dataList.getLast(); // 获取尾部数据
dataList.remove(1); // 移除指定位置的数据
dataList.removeFirst(); // 移除头部数据
dataList.removeLast(); // 移除尾部数据
dataList.clear(); // 清空数据
```
# set接口
set继承自collection,没有新增方法。
无序,不可重复,无序是指set中的元素没有索引,只能遍历查找;不可重复值不要允许加入重复的元素。
常用的实现类:HashSet和TreeSet,一般用HashSet.
## Haseset容器类
Hashset无重复、无序的,是线程不安全的,允许有null元素。采用哈希算法实现,底层实际用HashMap实现,因此**查询和增删效率较高**。
>**无序**:底层用hashmap存储元素,hashmap底层用的是数组和链表实现元素的存储。元素在数组中存放时(初始化为长度16),并不是有序存放的也不是随机的,而是对元素的哈希值进行运算决定元素在数组中的位置。
>**不重复**:当两个元素的哈希值进行运算后得到相同的在数组中的位置时,会调用equals()方法判断两个元素是否相同,如果相同,则不会添加,如果不同,则会使用单向链表保存该元素。
```java
package container;
import java.util.HashSet;
import java.util.Set;
public class HashSetTest {
public static void main(String[] args) {
//实例化HashSet
Set<String> s1 = new HashSet<>();
//添加元素
s1.add("tian");
s1.add("zhen");
s1.add("dong");
//获取元素,set中没有索引,没有对应的get方法
for(String s : s1){
System.out.println(s); //输出结果和添加元素的顺序无关
}
System.out.println("-----------");
//删除元素
s1.remove("dong");
for(String s : s1){
System.out.println(s); //输出结果和添加元素的顺序无关
}
//返回元素个数
System.out.println("-----------");
System.out.println(s1.size());
}
}
```
**存储自定义类对象,需要在类中重写equals()和hashCode()**
```java
package container;
import java.util.Objects;
public class Test {
private String u1;
private int a;
public String getU1() {
return u1;
}
public void setU1(String u1) {
this.u1 = u1;
}
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
public Test(String u1, int a) {
this.u1 = u1;
this.a = a;
}
public Test() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Test test = (Test) o;
return a == test.a && Objects.equals(u1, test.u1);
}
//重新hashCode
@Override
public int hashCode() {
return Objects.hash(u1, a);
}
public static void main(String[] args) {
Test t1 = new Test("tian",12);
Test t2 = new Test("tian",12);
System.out.println(t1.hashCode());
System.out.println(t2.hashCode());
}
}
```
## TreeSet容器类
>可以对元素进行排序的容器,底层用TreeMap实现,内部位置了一个简化版的TreeMap,通过key存储Set元素,内部需要对元素进行排序,因此需要给定排序规则
排序规则:
1. 通过元素自身实现比较规则,元素自身定义了比较规则;
2. 通过比较器指定比较规则。
**1.自定义比较规则**
```java
public class Users extend comparable<Users>{
...
...
..
//定义比较规则
//整数:大,负数:小,0:相等
//重写compareTo
public int compareTo(Users o){
if(this.age>o.getUserage()){
return 1;
}
if(this.age==o.getUserage()){
this.username.compareTo(o.username);//username是字符串,String中已经定义了compareTo()
}
return -1;
}
}
```
**2.通过比较器实现比较规则**
创建一个比较器,然后再实例化TreeSet时把比较器传递进去
```java
//创建比较器
public class UsersComparator implements Comparator<Users>{
//定义比较规则
//重写
public int compare(Users o1,Users o2){
if(o1.getUserage>o2.getUserage){
return 1;
}
return -1;
}
}
....
//实例化TreeSet
Set<Users> s1 = new TreeSet<>( new UsersComparator);
```
# 双例集合
## Map接口
>Map接口中的元素是成对出现的,由键-值两个部分组成,键不可重复,但是值可以重复
Map常用方法:
| 方法 | 说明 |
| ----------------------------------- | ---------------------------------------------------------- |
| V put(K key,V value) | 添加元素对,如果key已有值,则会替换,并会返回被替换的value |
| void putAll(Map m) | 从指定map中复制映射关系到此map中 |
| V remove(Object key) | 删除key对应的value并返回value |
| V get(Object key) | 获取key对应的value |
| Boolean containsKey(Object key) | 判断是否包含该key |
| boolean containsValue(Object value) | 判断是否包含该value |
| Set keySet() | 获取map中所有的key,并存储的set中 |
| Set<Map.Entry<K,V>> entrySet() | 返回一个Set基于Map.Entry类型包含Map中所有映射 |
| void clear() | 删除map中所有的映射 |
## HashMap容器类
> 采用hash算法实现,是map接口最常用的实现类,底层采用了哈希表存储数据,要求键不能重复,如果发生重复,新的值会替换旧的值,在查找、删除、修改方面都有比较高的效率
```java
package container;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* 获取hashmap中key对应值的三中方法
*/
public class HashMapTest {
public static void main(String[] args) {
//实例化hashmap
Map<String ,String> map1 = new HashMap<>();
Map<String,String> map2 =new HashMap<>();
//添加元素
map1.put("a","b");
//替换a对应的值并返回被替换的值,若未发生替换,则返回空
map1.put("a","A");
map1.put("b","B");
map1.put("c","C");
map2.put("d","D");
//合并map2到map1,如果有key相同的,map2中的会覆盖map1中的值
map1.putAll(map2);
//方法1,通过get方法
System.out.println("==========方法一============");
System.out.println("a--------"+map1.get("a"));
System.out.println("b--------"+map1.get("b"));
System.out.println("c--------"+map1.get("c"));
//方法二,通过keySet
System.out.println("==========方法二============");
Set<String> set1 = map1.keySet();
for (String s:set1){
System.out.println(s+"-------"+map1.get(s));
}
//方法三,通过Map.entry,entry是map中的子接口,有getKey和getValue两个方法
System.out.println("==========方法三=======");
Set<Map.Entry<String,String>> set2 = map1.entrySet();
for(Map.Entry<String,String> entry:set2){
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key+"-------"+value);
}
}
}
/*
==========方法一============
a--------A
b--------B
c--------C
==========方法二============
a-------A
b-------B
c-------C
==========方法三=======
a-------A
b-------B
c-------C
```
## hashmap底层存储
>hashmap底层实现采用了哈希表;数据结构中由数组和链表实现对数据的存储,各自特点如下:
1. 数组:占用空间连续,寻址容易查询速度看,但是增删效率低
2. 链表:占用空间不连续,寻址困难,查询速度慢,但是增删效率搞
>哈希表本质是数组+链表,查询和增删效率高
1. 初始化数组,延迟初始化,调用时通过resize方法生成2<<4=16的数组长度并进行0.75的扩容将值传到NODE<K,V>节点类中,再存储到数组中
2. 计算hash值:调用key对象的hashcode方法计算key的hashcode值,根据hashcode值计算hash值(值在[0,数组长度-1]之间),公式:hash值=hashcode &(数组长度-1)
3. 添加元素,根据计算的hash值,放在数组的相应索引下,如果该位置已有索引:如果两个key值相同,则进行覆盖,如果不相同,则在该位置形成单向链表
4. 如果链表长度大于8,则转换成红黑树,如果红黑树小于6,则转换为链表
## TreeMap
>TreeMap和HashMap同样实现了map接口。HashMap效率要高于TreeMap,但是TreeMap是可以对键进行排序的,底层基于红黑树实现
使用TreeMap要给定排序规则:
1. 通过元素自身实现比较规则,元素自身定义了比较规则;
2. 通过比较器指定比较规则。
# Iterator迭代器接口
>collection接口继承了Iterator接口,包含一个iterator方法,会返回一个Iterator接口类型的对象,包含了三个方法用于实现对单例容器的迭代处理。
方法:
| 方法 | 说明 |
| ----------------- | ---------------------------------------------------------- |
| boolean hasNext() | 判断游标当前位置是否铀元素,有返回True |
| Object next() | 获取当前游标所在位置的元素,并将游标移动到下一位置 |
| void remove() | 删除游标当前位置元素,在执行完next后使用,并且只能执行一次 |
```java
package container;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class IterotorTest {
public static void main(String[] args) {
//实例化容器
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
//迭代器
Iterator<String> iterator =list.iterator();
//通过while循环获取元素
System.out.println("通过while=========");
while (iterator.hasNext()){
String value = iterator.next();
System.out.println(value);
}
//通过for
System.out.println("通过for=====");;
for(Iterator<String> i = list.iterator();i.hasNext();){
String value = i.next();
System.out.println(value);
}
}
}
```
# Collections工具类
>Collections是一个工具类,提供了对Set、List、Map进行排序、填充、查找元素的辅助方法,该类中所有的方法都为静态方法。
常用方法:
| 方法 | 说明 |
| ----------------------------- | ---------------------------------------------------------- |
| void sort(List) | 对list容器内的元素排序,规则按照升序 |
| void shuffle(List) | 对List随机排列,打乱 |
| void reverse(List) | 逆序排列 |
| void fill(List,Object) | 用一个特定的对象重写整个List容器 |
| int binarySearch(List,Object) | 对于顺序的容器,采用折半查找法查找特定对象,放回对象的索引 |
```java
package container;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
public class CollectionsMethodsTest {
public static void main(String[] args) {
//实例化容器
List<String> list = new ArrayList<>();
list.add("c");
list.add("a");
list.add("b");
//sort排序
Collections.sort(list);
for (Iterator<String > i=list.iterator();i.hasNext();){
String s = i.next();
System.out.println(s);
}
System.out.println("================");
//shuffle打乱顺序
Collections.shuffle(list);
for (Iterator<String > i=list.iterator();i.hasNext();){
String s = i.next();
System.out.println(s);
}
}
}
```