【Java核心技术】Java集合技术详解

文章目录

集合

特点

可以存储多个数据而且长度可以改变的容器

Collection:包含子集合List,Set、Queue

三种常用集合

List

特点

  • List集合的特点:存入数据有序,可重复

常用实现类

  • ArrayList:线程不安全
  • Vector:线程安全
  • LinkedList:线程不安全
  • Stack:线程安全

Set

特点

  • Set集合的特点:存入数据无序,不可重复

常用实现类

  • HashSet:线程不同步,效率高
  • TreeSet:自动排序

Map

特点

  • Map集合的特点:使用key=value的形式存储数据(JavaEE的常用工具类的数据结构)

常用实现类

HashMap:使用key=value的形式存储数据

栈内存:多数用来做计算

泛型<E>

参数化类型

<E>—泛型,用于指定集合中元素的类型

泛型指定的集合中的元素类型只能是引用数据类型

  • Integer、Double、Character、String…………

特点

  • 泛型是JDK1.5的新特性
  • 书写形式:List<String> list = new ArrayList<>();
    • JDK1.7开始出现
  • 泛型可以接受所有引用类型,当指定了泛型的类型,后续操作都是这个类型,就不能接受其他类型:泛型的擦除(发生在编译期)
  • 泛型的上下限(规定接收类型的范围):
    • <? extends 类/接口>: 泛型的上限
      • 表示可以接收当前【类/接口】的【子类/子接口/实现类】
    • <? super String>:泛型的下限
      • 表示可以接收当前【类/接口】的【父类/父接口】

List

ArrayList

ArrayList集合的特点:

  • 存入数据有序,可重复

  • 查询效率高;增加,删除效率低

  • 线程不安全

  • 内存连续

  • 底层由数组实现,默认初始容量为10,底层根据右移运算来扩容在原有大小的基础上增加一半

创建

List<String> list = new ArrayList<String>();

添加add()

add();

list.add("String");
list.add("absd");
list.add("badsf");
list.add(1, "张三");

遍历get()

增强for或者普通for

list.get(index);------获取索引index处的元素

//方法一
for(String str:list) {
    
    
    System.out.println(str);
}
//方法二
for(int i = 0;i<list.size();i++) {
    
    
    System.out.println(list.get(i));
}

使用迭代器遍历iterator()

iterator()迭代器

底层通过指针的挪动来遍历集合元素

遍历期间不能增删原集合

public static void test2(){
    
    
    List<String> list = new ArrayList<>();
    list.add("a");
    list.add("b");
    list.add("c");
    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
    
    
        System.out.println(iterator.next());
    }
}

修改set()

set(int index, String str);------在索引为index处添加str元素

//修改
list.set(2, "admin");

删除remove()

remove(int index);------删除索引为index的元素

remove(String str);------删除第一次出现str的元素

//删除
list.remove(0);
list.remove("badsf");

清空集合clear()

clear();:清空该集合(格式化)

List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
System.out.println(list);//[1,2]
list.clear();
System.out.println(list);//[]

判断是否包含某元素contains()

判断指定的数据是否包含在集合中

//创建一个集合对象
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
System.out.println(list.contains("1"));//true

判断是否为空集合isEmpty()

isEmpty()

//创建一个集合对象
List<String> list = new ArrayList<>();
list.add("1");
System.out.println(list.isEmpty());//false

查询指定元素的下标indexOf()

返回指定数据在集合中第一次出现的下标

//创建一个集合对象
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("2");
System.out.println(list.indexOf("2"));//1

集合大小size()

返回集合的长度,大小

List<String> list = new ArrayList<>();
list.add("1");
System.out.println(list.size());//1

截取子列表subList()

[ 0,3) 截取子列表

//创建一个集合对象
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
System.out.println(list.subList(0,3));//[1,2,3]

集合转成数组toArray(T[] a)

list.toArray(new 类型[0]);

括号里声明转换数组的数据类型

//怎么把list集合转换成数组
String[] strArray = list.toArray(new String[0]);

