JavaSE--集合

发布时间 2023-08-15 22:19:03作者: 洛小依ovo

一、集合概述

java.util.*;包下

1、什么是集合

  集合实际上就就是一个容器,可以来容纳其他类型的数据,例如数组就是一个容器,一个集合

  集合在开发中使用较多,可以一次容纳多个对象,

  注意:集合不能直接存储基本数据类型,另外集合也不能直接存储java对象,集合当中存储的都是java对象的内存地址。(或者说集合中存储的是引用。) 任何时候存储的都是引用

2、Java中不同的集合,底层对应不同的数据结构

  往不同的集合中存储元素,等于将数据放到不同的数据结构当中

3、Java中集合分为两大类

  1)一类为单个方式存储元素:

    单个方式存储元素,这一类集合中超级父接口:java. util . Collection;

  2)一类为以键值对的方式存储元素:

    以键值对的方式存储元素,这一类集合中超级父接口:java. util .Map;

4、集合继承结构图(其中的类为常用的一些) 单个方式存储

5、Map集合继承结构图(其中的类是常用的一些)  键值对方式存储

二、单个方式存储元素的集合

1、java.util.Collection接口中常用方法

  a)boolean add(Object e)向集合中添加元素

// 因为接口无法实例化,用ArrayList做例子
Collection c = new ArrayList();
c.add(1200); // 自动装箱
c.add(3.14);
c.add(new Object());
c.add(new Student());
c.add(true); // 自动装箱

  b)int size()获取集合中元素的个数

  注意:在迭代集合过程中,不能调用集合对象的remove方法,删除元素,改变了基本结构,会出现异常

c.size();

  c)void clear();清空集合

c.clear();

  d)boolean contains(Object o);集合中是否包含某一个元素

c.add("hello");
c.add("world");
c.add(1);

boolean flag = c.contains("hello");
System.out.println(flag); // true
// 底层调用的是equal方法

  e)boolean remove();删除集合中某个元素

c.remove(1);
c.remove("hello");
// 底层调用了equal方法

  f)boolean isEmpty();判断该集合中元素个数是否为0

System.out.println(c.isEmpty()); // false

  g)Object[] toArray()  调用这个方法可以把集合转换成数组

Object[] objs = c.toArray();
for(int i = 0; i < objs.length; i++){
    Object o = objs[i];
    System.out.println(o);
}

  h)集合迭代Iterator迭代器(重点)

  hasNext、next、remove

注意:迭代器刚开始并没有指向第一个元素。这个迭代器在Collection中是通用的,Map中不能用

  一定注意:集合结构只要发生改变,迭代器也必须重新获取

  在迭代集合过程中,不能调用集合对象的remove方法,但是迭代器有一个remove()方法,可以使用迭代器的方法

迭代器对象:获取迭代器对象,迭代器用来遍历集合,此时相当于对当前集合的状态拍了一个快照

  迭代器去删除的时候,会自动更新迭代器,并且更新集合

public class CollectionTest02 {
    public static void main(String[] args) {
        // 注意:以下讲解的遍历方式/迭代方式,是所有Collection通用的一种方式。
        // 在Map集合中不能用。在所有的Collection以及子类中使用。
        // 创建集合对象
        Collection c = new ArrayList();
        // 添加元素
        c.add("abc");
        c.add("def");
        c.add(100);
        c.add(new Object());
        // 对集合Collection进行遍历/迭代
        // 第一步:获取集合对象的迭代器对象Iterator
        Iterator it = c.iterator();
        // 第二步:通过以上获取的迭代器对象开始迭代/遍历集合。
        /*
            以下两个方法是迭代器对象Iterator中的方法:
                boolean hasNext()如果仍有元素可以迭代,则返回 true。
                Object next() 返回迭代的下一个元素。
         */
        while(it.hasNext()){
            Object obj = it.next();
            System.out.println(obj);
        }
        /*
        while(it.hasNext()){
            Object obj = it.next();
            it.remove();
            System.out.println(obj);
        }
        */
    }
}

 

 2、List接口中特有的常用方法

  1)void add(int index, Object element)在列表的指定位置插入指定元素

  2)Object set(int index, Object element)修改指定位置的元素

  3)Object get(int index)根据下标获取元素

  4)int indexOf(Object o)获取指定对象第一次出现处的索引

  5)int lastIndexOf(Object o) 获取指定对象最后一次出现处的索引

  6)Object remove(int index)删除指定下标位置的元素

