基础容器

基本接口

java 提供了一些基础容器类,可以用特定的方式组织、存储和操作对象数据。这些集合框架分为两大分支:Collection 接口和 Map 接口。

所有容器都定义在 java.util 文件夹内,使用时需要进行导入。

Collection 接口

【集合】用特定的方式组织、存储和操作对象数据。有三个常用子接口 List 接口、Queue 接口、Set 接口。


// 修改
collection.add(1);                // 添加元素
collection.remove(1);             // 删除元素,如果有多个重复元素默认删除前面的第一个
collection.clear();               // 清除所有元素

// 查询
collection.isEmpty();             // 判断集合是否为空
collection.size();                // 返回集合元素个数
collection.contains(1):           // 判断集合中是否含有元素

// 多集合操作
collection.addAll(c2);            // 并操作,添加其他集合中元素
collection.removeAll(c2);         // 减操作,删除和其他集合共有元素
collection.retainAll(c2);         // 交操作,只保留和其他集合共有元素 
collection.equals(c2);            // 判断是否和其他集合元素相同
collection.containsAll(c2);       // 判断是否包含其它集合所有元素  

// 转化
collection.toArray();             // 集合转换成数组

// 创建迭代器
Iterator<Integer> iter = collection.iterator();   // 返回集合中的迭代器对象,该迭代器对象默认指向当前集合的0索引
iter.next();                                      // 判断当前位置是否有元素存在,存在返回true,不存在返回false
iter.hasNext()                                    // 获取当前位置的元素,并同时将迭代器对象移向下一个位置,注意防止取出越界
iter.remove();                                    // 删除迭代器所在位置的元素值,保证不后移,能够成功遍历到所有元素

List 接口

【列表】元素有序,可以按索引操作。

// 修改
list.add("data1");              // 末尾添加元素
list.add(0, "data0");           // 插入元素
list.remove(0);                 // 按索引删除元素(int)
list.remove("data");            // 按内容删除对象元素(Object)
list.remove(new Integer(3));    // 按内容删除基础类型元素
list.clear();                   // 清除所有元素
list.set(0, "data2");           // 修改元素

// 查找
list.isEmpty();                 // 判定是否为空
list.size();                    // 查询列表元素个数
list.contains("data3");         // 判定是否含有元素
list.get(1);                    // 按索引查找元素
list.indexOf("data1");          // 查询索引号:如果有返回第一个,没有返回-1
list.lastIndexOf("data1");      // 查询索引号:如果有返回最后一个,没有返回-1

// 转化
list.toString();                // 转化为字符串
list.toArray();                 // 转化为 Object[] 数组
(String [])list.toArray();      // 转化为对象数组,但不能是基础类型

// 创建不可变集合
List.of();            

Queue 接口

Set 接口

【集】数据不可重复。

// 修改
set.add("data");              // 添加元素
set.remove("data");           // 删除元素
set.clear();                  // 清除所有元素

// 查询
set.get(1);                   // 按序号查找元素(仅限于有序的 set 接口)
set.isEmpty();                // 判断是否为空
set.size();                   // 返回元素个数
set.contains("data");         // 判定是否含有元素

// 创建不可变集合
Set.of(); 

HashSet 类无序,因此不支持 get 方法:获取对象必须要通过 Iterator 来遍历。

Collections 类

Collections 类是针对集合类的一个帮助类,他提供一系列静态方法实现各种集合操作。

  1. 排序操作(主要针对List接口)
Collections.swap(list, 1, 2);          // 元素交换顺序
Collections.shuffle(list);             // 元素随机排序
Collections.reverse(list);             // 元素颠倒排序
Collections.sort(list);                // 元素按大小排序,可以自定义比较顺序
Collections.rotate(list, 2);           // 元素右移指定长度Copy to clipboardErrorCopied
  1. 查找和替换
Collections.binarySearch(list, "data");              // 二分查找元素索引,只适用于有序集合
Collections.max(list);                               // 返回最大元素,可以自定义比较顺序
Collections.min(list);                               // 返回最小元素,可以自定义比较顺序
Collections.frequency(list, "data");                 // 返回对象出现次数

