Java基础教程(11)-Java中的集合类

简介: 【4月更文挑战第11天】Java集合框架核心是Collection和Map接口,其中Collection分为List和Set,常用实现有ArrayList、LinkedList、HashSet和TreeSet。Map接口实现如HashMap和Hashtable,提供键值对存储。面试常问的HashMap与Hashtable差异在于线程安全、继承关系、null值接受及扩容策略。遍历集合通常使用Iterator,也可用for-each循环。Hash涉及数据压缩映射,用于快速查找,存在碰撞可能性。

集合类型也是Java标准库中被使用最多的类型;
通常也是面试时最常被问到的问题;

Java中的集合

在Java中,如果一个Java对象可以在内部持有若干其他Java对象,并对外提供访问接口,我们把这种Java对象称为集合。

Java的数组可以看作是一种集合

Java标准库自带的 java.util 包提供了集合类: Collection ;

Collection 除 Map 外所有其他集合类的根接口; 所以也可以时候集合类有两种:Collection和Map,各自有实现的子类;

Java的 java.util 包主要提供了以下三种类型的集合:

  • List :一种有序列表的集合
  • Set :一种保证没有重复元素的集合;
  • Map :一种通过键值(key-value)查找的映射表集合;

还有一小部分集合类是遗留类,不常使用:

  • Hashtable :一种线程安全的 Map 实现;
  • Vector :一种线程安全的 List 实现;
  • Stack :基于 Vector 实现的 LIFO 的栈。

Java访问集合总是通过统一的方式——迭代器(Iterator)来实现,它最明显的好处在于无需知道集合内部元素是按什么方式存储的。

Collection 和 Collections 区别

Collection 是一个集合接口。 它提供了对集合对象进行基本操作的通用接口方法。
Collection 接口在 Java 类库中有很多具体的实现。是 list,set 等的父接口。

Collections 是一个包装类。 它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于 Java 的 Collection 框架;

List和Set

Set 和 List 区别:

  • List,Set 都是继承自 Collection 接口。都是用来存储一组相同类型的元素的。
  • List 特点:元素有放入顺序,元素可重复 。有顺序,即先放入的元素排在前面。
  • Set 特点:元素无放入顺序,元素不可重复。无顺序,即先放入的元素不一定排在前面。 不可重复,即相同元素在 set 中只会保留一份。

List

在集合类中, List 是最基础的一种集合:它是一种有序链表。
List 的行为和数组几乎完全相同: List 内部按照放入元素的先后顺序存放,每个元素都可以通过索引确定自己的位置, List 的索引和数组一样,从 0 开始。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * List和Array转换
 */
public class ListDemo {
   

    public static void main(String[] args) {
   
        List<String> list = new ArrayList<>();
        list.add("123");
        list.add("456");

        //第一种是调用 toArray() 方法直接返回一个 Object[] 数组
        //会丢失类型信息
        Object[] objects = list.toArray();
        System.out.println(objects.length);
        //第二种方式是给 toArray(T[]) 传入一个类型相同的 Array ,
        // List 内部自动把元素复制到传入的 Array 中
        String[] strings = list.toArray(new String[0]);
        System.out.println(strings[1]);
        strings = list.toArray(new String[1]);
        //如果传入的数组不够大,那么 List 内部会创建一个新的刚好够大的数组,填充后返回;
        System.out.println(strings[1]);
        strings = list.toArray(new String[2]);
        System.out.println(strings.length);
        // 如果传入数组比 List 元素还要多,那么填充完元素后,剩下的数组元素一律填充 null
        strings = list.toArray(new String[3]);
        System.out.println(strings[2]);
        //最常用的是传入一个“恰好”大小的数组
        strings = list.toArray(new String[list.size()]);

        //数组转换为List 使用
        list = Arrays.asList(strings);

    }
}

Arrays.asList 获得的 List 使用时需要注意:

  • 1、asList 得到的只是一个 Arrays 的内部类,一个原来数组的视图 List,因此如果对它进行增删操作会报错。
  • 2、用 ArrayList 的构造器可以将其转变成真正的 ArrayList。

List 主要有 ArrayList、LinkedList 与 Vector 几种实现;可以通过new一个对象来创建;

ArrayList 是一个可改变大小的数组.当更多的元素加入到 ArrayList 中时,其大小将会动态地增长.内部的元素可以直接通过 get 与 set 方法进行访问,因为 ArrayList 本质上就是一个数组。

LinkedList 是一个双链表,在添加和删除元素时具有比 ArrayList 更好的性能.但在
get 与 set 方面弱于 ArrayList。

Vector 和 ArrayList 类似,但属于强同步类。如果你的程序本身是线程安全的(thread-safe,没有在多个线程之间共享同一个集合/对象),那么使用 ArrayList 是更好的选择。

Vector 和 ArrayList 在更多元素添加进来时会请求更大的空间。Vector 每次请求其大
小的双倍空间,而 ArrayList 每次对 size 增长 50%。而 LinkedList 还实现了 Queue 接口,该接口比List提供了更多的方法,包括 offer(), peek(),poll()等。

遍历一个List可以使用迭代器 Iterator 来访问 。

Iterator 本身也是一个对象,但它是由 List 的实例调用 iterator() 方法的时候创建的。 Iterator 对象知道如何遍历一个 List ,并且不同的 List 类型,返回的 Iterator 对象实现也是不同的;

