25 KiB
title | author | top | cover | toc | mathjax | summary | tags | categories | reprintPolicy | abbrlink | date | coverImg | img | password |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
java容器 | TianZD | true | true | true | false | java容器学习笔记,粗略学了一下,没有参考价值 | [java 容器 学习笔记] | [java] | cc_by | 17ea147a | 2022-04-29 11:00:40 | <nil> | <nil> | <nil> |
[TOC]
泛型
JDK1.5(即5.0)后增加,帮助我们建立类型安全的集合
本质是数据类型的参数化:
- 把类型当作是参数一样进行传递;
- <数据类型>只能是引用类型
好处:
- 代码可读性更好,不用强制转换
- 程序更加安全,只要编译时间没有警告,运行时期就不会出现ClassCastException异常;
类型擦除
编码时采用泛型写的参数类型,编译器会在编译时去掉,即“类型擦除”,类型参数再编译后会被替换成Object,运行时虚拟机并不知道泛型。
定义泛型
可以使用任何字符表示标识符,一般用下面的:
泛型标记 | 对应单词 | 说明 |
---|---|---|
E | Element | 在容器中使用,表示容器中的元素 |
T | Type | 表示普通的java类 |
K | Key | 表示键,如map中的键key |
V | Value | 表示值 |
N | Number | 表示数值类型 |
? | 表示不确定的java类型 |
//定义泛型类
public class Generic<T>{
private T flag;
public void setFlag(T flag){
this.flag = flag;
}
public T getFlag(){
return this.flag;
}
}
//泛型方法
public <泛型表示符号> void getName(泛型表示符号 name){
}
public <泛型表示符号> 泛型表示符号 getName(泛型表示符号 name){
}
//静态方法定义,静态方法中不能使用类定义的泛型
public static<泛型表示符号> void getName(泛型表示符号 name){
}
public static<泛型表示符号> 泛型表示符号 getName(泛型表示符号 name){
}
//通配符?,表示类型不确定的
public void showFlag(Generic<?> generic){
}
//通配符上限限定,如下,表示只能是number或者其子类
public void showFlag(Generic<? extend Number> generic){
}
//通配符下限限定,如下,表示只能是Integer或者其父类
public void showFlag(Generic<? super Integer> generic){
}
泛型总结
泛型主要用于编译阶段,编译生成的class文件不包含泛型中的类型信息,类型参数再编译后会被替换成Object
- 基本类型不能用于泛型,但是可以用其对应的包装类
如
Test<int> t;错误,可以用Test<Integer>t;
- 不能通过类型参数创建对象
T elm = new T();错误
容器
用来容纳和管理数据。
单例集合
将数据一个一个的进行存储;Collection接口,以单个数据未单位进行存储;
包括:
- List接口:存储有序,可重复,“动态”数组,实现:ArrayList类、LinkedList类、Vector类
- 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存储特征的具体实现,
底层使用数组实现的存储,特点:查询效率高(使用数组实现),增删效率低(增删后其余元素的索引都要变),线程不安全。
方法使用
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);
}
}
}
//转换为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倍
- 那我们本身就有数组了,为什么要用ArrayList呢?、
原生的数组会有一个特点:你在使用的时候必须要为它创建大小,而ArrayList不用。
- ArrayList是怎么实现的吧,为什么ArrayList就不用创建大小呢?
其实是这样的,我看过源码。当我们new ArrayList()的时候,默认会有一个空的Object数组,大小为0。当我们第一次add添加数据的时候,会给这个数组初始化一个大小,这个大小默认值为10;
数组的大小是固定的,而ArrayList的大小是可变的;因为ArrayList是实现了动态扩容的,假设我们给定数组的大小是10,要往这个数组里边填充元素,我们只能添加10个元素。而ArrayList不一样,ArrayList我们在使用的时候可以往里边添加20个,30个,甚至更多的元素。
- 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 |
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() | 判断是否为空 |
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()方法判断两个元素是否相同,如果相同,则不会添加,如果不同,则会使用单向链表保存该元素。
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()
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.自定义比较规则
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时把比较器传递进去
//创建比较器
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接口最常用的实现类,底层采用了哈希表存储数据,要求键不能重复,如果发生重复,新的值会替换旧的值,在查找、删除、修改方面都有比较高的效率
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底层实现采用了哈希表;数据结构中由数组和链表实现对数据的存储,各自特点如下:
- 数组:占用空间连续,寻址容易查询速度看,但是增删效率低
- 链表:占用空间不连续,寻址困难,查询速度慢,但是增删效率搞
哈希表本质是数组+链表,查询和增删效率高
- 初始化数组,延迟初始化,调用时通过resize方法生成2<<4=16的数组长度,并进行0.75的扩容;将值传到NODE<K,V>节点类中,再存储到数组中
- 计算hash值:调用key对象的hashcode方法计算key的hashcode值,根据hashcode值计算hash值(值在[0,数组长度-1]之间),公式:hash值=hashcode &(数组长度-1)
- 添加元素,根据计算的hash值,放在数组的相应索引下,如果该位置已有索引:如果两个key值相同,则进行覆盖,如果不相同,则在该位置形成单向链表
- 如果链表长度大于8,则转换成红黑树,如果红黑树小于6,则转换为链表
TreeMap
TreeMap和HashMap同样实现了map接口。HashMap效率要高于TreeMap,但是TreeMap是可以对键进行排序的,底层基于红黑树实现
使用TreeMap要给定排序规则:
- 通过元素自身实现比较规则,元素自身定义了比较规则;
- 通过比较器指定比较规则。
Iterator迭代器接口
collection接口继承了Iterator接口,包含一个iterator方法,会返回一个Iterator接口类型的对象,包含了三个方法用于实现对单例容器的迭代处理。
方法:
方法 | 说明 |
---|---|
boolean hasNext() | 判断游标当前位置是否铀元素,有返回True |
Object next() | 获取当前游标所在位置的元素,并将游标移动到下一位置 |
void remove() | 删除游标当前位置元素,在执行完next后使用,并且只能执行一次 |
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) | 对于顺序的容器,采用折半查找法查找特定对象,放回对象的索引 |
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);
}
}
}