public class ListTest01 {
    public static void main(String[] args) {
        // 创建List类型的集合。
        List myList = new ArrayList();

        // 添加元素
        myList.add("A"); // 默认都是向集合末尾添加元素。
        myList.add("C");
        myList.add("D");

        //在列表的指定位置插入指定元素(第一个参数是下标)
        myList.add(1, "sss");

        // 根据下标获取元素
        Object firstObj = myList.get(0);
        System.out.println(firstObj);

        // 因为有下标,所以List集合有自己比较特殊的遍历方式
        // 通过下标遍历。List集合特有的方式,Set没有。
        for(int i = 0; i < myList.size(); i++){
            Object obj = myList.get(i);
            System.out.println(obj);
        }

        // 获取指定对象第一次出现处的索引。
        System.out.println(myList.indexOf("C"));

        // 获取指定对象最后一次出现处的索引。
        System.out.println(myList.lastIndexOf("C"));

        // 删除指定下标位置的元素
        // 删除下标为0的元素
        myList.remove(0);

        // 修改指定位置的元素
        myList.set(2, "srr");

        // 遍历集合
        for(int i = 0; i < myList.size(); i++){
            Object obj = myList.get(i);
            System.out.println(obj);
        }
    }
}

3、ArrayList类常用方法

  1)初始化容量为10,底层是Object类型的数组

  2)构造方法

    new ArrayList();初始化 容量10

    new ArrayList(20);指定初始化容量

  3)ArrayList集合的扩容

    增长到原容量的1.5倍

public class ArrayListTest01 {
    public static void main(String[] args) {
        // 默认初始化容量是10
        List list1 = new ArrayList();
        // 集合的size()方法是获取当前集合中元素的个数。不是获取集合的容量。
        System.out.println(list1.size()); // 0

        // 指定初始化容量
        // 数组的长度是20
        List list2 = new ArrayList(20);
        // 集合的size()方法是获取当前集合中元素的个数。不是获取集合的容量。
        System.out.println(list2.size()); // 0

        list1.add(1);
        list1.add(2);
        list1.add(3);
        list1.add(4);
        list1.add(5);
        System.out.println(list1.size());

        // 再加一个元素
        list1.add(11);
        System.out.println(list1.size());
    }
}

4、链表

对于链表数据结构来说,基本的单元是节点Node

  1)单向链表

    a)对于单向链表来说

      每个节点都有Node都有两个属性,一个属性:存储的数据,另一个属性:是下一个节点的内存地址

    b)链表优点

      随机增删元素效率较高(增删元素不涉及到大量元素位移)

    c)链表缺点

      查询效率较低,每一次查找某个元素的时候都需要从头节点开始往下遍历

  2)双向链表

    a)对于双向链表来说

      每个节点都有Node都有三个属性,一个属性:前一个节点的内存地址,中间属性:存储的数据,另一个属性:是下一个节点的内存地址

    b)链表优点

      由于链表上的元素在空间存储上内存地址不连续。所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高。在以后的开发中,如果遇到随机增删集合中元素的业务比较多时,建议使用LinkedList

    c)链表缺点

      不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头节点开始遍历,直到找到为止。所以    LinkedList集合检索/查找的效率较低

    ArrayList:把检索发挥到极致(末尾添加元素效率还是很高的。)

    LinkedList:把随机增删发挥到极致

    加元素都是往末尾添加,所以ArrayList用的比LinkedList多

  3)LinkedList常用方法

    和ArrayList差不多,

