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

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

相关文章
|
12天前
|
Java 大数据 API
Java Stream API:现代集合处理与函数式编程
Java Stream API:现代集合处理与函数式编程
174 100
|
12天前
|
Java API 数据处理
Java Stream API:现代集合处理新方式
Java Stream API:现代集合处理新方式
174 101
|
25天前
|
算法 Java
50道java集合面试题
50道 java 集合面试题
|
4月前
|
存储 安全 Java
常见 JAVA 集合面试题整理 自用版持续更新
这是一份详尽的Java集合面试题总结,涵盖ArrayList与LinkedList、HashMap与HashTable、HashSet与TreeSet的区别,以及ConcurrentHashMap的实现原理。内容从底层数据结构、性能特点到应用场景逐一剖析,并提供代码示例便于理解。此外,还介绍了如何遍历HashMap和HashTable。无论是初学者还是进阶开发者,都能从中受益。代码资源可从[链接](https://pan.quark.cn/s/14fcf913bae6)获取。
212 3
|
3月前
|
Oracle Java 关系型数据库
掌握Java Stream API:高效集合处理的利器
掌握Java Stream API:高效集合处理的利器
342 80
|
3月前
|
安全 Java API
Java 8 Stream API:高效集合处理的利器
Java 8 Stream API:高效集合处理的利器
233 83
|
16天前
|
存储 Java Go
对比Java学习Go——函数、集合和OOP
Go语言的函数支持声明与调用,具备多返回值、命名返回值等特性,结合`func`关键字与类型后置语法,使函数定义简洁直观。函数可作为一等公民传递、赋值或作为参数,支持匿名函数与闭包。Go通过组合与接口实现面向对象编程,结构体定义数据,方法定义行为,接口实现多态,体现了Go语言的简洁与高效设计。
|
2月前
|
存储 缓存 安全
Java集合框架(二):Set接口与哈希表原理
本文深入解析Java中Set集合的工作原理及其实现机制,涵盖HashSet、LinkedHashSet和TreeSet三大实现类。从Set接口的特性出发,对比List理解去重机制,并详解哈希表原理、hashCode与equals方法的作用。进一步剖析HashSet的底层HashMap实现、LinkedHashSet的双向链表维护顺序特性,以及TreeSet基于红黑树的排序功能。文章还包含性能对比、自定义对象去重、集合运算实战和线程安全方案,帮助读者全面掌握Set的应用与选择策略。
150 23
|
2月前
|
存储 缓存 安全
Java集合框架(三):Map体系与ConcurrentHashMap
本文深入解析Java中Map接口体系及其实现类,包括HashMap、ConcurrentHashMap等的工作原理与线程安全机制。内容涵盖哈希冲突解决、扩容策略、并发优化,以及不同Map实现的适用场景,助你掌握高并发编程核心技巧。
|
2月前
|
安全 Java 开发者
Java集合框架:详解Deque接口的栈操作方法全集
理解和掌握这些方法对于实现像浏览器后退功能这样的栈操作来说至关重要,它们能够帮助开发者编写既高效又稳定的应用程序。此外,在多线程环境中想保证线程安全,可以考虑使用ConcurrentLinkedDeque,它是Deque的线程安全版本,尽管它并未直接实现栈操作的方法,但是Deque的接口方法可以相对应地使用。
121 12