把数组转成集合Arrays.asList()

Arrays.asList(数组名);

//把数组转换成集合
String[] strs = {
    
    "we","are","students"};
List<String> strsList = Arrays.asList(strs);

排序

Collections.sort(list);

public static void test2() {
    
    
    //给ArrayList的元素排序
    List<String> list = new ArrayList<>();
    list.add("a");
    list.add("c");
    list.add("b");

    //给list集合排序
    Collections.sort(list);

    for(String str:list) {
    
    
        System.out.println(str);
    }

}

对象集合的排序

Collections.sort(list);

自定义类要实现Comparable接口,重写compareTo方法

升序:this.age-o.age

降序:o.age-this.age(或者是-(this.age-o.age))

this.age - o.age = 0 表示相等

@Override
public int compareTo(Student o) {
    
    
    //从小到大 this.age-o.age
    //this.age-o.age=0;表示两个对象相等
    return this.age-o.age;
}	

LinkedList

使用链表的形式

特点

  • 底层基于—Node内部类:节点 实现;通过节点之间的地址值的指向来维系节点
  • 增删速度比ArrayList快,查询比较慢
  • 线程不安全
  • 内存不连续,不需要扩容

链表添加元素

如果是头节点:

  • this.first = node;
  • this.last = node;

如果不是头节点:

  • this.last.next=node;新节点指向原尾节点的下一个节点
  • node.prev=this.last;原尾节点指向新节点的删一个节点
//添加
public void add(String str){
    
    
    //新建节点
    Node node=new Node(str, null, null);

    //判断是头部添加还是尾部添加
    if(size0){
    
    
        //新节点指向头节点
        this.first=node;
    }else{
    
    
        //新节点指向原尾节点的下一个节点
        this.last.next=node;
        //原尾节点指向新节点的删一个节点
        node.prev=this.last;
    }
    //新节点指向尾节点
    this.last=node;
    //
    size++;
}

链表的删除

删除头节点:

  • this.first.next.prev = null;
  • this.first = this.fitst.next;

删除尾节点:

  • this.last.prev.next = null;
  • this.last = this.last.prev;

删除中间节点:先获取节点并通过变量(no)保存

  • no.next.prev = no.prev;
  • no.prev.next = no.next;

图片

在这里插入图片描述

//根据下标进行删除
public void remove(int index){
    
    
    //下标越界
    out(index);

    //判断删除的位置
    if(index0){
    
    //删除头节点
        //原头节点的下一个节点的上一个节点为null
        this.first.next.prev=null;
        //原头节点的下一个节点指向头节点
        this.first=this.first.next;
    }else if(indexsize-1){
    
    //尾节点
        //原尾节点的上一个节点的下一个节点为null
        this.last.prev.next=null;
        //原尾节点的上一个节点指向尾节点
        this.last=this.last.prev;
    }else{
    
    //删除中间元素
        //找到index下标对应的节点
        Node no=getNode(index);

        //no节点的上一个节点指向no结点下一个节点的上一个节点
        no.next.prev=no.prev;
        //no下一个节点指向no上一个节点的下一个
        no.prev.next=no.next;
    }

    size--;
}

链表的内存图

在这里插入图片描述

面试

ArrayList和Vector:线程安全性的不同

ArrayList和LinkedList:查询,删除和添加的效率不一样

  • 思考题:

    增删和查询相当,ArrayList和LinkedList消耗的时间几乎相同,决定两个集合效率高低的是占用的空间,因为

Vector

向量

特点

  • 底层基于数组实现;默认初始容量:10

    • 默认的扩容是根据底层的三目运算符扩容为原来的两倍
    • 如果指定了增量大小,每次扩容在原来的基础上加上增量值
  • 最早的集合类

  • 线程安全

构造

Vector(int initialCapacity, int capacityIncrement)

initialCapacity:初始容量

capacityIncrement:容量增量

使用指定的初始容量和容量增量构造一个空的向量。

