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

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

相关文章
|
21天前
|
存储 安全 Java
Java 集合框架中的老炮与新秀:HashTable 和 HashMap 谁更胜一筹?
嗨,大家好,我是技术伙伴小米。今天通过讲故事的方式,详细介绍 Java 中 HashMap 和 HashTable 的区别。从版本、线程安全、null 值支持、性能及迭代器行为等方面对比,帮助你轻松应对面试中的经典问题。HashMap 更高效灵活,适合单线程或需手动处理线程安全的场景;HashTable 较古老,线程安全但性能不佳。现代项目推荐使用 ConcurrentHashMap。关注我的公众号“软件求生”,获取更多技术干货!
39 3
|
3月前
|
算法 Java 数据处理
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其“不重复性”要求,彻底改变了处理唯一性数据的方式。
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其“不重复性”要求,彻底改变了处理唯一性数据的方式。HashSet基于哈希表实现,提供高效的元素操作;TreeSet则通过红黑树实现元素的自然排序,适合需要有序访问的场景。本文通过示例代码详细介绍了两者的特性和应用场景。
59 6
|
3月前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
56 3
|
3月前
|
存储 Java 数据处理
Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位
【10月更文挑战第16天】Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位。本文通过快速去重和高效查找两个案例,展示了Set如何简化数据处理流程,提升代码效率。使用HashSet可轻松实现数据去重,而contains方法则提供了快速查找的功能,彰显了Set在处理大量数据时的优势。
47 2
|
1月前
|
存储 缓存 安全
Java 集合江湖:底层数据结构的大揭秘!
小米是一位热爱技术分享的程序员,本文详细解析了Java面试中常见的List、Set、Map的区别。不仅介绍了它们的基本特性和实现类,还深入探讨了各自的使用场景和面试技巧,帮助读者更好地理解和应对相关问题。
49 5
|
2月前
|
存储 缓存 安全
Java 集合框架优化:从基础到高级应用
《Java集合框架优化:从基础到高级应用》深入解析Java集合框架的核心原理与优化技巧,涵盖列表、集合、映射等常用数据结构,结合实际案例,指导开发者高效使用和优化Java集合。
56 4
|
2月前
|
Java
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式。本文介绍了 Streams 的基本概念和使用方法,包括创建 Streams、中间操作和终端操作,并通过多个案例详细解析了过滤、映射、归并、排序、分组和并行处理等操作,帮助读者更好地理解和掌握这一重要特性。
46 2
|
2月前
|
存储 Java
判断一个元素是否在 Java 中的 Set 集合中
【10月更文挑战第30天】使用`contains()`方法可以方便快捷地判断一个元素是否在Java中的`Set`集合中,但对于自定义对象,需要注意重写`equals()`方法以确保正确的判断结果,同时根据具体的性能需求选择合适的`Set`实现类。
|
2月前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
2月前
|
存储 Java 开发者
在 Java 中,如何遍历一个 Set 集合?
【10月更文挑战第30天】开发者可以根据具体的需求和代码风格选择合适的遍历方式。增强for循环简洁直观,适用于大多数简单的遍历场景;迭代器则更加灵活,可在遍历过程中进行更多复杂的操作;而Lambda表达式和`forEach`方法则提供了一种更简洁的函数式编程风格的遍历方式。