5、Vector

  1)

    底层是一个数组

  2)初始化容量

    10

  3)扩容

    扩容之后是原来的两倍

  4)将一个线程不安全的ArrayList集合转换为线程安全的

    使用集合工具类:java.util.Collections;

List myList = new ArrayList(); // 非线程安全的
// 变成线程安全的
Collections.synchronizedList(myList);
public class VectorTest {
    public static void main(String[] args) {
        // 创建一个Vector集合
        List vector = new Vector();
        //Vector vector = new Vector();

        // 添加元素
        // 默认容量10个
        vector.add(1);
        vector.add(2);
        vector.add(3);
        vector.add(11);
        Iterator it = vector.iterator();
        while(it.hasNext()){
            Object obj = it.next();
            System.out.println(obj);
        }
    }
}

6、HashSet

public class HashSetTest01 {
    public static void main(String[] args) {
        Set<String> strs = new HashSet<>();
        strs.add("hello3");
        strs.add("hello4");
        strs.add("hello1");
        strs.add("hello2");
        strs.add("hello3");
        strs.add("hello3");

        // 放到HashSet集合中的元素实际上是放到HashMap集合的key部分了。
        for(String s : strs){
            System.out.println(s);
        }
    }
}

7、TreeSet

public class TreeSetTest01 {
    public static void main(String[] args) {
        // 创建集合对象
        Set<String> strs = new TreeSet<>();
        // 添加元素
        strs.add("A");
        strs.add("B");
        strs.add("Z");
        strs.add("Y");
        strs.add("Z");
        strs.add("K");
        strs.add("M");
        // 遍历
        /*
            A
            B
            K
            M
            Y
            Z
        从小到大自动排序!
         */
        for(String s : strs){
            System.out.println(s);
        }
    }
}

三、以键值对的方式存储元素

Map和Collection没有继承关系

1、java.util.Map接口中常用的方法

  1)V put(K key, V value) 向Map集合中添加键值对

Map<Integer, String> map = new HashMap<>();
// 向Map集合中添加键值对
map.put(1, "zhangsan"); // 1自动装箱
map.put(2, "lisi");
map.put(3, "wangwu");

  2)V get(Object key) 通过key获取value

// 通过key获取value
String value = map.get(2);
System.out.println(value);

  3)void clear()    清空Map集合

// 清空map集合
map.clear();
System.out.println("键值对的数量:" + map.size());

  4)boolean containsKey(Object key) 判断Map中是否包含某个key

// 判断是否包含某个key
System.out.println(map.containsKey(new Integer(4)));

  5)boolean containsValue(Object value) 判断Map中是否包含某个value

// 判断是否包含某个value
System.out.println(map.containsValue(new String("wangwu")));

  6)boolean isEmpty()   判断Map集合中元素个数是否为0

// 判断是否为空
System.out.println(map.isEmpty());

  7)V remove(Object key) 通过key删除键值对

// 通过key删除key-value
map.remove(2);

  8)int size() 获取Map集合中键值对的个数

// 获取键值对的数量
System.out.println("键值对的数量:" + map.size());

  9)Collection<V> values() 获取Map集合中所有的value,返回一个Collection

// 获取所有的value
Collection<String> values = map.values();
// foreach
for(String s : values){
    System.out.println(s);
}

  10)Set<K> keySet() 获取Map集合所有的key(所有的键是一个set集合)

// 获取所有的key,所有的key是一个Set集合
Set<Integer> keys = map.keySet();

  11)Set<Map.Entry<K,V>> entrySet()将Map集合转换成Set集合

    注意:Map集合通过entrySet()方法转换成的Set集合,Set集合中元素的类型是Map.Entry<K,V>

假设现在有一个Map集合,如下所示:
map1集合对象
key             value
----------------------------
1               zhangsan
2               lisi
3               wangwu
4               zhaoliu

Set set = map1.entrySet();
set集合对象
1=zhangsan 【注意:Map集合通过entrySet()方法转换成的这个Set集合,Set集合中元素的类型是 Map.Entry<K,V>】
2=lisi     【Map.Entry和String一样,都是一种类型的名字,只不过:Map.Entry是静态内部类,是Map中的静态内部类】
3=wangwu
4=zhaoliu ---> 这个东西就是一个Map.Entry

  12)Map集合的遍历