Vector<String> vector = new Vector<>(10,5);
for (int i = 0; i < 11; i++) {
    
    
    vector.add("a");
}
System.out.println(vector.capacity());//15,每次扩容5个容量

方法

当前容量capacity()

返回此向量的当前容量。

容量翻2倍增加

如果使用有参构造,自定义增量,每次扩容自定容量

Vector<String> vector = new Vector<>();
for (int i = 0; i < 11; i++) {
    
    
    vector.add("a");
}
System.out.println(vector.capacity());
//20,默认每次增加上一次相同的容量
//10+10
//20+20
//40+40

返回最早的迭代器,遍历集合elements()

Enumeration接口,最早的迭代器

只有vector能用

Vector<String> vector = new Vector<>(10,5);
for (int i = 0; i < 11; i++) {
    
    
    vector.add(String.valueOf(i));
}

Enumeration<String> elements = vector.elements();
while (elements.hasMoreElements()) {
    
    
    System.out.print(elements.nextElement()+"\t");
}

//0	 1	2	3	4	5	6	7	8	9	10	

返回迭代器:iterator()

Iterator接口,新版迭代器

所有的集合都可以使用

Iterator<String> iterator = vector.iterator();
while (iterator.hasNext()) {
    
    
    System.out.println(iterator.next());
}

Stack栈结构

入栈/压栈:向栈中添加元素

出栈/弹栈:从栈中获取元素

栈顶元素:最后存放的元素

栈底元素:第一个存放的元素

特点

  • 遵循先进后出(FILO)原则
  • 继承Vector(和Vector的特点一致)

方法

测试堆栈是否为空empty()

public static void test2(){
    
    
    Stack<String> s = new Stack<>();
    s.push("abc");
    s.push("ac");
    System.out.println(s.empty());//false
}

获取栈顶元素peek()

并不将他们从栈中删除

public static void test2(){
    
    
    Stack<String> s = new Stack<>();
    s.push("abc");
    s.push("ac");
    System.out.println(s.peek());//ac
}

获取栈顶元素并删除pop()

public static void test2(){
    
    
    Stack<String> s = new Stack<>();
    s.push("abc");
    s.push("ac");
    System.out.println(s.pop());
    //ac,并将ac从占栈中删除
}

查找元素位置search(Object o)

从栈顶开始查找,从1开始计数

public static void test2(){
    
    
    Stack<String> s = new Stack<>();
    s.push("abc");
    s.push("ac");
    s.push("bc");
    s.push("c");
    System.out.println(s.search("ac"));//3
}

Set

HashSet

底层由HashMap实现;HashMap底层由数组+链表实现数据存储

底层默认长度为16的数组,每个元素空间称之为:桶(bucket)

存储:

  • 先获取对象的哈希码值,再二次运算保证结果落在某个桶中

  • 用新的对象和对应桶上的所有对象一次进行equals比较,如果有相等的就把新的对象舍弃掉,如果都不想等就放在当前桶中所有对象的最前面—链式栈结构(单链表)

扩容:

  • 如果 【已经使用的桶数/总桶数 >加载因子(0.75)】,默认就需要扩容为原来的2倍
  • 对元素重新二次计算(高低位运算),重新分布,保证数据散列分布,该过程称为:rehash

加载因子:

  • 加载因子越大,导致大量的元素对象存储在某些链上,导致链的长度过长,降低了对元素查询的效率
  • 加载因子越小,导致频繁的扩容和rehash操作,浪费大量内存空间

JDK1.8中改良:

  • 在某一桶上的链式结构中,元素超过8个,会自动转换数据结构,以红黑二叉树存储(红黑树的查找效率最高)。
  • 新元素添加到最后

内存图详解:

特点

  • 无序的(不能保证迭代顺序),没有索引
  • 底层基于数组+链表实现
  • 加载因子
  • 从JDK1.8开始如果链表长度大于8就会扭转成二叉树

删除,获取不能通过索引操作

没有修改的概念