Collections.fill(list, "data");                      // 使用指定元素填充
Collections.replaceAll(list, "old", "new");          // 使用指定元素替换Copy to clipboardErrorCopied
  1. 上锁(主要针对List接口)

调用 Collections 类中的 synchronizedList 方法,可以将 List 接口转换成线程安全的容器使用。

List 接口中的方法都会被添加 synchronized 锁(效率不高)。但是 iterator 方法没有加锁,如果要遍历还需要在外层加锁。

List list = Collections.synchronizedList(new ArrayList());

synchronized (list) {
    Iterator i = list.iterator(); 
    while (i.hasNext())
        foo(i.next());
}

Map 接口

组织存储 key-value 的数据元素组合:内部实际存储的是 Map.Entry<K, V> 静态内部类。

Entry 类可以通过 getKey、getValue、setKey、setValue 方法调整数据。

Map集合的遍历方式有:3种

方式一:键找值的方式遍历:先获取Map集合全部的键,再根据遍历键找值。
方式二:键值对的方式遍历,把“键值对“看成一个整体,难度较大。
方式三:JDK 1.8开始之后的新技术:Lambda表达式。

Map 接口方法

map.put("key_1",1);               // 添加键值对,已有 key 则覆盖 value
map.putIfAbsent("key_2",2);       // 添加键值对,已有 key 则不操作

map.remove("key_1");              // 删除键值对(按值)           
map.remove("key_2",2);            // 删除键值对(按键值)
map.clear();			  // 删除所有键值对元素
    
map.get("key_1");                 // 获取值, key 不存在返回null
map.getOrDefault("key_2",-1);     // 获取值, key 不存在返回默认值

map.containsKey("key_1");         // 判断 key 是否存在  
map.containsValue(1);             // 判断 value 是否存在    

map.isEmpty();			  // 判断集合是否为空
map.size();			  // 获取集合长度(键值对个数)

Set<String> keys = maps.keySet(); 	                    // 获取全部键的集合
int value = maps.get(key);		                    // 根据键提取值
Collection<Integer> values = maps.values();		    // 获取全部值的集合

Set<Map.Entry<String, Integer>> entries = maps.entrySet();  // 把Map集合转换成Set集合
String key = entry.getKey();	  // 获取键
int value = entry.getValue();	  // 获取值

map1.putAll(map2); 	           // 合并其他Map集合(把集合map2的元素拷贝一份到map1中去)

maps.forEach(new BiConsumer<String, Integer>() {    // 遍历
    @Override
    public void accept(String key, Integer value) {
        System.out.println(key + "--->" + value);
    }
});

// 创建不可变集合
Map.of(); 

线性存储

ArrayList 类

【数组序列】实现了 List 接口,内部使用 Object 数组存储:

  1. 可以高效地按索引进行元素修改和查询。
  2. 添加元素时动态扩容:当容量满后,ArrayList 类会新建一个 1.5 倍容量的新数组,然后将当前数组数据全部复制过去。

ArrayList 构造方法

List<Integer> list = new ArrayList<>();              // 默认初始容量为 10
List<Integer> list = new ArrayList<>(100);           // 自定义初始容量
List<Integer> list = new ArrayList<>(queue);         // 构造时直接复制其他容器元素(可以是任何 Collection 类)

List list = new ArrayList();                         // 未指定元素类型则为 Object 类

ArrayList 常用API

// 1、往集合中添加数据
list.add("Java");


// 2、往集合中的某个索引位置处添加一个数据
list.add(1, "MySQL");


// 3、根据索引获取集合中某个索引位置处的值
String rs = list.get(1);


// 4、获取集合的大小(返回集合中存储的元素个数)
list.size();

// 5、根据索引删除集合中的某个元素值,会返回被删除的元素值给我们
list.remove(1);


// 6、直接删除某个元素值,删除成功会返回true,反之
list.remove("Java");

// 默认删除的是第一次出现的这个牛马的数据的
list.remove("牛马"));


// 7、修改某个索引位置处的数据,修改后会返回原来的值给我们
list.set(1, "牛马程序员");
   

LinkedList 类

【链表序列】实现了 List 和 Deque 接口。内部使用双向链表存储:

  1. 可以高效地进行元素插入和删除。
  2. 容量无限。

