一.Java集合概述
java集合类存放于java.util包里, 是一个用来存放对象的容器。
- 集合只能存放对象。
如果存一个int数据, 其实是自动转换为Integer包装类后存入的---->自动装箱 - 集合存放的是多和对象的引用, 对象本身还是存放在堆内存中。
- 集合可以存放不同的类型, 不限数量的数据类型。
Java集合可分Set、List和Map三种大体系
- Set: 无序、不可重复的集合
- List: 有序、可重复的集合
- Map: 有映射关系的集合
在JDK5之后, 集合增加了泛型, 可以通过泛型限制Java集合中存储的对象的数据类型
二.集合框架
集合按照其存储结构分为两大类 : 分别是单列集合java.util.Collections 和双例集合 java.util.Map
1.单列集合体系–Collection接口
- Collection : Java中单列集合的根接口是Collection接口。 单列集合用于存储一系列符合某种规则的元素, 他有两个重要的子接口, 分别是java.util.List 和java.util.Set。其中, List的特点是元素有序、元素可重复。Set的特点是元素无序,且不可重复。
List接口的主要实现类有java.util.ArrayList 和 java.util.LinkedList, Set接口的主要实现类有java.util.HashSet 和 java.util.TreeSet。
1.List接口 : 有索引, 可以存储重复元素, 存取有序
常用实现类:
- ArrayList : 底层是数组实现的, 查询快, 增删慢
- LingkedList : 底层是双向链表实现的, 查询慢, 增删快
- Vector : 底层实现和ArrayList很相似, 数组实现, 主要特点是线程安全(性能较低, 使用较少)
关于元素重复 : List集合保证元素存取顺序, 元素可重复
关于null值 : List集合可以存放多个null值。
2.Set接口 : 无索引, 不可存储重复元素, (存取无序)
常用实现类:
- HashSet : 底层其实是HashMap实现的。 哈希表(数组+链表/红黑树)实现的, 无索引, 存取无序
- LinkedHashSet : 底层其实是LinkedHashMap实现的。 哈希表(数组+链表/红黑树)+链表 实现的, 无索引, (最主要特点是可以保证存取顺序)
- TreeSet : 其实是TreeMap实现的。 红黑树实现, 一般用于排序
关于元素重复 : Set集合都不可存储重复元素
关于null值 : 由于不可重复性, Set集合最多存放一个null值。另外, TreeSet不可存放null值, 会报空指针异常。因为TreeSet底层涉及调用compare()和compareto()方法进行排序
关于线程安全 : Set集合的三个子类都是线程不安全的
关于Set集合跟Map集合 : Set集合三个子类都是通过Map集合实现的, 是如何存储单列元素的?Set集合的元素存储在底层Map集合的key值中, 其value值用new Object()充当。
Collection接口(常用的通用方法)
Collection接口下集合常用方法
- boolean add(E e); 向集合中添加元素
如果此coolection由于调用而发生更改(成功添加元素),则返回 true - boolean addAll(Collection<? extends E> c): 将c中的所有元素都加到当前集合
- boolean remove (E e); 删除集合中的某个元素, 返回boolean值
- void clear(); 清空集合中所有元素
- boolean contains(E e); 判断集合中是否包含某个元素
- boolean isEmpty(); 判断集合是否为空
- int size(); 获取集合长度
- Object[] toArray(); 将集合转换成一个数组
3.双列集合
双列集合–Map
- Map接口 : Java中双列集合的根接口是Map接口。
主要实现类:
- HashMap : 在JDK1.8中HashMap数据结构的存储由数组+链表的方式,变化为数组+链表+红黑树的存储方式,在性能上得到优化。
HashMap基于Map接口实现,元素以键值对的方式存储。同时为了保证元素不重复, 需要重写hashCode和equals方法。另外HashMap不能保证放入元素的顺序, 它是无序的, 和放入的顺序并不能相同。 - Hashtable : Hashtable的主要特点是线程安全, 且不可存放null值。(Hashtable是四个实现类中唯一线程安全的)
- LinkedHashMap : 底层在哈希表基础上增加一个链表来保证迭代顺序, 即LinkedHashMap是存取有序的。
- TreeMap : 底层实现为二叉树的红黑树。最主要的特点是可根据key值排序。
关于元素重复 : 双列集合的key值都不可重复, value值则不限制。
关于null值 : Map集合的key值由于不可重复最多存储一个null值, value值不限制, 另外由于TreeMap涉及使用compare()和compareto()方法排序, 其key不能存放null值。
HashTable则规定key和value都不能存放null值。需要注意下
补充 : HashMap 有相当快的查询速度主要是因为它是通过计算散列码来决定存储的位置。
HashMap中主要是通过key的hashCode来计算hash值的,只要hashCode相同,计算出来的hash值就一样。
如果存储的对象对多了,就有可能不同的对象所算出来的hash值是相同的,这就出现了所谓的hash冲突。解决hash冲突的方法有很多,HashMap底层是通过链表来解决hash冲突的
扩展-Iterator接口
在程序开发中, 经常要遍历集合中所有元素。针对这种需求, JDK专门提供了一个接口java.util.Iterator。
Iterator接口也是Java集合中的一员, 但它与Collection、Map接口有所不同, Collection接口与Map接口主要用于存储元素, 而Iterator接口主要用于迭代访问(遍历)Colletion中的元素, 因此Iterator对象也被称为迭代器。
想要遍历Collection集合, 就要获取该集合迭代器完成迭代操作:
- public Iterator iterator(): 获取集合对应的迭代器, 用来遍历集合中的元素
迭代的概念:
- **迭代:**即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素, 如果有,就把这个元素取出来,并继续下一个元素的判断, 直到集合中的所有元素全部取出。
Iterator接口常用方法:
- public boolean hasNext() : 如果仍有元素可迭代, 返回true
- public E next() : 返回迭代的下一个元素
具体操作:
Collection set = new HashSet();//多态
//...存入集合元素
//使用Iterator()接口
Iterator it = set.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
迭代器实现原理:
扩展-增强for循环(推荐使用)
增强for循环是JDK1.5后出现的新特性, 用来遍历集合和数组
其底层使用的也是迭代器, 使用for循环的格式, 简化了迭代器的书写。
Collection实现了Iterable接口, 实现这个接口允许对象成为"foreach"语句的目标。所有的单列集合都可以使用增强for循环。
格式:
for(集合/数组的数据类型 变量名 : 集合/数组名){
System.out.println(变量名);
其他语句;
...;
}
或者写成:
集合/数组名.forEach(System.out::print);
示例:
public static void main(String[] args) {
Collection c1 = new HashSet();
c1.add(1);
c1.add(2);
c1.add(3);
for (Object o : c1) {
System.out.println(o);
}
c1.forEach(System.out::println);
}
三.List集合
List代表一个元素有序、可重复的集合, 集合中共每个元素都有其对应的顺序索引
- List允许使用重复元素, 可以通过索引来访问指定位置的集合元素。
- List默认按元素的添加顺序设置元素的索引。
- List集合里添加了一些根据索引来操作集合元素的方法。
1.ArrayList类
- ArrayList : 底层是数组结构实现的, 查询快, 增删慢
import java.util.List;
import java.util.ArrayList;
public class TestArrayList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
//list根据添加顺序创建索引
list.add("b");//第一个,索引下标0
list.add("c");//索引下标1
list.add("a");//索引下标2
list.add("c");//索引下标3,元素可重复
System.out.println(list.get(2));//通过索引来访问指定位置的集合元素
list.add(1,"2");//在第二位添加一个元素"d"
System.out.println(list);
List<String> ex = new ArrayList<String>();
ex.add("111");
ex.add("222");
list.addAll(1,ex);//指定位置插入一个集合的所有元素
System.out.println(list);
System.out.println(list.indexOf("c"));//获取指定元素在集合中第一次出现的索引下标
System.out.println(list.lastIndexOf("c"));//获取指定元素在集合中最后一次出现的索引下标
list.remove("c");//移除指定元素,只会移除第一次出现的
System.out.println(list);
list.remove(3);//根据指定的索引下标移除元素
System.out.println(list);
list.set(0,"f");//根据指定的索引下标修改元素
System.out.println(list);
System.out.println(list.subList(2, 5));//根据指定的索引下标区间截取元素,形成一个新集合。
//新集合包含截取的开始位不包含结束位,左闭右开,如[2,5)
}
}
2.LinkedList类
- java.util.LinkedList集合数据存储的结构是链表结构, 增删快, 查询慢。LinkedList是一个双向链表。
Linkedlist集合提供了大量操作收尾元素的方法。注意 : 要使用LinkedList类特有的方法, 就不能使用多态写法。
注意: 此实现不是同步的。多线程操作必须保持外部同步。
-
public void addFirst(E e) : 将指定元素插入此列表的开头。
-
public void addLast(E e) : 将指定元素插入此列表的结尾。
-
public void push(E e) : 将元素推入此列表所表示的堆栈。推入集合的开头, 等效于addFirst()。
-
public void getFirst() : 获取此列表的第一个元素。
-
public void getLast() : 获取此列表的最后一个元素。
-
public void removeFirst() : 移除并返回此列表的第一个元素。
-
public void removeLast() : 移除并返回此列表的最后一个元素。
-
public void pop() : 从此列表所表示的堆栈处弹出一个元素。从集合开头弹出, 等效于removeFirst()。
-
public boolean isEmpty() : 如果列表不包含元素, 返回true。
-
public void clear(): 清空集合元素
public class testLinkedList {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList();
list.add("a");
list.add("b");
list.add("c");
list.add("1");
list.add("2");
list.add("3");
list.add("4");
System.out.println(list);//[a, b, c, 1, 2, 3, 4]
System.out.println("=================");
list.addFirst("start" );
list.addLast("end");
list.push("push");//将元素推入此列表所表示的堆栈。推入集合的开头,等效于addFirst()
System.out.println(list);//[push, start, a, b, c, 1, 2, 3, 4, end]
System.out.println("=================");
System.out.println(list.getFirst());//push
System.out.println(list.getLast());//end
System.out.println("=================");
System.out.println(list.removeFirst());//push
System.out.println(list.removeLast());//end
System.out.println(list.pop());//从此列表所表示的堆栈处弹出一个元素。从开头弹出,等效removeFirst()
System.out.println(list);//[a, b, c, 1, 2, 3, 4]
System.out.println("=================");
System.out.println(list.isEmpty());//false
}
}
3.Vector类(了解)
ArrayList和Vector是List接口的两个典型实现, Vector用法与ArrayList基本相同
区别:
- Vector是JDK1.0版本最早期的集合类, 通常使用 ArrayList
- ArrayList是线程不安全的, 而Vector是线程安全的, 所有效率低下
- 即使为保证List集合线程安全, 也不推荐使用Vector, 尽量用其他方式实现
四.Set集合
1.HashSet类
扩展-Hash值与哈希表
1.哈希值 (也称散列值)
哈希值 : 是一个十进制的整数, 由系统随机给出, 是一个逻辑地址, 是模拟出来的地址而非数据实际存储的物理地址。
hash算法/散列算法 : 将无限范围内的数据, 映射到一个有限的范围内(Java中为数组容量, 即Integer的最大值 : 0~ 231-1)。 (数据压缩, 抽屉原理)
hash冲突 : 散列表要解决的一个问题就是散列值的冲突问题 (多个不同对象映射到同一个hash值),通常是两种方法:链表法和开放地址法。链表法就是将相同hash值的对象组织成一个链表放在hash值对应的槽位;开放地址法是通过一个探测算法,当某个槽位已经被占据的情况下继续查找下一个可以使用的槽位。
在Object类中有一个hashCode()方法, 可以返回对象的哈希值。
hashCode()方法的源码;
- public native int hashCode();
native : 表示该方法调用的是本地操作系统的方法
( toString()方法打印的地址值实际就是hashCoe的16进制表示 )
字符串的哈希值 :
String类重写了hashCode()方法, 所以即使通过new创建的 字面值相等的字符串对象的hashCode()值相等(实际不是一个对象)。
2.哈希表 (也称散列表)
JDK1.8之前, 哈希表=数组+链表
JDK1.8之后, 哈希表=数组+链表(红黑树), 链表的长度超过了8位, 自动转换成红黑树结构, 以优化查询的性能。
哈希表主要的特点就是查询速度快。
数组结构, 把元素进行了分组, 相同哈希值的元素分为一组, 并在数组中存储哈希值, 通过链表/红黑树把相同哈希值的元素连接到一起。
1.HashSet类概述
- HashSet : 底层是哈希表(数组+链表/红黑树)实现的(查询速度快), 无索引, 不可存储重复元素, 存取无序。
哈希表实际上是一个HashMap实例。HashSet 允许使用null元素。
HashSet类实现set接口, set接口继承自Collection接口, 常用方法基本继承自Collection接口
HashSet是Set接口的典型实现类, 大多数时候使用Set集合时都使用HashSet这个典型实现类, 所说的Set集合大多数都是指HashSet
HashSet按 Hash 算法来存储集合中的元素, 因此具有很好的存取和查找性能。
HashSet具有以下特点:
- 不能保证元素的排列顺序 (对象的存储位置根据hashCode值决定)
- 不可重复 (指hashCode不可重复)
- HashSet不是线程安全的
- 集合元素可以是 null
存储过程:
- 当向 HashSet 集合中存入一个元素时, HashSet会调用该对象的 hashCode() 方法和equals()方法, 判断元素是否重复。
- add()方法会调用元素的hashCode方法, 获取元素的hash值, 并查找集合中有没有相同hash值的元素。发现没有就会把数据存储到集合中, 如果发现有(hash冲突), 就会调用equals()方法进行比较, s2.equals(s1) ,返回true则不会存储s2到集合中。如果false就存储到s1的链表/红黑树中。
- hashCode()返回值和equals()返回值都相等的元素判断为重复元素, 不会重复添加到Set集合中。
equals()相等的两个对象,hashcode()一定相等;
equals()不相等的两个对象,hashcode()一般不等, 但有可能相等(因为存在hash冲突的情况)。
equals()和hashCode()这两个方法都是从Object类中继承过来的。
equals()方法在Object类中定义如下: public boolean equals(Object obj) { return(this == obj); } 是对两个对象的地址值进行的比较(即比较引用是否相同)。
但是String、Math、还有Integer、Double等封装类使用equals()方法时,已经覆盖了object类的 equals() 方法。
为什么不同的对象hashcode可能相同:
因为当输入数据量太大,哈希值却是固定32长度的,这意味着哈希值是一个有限集合,无法建立一对一关系,所以hashcode相等是有可能会发生的。
———————————————— 版权声明:本文为CSDN博主「键盘侠001」的原创文章,遵循CC 4.0
BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_43126186/article/details/99684972
2.HashSet类的操作:
- 添加元素 set.add();
- 移除元素 set.remove();
- 清空元素 set.clear();
- 判断是否包含元素 set.contains(); 返回boolean值
- 获取集合的元素个数 set.size();
- 遍历集合元素(使用迭代器)
- 使用 Iterator() 接口
- 使用 for each迭代集合
使用Iterator接口遍历元素
Iterator接口主要用于遍历Collection集合中的元素, Iterator对象也被称为迭代器
Iterator接口隐藏了各种Collection实现类的底层细节, 向应用程序提供了遍历Collection集合元素的统一编程接口
Iterrator仅用于遍历集合, Iterator本身并不提供承装对象的能力。如果要创建Iterator对象, 则必须有一个被迭代的集合。
Java5 提供了 foreach 循环迭代访问Collection
for(Object obj : set)
//把set的每一个值取出来,赋值给obj, 直到循环set的所有值
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class TestHashSet {
public static void main(String[] args) {
Set set = new HashSet();
set.add(1);//添加元素
set.add("a");
System.out.println(set);
set.remove(1);//移除元素
System.out.println(set);
System.out.println(set.contains("a"));//判断是否包含元素
System.out.println(set.size());//获取集合的元素个数
set.clear();//清空元素
System.out.println(set);
//集合如何遍历
set.add("a");
set.add("b");
set.add("c");
set.add("d");
set.add("d");//set集合存的值是不重复的
set.add(null);//set集合存的值是不重复的
//使用迭代器遍历集合
//使用Iterator()接口
Iterator it = set.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
//for each迭代集合,推荐使用
for(Object obj : set) {//把set的每一个值取出来,赋值给obj,直到循环set的所有值
System.out.println(obj);
}
}
}
3.HashSet存储自定义元素
hashSet存储自定义类的元素, 并且通过重写hashCode()和equals()方法来自定义筛选重复元素。
import java.util.HashSet;
import java.util.Objects;
public class testHashSet {
public static void main(String[] args) {
/**
* hashSet存储自定义类型的元素
* 并且通过重写hashCode()和equals()方法来自定义筛选重复元素。
*/
Person p1 = new Person("张三",18);
Person p2 = new Person("张三",18);
Person p3 = new Person("张三",19);
// 重写hashCode()方法前获取的hashCode值不等
// System.out.println(p1.hashCode());//hashCode:1239731077
// System.out.println(p2.hashCode());//hashCode:557041912
// System.out.println(p3.hashCode());//hashCode:1134712904
//创建hashSet存储Person对象
HashSet<Person> personSet = new HashSet<>();
personSet.add(p1);
personSet.add(p2);
personSet.add(p3);
System.out.println(personSet);
}
}
/**
* 需求: 在hashSet中存储Person类对象中,同名且同年龄的是一个人,不能重复存储
*/
class Person{
private String name;
private int age;
public Person() {
}
public Person(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;
}
@Override
public String toString() {//重写toString方法
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {//重写equals方法
if (this == o) return true;//对象引用相同,即同一个对象,返回true
if (o == null || getClass() != o.getClass()) return false;//被比较对象为null或者类型不同返回false
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);//属性的值都相等返回true
}
@Override
public int hashCode() {//重写hashCode方法
return Objects.hash(name, age);//public static int hash(Object... values)
}
}
4.扩展-泛型概述
//如果想要让集合只能存特定类型的对象
//就要使用泛型
Set<String> set1 = new HashSet<String>();//指定String为集合set1的泛型
set1.add("abc");
//set1.add(1);指定String泛型后, 这个集合不能存String类型以外的数据
2.LinkedHashSet类
- LinkedHashSet继承了HashSet集合。
- LinkedHashSet : 底层是 哈希表(数组+链表/红黑树)+链表 实现的, 多一条链表记录元素的存储顺序,保证元素有序。
- LinkedHashSet 跟 HashSet相同的是: 无索引, 不可存储重复元素, 不同的是 LinkedHashSet 可以保证存取顺序。
import java.util.HashSet;
import java.util.LinkedHashSet;
public class testLinkedHashSet {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("www");
set.add("abc");
set.add("abc");
set.add("itcast");
System.out.println(set);//[abc, www, itcast],元素无序,不重复
HashSet<String> linkset = new LinkedHashSet<>();
linkset.add("www");
linkset.add("abc");
linkset.add("abc");
linkset.add("itcast");
System.out.println(linkset);//[www, abc, itcast],元素有序,不重复
}
}
3.TreeSet类
1.TreeSet概述
- TreeSet : 底层是二叉树实现, 一般用于排序
TreeSet是SortedSet接口的实现类, TreeSet可以确保元素处于排序状态。
TreeSet支持两种排序方法: 自然排序和定制排序。默认情况下, TreeSet采用自然排序。
2.TreeSet的自然排序
TreeSet 默认会对实现了Comparable接口的元素进行自然排序。通过调用集合元素的 compareTo(Object obj) 方法来比较元素之间的大小关系, 然后将元素升序排序。
也可以通过重写compareTo(Object obj) 方法来自定义元素的排序规则。
compareTo(Object obj) 方法:
- 如果 this>obj, 返回正数, 如:1
- 如果 this<obj, 返回负数, 如-1
- 如果 this==obj, 返回0
- 可以进行减法运算时, 可以直接返回 this - obj
必须放入同样类的对象(默认进行排序), 否则可能会发生类型转换异常。我们可以使用泛型来进行现限制。
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
public class TestTreeSet {
public static void main(String[] args) {
Set<Integer> set = new TreeSet<Integer>();
//TreeSet无参时采用自然排序
//通过泛型<Integer>对传入集合的对象类型进行限制
set.add(8);
set.add(5);
set.add(12);
System.out.println(set);
for(Integer i:set) {
System.out.println(i);
}
Iterator<Integer> it = set.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
}
3.TreeSet的自定义排序
- TreeSet()无参时合集元素默认对实现了Comparable接口的元素采用自然排序, 或按重写的CompareTo方法排序
- TreeSet()定义参数时, 也可以对实现了Comparator接口的元素自定义排序功能, 如TreeSet(new Person())
- 此时, 传入的这个实参被要求是实现了Comparator接口的类的对象
对Person类进行自定义排序时, 操作过程
- 用Person类实现Comparator接口
- 同时对继承自Comparator接口的 compare() 方法进行重写, 定义排序功能
compare(Object o1, Object o2) 方法:
- 如果 o1>o2, 返回正数, 如:1
- 如果 o1<o2, 返回负数, 如-1
- 如果 o1==o2, 返回0
- 可以进行减法运算时, 可以直接返回 o1 - o2
注意:无论是自然排序还是自定义排序, TreeSet不能添加重复元素, 也就是通过排序规则判断相等的元素
import java.util.Comparator;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
public class TestTreeSet {
public static void main(String[] args) {
/**
* TreeSet的自定义排序
*/
Person p1 = new Person("张三",23);
Person p2 = new Person("李四",21);
Person p3 = new Person("王五",27);
Person p4 = new Person("赵六",18);
Set<Person> set1 = new TreeSet<Person>(new Person());
//自定义排序时,要求传入TreeSet()一个实现了Comparator接口的类的对象作为实参
//也可以用其他有参对象,如new Person("小白",16),则无需对应创建无参构造
set1.add(p1);
set1.add(p2);
set1.add(p3);
set1.add(p4);
for(Person p:set1) {
System.out.println(p.name+p.age);
}
}
}
/**
- 把Person对象存到TreeSet中并按照年龄排序
- @author chenyao
*/
class Person implements Comparator<Person>{//实现Comparator接口,以<Person>的泛型进行限制
int age;
String name;
public Person(){//对应new Person()创建无参构造
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
@Override
public int compare(Person o1, Person o2) {//重写Comparator接口的compare方法,自定义排序功能
return o1.age - o2.age;
}
}
4.扩展-Comparable/Comparator接口
如前面所说, TreeSet()不填写参数时, 合集元素默认对实现了Comparable接口的元素采用自然排序, 或按重写的CompareTo方法排序
TreeSet()定义参数时, 也可以对实现了Comparator接口的元素自定义排序功能, 如TreeSet(new Person())。此时, 传入的这个实参被要求是实现了Comparator接口的类的对象
区别: Comparable是当前对象(this)和参数对象的比较, Comparator是找一个第三方的比较器, 对两个参数对象进行比较。
两个接口的实现方法非常相似:
在定义需要排序的元素类时, 注意要给实现的接口加上泛型限制, 以限制CompareTo/Compare方法的参数的类型
- class Person1 implements Comparable {…}
- class Person2 implements Comparator {…}
- 实现Comparable接口排序, 需要实现接口的compareTo(Object obj) 方法
格式如下(升序):
@Override
public int compareTo(Person1 o) {
// if(this.age>o.age){
// return 1;
// }else if(this.age<o.age){
// return -1;
// }else{
// return 0;
// }
//或者直接返回两者之差
return this.age-o.age;
}
}
- 实现Comparator接口排序, 需要实现接口的compare(Object o1, Object o2) 方法
格式如下(升序):
@Override
// if(o1.age>o2.age){
// return 1;
// }else if(o1.age<o2.age){
// return -1;
// }else{
// return 0;
// }
//或者直接返回两者之差
return o1.age-o2.age;
}
示例:
import java.util.Collections;
import java.util.Comparator;
import java.util.TreeSet;
public class testTreeSet {
public static void main(String[] args) {
/**
* - TreeSet()无参时合集元素默认对实现了Comparable接口的元素采用自然排序, 或按重写的CompareTo方法排序
* - TreeSet()定义参数时, 也可以对实现了Comparator接口的元素自定义排序功能, 如TreeSet(new Person())
* - 此时, 传入的这个实参被要求是**实现了Comparator接口的类的对象**
*/
TreeSet set1 = new TreeSet();
Collections.addAll(set1, new Person1("张三",17), new Person1("李四",18));
System.out.println(set1);
TreeSet set2 = new TreeSet(new Person2());
// 或者直接采用匿名Comparator写法,更为简便,优先级还要高于元素类实现的compare方法
// TreeSet set2 = new TreeSet(new Comparator<Person2>() {
// @Override
// public int compare(Person2 o1, Person2 o2) {
// return o2.age-o1.age;
// }
// });
Collections.addAll(set2, new Person2("张三",17), new Person2("李四",18));
System.out.println(set2);
}
}
class Person1 implements Comparable<Person1>{
String name;
int age;
public Person1() {
}
public Person1(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person1{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Person1 o) {
return this.age-o.age;
}
}
class Person2 implements Comparator<Person2> {
String name;
int age;
public Person2() {
}
public Person2(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person2{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compare(Person2 o1, Person2 o2) {
return o1.age-o2.age;
}
}
4.扩展-创建线程安全的Set集合
Set集合怎么实现线程安全?
方案一:
和List一样,使用Colletcions这个工具类syn方法类创建个线程安全的set.
Set< String > synSet = Collections.synchronizedSet(new HashSet<>());
方案二:.
使用JUC包里面的CopyOnWriteArraySet
Set< String > copySet = new CopyOnWriteArraySet<>();
总结:
创建set有五种方法,其中通过三个子类直接创建出来的是线程不安全的。想要创建线程安全的set可以通过工具类或者是juc包下相关的类创建。
五.Map集合
Map集合用于保存具有映射关系的数据, 因此Map集合里保存着两组值, 一组值用于保存Map里的Key, 另外一组用于保存Map里的Value
- Map中的key和value都可以是任何引用类型的数据
- Map中的Key不允许重复, 即同一个Map对象的任何两个Key通过equals方法比较中返回false
- Key和Value之间存在单向一对一关系, 即通过指定的Key总能找到唯一的, 确定的Value
1.HashMap类
1.概述
- HashMap : 存储数据采用的哈希表结构。在JDK1.8中HashMap数据结构的存储由数组+链表的方式,变化为数组+链表+红黑树的存储方式,在性能上得到优化。
- 元素的存取无序, 由于要保证键的唯一、不重复, 需要重写键的hashCode()、equals()方法, 键的特性类似于HashSet。
2.常用方法
- public V put(K key, V value) : 添加key-value元素, 返回与key关联的旧value值; 如果前面 key 没有任何映射关系, 或映射null, 则返回 null。
- public V remove(Object key) : 从此映射中移除指定键的映射关系(如果存在), 返回: 与 key 关联的旧值;如果 key 没有任何映射关系, 则返回 null。
- public V get(Object key) : 返回指定键所映射的值;如果对于该键来说,此映射不包含任何映射关系,则返回 null。
- public Set< K > keySet() : 返回此映射中所包含的键的 Set 集合。
- public Collection values() : 返回此映射所包含的值的 Collection 集合。
- public Set<Map.Entry<K,V>> entrySet() : 返回此映射所包含的映射关系的 Set 集合。
- public boolean containsKey(Object key)
- public boolean containsValue(Object value)
- size()、clear()、isEmpty()…
遍历HashMap集合的两种方式:
- 通过map.keySet(); 遍历map集合 (仅遍历key或者value时适用)
- 通过map.entrySet(); 遍历Map集合 (推荐使用, 尤其当集合的容量较大时)
Map.Entry 是Map中的一个接口,他的用途是表示一个映射项(里面有Key和Value)
而Set<Map.Entry<K,V>>表示一个映射项的Set。
Map.Entry里有相应的getKey和getValue方法,即JavaBean,让我们能够从一个项中取出Key和Value。
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class TestHashMap {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("b", 2);//添加数据
map.put("c", 3);
map.put("a", 1);
System.out.println(map);
System.out.println(map.get("a"));//根据key取值
map.remove("c");//根据key移除
System.out.println(map);
System.out.println(map.size());//map集合长度
System.out.println(map.containsKey("a"));//判断当前集合是否包含指定的key
System.out.println(map.containsValue(2));//判断当前集合是否包含指定的value
// map.clear();清空集合
map.keySet();//获取map集合的key的集合
map.values();//获取map集合的value的集合
/**
* 遍历map集合
*/
//1.通过map.keySet();遍历map集合
Set<String> keys = map.keySet();
for (String key : keys) {
System.out.println("key:"+ key + ",value:" + map.get(key));
//通过map.keySet();需要访问两次map
}
//2.通过map.entrySet();遍历map集合
Set< Entry<String,Integer> > entrys = map.entrySet();//entry
for( Entry<String,Integer> en : entrys ) {
System.out.println("key:"+ en.getKey() + ",value:" + en.getValue());
//通过map.entrySet();,只需要访问一次map
}
}
}
2.TreeMap类
TreeMap集合是基于红黑树的 NavigableMap实现。该集合最重要的特点就是可排序。
HashMap与TreeMap比较:
- HashMap底层使用数组,容量存在上限,Integer的最大值,而TreeMap没有;
- HashMap中的红黑树使用的是key的hash值进行排序,而TreeMap使用比较器的compare或者key的compareTo方法进行比较大小,从而排序;
- HashMap可加入key为null的键,而TreeMap不可以;
- HashMap的key需要实现hashCode和equals方法,而TreeMap不需要,但是需要实现接口Comparable或者自定义比较器;
- 查询、添加、删除的时间复杂度上,TreeMap为log(n),HashMap若不存在碰撞,则为O(1),存在碰撞则看存储结构是链表还是红黑树,链表则为O(n),红黑树则为log(n),总的来说HashMap直接定位到桶位置是O(1)的,效率高于TreeMap。
———————————————— 版权声明:<HashMap与TreeMap比较>为CSDN博主「小方好方」的原创文章,遵循CC 4.0
BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_36142042/article/details/105104007
TreeMap存储Key-Value对时, 需要根据 Key 对key-value对进行排序。
TreeMap可以保证所有的key-value对处于有序状态。
TreeMap的Key排序:
- 自然排序: TreeMap的所有Key必须实现 Comparable 接口, 而且所有的Key应该是同一个类的对象, 否则将抛出ClassCastException
TreeMap的自然排序是字典排序。 - 定制排序(了解): 创建TreeMap时, 传入一个Comparator对象, 该对象负责对TreeMap中的所有key进行排序。此时不需要 Map的Key实现Comparable接口。
一般使用Map集合, 不会使用过于复杂的对象做key。
3.LinkedHashMap类
- HashMap下有个子类LinkedHashMap, 存储数据采用的哈希表+链表结构(保证迭代顺序)。通过链表结构保证元素的存取顺序一致。类似于HashSet的子类LinkedHashSet。
- LinkedHashMap 跟 HashMap相同的是: 双列, 映射关系, Key值不可重复, 不同的是 LinkedHashMap 可以保证存取顺序。
4.Hashtable类(了解)
HashMap 和 Hashtable 是 Map 接口的两个典型实现类
两者的区别:
- Hashtable是一个古老的Map实现类, 不建议使用
- Hashtable是线程安全的,效率低, 而HashMap是线程不安全的。
- Hashtable不允许使用null值作为key和value, HashMap可以
与HashSet集合不能保证元素的顺序一样, Hashtable、HashMap也不能保证其中key-value对 的顺序
Hashtable、HashMap 判断两个Key相等的标准是:
- 两个Key通过equals方法返回true,
- 且两者hashCode值也相等
Hashtable、HashMap 判断两个Value相等的标准是:
- 两个Value通过equalHashMap方法判断两个Values返回true
5.扩展-Properties类
Properties集合是Hashtable的子类。Properties集合是唯一和IO流相结合的集合。
六.操作集合的工具类: Collections
Collections 是操作Set、List和Map等集合的工具类
Collections中提供了大量方法对集合元素进行排序、查询和修改等操作, 还提供了对集合对象设置不可变、对集合对象实现同步控制等方法
1.一次添加多个元素
对Collection单列集合的操作:
- public static < T > boolean addAll(Collection<? super T> c, T… elements) 添加不定数量的多个元素
扩展-JDK9的新特性
JDK9对集合的优化, Java9添加了集中集合工厂方法, 更方便地创建少量元素的集合、map实例。新的List、Set、Map的静态工厂方法可以更方便地创建集合的不可变实例。
- static < E > List< E > of(E… elements)
- static < E > Set< E > of(E… elements)
- static <K,V> Map<K,V> of(K k1, V v1 …, K k10, V v10) //Map最多包含十个映射
使用前提: 当集合中存储的元素的个数已经确定了, 不能改变时使用
注意:
- of() 方法只适用于List接口, Set接口, Map接口, 不适用于接口的实现类
- of() 方法的返回值是一个不能改变的集合, 集合不能再使用add, put方法添加, 或通过remove方法移除元素
- Set接口和Map接口调用 of() 方法的时候, 不能有重复的元素, 否则会抛出异常。
2.排序操作:
对List集合的操作 :
- reverse(List): 反转List中元素的顺序
- shuffle(List): 对List集合元素进行随机排序
- sort(List): 根据元素的自然顺序对指定List集合元素按升序排序(默认字典排序)
- sort(List, Comparator): 根据指定的Comparator 产生的顺序对List集合元素进行排序
注意 : sort()方法使用前提是, 被排序的集合里的元素必须实现Comparable接口中的ComparaTo()方法 - swap(List, int i, int j): 将指定list集合中的 i 处元素和 j 处元素进行交换
public class TestCollections {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("b");
list.add("cd");
list.add("ca");
list.add("a");
list.add("1");
System.out.println(list);
Collections.reverse(list);//反转List中的元素
System.out.println(list);
Collections.shuffle(list);//对List集合元素进行随机排序
System.out.println(list);
Collections.sort(list);//对List集合元素进行字典升序排序
System.out.println(list);
Collections.swap(list, 0, 2);
System.out.println(list);//将指定list集合中的 i 处元素和 j 处元素进行交换
//下标从 0 开始
}
}
根据指定的Comparator 产生的顺序对List集合元素进行排序:
public class TestCollections {
public static void main(String[] args) {
/**
* 利用Collection对Studengt类对象的集合list1自定义排序
*/
Student s1 = new Student("张三",18);
Student s2 = new Student("李四",23);
Student s3 = new Student("王五",17);
Student s4 = new Student("赵六",24);
List<Student> list1 = new ArrayList<Student>();
list1.add(s1);
list1.add(s2);
list1.add(s3);
list1.add(s4);
Collections.sort(list1,new Student("",0));//执行自定义排序
for(Student s:list1) {
System.out.println(s.name+s.age);
}
// System.out.println(list1);
}
}
class Student implements Comparator<Student>{//根据年龄升序排列对象
int age;
String name;
public Student(String name,int age) {
this.name = name;
this.age = age;
}
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
}
}
3.查找、替换
- Object max(Collection): 根据元素的(默认)自然排序, 返回给定集合中的最大元素
- Object max(Collection, Comparator): 根据Comparator指定的顺序, 返回给定集合中的最大元素(升序排序时取最大值)
- Object min(Collection)
- Object min(Collection, Comparator)
- int frequency(Collection, Object) :返回指定结合中指定元素的出现次数
如果元素不存在, 则返回值为0 - boolean replaceAll(List list, Object oldVal, Object newVal): 使用新值替换List对象的所有旧值
System.out.println("最大值是"+Collections.max(list));
//查找最大值,与前面有没有进行排序无关
//-----------------------------------------------------
//取得最大值
Student stuMax = Collections.max(list1,new Student("",0));
System.out.println("年龄最大的是"+stuMax.name+","+stuMax.age+"岁");
System.out.println(Collections.frequency(list, "cd"));
//如果元素不存在,返回值为0
Collections.replaceAll(list, "cd", "xx");
//会替换所有查找到的重复元素,不仅限于一个
4.同步控制(线程安全)
Collections类中提供了多个 synchronizedXxx() 方法, 该方法可将指定集合包装成线程同步的集合, 从而可以解决多线程并发访问集合时的线程安全问题