关于这些容器的底层实现、大小扩容机制、线程安全性等可以查看这个:
浅谈
-
容器: 容器是能够盛放物质的器皿,比如水杯,可以盛放水
- 编程语言中的容器: 用于存储数据的数据结构,称为容器
Java中的容器可以整体认为三类: Array、Collection 和 Map,图中加粗是重点学习对象
(Collection里其实还有一个Queue队列,在开头的跳转链接里,很少提到)
泛型类
ArrayList:实现了List接口的动态数组。可以根据需要动态增加或减少元素的大小,并提供快速的随机访问操作。常用于存储一组元素,支持按索引访问、插入和删除操作。
LinkedList:实现了List接口的双向链表。可以用作队列、栈或双端队列。
HashSet:实现了Set接口的哈希集合。用于存储不重复元素,提供快速的查找和插入操作。
TreeSet:实现了Set接口的红黑树集合。用于存储不重复元素,并按照元素的自然顺序或自定义顺序进行排序。
HashMap:实现了Map接口的哈希表。用于存储键值对,提供快速的查找和插入操作。
TreeMap:实现了Map接口的红黑树。用于存储键值对,并按照键的自然顺序或自定义顺序进行排序。
其它语言的类似容器
1. Python:
- 列表(List):类似Java中的ArrayList,可以动态增加或删除元素。
- 字典(Dictionary):类似Java中的HashMap,使用键值对存储数据。
2. C++:
- 向量(Vector):类似Java中的ArrayList,可以动态增加或删除元素。
- 映射(Map):类似Java中的HashMap,使用键值对存储数据。
3. JavaScript:
- 数组(Array):类似Java中的ArrayList,可以动态增加或删除元素。
- 对象(Object):类似Java中的HashMap,使用键值对存储数据。
Array数组
初始化(动与静)
动态初始化
// 动态初始化:数组动态初始化就是只给定数组长度, 由系统给出默认初始化值
// 动态初始化格式:数组类型[] 数组名 = new 数组类型[数组长度]
// 示例: int[] arr = new int[3] // 数组类型为int类型, 数组名为arr, 数组长度为3
// 示例: 动态初始化一个长度为5的数组, 并循环打印内部的所有元素
public class DemoTh {
public static void main(String[] args) {
// 动态初始化一个长度为5的数组
int[] arr = new int[5];
System.out.println(arr);
}
}
静态初始化
// 静态初始化: 数组静态初始化是指在创建数组时, 直接将元素确定.
// 静态初始化格式:
// 完整版:数据类型[] 数组名 = new 数据类型[]{元素1, 元素2, ....}
// 简化版:数组类型[] 数组名 = {元素1, 元素2, ....}
// 示例:创建一个包含1, 3, 5, 7, 9的数组, 并遍历输出其每一个元素
public class DemoOneToNine {
public static void main(String[] args) {
// 静态初始化数组
int[] arr = {1, 3, 5, 7, 9};
System.out.println(arr);
}
}
CRUD
增:看初始化
查:
1、索引取值
- 索引即下标,代表元素在数组中的位置。Java数组的索引从0开始
public class DemoOneToNine {
public static void main(String[] args) {
// 静态初始化数组
int[] arr = {1, 3, 5, 7, 9};
System.out.println(arr[3]); // 7
}
}
2、遍历
// for循环示例: 按索引取出元素
public class DemoOneToNine {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
for (int i = 0;i<arr.length;i++) {
System.out.println(arr[i]);
};
}
}
// for循环示例: 直接遍历出元素
public class DemoOneToNine {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
for(int item:arr) {
System.out.println(item);
}
}
}
改:
public class Test01 {
public static void main(String[] args) {
// 静态初始化数组
int[] arr = {1, 3, 5, 7, 9};
arr[0] = 666;
for(int item:arr) {
System.out.println(item);
}
}
}
删:
走进底层
栈与堆
- 栈:保存局部变量的值,包括:基本数据类型的值、类的实例(堆区对象的引用(指针)。
- 堆:用来存放动态产生的数据,比如new出来的对象。注意创建出来的对象只包含属于各自的成员变量,并不包括成员方法。因为同一个类的对象拥有各自的成员变量,存储在各自的堆中,但是他们共享该类的方法,并不是每创建一个对象就把成员方法复制一次。
一个数组的诞生
public class JavaMem {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
System.out.println(arr);
}
}
多数组
arr2 = arr1
public class JavaMem {
public static void main(String[] args) {
int[] arr1 = {1, 2, 3};
int[] arr2 = arr1;
System.out.println(arr1[0]); // 1
System.out.println(arr2[0]); // 1
arr1[0] = 66;
System.out.println(arr1[0]); // 66
System.out.println(arr2[0]); // 1
}
}
arr2 = {arr1[0], arr1[1], arr1[2]}
public class JavaMem {
public static void main(String[] args) {
int[] arr1 = {1, 2, 3};
int[] arr2 = {arr1[0], arr1[1], arr1[2]};
System.out.println(arr1[0]); // 1
System.out.println(arr2[0]); // 1
arr1[0] = 666;
System.out.println(arr1[0]); // 66
System.out.println(arr2[0]); // 66
}
}
避坑指南
索引越界
索引越界是指使用索引获取数组元素时, 使用的索引不能超过数组元素的最大索引值, 超过后无法通过改索引获取指定的元素, 就会出现索引越界错误, 异常名称位: ArrayIndexOutOfBoundsException
空指针异常
当一个数组对象被创建了, 当使用的过程中如果让数组对象指向了null, 即将null赋值给数组, 意味着变量arr将不会再保存数组的内存地址,也就不允许再操作数组了,因此运行的时候会抛出 NullPointerException 空指针异常。
小试牛刀
找出数组的最大值
public class GetMax {
public static void main(String[] args) {
int[] my_arr = {1, 5, 2, 0, -9, 8, 4};
int max = my_arr[0];
for (int i = 1; i < my_arr.length; i++) {
if (my_arr[i] > max) {
max = my_arr[i];
}
}
System.out.println(max);
}
}
返回两个元素的和等于定值,的这两个元素的索引
public class FindTarget {
public static void main(String[] args) {
int target = 8;
int[] arr = {1, 3, 5, 7, 9};
for (int i = 0; i < arr.length; i++) {
for (int j = i + 1; j < arr.length; j++) {
if (arr[i] + arr[j] == target) {
System.out.println("索引分别为: " + i + "," + j);
}
}
}
}
}
Collection
List部落
- List集合为有序集合(也称为序列),用户可以精确控制列表中每个元素的插入位置。用户可以通过整数索引访问元素, 并搜索列表中的元素
- 列表通常允许重复的元素
- 特点:(1) 有索引 (2) 可以存储重复元素 (3) 元素存取有序
方法
方法名 | 描述 |
void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
ArrayList
在java中ArrayList提供一种存储空间可变的存储模型, 存储的数据容量是可以发生改变的. ArrayList底层是「数组」实现的, 长度可以变化.
方法
方法名 | 说明 |
public boolean add(E e) | 将指定的元素追加到此集合的末尾 |
public void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
public boolean remove(Object o) | 删除指定的元素,返回删除是否成功 |
public E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
public E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
public E get(int index) | 返回指定索引处的元素 |
public int size() | 返回集合中的元素的个数 |
import java.util.ArrayList;
public class ArrayListDemo {
public static void main(String[] args) {
//创建ArrayList集合对象, 添加四个元素, 并输出在控制台中
ArrayList<String> al = new ArrayList<>();
al.add("Hello");
al.add("World");
al.add("Hello");
al.add("Java");
System.out.println("al的值:" + al);
//获取索引为1的元素, 输出在控制台中
System.out.println("al索引为1的元素: " + al.get(1));
//修改索引为3的元素为"Hi"
al.set(3,"Hi");
//查看修改后的al
System.out.println("修改索引为3的元素后的al: " + al);
//移除索引为0的元素后输出al
al.remove(0);
System.out.println("移除索引为0的元素后的al: " + al);
//移除内容为"Java"的元素后输出al
al.remove("Java");
System.out.println("移除Java后的al: " + al);
}
}
遍历
import java.util.ArrayList;
public class ArrayListIterDemo {
public static void main(String[] args) {
ArrayList<String> al = new ArrayList<>();
al.add("I");
al.add("love");
al.add("you");
//普通for遍历
for (int i=0;i<al.size();i++){
System.out.println(al.get(i));
};
// 增强for遍历
for (String item : al) {
System.out.println(item);
}
}
}
LinkedList
LinkedList功能与ArrayList一样,同样是一个List的实现类,功能也是存储数据使用。但LinkedList与ArrayList本质完全不同。
- ArrayList:底层有数组实现
- LinkedList:底层有链表实现
方法(特有)
方法名 | 说明 |
public void addFirst(E e) | 在该列表开头插入指定的元素 |
public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
public E getFirst() | 返回此列表中的第一个元素 |
public E getLast() | 返回此列表中的第一个元素 |
public E removeFirst() | 从此列表中删除并返回第一个元素 |
public E removeLast() | 从此列表中删除并返回最后一个元素 |
import java.util.LinkedList;
public class LinkedListDemo {
public static void main(String[] args) {
LinkedList<Integer> numbers_linklist = new LinkedList<>();
numbers_linklist.add(1);
numbers_linklist.add(2);
numbers_linklist.add(3);
numbers_linklist.add(4);
numbers_linklist.add(5);
// addFirst方法
numbers_linklist.addFirst(0);
System.out.println(numbers_linklist);
// addLast方法
numbers_linklist.addLast(6);
System.out.println(numbers_linklist);
// getFirst方法
System.out.println(numbers_linklist.getFirst());
// getLast方法
System.out.println(numbers_linklist.getLast());
// removeFirst方法
numbers_linklist.removeFirst();
System.out.println(numbers_linklist);
// removeLast方法
numbers_linklist.removeLast();
System.out.println(numbers_linklist);
}
}
Set部落
Set为集合,不包含重复元素的集合, 并且最多只有一个空元素。
方法
方法名 | 说明 |
add(E e) | 如果指定的元素不存在,则将其指定的元素添加(可选操作)。 |
clear() | 从此集合中删除所有元素(可选操作)。 |
contains(Objcet o) | 如果此集合包含指定的元素,则返回 true 。 |
equals(Object o) | 将指定的对象与此集合进行比较以实现相等。 |
hashCode() | 返回此集合的哈希码值。 |
isEmpty() | 如果此集合不包含元素,则返回 true 。 |
remove(Object o) | 如果存在,则从该集合中删除指定的元素(可选操作)。 |
size() | 返回此集合中的元素数(其基数)。 |
toArray() | 返回一个包含此集合中所有元素的数组。 |
HashSet
- 底层数据结构是哈希表
- 对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的元素顺序一致
- 没有带索引的方法,所以不能使用普通for循环遍历(可以使用增强for循环和迭代器实现遍历)
- 由于是Set集合,所以是不包含重复元素的集合
唯一性
前置知识:HashTable: 哈希表;HashCode: 哈希值
- a.根据对象的哈希值计算存储位置(哈希值对16取模即可得到存储位置, 一个HashSet默认存储16个元素)
- 如果当前位置没有元素则直接存入
- 如果当前位置有元素存在,则进入第二步
- b.当前元素和已经存在的元素比较哈希值
- 如果哈希值不同,则将当前元素进行存储
- 如果哈希值相同,则进入第三
- c.通过equals()方法比较两个元素的内容
- 如果内容不相同,则将当前元素进行存储
- 如果内容相同,则不存储当前元素
LinkedHashSet
- 哈希表和链表实现的Set接口,具有可预测的迭代次序
- 由链表保证元素有序,也就是说元素的存储和取出顺序是一致的
- 由哈希表保证元素唯一,也就是说没有重复的元素
TreeSet
- TreeSet元素排序方式取决于构造方法
- Comparable(自然排序): 创建TreeSet对象时, 使用TreeSet()无参构造
- Comparator(比较器): 创建TreeSet对象时, 使用TreeSet(Comparator comparator)有参构造方法, 传递的参数是Comparator接口的实现类对象, 则根据指定的比较器进行排序
- TreeSet没有带索引的方法, 所以不能使用普通for循环遍历
- 由于是Set集合, 所以不能存储重复的元素
public class TreeSetDemo01 {
public static void main(String[] args) {
//创建集合对象
TreeSet<Integer> ts = new TreeSet<Integer>();
//添加元素
ts.add(10);
ts.add(40);
ts.add(30);
ts.add(50);
ts.add(20);
ts.add(30);
//遍历集合
for(Integer i : ts) {
System.out.println(i);
}
}
}
自然排序
自然排序是在添加对象到集合内部时, 让其按照对象内部的比较方法进行自动排序. 构建TreeSet时使用无参构造方法即可.
上面的例子中, 我们构建ts对象, 并向其中添加一些整型, 遍历ts对象会发现, 打印的顺序是从小到达的. 经查看Integer类源码发现, Integer底层实现了两个方法, 一个compare方法和compareTo方法, 其中compare负责进行比较, 而compareTo内部调用compare方法, 当前数与另一个数进行比较时, 如果当前数小于那个数, 则返回-1, 如果相等则返回0, 如果当前数大于那个数则返回1. 如果是自定义类实例化而来的对象, 需要自己定义compareTo()方法, 在方法内部定义比较规则.
需求:定义学生类, 学生类内部定义compareTo方法, 按照学生年龄排序. 将学生类添加至TreeSet对象中, 遍历集合对象
public class Student implements Comparable<Student> {
private String name;
private int age;
public Student(){};
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public int compareTo(Student s) {
return this.age - s.age;
}
}
public class TreeSetDemo {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<Student>();
Student s1 = new Student("成成", 18);
Student s2 = new Student("宝中", 17);
Student s3 = new Student("金喜", 20);
ts.add(s1);
ts.add(s2);
ts.add(s3);
for (Student stu : ts) {
System.out.println(stu.getName());
}
}
}
比较器排序
使用比较器排序, 需要自己实现一个比较器, 并在初始化TreeSet时使用有参构造, 并把比较器传入进去, 对象加入TreeSet对象时, 就会按照比较器的规则进行排序了
需求:定义学生类, 学生类内部定义compareTo方法, 按照学生年龄排序. 将学生类添加至TreeSet对象中, 遍历集合对象
public class Student {
private String name;
private int age;
public Student(){};
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class TreeSetDemo {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<Student>(
new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
return s1.getAge() - s2.getAge();
}
}
);
Student s1 = new Student("小明", 18);
Student s2 = new Student("小红", 17);
Student s3 = new Student("小强", 20);
ts.add(s1);
ts.add(s2);
ts.add(s3);
for (Student stu : ts) {
System.out.println(stu.getName());
};
}
}
Map
Map为一个接口, 将键映射到值的对象。 Map不能包含重复的键; 每个键可以映射到最多一个值。
interface Map<K,V> K:键的类型;V:值的类型
特点:
- 键值对映射关系
- 一个键对应一个值
- 键不能重复, 值可以重复
- 元素存取无序
基本使用:
import java.util.HashMap;
import java.util.Map;
public class MapDemo01 {
public static void main(String[] args) {
//创建HashMap对象
Map<String, String> m = new HashMap<String, String>();
//添加键值对
m.put("name_1", "Jeremy");
m.put("name_2", "Fish");
//输出
System.out.println(m); //{name_2=Fish, name_1=Jeremy}
}
}
方法的介绍
//增:
- put(key, value): 添加键值对, 返回键对应的值
//删:
- remove(key, value): 如果指定的键对应的值为value, 则进行删除
- remove(key): 根据键删除键值对元素, 返回键对应的值
- clear(): 清空所有键值对元素
//改:
- put(key, value): 同则修改, 异则添加
//查:
- containsKey(key): 判断是否存在某个特定的key, 返回值为布尔值
- containsValue(Value): 判断是否包含指定的值, 返回值为布尔值
- isEmpty(): 判断Map对象是否为空, 返回值为布尔值
- size(): 获取Map对象键值对个数, 返回值为整型
- get(key): 获取指定键的值
- keySet(): 获取所有的键, 返回值为Set类型
- Values(): 获取所有值的集合
- enterSet(): 获取所有键值对对象的集合
示例
import java.util.HashMap;
import java.util.Map;
public class MapDemo01 {
public static void main(String[] args) {
Map<String, String> m = new HashMap<String, String>();
m.put("name_1", "Jeremy");
m.put("name_2", "Fish");
System.out.print("put增加键值对后的m为: ");
System.out.println(m); //put增加键值对后的m为: {name_2=Fish, name_1=Jeremy}
m.remove("name_1", "Jer");
System.out.print("如果键name_1对应的值为Jer, 则移除: ");
System.out.println(m); //如果键name_1对应的值为Jer, 则移除: {name_2=Fish, name_1=Jeremy}
//直接移除name_1的键值对
m.remove("name_1");
System.out.print("移除name_1的键值对后, m的值: ");
System.out.println(m); //移除name_1的键值对后, m的值: {name_2=Fish}
//clear()后的m
m.clear();
System.out.print("clear()后m的值为: ");
System.out.println(m); //clear()后m的值为: {}
//空对象m重新添加name_1键值对
m.put("name_1", "Jeremy");
m.put("name_2", "Lucy");
System.out.print("重新添加name_1键值对的m为: ");
System.out.println(m); //重新添加name_1键值对的m为: {name_2=Lucy, name_1=Jeremy}
//重复添加name_1的键, 但是改变对应的值
m.put("name_1", "LiLi");
System.out.print("使用name_1键的新值覆盖原有值后m的值: ");
System.out.println(m); //使用name_1键的新值覆盖原有值后m的值: {name_2=Lucy, name_1=LiLi}
//判断是否包含指定的key
System.out.println(m.containsKey("name_2")); //true
//判断是否包含指定的value
System.out.println(m.containsValue("Lucy")); //true
//判断是否为空
System.out.println(m.isEmpty()); //false
//获取Map对象的长度
System.out.println(m.size()); //2
//获取指定键对应的值
System.out.println(m.get("name_1")); // LiLi
//获取所有键的集合
System.out.println(m.keySet()); //[name_2, name_1]
//获取所有值的集合
System.out.println(m.values()); //[Lucy, LiLi]
//获取所有的键值对
System.out.println(m.entrySet()); //[name_2=Lucy, name_1=LiLi]
}
}
遍历方式
方式一
使用增强for, 遍历集合的所有键或值进行操作
import java.util.HashMap;
import java.util.Map;
public class MapDemo02 {
public static void main(String[] args) {
Map<String, String> m = new HashMap<String, String>();
m.put("name_1", "金喜");
m.put("name_2", "帅帅");
m.put("name_3", "晨阳");
for(String key: m.keySet()){
System.out.println(key);
}
for (String value:m.values()){
System.out.println(value);
}
}
}
方式二
通过增强for遍历键值对组成的EntrySet对象
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapDemo03 {
public static void main(String[] args) {
Map<String, String> m = new HashMap<String, String>();
m.put("name_1", "Jeremy");
m.put("name_2", "刘桂宁");
m.put("name_3", "毕奎成");
Set<Map.Entry<String, String>> items = m.entrySet();
for(Map.Entry<String, String> item:items) {
System.out.println("键值对为: " + item);
System.out.println("键为: " + item.getKey());
System.out.println("值为: " + item.getValue());
System.out.println("-------------------------");
}
}
}