一, 描述 Java 集合框架
集合, 在 Java 语言中, 将一系类的对象看成一个整体.
首先查看 jdk 中的 Collection 类的源码后会发现 Collection 是一个接口类, 其继承了 java 迭代接口 Iterable.
API:
java 集合框架图:
在图当中, 虚线框的类型是接口, 短横线框的类型是抽象类, 实线框的类型是实现类.
集合的包在 java.util 下
集合框架的顶层接口: java.util.Collection, 迭代器接口: Java.util.Iterator;
Collection 接口有两个主要的字接口 List 和 Set, 但是要注意 Map 不是 Collection 的子接口, 因为其本身就是一个顶层接口, 放在此是由于映射键与值使用了 set.Collection 中可以储存的元素间无序可以重复组各自独立的元素, 即其内的每个位置仅持有一个元素, 同时允许有多个 null 元素对象.
Collection 接口中的方法如下:
集合分类有三种 List ,Set,Map
1, 列表: java.util.List
List 中储存的元素实现类排序, 而且可以重复储存相关元素. List 有 ArrayList,Vector,LinkedList 等等的实现类, ArrayList 和 Vector 都是数组的实现, Vector 线程是安全的, LinkedList 是链表的实现.
List 接口有两个常用的实现类 ArrayList 和 LinkedList.
(一)ArrayList:
ArrayList 数组线性表的特点为: 类似数组的形式进行存储, 因此它的随机访问速度极快.
ArrayList 数组线性表的缺点为: 不适合于在线性表中间需要频繁进行插入和删除操作. 因为每次插入和删除都需要移动数组中的元素. 可以这样理解 ArrayList 就是基于数组的一个线性表, 只不过数组的长度可以动态改变而已.
编程练习 ArrayList 的使用, 示例:
- 1,
- import java.util.ArrayList;
- import java.util.List;
- public class ArrayListDemo {
- public static void main(String[] args) {
- // 实例化一个集合实例, 下面两个集合实例化都是可以的
- List list0 = new ArrayList();
- ArrayList list1 = new ArrayList();
- // 向列表中添加元素
- list0.add(123456);
- list0.add(null);
- list0.add("abcdef");
- // 显示指定位置的元素
- System.out.println(list0.get(1));// 索引是从 0 开始的, 结果显示 null
- // 显示列表中所有的元素
- for(Object l : list0) {
- System.out.println(l);
- }
- 2,
- // 在列表集合 ArrayList 中查找一个元素, 显示 "找到" 与 "未找到"
- ArrayList list2 = new ArrayList();
- list1.add(123);
- list1.add(null);
- list1.add("abc");
- if(list2.contains(123)) {
- System.out.println("元素存在");
- }
- else {
- System.out.println("元素不存在");// 结果: 元素存在
- }
3, 来一个 Vector 使用的例子
- package org.sl.jiheDemo;
- import java.util.Vector;
- public class VectorDemo {
- public static void main(String[] args) {
- Vector v = new Vector();
- v.add("小小酥");
- v.add(123);
- v.add(null);
- for(Object a : v) {
- System.out.println(a);
- }
- }
- }
ArrayList 面试小问答:
a, 如果在初始化 ArrayList 的时候没有指定初始化长度的话, 默认的长度为 10.
b,ArrayList 在增加新元素的时候如果超过了原始的容量的话, ArrayList 扩容 ensureCapacity 的方案为 "原始容量 * 3/2+1" 哦
c,ArrayList 是线程不安全的, 在多线程的情况下不要使用.
如果一定在多线程使用 List 的, 您可以使用 Vector, 因为 Vector 和 ArrayList 基本一致, 区别在于 Vector 中的绝大部分方法都使用了同步关键字修饰, 这样在多线程的情况下不会出现并发错误哦, 还有就是它们的扩容方案不同, ArrayList 是通过 "原始容量 * 3/2+1", 而 Vector 是允许设置默认的增长长度, Vector 的默认扩容方式为原来的 2 倍.
切记 Vector 是 ArrayList 的多线程的一个替代品.
d,ArrayList 实现遍历的几种方法, 三种, 示例:
- import java.util.ArrayList;
- import java.util.Iterator;
- import java.util.List;
- public class ListDemo {
- public static void main(String[] args) {/*
- * ArrayList 实现遍历有哪几种方法
- */
- ArrayList<String> list = new ArrayList<String>();
- list.add("abc");
- list.add("小小酥");
- list.add("def");
- // 第一种方法是使用 foreach 遍历 List
- for(String str : list) { // 这里也可以用 for(int i=0; i<list.size(); i++)
- System.out.println(str);
- }
- // 第二种方法把链表变为数组相关的内容进行遍历
- String[] strArray = new String[list.size()];
- list.toArray(strArray);
- for(String str1 : strArray) { // 也可以用 for(int i=0; i<strArray.length; i++)
- System.out.println(str1);
- }
- // 第三种方法 使用迭代器进行相关遍历
- // 首先获取迭代器对象, Iterator
- Iterator<String> ite = list.iterator();
- while(ite.hasNext()) { //hasNext(): 表示是否有下一个元素
- System.out.println(ite.next()); //next(): 指向下一个元素
- }
- }
- }
显示结果: 我截图就行了, 三种方法显示了三次结果, 结果都是一样的.
(二)LinkedList
LinkedList 的链式线性表的特点为: 适合于在链表中间需要频繁进行插入和删除操作.
LinkedList 的链式线性表的缺点为: 随机访问速度较慢. 查找一个元素需要从头开始一个一个的找. 速度你懂的.
可以这样理解 LinkedList 就是一种双向循环链表的链式线性表, 只不过存储的结构使用的是链式表而已.
对于 LinkedList 的详细使用信息以及创建的过程可以查看 jdk 中 LinkedList 的源码, 这里不做过多的讲解.
LinkedList 面试小问答:
a.LinkedList 和 ArrayList 的区别和联系
ArrayList 数组线性表的特点为: 类似数组的形式进行存储, 因此它的随机访问速度极快.
ArrayList 数组线性表的缺点为: 不适合于在线性表中间需要频繁进行插入和删除操作. 因为每次插入和删除都需要移动数组中的元素.
LinkedList 的链式线性表的特点为: 适合于在链表中间需要频繁进行插入和删除操作.
LinkedList 的链式线性表的缺点为: 随机访问速度较慢. 查找一个元素需要从头开始一个一个的找. 速度你懂的.
b.LinkedList 的内部实现
对于这个问题, 你最好看一下 jdk 中 LinkedList 的源码. 这样你会醍醐灌顶的.
这里我大致说一下:
LinkedList 的内部是基于双向循环链表的结构来实现的. 在 LinkedList 中有一个类似于 c 语言中结构体的 Entry 内部类.
在 Entry 的内部类中包含了前一个元素的地址引用和后一个元素的地址引用类似于 c 语言中指针.
c.LinkedList 不是线程安全的
注意 LinkedList 和 ArrayList 一样也不是线程安全的, 如果在对线程下面访问可以自己重写 LinkedList
然后在需要同步的方法上面加上同步关键字 synchronized
d.LinkedList 的遍历方法: 示例
- import java.util.LinkedList;
- import java.util.List;
- public class LinkedListDemo {
- public static void main(String[] args) {
- List<String> list = new LinkedList<String>();
- list.add("小小酥");
- list.add("小张");
- list.add("abc");
- // 使用 foreach 遍历 LinkedList
- for(String str : list) {
- System.out.println(str);
- }
- // 使用数组遍历
- String[] strArray = new String[list.size()];
- list.toArray(strArray);
- for(String str1 : strArray) {
- System.out.println(str1);
- }
- // 还有其他的方法的话我没去研究了
- }
- }
结果如下:
e.LinkedList 可以被当做堆栈来使用
由于 LinkedList 实现了接口 Dueue, 所以 LinkedList 可以被当做堆栈来使用, 这个你自己研究吧.
2, 集: java.util.Set
Set 接口也是 Collection 接口的一个常用的子接口 ,Set 中的元素实现了不重复, 无序, 不允许有重复的元素, 最多允许一个 null 元素对象.
查看 Set 接口的源码:
这里就自然而然的知道 Set 接口是 Collection 接口的子接口了吧.
需要注意的是: 虽然 Set 中元素没有顺序, 但是元素在 set 中的位置是由该元素的 HashCode 决定的, 其具体位置其实是固定的.
此外需要说明一点, 在 set 接口中的不重复是由特殊要求的.
举一个例子: 对象 A 和对象 B, 本来是不同的两个对象, 正常情况下它们是能够放入到 Set 里面的, 但是
如果对象 A 和 B 的都重写了 hashcode 和 equals 方法, 并且重写后的 hashcode 和 equals 方法是相同的话. 那么 A 和 B 是不能同时放入到 Set 集合中去的, 也就是 Set 集合中的去重和 hashcode 与 equals 方法直接相关.
示例: 截图(set 集是不保证顺序的, 没有索引的说明)
Set 接口的常见实现类有 HashSet,LinedHashSet 和 TreeSet 这三个. 下面我们将分别讲解这三个类:
(1)HashSet
HashSet 是 Set 接口的最常见的实现类了. 其底层是基于 Hash 算法进行存储相关元素的.
下面是 HashSet 的部分源码::
对于 HashSet 的底层就是基于 HashMap 来实现的.
HashSet 使用和理解中容易出现的误区:
a.HashSet 中存放 null 值
HashSet 中时允许出入 null 值的, 但是在 HashSet 中仅仅能够存入一个 null 值哦.
b.HashSet 中存储元素的位置是固定的
HashSet 中存储的元素的是无序的, 这个没什么好说的, 但是由于 HashSet 底层是基于 Hash 算法实现的, 使用了 hashcode,
所以 HashSet 中相应的元素的位置是固定的
c. 遍历 HashSet 的几种方法:
- import java.util.HashSet;
- import java.util.Iterator;
- import java.util.Set;
- public class SetDemo {
- public static void main(String[] args) {
- Set<String> set=new HashSet<String>();
- // 向集合中放入元素, set 集是不保证顺序的, 没有索引的说明
- set.add("Hello");
- set.add("world");
- set.add("小小酥");
- // 第一种 set 集合直接遍历
- for(String str : set) {
- System.out.println(str);
- }
- // 第二种使用数组方法
- String[] strArray = new String[set.size()];
- set.toArray(strArray);
- for(String str1 : set) {
- System.out.println(str1);
- }
- // 第三种使用迭代器的方法, Iterator
- Iterator<String> ite = set.iterator();
- while(ite.hasNext()) {
- System.out.println(ite.next());
- }
- System.out.println("集合的元素个数为:"+set.size());
- System.out.println("集合中的元素为:"+set.toString());
- }
- }
结果输出:
(2)LinkHashSet
LinkHashSet 不仅是 Set 接口的子接口而且还是上面 HashSet 接口的子接口.
查看 LinkedHashSet 的部分源码如下:
3)TreeSet
TreeSet 是一种排序二叉树. 存入 Set 集合中的值, 会按照值的大小进行相关的排序操作. 底层算法是基于红黑树来实现的.
TreeSet 和 HashSet 的主要区别在于 TreeSet 中的元素会按照相关的值进行排序~
TreeSet 和 HashSet 的区别和联系
1. HashSet 是通过 HashMap 实现的, TreeSet 是通过 TreeMap 实现的, 只不过 Set 用的只是 Map 的 key
2. Map 的 key 和 Set 都有一个共同的特性就是集合的唯一性. TreeMap 更是多了一个排序的功能.
3. hashCode 和 equal()是 HashMap 用的, 因为无需排序所以只需要关注定位和唯一性即可.
a. hashCode 是用来计算 hash 值的, hash 值是用来确定 hash 表索引的.
b. hash 表中的一个索引处存放的是一张链表, 所以还要通过 equal 方法循环比较链上的每一个对象
才可以真正定位到键值对应的 Entry.
c. put 时, 如果 hash 表中没定位到, 就在链表前加一个 Entry, 如果定位到了, 则更换 Entry 中的 value, 并返回旧 value
4. 由于 TreeMap 需要排序, 所以需要一个 Comparator 为键值进行大小比较. 当然也是用 Comparator 定位的.
a. Comparator 可以在创建 TreeMap 时指定
b. 如果创建时没有确定, 那么就会使用 key.compareTo()方法, 这就要求 key 必须实现 Comparable 接口.
c. TreeMap 是使用 Tree 数据结构实现的, 所以使用 compare 接口就可以完成定位了.
3, 映射: java.util.Map
Map 接口实现的是一组 Key-Value 的键值对的组合. Map 中的每个成员方法由一个关键字 (key) 和一个值 (value) 构成. Map 接口不直接继承于 Collection 接口(需要注意啦), 因为它包装的是一组成对的 "键 - 值" 对象的集合, 而且在 Map 接口的集合中也不能有重复的 key 出现, 因为每个键只能与一个成员元素相对应. 如图
(图简略将就看看)
在我们的日常的开发项目中, 我们无时无刻不在使用者 Map 接口及其实现类. Map 有两种比较常用的实现: HashMap 和 TreeMap 等. HashMap 也用到了哈希码的算法, 以便快速查找一个键, TreeMap 则是对键按序存放, 因此它便有一些扩展的方法, 比如 firstKey(),lastKey()等, 你还可以从 TreeMap 中指定一个范围以取得其子 Map. 键和值的关联很简单, 用 pub(Object key,Object value)方法即可将一个键与一个值对象相关联. 用 get(Object key)可得到与此 key 对象所对应的值对象.
简单介绍一下 HashMap 和 Hashtable
HashMap
HashMap 实现了 Map,CloneMap,Serializable 三个接口, 并且继承自 AbstractMap 类. HashMap 基于 hash 数组实现, 若 key 的 hash 值相同则使用链表方式进行保存.
新建一个 HashMap 时, 默认的话会初始化一个大小为 16, 负载因子为 0.75 的空的 HashMap
先来个示例, 我懒得敲了, 直接截图
HashMap 与 Hashtable 的区别:
HashMap: 线程不安全, 可以在列表中放置一个 key 为 null 的元素, 也可以多个 value 为 null 的元素
Hashtable: 线程安全, 不允许键和值有 null 的元素.
还有一个集合工具排序那个, 见下章, 对了不对的请指教, 谢谢
来源: https://www.cnblogs.com/xss512/p/10523568.html