// 在map集合中放入数据
Map<Integer, String> map = new HashMap<>();
map.put(1, "zhangsan");
map.put(2, "lisi");
map.put(3, "wangwu");
map.put(4, "zhaoliu");

    a)第一种方式(先获取key,再用key获取value)

// 先获取所有的key,所有的key是一个Set集合
// 然后遍历key获取value
Set<Integer> keys = map.keySet();

// 迭代器获取
/*Iterator<Integer> it = keys.iterator();
        while(it.hasNext()){
            // 取出其中一个key
            Integer key = it.next();
            // 通过key获取value
            String value = map.get(key);
            System.out.println(key + "=" + value);
        }*/
// foreach获取
for(Integer key : keys){
    System.out.println(key + "=" + map.get(key));
}

    b)第二种方式(先转换成Set集合,然后通过Set集合获取)  推荐使用

// 第二种方式:Set<Map.Entry<K,V>> entrySet()
Set<Map.Entry<Integer,String>> set = map.entrySet();
// 遍历Set集合,每一次取出一个Node
// 迭代器
/*Iterator<Map.Entry<Integer,String>> it2 = set.iterator();
while(it2.hasNext()){
    Map.Entry<Integer,String> node = it2.next();
    Integer key = node.getKey();
    String value = node.getValue();
    System.out.println(key + "=" + value);
}*/

// foreach
// 这种方式效率比较高,因为获取key和value都是直接从node对象中获取的属性值。
// 这种方式比较适合于大数据量
for(Map.Entry<Integer,String> node : set){
    System.out.println(node.getKey() + "---" + node.getValue());
}

2、HashMap

  1)HashMap集合概述

    HashMap集合底层是哈希表/散列表的数据结构

    哈希表/散列表:一维数组,这个数组中每一个元素是一个单向链表。(数组和链表的结合体。)

  2)HashMap数据结构

     哈希表是一个数组和单向链表的结合体。数组:在查询方面效率很高,随机增删方面效率很低。单向链表:在随机增删方面效率较高,在查询方面效率很低

    哈希表将以上的两种数据结构融合在一起,充分发挥它们各自的优点

  3)HashMap底层源代码

public class HashMap{
    // HashMap底层实际上就是一个数组。(一维数组)
    Node<K,V>[] table;
    // 静态的内部类HashMap.Node
    static class Node<K,V> {
        final int hash; // 哈希值(哈希值是key的hashCode()方法的执行结果。hash值通过哈希函数/算法,可以转换存储成数组的下标。)
        final K key; // 存储到Map集合中的那个key
        V value; // 存储到Map集合中的那个value
        Node<K,V> next; // 下一个节点的内存地址
    }
}

  4)HashMap两个方法

  • map.put(k,v)实现原理

    • 1:先将k,v封装到Node对象当中

    • 2:底层会调用k的hashCode0方法得出hash值,然后通过哈希函数/哈希算法,将hash值转换成数组的下标,

      • 如果下标位置上如果没有任何元素,就把Node添加到这个位置上

      • 如果说下标对应的位置上有链表,此时会拿着k和链表上每一个节点中的k进行equals ,如果所有的equals方法返回都是false ,那么这个新节点将会被添加到链表的末尾。如果其中有一个equals返回了true,那么这个节点的value将会被覆盖

  • v = map.get(k)实现原理

    • 先调用k的hashCode0方法得出哈希值,通过哈希算法转换成数组下标,通过数组下标快速定位到某个位置

      上,

      • 如果这个位置上什么也没有,返回null

      • 如果这个位置上有单向链表,那么会拿着参数k和单向链表上的每个节点中的k进行equals ,如果所有equals方法返回false ,那么get方法返回null ,只要其中有一个节点的k和参数k equals的时候返回true ,那么此时这个节点的value就是我们要找的value , get方法最终返回这个要找的value

  5)HashMap集合的key的特点:

    无序,不可重复。

    重点:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法。

    注意:同一个单向链表上所有节点的hash相同,因为数组下标都是一样的

  6)默认初始化

    HashMap集合的默认初始化容量是16,默认加载因子是0.75,这个默认加载因子是当HashMap集合底层数组的容量达到75%的时候,数组开始扩容

        重点:HashMap集合初始化容量必须是2的倍数,这也是官方推荐的,

