Java集合(二)

简介: Java集合(二)

Set

存储的数据是不重复的,无序的。

HashSet

为什么是无序的呢?这是因为HashSet中运用了Hash算法来计算插入Set中的数据的位置(幂等性算法)。

image-20230110194730066

为什么是不重复的呢?因为Hash算法是幂等性算法,也就是说同一个值经过Hash算法计算出的位置是一定的。而存储相同数据相当于会进行覆盖,这样是没有意义的。所以HashSet也不会这样做,不会进行任何处理。我们可以插入相同的数据,但是存储的数据不会有重复的。

添加数据

 HashSet set = new HashSet();
 set.add("kevin");
 set.add("kevin");
 set.add("qian");
 set.add("kun");
 set.add("yt");
 ​
 System.out.println(set);//[qian, kevin, kun, yt]

修改数据

由于Hash算法的存在,无法确定修改后的数据经过Hash算法得出的位置与原位置一致,所以也无法修改数据。只能删除了数据后再进行添加。

删除数据

 HashSet set = new HashSet();
 set.add("kevin");
 set.add("qian");
 set.add("kun");
 ​
 set.remove("kun");
 System.out.println(set);// [qian, kevin]

查询数据

只能使用for循环查询数据

 HashSet set = new HashSet();
 set.add("kevin");
 set.add("qian");
 set.add("kun");
 ​
 for (Object o : set) {
     System.out.println(o);
 }
 // qian
 // kevin
 // kun

其他常用方法

Set的其他方法与List的差不多:

 HashSet set = new HashSet();
 ​
 ArrayList list = new ArrayList();
 list.add("kevin");
 list.add("kevin");
 list.add("qian");
 ​
 set.addAll(list);
 ​
 Object[] objects = set.toArray();
 Object clone = set.clone();
 // set.clear();
 ​
 System.out.println(set);// [qian, kevin]
 System.out.println(set.size());// 2
 System.out.println(set.isEmpty());// false

HashSet重复数据

对于new一个对象,它们的地址肯定是不同的,所以HashSet认为这是两个不同的数据

 HashSet<Student> set = new HashSet();
 set.add(new Student(1001, "kevin"));
 set.add(new Student(1001, "kevin"));
 set.add(new Student(1002, "qian"));
 ​
 for (Student student : set) {
     System.out.println("id:" + student.id + ",name:" + student.name);
 }
 // id:1002,name:qian
 // id:1001,name:kevin
 // id:1001,name:kevin

在进行hash运算时,就是通过hashcode的值来进行运算的。

那么有没有可能不同的hashcode被hash运算计算为同一个位置呢?这是可能的,新的值会像链表结构一样被放在已经在当前位置的数据的后面

image-20230110234629951

所以说HashSet的底层数据结构为数组+链表

如果我们想让传入的引用类型数据的构造时传入的参数一致,就认为是同一数据,我们需要重写hashCode和equals

 public class JavaCollection_08 {
     public static void main(String[] args) {
         HashSet<Student> set = new HashSet();
         set.add(new Student(1001, "kevin"));
         set.add(new Student(1001, "kevin"));
         set.add(new Student(1002, "qian"));
 ​
         for (Student student : set) {
             System.out.println("id:" + student.id + ",name:" + student.name);
         }
         // id:1001,name:kevin
         // id:1002,name:qian
     }
 }
 ​
 class Student {
     public int id;
     public String name;
 ​
     Student(int id, String name) {
         this.id = id;
         this.name = name;
     }
 ​
     @Override
     public int hashCode() {
         return this.id;
     }
 ​
     @Override
     public boolean equals(Object obj) {
         if (obj instanceof Student) {
             Student theStudent = (Student) obj;
             if (theStudent.id == this.id || theStudent.name.equals(this.name)) {
                 return true;
             }
         }
         return false;
     }
 }

Queue

ArrayBlockingQueue

Array+blocking(阻塞)+Queue

 ArrayBlockingQueue queue = new ArrayBlockingQueue(3);
 queue.add("kevin");
 queue.add("qian");
 queue.add("kun");
 ​
 System.out.println(queue);// [kevin, qian, kun]

ArrayBlockingQueue有个数限制。

 ArrayBlockingQueue queue = new ArrayBlockingQueue(3);
 queue.add("kevin");
 queue.add("qian");
 queue.add("kun");
 queue.add("yes");// 报错:Queue full
 ​
 System.out.println(queue);// [kevin, qian, kun]

如何表现出Blocking?使用put添加数据

 ArrayBlockingQueue queue = new ArrayBlockingQueue(3);
 queue.put("kevin");
 System.out.println("第一个");// 第一个
 queue.put("qian");
 System.out.println("第二个");// 第二个
 queue.put("kun");
 System.out.println("第三个");// 第三个
 queue.put("yes");// 由于blocking,不继续执行后面的代码。整个程序不会结束,会一直blocking
 System.out.println("第四个");
 ​
 System.out.println(queue);