只要实现了 Iterable 接口的集合类都可以直接用 for each 循环来遍历,Java编译器本身并不知道如何遍历集合对象,但它会自动把 for each 循环变成 Iterator 的调用,原因就在于 Iterable 接口定义了一个 Iterator iterator() 方法,强迫集合类必须返回一个 Iterator 实例

Set

Set 用于存储不重复的元素集合,它主要提供以下几个方法:

  • 将元素添加进 Set : boolean add(E e)
  • 将元素从 Set 删除: boolean remove(Object e)
  • 判断是否包含元素: boolean contains(Object e)

最常用的 Set 实现类是 HashSet; 其他实现类还有SortedSet; SortedSet 接口保证元素是有序的,实现类包含有TreeSet:

  • HashSet 是无序的,因为它实现了 Set 接口,并没有实现 SortedSet 接口;

    HashSet 是哈希表实现的,HashSet 中的数据是无序的,可以放入 null,但只能放入一个 null,两者中的值都不能重复;

  • TreeSet 是有序的,因为它实现了 SortedSet 接口。

    TreeSet 是二叉树实现的,Treeset 中的数据是自动排好序的,不允许放入 null 值

Map

Map 是一种键值(key-value)映射表的数据结构,作用是能高效通过 key 快速查找 value (元素);

Map 是一种键-值映射表,当我们调用 put(K key, V value) 方法时,就把 key 和 value 做了映射并放入 Map 。当我们调用 V get(K key) 时,就可以通过 key 获取到对应的 value 。如果 key 不存在,则返回 null 。和 List 类似, Map 也是一个接口,最常用的实现类是 HashMap 。

HashMap 之所以能根据 key 直接拿到 value ,原因是它内部通过空间换时间的方法,用一个大数组存储所有 value ,并根据key直接计算出 value 应该存储在哪个索引; 通过 key 计算索引的方式就是调用 key 对象的 hashCode() 方法,它返回一个 int 整数。

Map中不存在重复的key,因为放入相同的key,只会把原有的key-value对应的value给替换掉。虽然 key 不能重复,但 value 是可以重复的;


面试题:
HashMap 和 HashTable 有何不同?

线程安全:HashTable 中的方法是同步的,而 HashMap 中的方法在默认情况下是非同步的。在多线程并发的环境下,可以直接使用 HashTable,但是要使用 HashMap 的话就要自己增加同步处理了。

继承关系: HashTable 是基于陈旧的 Dictionary 类继承来的。HashMap 继承的抽象类 AbstractMap 实现了 Map 接口。

允不允许 null 值: HashTable 中,key 和 value 都不允许出现 null值,否则会抛出NullPointerException 异常。 HashMap 中,null 可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为 null。

默认初始容量和扩容机制: HashTable 中的 hash 数组初始大小是 11,增加的方式是 old*2+1。HashMap 中 hash 数组的默认大小是 16,而且一定是 2 的指数。

哈希值的使用不同 : HashTable 直接使用对象的 hashCode。HashMap 重新计算 hash 值。

遍历方式的内部实现上不同 : Hashtable、HashMap 都使用了 Iterator。而由于历史原因,Hashtable 还使用了 Enumeration 的方式


hash是啥?

Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入,通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入值。
简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
所有散列函数都有如下一个基本特性:根据同一散列函数计算出的散列值如果不同,那么输入值肯定也不同。但是,根据同一散列函数计算出的散列值如果相同,输入值不一定相同。
两个不同的输入值,根据同一散列函数计算出的散列值相同的现象叫做碰撞。

相关文章
|
6天前
|
Java
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式。本文介绍了 Streams 的基本概念和使用方法,包括创建 Streams、中间操作和终端操作,并通过多个案例详细解析了过滤、映射、归并、排序、分组和并行处理等操作,帮助读者更好地理解和掌握这一重要特性。
14 2
|
6天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
10天前
|
存储 Java
判断一个元素是否在 Java 中的 Set 集合中
【10月更文挑战第30天】使用`contains()`方法可以方便快捷地判断一个元素是否在Java中的`Set`集合中,但对于自定义对象,需要注意重写`equals()`方法以确保正确的判断结果,同时根据具体的性能需求选择合适的`Set`实现类。
|
10天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
49 4
|
10天前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
10天前
|
存储 Java 开发者
在 Java 中,如何遍历一个 Set 集合?
【10月更文挑战第30天】开发者可以根据具体的需求和代码风格选择合适的遍历方式。增强for循环简洁直观,适用于大多数简单的遍历场景;迭代器则更加灵活,可在遍历过程中进行更多复杂的操作;而Lambda表达式和`forEach`方法则提供了一种更简洁的函数式编程风格的遍历方式。
|
10天前
|
存储 Java 开发者
Java中的集合框架深入解析
【10月更文挑战第32天】本文旨在为读者揭开Java集合框架的神秘面纱,通过深入浅出的方式介绍其内部结构与运作机制。我们将从集合框架的设计哲学出发,探讨其如何影响我们的编程实践,并配以代码示例,展示如何在真实场景中应用这些知识。无论你是Java新手还是资深开发者,这篇文章都将为你提供新的视角和实用技巧。
11 0
|
安全 Java 程序员
4月7日云栖精选夜读:给 Java 学习者的超全教程整理
作为Java程序员的我们,应该不仅对这门语言有所了解,而且我们在平常编程时也需要使用众多的库。比如小编知道的,如果要学习Java Web的话,SSH(Spring, Struts和Hibernate)肯定得会吧,或者至少了解基本的原理吧。
2760 0
|
8天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
4天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
21 9