public class HashMapTest01 {
    public static void main(String[] args) {
        // 测试HashMap集合key部分的元素特点
        // Integer是key,它的hashCode和equals都重写了
        Map<Integer,String> map = new HashMap<>();
        map.put(1111, "zhangsan");
        map.put(6666, "lisi");
        map.put(7777, "wangwu");
        map.put(2222, "zhaoliu");
        map.put(2222, "king"); //key重复的时候value会自动覆盖。

        System.out.println(map.size());

        // 遍历Map集合
        Set<Map.Entry<Integer,String>> set = map.entrySet();
        for(Map.Entry<Integer,String> entry : set){
            // 结果:HashMap集合key部分元素:无序不可重复
            System.out.println(entry.getKey() + "=" + entry.getValue());
        }
    }
}

  7)同时重写hashCode和equal方法

    因为在HashMap集合中如果equals方法返回是true,那么hashCode方法返回的值必须一致,equals方法返回true表示两个对象相同,在同一个单向链表上比较的话,对于同一个单向链表上的节点来说,他们的哈希值都是相同的,所以hashCode方法的返回值也是相同的

  8)HashMap集合的key值允许为空

// 但是注意:null也是只能有一个
Map map = new HashMap();
map.put(null,null);
map.put(null,"123");
System.out.println(map.get(null));// 123

  9)HashMap和Hashtable的区别

    a)key和value是否可以为null

      Hashtable的key和value都是不能为null的。

      HashMap集合的key和value都是可以为null的。

    b)Hashtable和HashMap一样,底层都是哈希表数据结构

    c)Hashtable默认容量

      Hashtable的初始化容量是11,默认加载因子是:0.75f。Hashtable的扩容是:原容量 * 2 + 1

public class HashtableTest01 {
    public static void main(String[] args) {
        Map map = new Hashtable();

        //map.put(null, "123");
        map.put(100, null);
    }
}

 

3、Properties属性类

  1)Properties概述

    Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型

    Properties被称为属性类对象

    Properties是线程安全的

  2)相关方法

  • setProperty
  • getProperty
public class PropertiesTest01 {
    public static void main(String[] args) {

        // 创建一个Properties对象
        Properties pro = new Properties();

        // 需要掌握Properties的两个方法,一个存,一个取。
        pro.setProperty("url", "jdbc:mysql://localhost:3306/nvrg");
        pro.setProperty("driver","com.mysql.jdbc.Driver");
        pro.setProperty("username", "root");
        pro.setProperty("password", "123");

        // 通过key获取value
        String url = pro.getProperty("url");
        String driver = pro.getProperty("driver");
        String username = pro.getProperty("username");
        String password = pro.getProperty("password");

        System.out.println(url);
        System.out.println(driver);
        System.out.println(username);
        System.out.println(password);

    }
}

 

4、TreeSet

  1)TreeSet概述

    TreeSet集合底层实际上是一个TreeMap

    TreeMap集合底层是一个二叉树

    放到TreeSet集合中的元素,等同于放到TreeMap集合key部分了

    TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序。称为:可排序集合

  2)TreeSet对自定义的类型是否可以排序

    a)没有指定自定义对象之间的比较的规则无法排序

以下程序运行的时候出现了这个异常:
    java.lang.ClassCastException:
        class com.bjpowernode.javase.collection.Person
        cannot be cast to class java.lang.Comparable
出现这个异常的原因是:
    Person类没有实现java.lang.Comparable接口

    b)自定义比较规则,实现java.lang.Compareble接口