我们可以使用offer存值,它会返回一个布尔值。成功为true,失败为false

 ArrayBlockingQueue queue = new ArrayBlockingQueue(3);
 System.out.println(queue.offer("kevin"));// true
 System.out.println(queue.offer("qian"));// true
 System.out.println(queue.offer("kun"));// true
 System.out.println(queue.offer("yt"));// false
 ​
 System.out.println(queue);// [kevin, qian, kun]

可以使用poll方法取出来值,返回值为取出的值。

 ArrayBlockingQueue queue = new ArrayBlockingQueue(3);
 queue.offer("kevin");
 queue.offer("qian");
 queue.offer("kun");
 queue.offer("yt");
 ​
 System.out.println(queue.poll());// kevin
 System.out.println(queue.poll());// qian
 System.out.println(queue.poll());// kun
 System.out.println(queue.poll());// null
 ​
 System.out.println(queue);// []

也可以使用take取值,当取完set中的值后再取值也会blocking:

 ArrayBlockingQueue queue = new ArrayBlockingQueue(3);
 queue.offer("kevin");
 queue.offer("qian");
 queue.offer("kun");
 queue.offer("yt");
 ​
 System.out.println(queue.take());// kevin
 System.out.println(queue.take());// qian
 System.out.println(queue.take());// kun
 System.out.println(queue.take());// 会blocking,后面的代码不会执行,并且程序不会退出。
 ​
 System.out.println(queue);

Map

键值对集合

HashMap

类似HashSet,但是有覆盖的概念。根据key定位,value不同会进行覆盖。

image-20230111214113798

当key不同,但通过hash算法得到了同一个位置时。会通过链表结构存储,并且是单向链表。如果单向链表中数据存储的很多。查询起来效率很低,所以jdk中提供了一种特殊的结构——红黑二叉树。

添加数据/修改数据

覆盖数据时,返回值为原数据。如果无原数据返回为空(null)

 HashMap map = new HashMap();
 map.put("kevin", 1);
 map.put("qian", 2);
 System.out.println(map.put("kun", 3));// null
 System.out.println(map.put("kevin", 4));// 1
 System.out.println(map);// {qian=2, kevin=4, kun=3}

也可以使用putIfAbsent进行添加数据,如果有数据就不做处理,并返回那个数据的value

 HashMap map = new HashMap();
 map.putIfAbsent("kevin", 1);
 System.out.println(map.putIfAbsent("qian", 2));// null
 System.out.println(map.putIfAbsent("kevin", 4));// 1
 System.out.println(map);// {qian=2, kevin=1}

而修改数据可以使用replace方法,返回原数据的value

 HashMap map = new HashMap();
 map.putIfAbsent("kevin", 1);
 System.out.println(map.replace("kevin", 3));// 1
 System.out.println(map.replace("yt", 3));// null
 System.out.println(map);// {kevin=3}

查询数据

返回值为value

 HashMap map = new HashMap();
 map.put("kevin", 1);
 map.put("qian", 2);
 map.put("kun", 3);
 map.put("kevin", 4);
 ​
 System.out.println(map.get("kevin"));// 4

删除数据

 HashMap map = new HashMap();
 map.put("kevin", 1);
 map.put("qian", 2);
 map.put("kun", 3);
 map.put("kevin", 4);
 ​
 System.out.println(map.remove("kevin"));// 4
 System.out.println(map);// {qian=2, kun=3}

当传两个值时是value为传入值时才删除,这时候返回值为是否删除(或者是否有这个数据):

 HashMap map = new HashMap();
 map.put("kevin", 1);
 map.put("qian", 2);
 map.put("kun", 3);
 map.put("kevin", 4);
 ​
 System.out.println(map.remove("kevin", 1));// false
 System.out.println(map.remove("kevin", 4));// true
 System.out.println(map);// {qian=2, kun=3}

其它常用方法

将key取为一个set,并进行遍历来取key

 HashMap map = new HashMap();
 map.put("kevin", "1");
 map.put("qian", "2");
 map.put("kun", "3");
 ​
 Set set = map.keySet();
 for (Object o : set) {
     System.out.println(o);
 }
 // qian
 // kevin
 // kun

将value取为一个集合,并遍历来取value

 HashMap map = new HashMap();
 map.put("kevin", "1");
 map.put("qian", "2");
 map.put("kun", "3");
 ​
 Collection values = map.values();
 for (Object value : values) {
     System.out.println(value);
 }
 // 2
 // 1
 // 3