LinkedList 构造方法

List<String> list = new LinkedList<>();              // 创建空对象
List<String> list = new LinkedList<>(queue);         // 复制其他容器元素

ArrayList 常用API

//LinkedList可以完成队列结构,和栈结构 (双链表)
// 1、做一个队列:
LinkedList<String> queue = new LinkedList<>();
// 入队
queue.addLast("1号");
queue.addLast("2号");
queue.addLast("3号");
System.out.println(queue);
// 出队
// System.out.println(queue.getFirst());
System.out.println(queue.removeFirst());
System.out.println(queue.removeFirst());
System.out.println(queue);

// 2、做一个栈
LinkedList<String> stack = new LinkedList<>();
// 入栈 压栈 (push)
stack.push("第1颗子弹");
stack.push("第2颗子弹");
stack.push("第3颗子弹");
stack.push("第4颗子弹");
System.out.println(stack);

// 出栈  弹栈 pop
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack);

哈希存储

HashMap 类

【哈希表】 实现 Map 接口。底层使用散列存储:构造一个 Entry 数组,根据 key 的 hash 值将 Entry 存入指定位置。

  • key 值无序且不可重复,且允许 null 作为 key 值存在。
  • 发生哈希冲突时,HashMap 采用链表保存多个元素。当链表长度大于 8 时,链表自动转化为红黑树。
  • 达到负载因数后,HashMap 将调用 resize 方法动态扩容:新建一个 2 倍容量的新数组复制当前数组的数据。

HashMap 构造方法

Map<String,Integer> map = new HashMap<>();                       // 默认初始容量 16 负载因数 0.75
Map<String,Integer> map = new HashMap<>(32);                     // 自定义初始容量
Map<String,Integer> map = new HashMap<>(32, 0.5f);               // 自定义初始容量和负载因数

LinkedHashMap 类

【链式哈希表】继承 HashMap 类。

  1. 底层使用散列存储:构造一个 Entry 数组,根据 key 的 hash 值将 Entry 存入指定位置。
  2. Entry 额外添加了引用 before & after ,使哈希表内的所有 Entry 构成一个双向链表维护 Entry 的顺序。

LinkedHashMap 构造方法

在默认情况下 Entry 按照插入顺序排序,可指定创建时的初始容量和负载因数。

Map<String,Integer> map = new LinkedHashMap<>();                  // 默认初始容量 16 负载因数 0.75 
Map<String,Integer> map = new LinkedHashMap<>(32);                // 自定义初始容量
Map<String,Integer> map = new LinkedHashMap<>(32, 0.5f);          // 自定义初始容量和负载因数

Entry 也可以按照访问顺序排序:对 Entry 进行操作时会先删除再插入,将 Entry 移动到双向链表的表尾。

Map<String,Integer> map = new LinkedHashMap<>(32,0.5f, true);    // 基于访问顺序排序

LinkedHashMap 类提供了 removeEldestEntry 方法,在使用 put 操作插入 Entry 时将自动调用此方法决定是否移除双向链表表头的 Entry:默认返回 false ,可通过重写此方法以实现 LRU 算法。

// Entry 超过容量后自动删除最久未使用的 Entry
Map<String,Integer> map = new LinkedHashMap<>(capacity, 0.5f, true){
    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {  
        return size() > capacity;  
    }  
};

TreeMap 类

【树表】 实现了 Map 接口。底层使用红黑树存储:Entry 按照 key 值大小插入红黑树,并动态调整红黑树高度。

TreeMap 类方法

TreeMap 类提供了以下专属方法使用。

map.firstKey();                   // 返回最小 key
map.lastKey();                    // 返回最大 key

map.ceilingKey("10");             // 返回大于等于10的最小 Key,不存在则返回 null
map.ceilingEntry("10");           // 返回大于等于10的最小 Key 的键值对(getKey / getValue 方法)

map.floorKey("10");               // 返回小于等于10的最大 Key,不存在则返回 null
map.floorEntry("10");             // 返回小于等于10的最大 Key 的键值对

HashSet 类

无序,不重复,无索引