public class TreeSetTest04 {
    public static void main(String[] args) {
        Customer c1 = new Customer(32);
        Customer c2 = new Customer(20);
        Customer c3 = new Customer(30);

        // 创建TreeSet集合
        TreeSet<Customer> customers = new TreeSet<>();
        // 添加元素
        customers.add(c1);
        customers.add(c2);
        customers.add(c3);

        // 遍历
        for (Customer c : customers){
            System.out.println(c);
        }
    }
}

// 放在TreeSet集合中的元素需要实现java.lang.Comparable接口
class Customer implements Comparable<Customer>{

    int age;
    public Customer(int age){
        this.age = age;
    }
    
    /*  compareTo方法很重要
        返回0表示相同,value会覆盖
        返回>0,会继续在右子树上找
        返回<0,会继续在左子树上找
    */
    // 需要在这个方法中编写比较的逻辑,或者说比较的规则,按照什么进行比较
    // k.compareTo(t.key)
    // 拿着参数k和集合中的每一个k进行比较,返回值可能是>0 <0 =0
    // 比较规则最终还是由程序员指定的:例如按照年龄升序或者按照年龄降序
    @Override
    public int compareTo(Customer c) { // c1.compareTo(c2);
        //return this.age - c.age; // =0 >0 <0
        return c.age - this.age;
    }

    public String toString(){
        return "Customer[age="+age+"]";
    }
}

    c)创建比较器,实现java.util.Comparator接口

// 乌龟类
class WuGui{

    int age;

    public WuGui(int age){
        this.age = age;
    }

    @Override
    public String toString() {
        return "小乌龟[" +
                "age=" + age +
                ']';
    }
}

// 单独在这里编写一个比较器
// 比较器实现java.util.Comparator接口。(Comparable是java.lang包下的。Comparator是java.util包下的。)
class WuGuiComparator implements Comparator<WuGui> {

    @Override
    public int compare(WuGui o1, WuGui o2) {
        // 指定比较规则
        // 按照年龄排序
        return o1.age - o2.age;
    }
}
public class TreeSetTest06 {
    public static void main(String[] args) {

        // 给构造方法传递一个比较器
        TreeSet<WuGui> wuGuis = new TreeSet<>(new WuGuiComparator());
        
        // 大家可以使用匿名内部类的方式(这个类没有名字。直接new接口。)
        /*
        TreeSet<WuGui> wuGuis = new TreeSet<>(new Comparator<WuGui>() {
            @Override
            public int compare(WuGui o1, WuGui o2) {
                return o1.age - o2.age;
            }
        });
        */

        wuGuis.add(new WuGui(1000));
        wuGuis.add(new WuGui(800));
        wuGuis.add(new WuGui(810));

        for(WuGui wuGui : wuGuis){
            System.out.println(wuGui);
        }
    }
}

 四、Collections工具类

    // ArrayList集合不是线程安全的。
        List<String> list = new ArrayList<>();

        // 变成线程安全的
        Collections.synchronizedList(list);

        // 排序
        list.add("abf");
        list.add("abx");
        list.add("abc");
        list.add("abe");

        Collections.sort(list);
        for(String s : list){
            System.out.println(s);
        }
// 注意:对List集合中元素排序,需要保证List集合中的元素实现了:Comparable接口。
//传入比较器对象也可以Collections.sort(list集合, 比较器对象);
List<WuGui2> wuGuis = new ArrayList<>();
wuGuis.add(new WuGui2(1000));
wuGuis.add(new WuGui2(8000));
wuGuis.add(new WuGui2(500));

Collections.sort(wuGuis);
for(WuGui2 wg : wuGuis){
    System.out.println(wg);
}
// 对Set集合排序
// 将Set集合转换成List集合
Set<String> set = new HashSet<>();
set.add("king");
set.add("kingsoft");
set.add("king2");
set.add("king1");

List<String> myList = new ArrayList<>(set);
Collections.sort(myList);
for(String s : myList) {
    System.out.println(s);
}

 


五、二叉树