查询是否有key或者value:

 HashMap map = new HashMap();
 map.put("kevin", "1");
 map.put("qian", "2");
 map.put("kun", "3");
 ​
 System.out.println(map.containsKey("kevin"));// true
 System.out.println(map.containsKey("kkk"));// false
 System.out.println(map.containsValue("2"));// true
 System.out.println(map.containsValue("5"));// false

使用entrySet将其转换为Set,并且使用for遍历,entry还有getKey方法和getValue方法来取key和value。

 HashMap<String, String> map = new HashMap();
 map.put("kevin", "1");
 map.put("qian", "2");
 map.put("kun", "3");
 ​
 Set<Map.Entry<String, String>> entries = map.entrySet();
 for (Map.Entry<String, String> entry : entries) {
     System.out.print(entry + "   ");
     System.out.println(entry.getKey() + "," + entry.getValue());
 }
 // qian=2   qian,2
 // kevin=1   kevin,1
 // kun=3   kun,3

Hashtable

Hashtable与HashMap区别:

  • 1、实现方式不同(继承的父类不同)
    • Hashtable继承的Dictionary类
    • HashMap继承的AbstractMap类
  • 2、底层结构的默认容量不同:HashMap默认为16,Hashtable默认为11
  • 3、HashMap的K和V都可以为null,而Hashtable的K和V不能为null
  • 4、HashMap的数据定位采用的Hash算法,而Hashtable采用的hashcode
  • 5、HashMap的性能高,Hashtable的性能低。在多线程的并发操作下HashMap会出问题,而Hashtable不会出问题。
 HashMap map = new HashMap();
 map.put(null, null);
 System.out.println(map);//{null=null}
 ​
 Hashtable hashtable = new Hashtable();
 hashtable.put(null, null);// NullPointerException 空指针异常

迭代器

在使用for循环来删除map中的数据,并且使用set遍历的key来取value。

 HashMap<String, Integer> map = new HashMap<>();
 map.put("a", 1);
 map.put("b", 2);
 map.put("c", 3);
 ​
 Set<String> keys = map.keySet();
 for (String key : keys) {
     if ("b".equals(key)) {
         map.remove(key);
     }
     System.out.println(map.get(key));
 }
 // 1
 // null
 // ConcurrentModificationException

但是,map中删除了,set并不能同步,set中还存有三个key的位置。所以会继续进行遍历,而遍历到最后一个数据时,由于删除了前面的数据。导致现在set中只有两个数据,而没有第三个数据,所以会出现错误。那有什么办法能够使其同步呢?

我们可以使用迭代器来进行操作:

 HashMap<String, Integer> map = new HashMap<>();
 map.put("a", 1);
 map.put("b", 2);
 map.put("c", 3);
 ​
 Set<String> keys = map.keySet();
 ​
 Iterator<String> iterator = keys.iterator();
 // hashNext用于判断是否存在下一条数据
 while (iterator.hasNext()) {
     // 获取下一条数据
     String key = iterator.next();
     if ("b".equals(key)) {
         iterator.remove();// 只能对当前数据删除
     }
     System.out.println(map.get(key));
 }
 // 1
 // null
 // 3

集合中的工具类

Arrays工具类

数组转为字符串:

 int[] ints = {1, 2, 3};
 System.out.println(ints);// [I@776ec8df
 System.out.println(Arrays.toString(ints));// [1, 2, 3]

在声明List时初始化List:

 List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
 System.out.println(integers);// [1, 2, 3, 4, 5]

排序,默认为升序:

 int[] ints = {2, 1, 4, 3, 0};
 Arrays.sort(ints);
 System.out.println(Arrays.toString(ints));// [0, 1, 2, 3, 4]

二分查找法(查找排序后的数组):

 int[] ints = {2, 1, 4, 3, 0};
 Arrays.sort(ints);
 System.out.println(Arrays.toString(ints));// [0, 1, 2, 3, 4]
 System.out.println(Arrays.binarySearch(ints, 4));// 4

数组的比较,顺序和数值都相同,也可以比较某一段数据,参数为:数组1,第一个索引(包含),第二个索引(不包含),数组2,第一个索引(包含),第二个索引(不包含)

 int[] ints = {1, 2, 3, 4, 5};
 int[] ints1 = {1, 2, 3, 4, 5};
 int[] ints2 = {0, 2, 6, 4, 5};
 int[] ints3 = {0, 2, 6, 4, 5, 7, 8};
 ​
 System.out.println(Arrays.equals(ints, ints1));// true
 System.out.println(Arrays.equals(ints, ints2));// false
 ​
 System.out.println(Arrays.equals(ints2, 0, 5, ints3, 0, 5));// true

集合中的常见异常

ArrayList

IndexOutOfBoundsException,虽然容量为10。但是它的索引范围为:0——数据长度-1

 ArrayList list = new ArrayList(10);
 list.add("kevin");
 list.add("qian");
 ​
 System.out.println(list.get(2));// IndexOutOfBoundsException: Index 2 out of bounds for length 2

