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

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

相关文章
|
1月前
|
安全 Java 容器
【Java集合类面试二十七】、谈谈CopyOnWriteArrayList的原理
CopyOnWriteArrayList是一种线程安全的ArrayList,通过在写操作时复制新数组来保证线程安全,适用于读多写少的场景,但可能因内存占用和无法保证实时性而有性能问题。
|
1月前
|
存储 安全 Java
【Java集合类面试二十五】、有哪些线程安全的List?
线程安全的List包括Vector、Collections.SynchronizedList和CopyOnWriteArrayList,其中CopyOnWriteArrayList通过复制底层数组实现写操作,提供了最优的线程安全性能。
|
1月前
|
Java
【Java集合类面试二十八】、说一说TreeSet和HashSet的区别
HashSet基于哈希表实现,无序且可以有一个null元素;TreeSet基于红黑树实现,支持排序,不允许null元素。
|
1月前
|
Java
【Java集合类面试二十三】、List和Set有什么区别?
List和Set的主要区别在于List是一个有序且允许元素重复的集合,而Set是一个无序且元素不重复的集合。
|
1月前
|
Java
【Java集合类面试二十六】、介绍一下ArrayList的数据结构?
ArrayList是基于可动态扩展的数组实现的,支持快速随机访问,但在插入和删除操作时可能需要数组复制而性能较差。
|
1月前
|
存储 Java 索引
【Java集合类面试二十四】、ArrayList和LinkedList有什么区别?
ArrayList基于动态数组实现,支持快速随机访问;LinkedList基于双向链表实现,插入和删除操作更高效,但占用更多内存。
|
6天前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
21天前
|
Java
用JAVA架建List集合为树形结构的代码方法
这段代码定义了一个表示树形结构的 `Node` 类和一个用于构建树形结构的 `TreeController`。`Node` 类包含基本属性如 `id`、`pid`、`name` 和 `type`,以及子节点列表 `children`。`TreeController` 包含初始化节点列表并将其转换为树形结构的方法。通过过滤和分组操作实现树形结构的构建。详情可见:[代码示例链接1](http://www.zidongmutanji.com/zsjx/43551.html),[代码效果参考链接2](https://www.257342.com/sitemap/post.html)。
28 5
|
21天前
|
存储 Java 程序员
Java中的集合框架:从入门到精通
【8月更文挑战第30天】在Java的世界里,集合框架是一块基石,它不仅承载着数据的存储和操作,还体现了面向对象编程的精髓。本篇文章将带你遨游Java集合框架的海洋,从基础概念到高级应用,一步步揭示它的奥秘。你将学会如何选择合适的集合类型,掌握集合的遍历技巧,以及理解集合框架背后的设计哲学。让我们一起探索这个强大工具,解锁数据结构的新视角。
|
22天前
|
存储 算法 Java
Java中的集合框架深度解析云上守护:云计算与网络安全的协同进化
【8月更文挑战第29天】在Java的世界中,集合框架是数据结构的代言人。它不仅让数据存储变得优雅而高效,还为程序员提供了一套丰富的工具箱。本文将带你深入理解集合框架的设计哲学,探索其背后的原理,并分享一些实用的使用技巧。无论你是初学者还是资深开发者,这篇文章都将为你打开一扇通往高效编程的大门。