Java的集合
- 内存层面需要针对于多个数据进行存储。此时,可以考虑的容器有:数组、集合类
- 数组既可以存储基本数据类型,也可以存储引用数据类型(定义成
Object
类的数组) - 集合只能存储引用数据类型
- 数组既可以存储基本数据类型,也可以存储引用数据类型(定义成
- Java集合框架体系(
java.util
包下)java.util.Collection
:存储一个一个的数据- 子接口:
List
:存储有序的、可重复的数据(“动态”数组)Arraylist
(主要实现类)、LinkedList
、Vector
- 子接口:
Set
:存储无序的、不可重复的数据HashSet
(主要实现类)、LinkedHashSet
、TreeSet
- 子接口:
java.util.Map
:存储一对一对的数据HashMap
(主要实现类)、LinkedHashMap
、TreeMap
、Hashtable
、Properties
一、集合的体系结构
1、单列集合Collection
- 在添加数据的时候,每次只能添加一个元素
单列集合又可分为:
- List系列集合:添加的元素是有序的、可重复、有索引
- Set系列集合:添加的元素是无序的、不重复、无索引
单列集合Collection:
Collection
是单列集合的祖宗接口,它的功能是全部单列表集合都可以继承使用的
(1) List系列集合
List
是 Java 中的一种接口,它继承自 Collection
接口,属于 Java 集合框架的一部分。List
代表一个有序的元素集合,允许重复元素,并且可以通过索引访问元素。常见的 List
实现类有 ArrayList
、LinkedList
和 Vector
等。
特点:
- 有序性:
List
保证元素的插入顺序。即使存在重复元素,元素的插入顺序也会被保留。 - 允许重复:与集合框架中的
Set
不同,List
允许包含重复的元素。 - 随机访问:
List
允许根据索引访问元素,因此可以高效地随机访问。 - 支持 null 元素:
List
中允许存储null
元素。
常见的 List
实现类:
ArrayList
:- 基于动态数组实现,提供快速的随机访问。
- 插入和删除操作相对较慢,特别是在中间插入或删除元素时,可能需要移动元素。
- 适合频繁读取操作的场景。
LinkedList
:- 基于双向链表实现,插入和删除操作效率较高,尤其是在列表头或中间进行插入/删除时。
- 不支持快速随机访问,因此访问元素时需要从头开始遍历。
Vector
:- 类似于
ArrayList
,但它是线程安全的。 - 由于线程安全机制的开销,
Vector
比ArrayList
性能稍差,且不常用。
- 类似于
常用操作:
- 添加元素:
add(E e)
、add(int index, E element)
,在指定位置插入元素。 - 访问元素:
get(int index)
,通过索引获取元素。 - 删除元素:
remove(Object o)
、remove(int index)
,删除指定元素或索引位置的元素。 - 修改元素:
set(int index, E element)
,替换指定位置的元素。 - 大小:
size()
,返回列表的大小。 - 清空列表:
clear()
,清空列表中所有元素。
Arraylist中的一些常用方法:
- 添加元素
add(E e)
:将元素添加到ArrayList
的末尾。add(int index, E element)
:在指定索引位置插入元素,将原位置及后面的元素向后移动。
- 获取元素
get(int index)
:返回指定索引位置的元素。
- 删除元素
remove(Object o)
:删除第一次出现的指定元素,如果有多个相同元素,只会删除第一个。remove(int index)
:删除指定索引位置的元素。clear()
:删除ArrayList
中的所有元素。
- 查找元素
contains(Object o)
:检查列表中是否包含指定的元素。indexOf(Object o)
:返回指定元素第一次出现的索引位置。如果不存在返回-1
。lastIndexOf(Object o)
:返回指定元素最后一次出现的索引位置。如果不存在返回-1
。
- 更新元素
set(int index, E element)
:用指定的元素替换指定索引位置的元素。
- 列表大小
size()
:返回ArrayList
中元素的数量。
- 检查是否为空
isEmpty()
:检查ArrayList
是否为空,若没有元素返回true
,否则返回false
。
- 转换为数组
toArray()
:将ArrayList
转换为一个数组,返回一个Object[]
类型的数组。toArray(T[] a)
:将ArrayList
转换为指定类型的数组。
- 集合操作
addAll(Collection<? extends E> c)
:将另一个集合中的所有元素添加到ArrayList
中。removeAll(Collection<?> c)
:删除ArrayList
中所有在指定集合中的元素。retainAll(Collection<?> c)
:保留ArrayList
中所有在指定集合中的元素,删除其他元素。
- 子列表
subList(int fromIndex, int toIndex)
:返回ArrayList
的一个子列表,包含从fromIndex
到toIndex-1
索引之间的元素。
- 迭代器
iterator()
:返回一个Iterator
,用于遍历ArrayList
中的元素。
示例代码:
import org.junit.Test;import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;public class ListTest {@Testpublic void test01() {List list = new ArrayList();list.add("AA");list.add(123);list.add("BB");Person p = new Person("Tom", 23);list.add(p);System.out.println(list.toString());// 在索引位置插入元素list.add(2, "CC");System.out.println(list);// 添加整个元素List<Integer> list1 = Arrays.asList(1, 2, 3);list.addAll(1, list1);System.out.println(list);}@Testpublic void test02() {List list = new ArrayList();list.add("AAA");list.add(123); // 自动装箱list.add("BB");list.add(2); // 自动装箱Person p = new Person("小明", 21);list.add(p);System.out.println(list);// 删除索引2的元素list.remove(2); // 如果想要删除元素2,此位置不会自动装箱,需要手动装箱System.out.println(list);System.out.println(list.get(2)); // 删除BB元素后,后面的元素前移// 删除数据为2的元素,进行手动装箱list.remove(Integer.valueOf(2));System.out.println(list);}@Testpublic void indexList() {List list = new ArrayList();list.add("AAA");list.add(123); // 自动装箱list.add("BB");list.add(2); // 自动装箱Person p = new Person("小明", 21);list.add(p);// 方式一:迭代器Iterator iterator = list.iterator();// 判断下一个位置是否有数据while (iterator.hasNext()) {// 打印下一个位置的集合元素System.out.println(iterator.next());}System.out.println("*******************");// 方式二:增强for循环for (Object obj: list) {System.out.println(obj);}System.out.println("*******************");// 方式三:普通的for循环for (int i = 0; i < list.size(); i++) {System.out.println(list.get(i));}}
}
(2) Set系列集合
- 存储无序的、不可重复的数据
HashSet
:主要实现类;底层使用的是HashMap
,即使用数组+单向链表+红黑树结构进行存储(jdk8中添加的这个红黑树结构)LinkedHashSet
:是HashSet
的子类;在父类使用的结构的基础上,又添加了一组双向链表,用于记录添加元素的先后顺序。即:我们可以按照添加元素的顺序实现遍历。便于频繁的查询操作TreeSet
:底层使用红黑树存储。可以按照添加的元素的指定的属性的大小顺序进行遍历
- Set中无序性、不可重复性的理解:
- 无序性:
- 无序性不等于随机性,添加和遍历元素的顺序不一致也不是无序性
- 无序性与添加的元素的位置有关,根据添加元素的哈希值,计算其在数组中的存储位置。此位置不是依次排列的,表现为无序性
- 不可重复性:
- 添加到Set中的元素是不能相同的
- 比较的标准,需要判断
hashCode()
得到的哈希值以及equals()
得到的boolean
型的结果 - 哈希值相同才会判断
equals()
方法,如果该方法返回的为true
,则认为元素是相同的 - 因此要添加不重复的自定义的类的值时,需要重写
equals()
方法和hashCode()
方法
- 无序性:
HashSet的示例代码:
import org.junit.Test;import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;public class SetTest {@Testpublic void test() {Set set = new HashSet();set.add("aaa");set.add(123);set.add("bbb");set.add(new Person("小明", 20));// 使用迭代器遍历// 遍历出来的顺序可能和添加时的不一致,但该顺序也不存在随机性Iterator iterator = set.iterator();while (iterator.hasNext()) {System.out.println(iterator.next());}// 判断是否存在 (必须在Person类中重写equals方法和hasCode方法才能正确判断)// 因为:如果没有重写hasCode方法的话,会默认调用Object中的,Object会随机生成两个不同的数,因此就不会使用equals方法,所有返回为falseSystem.out.println(set.contains(new Person("小明", 20)));}@Testpublic void test2() {Set set = new LinkedHashSet();set.add("aaa");set.add(123);set.add("bbb");set.add(new Person("小明", 20));// 使用迭代器遍历// 使用LinkedHashSet,内部添加了双向链表,所以输出的顺序即是添加数据的顺序Iterator iterator = set.iterator();while (iterator.hasNext()) {System.out.println(iterator.next());}}
}
TreeSet
- 向TreeSet中添加的元素的要求:必须是同一个类型的对象,否则会报ClassCastException错误
- 添加的元素需要考虑排序:①自然排序 ②定制排序
判断数据是否相同的标准:
- 不再是考虑
hashCode()
和equals()
方法了,也就意味着添加到TreeSet
中的元素所在的类不需要重写hashCode()
和equals()
方法 - 比较元素大小的或是比较元素是否相同的标准就是考虑自身排序或定制排序中,
compareTo()
或compare()
的返回值- 如果
compareTo()
或compare()
的返回值为0,则认为两个对象是相等的。由于TreeSet
中不能存放相同的元素,则后一个相等的元素就不能添加到这个TreeSet
中了
- 如果
示例代码:
import org.junit.Test;import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;public class TreeSetTest {@Testpublic void test1() {TreeSet set = new TreeSet();set.add("aaa");set.add("sss");set.add("bbb");set.add("mmm");set.add("ooo");set.add("ddd");
// set.add("ddd");
// set.add(123); // 数据类型必须一致,否则会ClassCastException报错Iterator iterator = set.iterator();while (iterator.hasNext()) {System.out.println(iterator.next()); // 会自动排序}}@Testpublic void test2() {TreeSet set = new TreeSet();// 自然排序,在User类中重写compareTo()方法User user1 = new User("小明", 19);User user2 = new User("小李", 21);User user3 = new User("小琦", 19);User user4 = new User("小刘", 22);User user5 = new User("小钰", 18);set.add(user1);set.add(user2);set.add(user3);set.add(user4);set.add(user5);Iterator iterator = set.iterator();while (iterator.hasNext()) {System.out.println(iterator.next());}}// 定制排序@Testpublic void test3() {/*** 自定义比较器,来进行排序*/Comparator comparator = new Comparator() {@Overridepublic int compare(Object o1, Object o2) {if (o1 == o2) {return 0;}if (o1 instanceof User && o2 instanceof User) {User u1 = (User)o1;User u2 = (User)o2;int value = u1.getName().compareTo(u2.getName());if (value == 0) {return u1.getAge() - u2.getAge();}else {return value;}}throw new RuntimeException("类型不匹配");}};TreeSet set = new TreeSet(comparator);User user1 = new User("小明", 19);User user2 = new User("小李", 21);User user3 = new User("小明", 23);User user4 = new User("小刘", 22);User user5 = new User("小钰", 18);set.add(user1);set.add(user2);set.add(user3);set.add(user4);set.add(user5);Iterator iterator = set.iterator();while (iterator.hasNext()) {System.out.println(iterator.next());}}
}
使用到的User类
public class User implements Comparable{private String name;private int age;public User(String name, int age) {this.name = name;this.age = age;}public User() {}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;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic int compareTo(Object o) {if (this == o) {return 0;}if (o instanceof User) {User u = (User)o;int value = this.age - u.age;if (value != 0) {return value;}return -this.name.compareTo(u.name);}throw new RuntimeException("类型不匹配");}
}
2、双列集合Map
- 在添加数据的时候,每次添加一对数据
Map及其实现类的对比
java.util.Map
:存储一对一对的数据(key-value)
键值对HashMap
:主要实现类;线程不安全,效率高;可以添加null的key
和value
值;底层使用数组+单向链表+红黑树结构存储LinkedHashMap
:是HashMap
的子类;在HashMap
使用的数据结构的基础上,增加了一对双向链表,用于记录添加的元素的先后顺序,进而使我们在遍历元素时,就可以按照添加的顺序显示;在开发中,对于频繁的遍历操作,建议使用此类
TreeMap
:底层使用红黑树存储;可以按照添加的key-value
中的key
元素的指定的属性的大小顺序进行遍历,考虑自然排序和定制排序Hashtable
:古老实现类;线程是安全的,但效率低;可以添加null的key
和value
值;底层使用数组+单向链表结构存储Properties
:其key
和value
都是String
类型,常用来处理属性文件
- [面试题] 区别
HashMap
和Hashtable
、区别HashMap
和LinkedHashMap
、HashMap
的底层实现 (①new HashMap()
②put(key,value)
)
(1) HashMap
HashMap
中元素的特点:HashMap
中的所有的key
彼此之间是不可重复的、无序的。所有的key
就构成一个Set
集合。—>key
所在的类要重写hashCode()
和equals()
HashMap
中的所有的value
彼此之间是可重复的、无序的。所有的value
就构成一个Collection
集合。—>value
所在的类要重写equals()
HashMap
中的—个key-value
,就构成了—个entry
HashMap
中的所有的entry
彼此之间是不可重复的、无序的。所有的entry
就构成了一个Set
集合
常用方法:
put(K key, V value)
:将指定的键值对添加到 HashMap
中。如果键已经存在,则更新其对应的值。
get(Object key)
:返回指定键对应的值。如果键不存在,返回 null
。
remove(Object key)
:移除指定键及其对应的值。
containsKey(Object key)
:判断 HashMap
中是否包含指定的键。
containsValue(Object value)
:判断 HashMap
中是否包含指定的值。
size()
:返回 HashMap
中键值对的数量。
clear()
:清空 HashMap
中的所有元素。
@Testpublic void test4() {Map map = new HashMap();// 修改也是直接使用putmap.put("AA", 1);map.put("BB", 2);map.put("CC", 3);map.put("AA", 4);map.put(new Person("小明", 20), 100000);System.out.println(map);Map map1 = new HashMap();map1.put("AA", 2);map1.put("BB", 15);map1.put("FF", 666);map.putAll(map1);System.out.println(map);// 查看map的长度 size()System.out.println(map.size());// 删除数据 remove()System.out.println(map.remove("AA"));System.out.println(map);System.out.println(map.remove(new Person("小明", 20)));System.out.println(map);// 遍历数据// 遍历key集 keySet()System.out.println(map.keySet());// 遍历values集 values()System.out.println(map.values());// 遍历entry集 entrySet()System.out.println(map.entrySet());// 方式一: 获取entry对象Set set = map.entrySet();Iterator iterator = set.iterator();while (iterator.hasNext()) {Map.Entry next = (Map.Entry) iterator.next();System.out.println(next.getKey() + "--->" + next.getValue());}// 方式二: 获取keyset对象Set set1 = map.keySet();for (Object key : set1) {System.out.println(key + "--->" + map.get(key));}}
(2) TreeMap
- 底层使用红黑树存储
- 可以按照添加的key-value中的key元素的指定的属性的大小顺序进行遍历
- 需要考虑使用的排序①自然排序 ②定制排序
- 注意:向
TreeMap
中添加的key
必须是同一个类型的对象
@Testpublic void test6() {/*** 定制排序*/Comparator comparator = new Comparator() {@Overridepublic int compare(Object o1, Object o2) {if (o1 instanceof User && o2 instanceof User) {User u1 = (User) o1;User u2 = (User) o2;int value = u1.getName().compareTo(u2.getName());if (value != 0) {return value;}return u1.getAge() - u2.getAge();}throw new RuntimeException("类型不匹配");}};TreeMap treeMap = new TreeMap(comparator);User user = new User("Jack", 21);User user1 = new User("Mary", 23);User user2 = new User("Tom", 19);User user3 = new User("Jerry", 21);User user4 = new User("Merck", 29);treeMap.put(user, 78);treeMap.put(user1, 66);treeMap.put(user2, 58);treeMap.put(user3, 67);treeMap.put(user4, 88);Set set = treeMap.entrySet();Iterator iterator = set.iterator();while (iterator.hasNext()) {Map.Entry next = (Map.Entry) iterator.next();System.out.println(next.getKey() + "--->" + next.getValue());}}
(3) Properties
Properties
是 Java 中的一个特殊类,继承自 Hashtable
,用于处理键值对的存储和读取,特别用于存储配置文件中的属性(例如 .properties
文件)。通常,Properties
类用于存储应用程序的配置参数,像数据库连接信息、系统配置等。
主要特点:
- 键和值都是字符串类型:
Properties
的键和值都是String
类型。这使得它非常适合用来表示配置文件,因为配置文件的内容通常都是键值对的形式。
- 继承自
Hashtable
:- 由于
Properties
类继承了Hashtable
,它拥有Hashtable
的大部分方法,如put()
、get()
、remove()
等,但它的用途和Hashtable
略有不同,专门用于存储配置文件中的属性。
- 由于
- 默认加载和存储:
Properties
类具有自动加载和存储的能力,能够方便地从文件中读取配置项,并且将其保存到文件中。这些配置文件通常有.properties
扩展名。
- 用于配置管理:
Properties
常用于 Java 应用程序中的配置管理,例如读取和写入应用的配置文件。
常用方法:
load(InputStream inStream)
:从输入流中加载属性文件,通常用于从.properties
文件读取数据。store(OutputStream outStream, String comments)
:将Properties
对象中的数据保存到输出流中,通常用于将数据写入到.properties
文件。getProperty(String key)
:获取指定键的值。setProperty(String key, String value)
:设置指定键的值。list(PrintStream out)
:列出所有属性及其键值对,输出到打印流中。
@Testpublic void test1() throws Exception {// 将数据封装到具体的配置文件中,在程序中读取配置文件的信息// 实现了数据和代码的解耦,此时我们并没有修改代码,就省去了重新编译和打包的过程File file = new File("info.properties"); // 注意:需要提前创建好System.out.println(file.getAbsolutePath()); // main方法中和test方法中需要创建的文件的路径不同FileInputStream fis = new FileInputStream(file);Properties pros = new Properties();pros.load(fis); // 加载流中的文件中的数据// 读取数据String name = pros.getProperty("name");String password = pros.getProperty("password");System.out.println(name + ":" + password);fis.close();}