LinkedList

NoSuchElementException,不存在数据。

 LinkedList list = new LinkedList();
 System.out.println(list.getFirst());// NoSuchElementException

HashMap

HashMap一旦在遍历时进行数据的增加和修改,就会发生异常:

比如这个之前的例子

 HashMap map = new HashMap();
 map.put("a", 1);
 map.put("b", 2);
 map.put("c", 3);
 ​
 for (Object o : map.keySet()) {
     if ("b".equals(o)) {
         map.put("d", 4);
     }
 }
 // ConcurrentModificationException

所以这里尽量使用迭代器来进行操作。

相关文章
|
15天前
|
算法 Java 数据处理
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其“不重复性”要求,彻底改变了处理唯一性数据的方式。
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其“不重复性”要求,彻底改变了处理唯一性数据的方式。HashSet基于哈希表实现,提供高效的元素操作;TreeSet则通过红黑树实现元素的自然排序,适合需要有序访问的场景。本文通过示例代码详细介绍了两者的特性和应用场景。
33 6
|
15天前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
33 3
|
15天前
|
存储 Java 数据处理
Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位
【10月更文挑战第16天】Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位。本文通过快速去重和高效查找两个案例,展示了Set如何简化数据处理流程,提升代码效率。使用HashSet可轻松实现数据去重,而contains方法则提供了快速查找的功能,彰显了Set在处理大量数据时的优势。
26 2
|
17天前
|
存储 算法 Java
Java Set因其“无重复”特性在集合框架中独树一帜
【10月更文挑战第14天】Java Set因其“无重复”特性在集合框架中独树一帜。本文深入解析Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定的数据结构(哈希表、红黑树)确保元素唯一性,并提供最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的`hashCode()`与`equals()`方法。
25 3
|
23天前
|
存储 安全 算法
Java面试题之Java集合面试题 50道(带答案)
这篇文章提供了50道Java集合框架的面试题及其答案,涵盖了集合的基础知识、底层数据结构、不同集合类的特点和用法,以及一些高级主题如并发集合的使用。
60 1
Java面试题之Java集合面试题 50道(带答案)
|
12天前
|
安全 Java 程序员
深入Java集合框架:解密List的Fail-Fast与Fail-Safe机制
本文介绍了 Java 中 List 的遍历和删除操作,重点讨论了快速失败(fail-fast)和安全失败(fail-safe)机制。通过普通 for 循环、迭代器和 foreach 循环的对比,详细解释了各种方法的优缺点及适用场景,特别是在多线程环境下的表现。最后推荐了适合高并发场景的 fail-safe 容器,如 CopyOnWriteArrayList 和 ConcurrentHashMap。
41 5
|
13天前
|
安全 Java 程序员
Java集合之战:ArrayList vs LinkedList,谁才是你的最佳选择?
本文介绍了 Java 中常用的两个集合类 ArrayList 和 LinkedList,分析了它们的底层实现、特点及适用场景。ArrayList 基于数组,适合频繁查询;LinkedList 基于链表,适合频繁增删。文章还讨论了如何实现线程安全,推荐使用 CopyOnWriteArrayList 来提升性能。希望帮助读者选择合适的数据结构,写出更高效的代码。
42 3
|
15天前
|
存储 Java 数据处理
Set 是 Java 集合框架中的一个接口,不包含重复元素且不保证元素顺序。
【10月更文挑战第16天】Java Set:无序之美,不重复之魅!Set 是 Java 集合框架中的一个接口,不包含重复元素且不保证元素顺序。通过 hashCode() 和 equals() 方法实现唯一性,适用于需要唯一性约束的数据处理。示例代码展示了如何使用 HashSet 添加和遍历元素,体现了 Set 的高效性和简洁性。
21 4
|
17天前
|
存储 Java 数据处理
Set 是 Java 集合框架中的一个接口,不包含重复元素且不保证元素顺序。
Java Set:无序之美,不重复之魅!Set 是 Java 集合框架中的一个接口,不包含重复元素且不保证元素顺序。它通过 hashCode() 和 equals() 方法确保元素唯一性,适用于需要唯一性约束的数据处理。示例代码展示了如何使用 HashSet 实现这一特性。
22 5
|
18天前
|
Java 开发者
在Java的集合世界里,Set以其独特的特性脱颖而出,它通过“哈希魔法”和“红黑树防御”两大绝技
【10月更文挑战第13天】在Java的集合世界里,Set以其独特的特性脱颖而出。它通过“哈希魔法”和“红黑树防御”两大绝技,有效抵御重复元素的侵扰,确保集合的纯洁性和有序性。无论是“人海战术”还是“偷梁换柱”,Set都能从容应对,成为开发者手中不可或缺的利器。
30 6