线程不同步,效率高

方法和List的基本一致

TreeSet

特点

  • 默认对存储的内容排序:升序(自然排序)
  • 底层使用二叉树实现
  • 自定义存储对象的排序,实现Comparable接口,重写comparTo方法,参考

LinkedHashSet(不是重点)

特点

  • 采用hash存储,并用双向循环链表数据结构
  • 内部是LinkedHashMap

Queue队列结构

队列

队头元素:第一个存放到队列的元素

队尾元素:最后一个存放到队列的元素

特点

  • 遵循先进先出(FIFO)的原则

方法

获取队头但不删除element()

没有队头元素就会报错:NoSuchElementException

获取队头但不删除peek()

没有队头元素就会返回null

方法预览

Throws exception Returns special value
Insert add(e) offer(e)
Remove remove() poll()
Examine element() peek()

映射Map:不是集合

映射

映射不是集合,但是java collections FrameWork(java集合类框架)的成员;同样的:Map、Collection、Iterator、Arrays、Collections…………

  • 可以存储多个有关系的一对数据的容器
  • 一个键对应一个值,键不能重复,值可以重复,映射由多个键和值组成
  • K:键 V:值;泛型的指定使映射中的元素只能是引用数据类型
  • 因为映射存储的是一对对数据,为了方便表示与操作这些数据,可以把这些数据抽取成一个类(Entry),Entry产生的对象是具体的一对数据(键值对),多个映射也是由多个键值对组成

比较

Hashtable :不允许null键null值,线程同步,线程安全

HashMap : 允许null键null值,线程不同步,线程不安全。

HashMap

特点

  • 底层由数组+链表实现数据存储
  • HashMap : 允许null键null值
  • 默认初始容量为16(数组长度),默认的加载因子(是使用桶数/总桶数)为0.75;
  • 可以指定初始容量,如果初始容量在2n<指定容量<(2n+1)之间,结果最终的数组长度就是2n+1
  • 大于0.75后,默认扩容增加一倍;扩容后需要进行rehash
  • 链表长度大于8个在JDK1.8就开始转成二叉树
  • 异步式线程不安全

创建

可以指定长度,但是最后的长度会经过一系列计算,转换为2的整数次幂的长度

Map<Integer,String> map = new HashMap<>();

添加、修改

put(key,value)

map.put(1, "张三");
map.put(2, "李四");
map.put(3, "wangwu");

删除

remove(key):根据key删除

remove(key,value):根据key,value删除

map.remove(3);
map.remove(3"wangwu");

查询是否包含Key,Value

containsKey(Key) :存在Key返回true

containsValue(Value):存在Value返回true

System.out.println(map.containsKey("a"));
System.out.println(map.containsValue(1));

获取值

get(key)

当获取一个不存在的key-value时,返回null

String valueby1 = map.get(1);
String valueby2 = map.get(2);
String valueby3 = map.get(3);

返回所有value

values():把所有的value存放到collection集合中

public static void main(String[] args) {
    
    
    Map<String, Integer> map = new HashMap<>();
    map.put("a", 1);
    map.put("12a", 6);
    map.put("a12", 4);
    map.put("7658a", 2);

    Collection<Integer> values = map.values();
    System.out.println(values);//[6, 1, 4, 2]
}

遍历

keySet()

循环获取map集合中的数据

Set<E> keys = map.keySet();

//使用循环获取map集合中的数据
public static void test2() {
    
    
    Map<String,Object> map = new HashMap<>();
    //设置key,value
    map.put("name", "张三");
    map.put("pw", "123456");
    map.put("age", "20");
    map.put("phone", "17603393007");
    map.put("Email", "[email protected]");

    //获取map集合中所有的key
    Set<String> keys = map.keySet();
    for(String key:keys) {
    
    
        System.out.println(key+"="+map.get(key));
    }
}

entrySet()

通过entrySet获取Map中的数据