**HashSet元素无序的底层原理:哈希表**
- HashSet集合底层采取哈希表存储的数据。
- 哈希表是一种对于增删改查数据性能都较好的结构。

**哈希表的组成**
- JDK8之前的,底层使用数组+链表组成
- JDK8开始后,底层采用数组+链表+红黑树组成。

**哈希值**
是JDK根据对象的地址,按照某种规则算出来的int类型的数值。

**Object类的API**
public int hashCode​():返回对象的哈希值

**对象的哈希值特点**
同一个对象多次调用hashCode()方法返回的哈希值是相同的,默认情况下,不同对象的哈希值是不同的。

**HashSet1.7版本原理解析:数组 + 链表 +(结合哈希算法)**

- 1.创建一个默认长度16的数组,数组名table
- 2.根据元素的哈希值跟数组的长度求余(余数小于除数,在0-15之间)计算出应存入的位置(哈希算法)
- 3.判断当前位置是否为null,如果是null直接存入,如果位置不为null,表示有元素, 则调用equals方法比较属性值,如果一样,则不存,如果不一样,则存入数组。
- 4.当数组存满到16*0.75=12时,就自动扩容,每次扩容原先的两倍
  - JDK 7新元素占老元素位置,指向老元素
  - JDK 8中新元素挂在老元素下面

**结论:如果希望Set集合认为2个内容一样的对象是重复的,必须重写对象的hashCode()和equals()方法**

**JDK1.8版本开始HashSet原理解析**
- 底层结构:哈希表(数组、链表、红黑树的结合体)
- 当挂在元素下面的数据过多时,查询性能降低,从JDK8开始后,当链表长度超过8的时候,自动转换为红黑树。

LinkedHashSet 类

有序、不重复、无索引。
这里的有序指的是保证存储和取出的元素顺序一致
原理:底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。

TreeSet 类

**TreeSet集合概述和特点**

- 不重复、无索引、可排序
- 可排序:按照元素的大小默认升序(有小到大)排序。
- TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。
- 注意:TreeSet集合是一定要排序的,可以将元素按照指定的规则进行排序。

**TreeSet集合默认的规则**

- 对于数值类型:Integer , Double,官方默认按照大小进行升序排序。
- 对于字符串类型:默认按照首字符的编号升序排序。
- 对于自定义类型如Student对象,TreeSet无法直接排序。

**自定义排序规则**

- TreeSet集合存储对象的的时候有2种方式可以设计自定义比较规则
- 方式一
  - 让自定义的类(如学生类)实现Comparable接口重写里面的compareTo方法来定制比较规则。
- 方式二
  - TreeSet集合有参数构造器,可以设置Comparator接口对应的比较器对象,来定制比较规则。

**两种方式中,关于返回值的规则:**

- 如果认为第一个元素大于第二个元素返回正整数即可。
- 如果认为第一个元素小于第二个元素返回负整数即可。
- 如果认为第一个元素等于第二个元素返回0即可,此时Treeset集合只会保留一个元素,认为两者重复。

**注意:如果TreeSet集合存储的对象有实现比较规则,集合也自带比较器,默认使用集合自带的比较器排序。**

元素遍历

遍历容器

Iterable 接口

是集合框架的顶级接口,被所有容器类都实现。

    1.提供 iterator 方法,用来创建一个实现了 Iterator 接口的 iterator 对象:按容器类规定的顺序实现遍历集合。
    2.JDK 1.8 引入 foreach 方法遍历集合。效率更高,但不能对元素进行删除操作,否则会抛出异常。
    3.提供了 hasNext、next、remove 三个方法,可以按容器类规定的顺序实现遍历集合。

遍历方法

Collection 接口

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

// iterator 遍历
Iterator<Integer> iter = list.iterator();
while(iter.hasNext()){              
      int num = iter.next();
      if(num < 0) iter.remove();
}

// foreach(增强for循环)(效率更高,但不能进行删除操作)
for (String str : list) {
      System.out.println(str);
}

// JDK 1.8开始之后的新技术Lambda表达式
list.forEach(new Consumer<String>() {
    @Override
    public void accept(String str) {
        System.out.println(str);
    }
});


posted @ 2023-03-13 08:27  晚点心动。  阅读(30)  评论(0)    收藏  举报