//通过entrySet获取map中的数据
public static void test3() {
    
    
    Map<String,Object> map = new HashMap<>();
    //设置key,value
    map.put("name", "张三");
    map.put("pw", "123456");
    map.put("age", "20");
    map.put("phone", "17603393007");
    map.put("Email", "[email protected]");

    Set<Map.Entry<String, Object>> set = map.entrySet();
    for(Map.Entry<String, Object> entry:set) {
    
    
        System.out.println(entry.getKey()+"="+entry.getValue());
    }
    
    //第二种简单写法;两步合一步
    for (Map.Entry<String, Integer> i : map.entrySet()){
    
    
        System.out.println(i.getKey() + ":" + i.getValue());
    }
}

computeIfAbsent

Map<String,Object> locks = new HashMap<>();
Object o = locks.computeIfAbsent(userId, k -> new Object());

判断key为userId的Object是否存在,存在返回,不存在向集合中添加一个新的Object

Hashtable

最早的映射类

特点

  • 最早的映射类

  • 底层基于数组+链表实现存储

  • Hashtable :不允许null键null值,线程同步

  • 默认初始容量为11,默认加载因子为0.75

  • 初始容量指定多少就是多少

  • 默认的扩容是增加一倍再加一【11----> 11*2+1】

  • 同步式线程安全

Iterator

特点

  • 底层通过指针的挪动来遍历集合元素

  • 遍历期间不能增删原集合

  • Iterable接口里定义了可以返回Iterator接口的方法iterator()

  • 只要实现了Iterable接口的集合,就可以使用foreach遍历

    • 增强for循环底层使用迭代器实现(JDK1.5新特性)

使用迭代器遍历iterator()

iterator()迭代器

public static void test2(){
    
    
    List<String> list = new ArrayList<>();
    list.add("a");
    list.add("b");
    list.add("c");
    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
    
    
        System.out.println(iterator.next());
    }
}

remove()方法

把当前指向的元素删除

是根据在遍历期间改变标记值来简介删除原集合元素

如果直接操作集合的remove()方法,

  • 遍历期间改变原集合,遍历结束时会根据标记值和原集合比较
  • 就会报错,无法继续迭代
public static void test2(){
    
    
    List<String> list = new ArrayList<>();
    list.add("a");
    list.add("b");
    list.add("c");
    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
    
    
        String str = iterator.next();
        //是根据在遍历期间改变标记值来简介删除原集合元素
        iterator.remove();//把当前指向的元素删除
        System.out.println(str);
    }
    System.out.println(list);//[]
}

Comparator比较器

函数式接口

list.sort(Comparator c)

Comparator:比较器

返回值>0 表面前面对象大于后面对象
返回值<0 表明前面对象小于后面对象
返回值=0 表明前面对象等于后面对象

匿名内部类使用

public static void test1(){
    
    
    List<String> list = new ArrayList<>();
    list.add("abc");
    list.add("dc");
    list.add("cc");
    list.add("ab");
    list.sort(new Comparator<String>() {
    
    
        //重写方法-指定比较规则
        @Override
        public int compare(String o1, String o2) {
    
    
            /**
             * 返回值>0 表面前面对象大于后面对象
             * 返回值<0 表明前面对象小于后面对象
             * 返回值=0 表明前面对象等于后面对象
             */
            int i = o1.compareTo(o2);
            return i;
        }
    });
    System.out.println(list);
}

λ表达式

list.sort(
    (str1,str2)->str1.charAt(0)-str2.charAt(0)
);

对象自定义排序

使用

自定义类要实现Comparable接口,重写compareTo方法

升序:this.age-o.age

降序:o.age-this.age(或者是-(this.age-o.age))

this.age - o.age = 0 表示相等

public class Student implement Comperable<Student>{
    
    
    .......
}

@Override
public int compareTo(Student o) {
    
    
    //从小到大 this.age-o.age
    //this.age-o.age=0;表示两个对象相等
    return this.age-o.age;
}	

猜你喜欢

转载自blog.csdn.net/weixin_54707168/article/details/114125863