1. 集合的其他内容
1.1. Iterator
- Iterator专门为遍历集合而生,集合并没有提供专门的遍历方法
- Iterator是用迭代器设计模式实现的
- Iterator的常用方法
- boolean hasNext():判断是否存在另一个可访问的元素
- Object next():返回要访问的下一个元素
- void remove():删除上次访问返回的对象
- 哪些集合可以使用Iterator遍历
- Collection、List、Set可以,Map不可以
- 提供Iterator()方法的就可以将元素交给Iterator
- 实现Iterator接口的集合类都可以使用迭代器遍历
- for-each循环和Iterator的联系
- for-each循环时(遍历集合),底层使用的是Iterator
- 凡是可以使用for-each循环(遍历的集合),肯定也可以使用Iterator进行遍历
- for-each循环和Iterator的区别
- for-each还能遍历数组,Iterator只能遍历集合
- 使用for-each遍历集合时,不能删除元素,会抛出异常ConcurrentModificationException,使用Iterator遍历时能删除元素
- Iterator是一个接口,他的实现类在相应的集合实现类中,比如在ArrayList中存在一个内部类itr implements Iterator
- Iterator不设计成一个类,而是接口的原因:不同的集合类,底层结构不同,迭代方式不同,所以提供一个接口,让相应的实现类来实现
public class TestIterator {
public static void main(String[] args) {
// 创建一个集合对象
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
System.out.println(list); // [1, 2]
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
int elem = it.next();
if(elem == 2) it.remove();
}
System.out.println(list); // [1]
}
}
- Iterator是怎么工作的:不同集合的遍历由Iterator的不同实现类完成,以Iterator遍历ArrayList为例进行说明
public Iterator<E> iterator() {
return new Itr();
}
public class Itr implements Iterator<E> {
int cursor;
int lastRet = -1;
public boolean hasNext() {
return cursor != size;
}
public E next() {
int i = cursor;
Object[] elementData = ArrayList.this.elementData;
cursor = i + 1;
return (E)elementData[lastRet = i];
}
}
1.2. ListIterator
- ListIterator和Iterator的关系
- public interface ListIterator<E> extends Iterator<E>
- 都可以遍历List
- ListIterator和Iterator的区别
- 使用范围不同
- Iterator可以应用于更多的集合,Set、List和这些集合的子类型
- ListIterator只能用于List及其子类型
- 遍历顺序不同
- Iterator只能向后遍历
- ListIterator还可以逆序向前遍历
- Iterator可以在遍历过程中remove();ListIterator可以在遍历的过程中remove()、add()、set()
- ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现;Iterator没有此功能
- 使用范围不同
public class TestListIterator {
public static void main(String[] args) {
// 创建一个集合对象
List<Integer> list = new ArrayList<Integer>();
list.add(1);
ListIterator<Integer> lit = list.listIterator();
while (lit.hasNext()) {
lit.next();
}
while (lit.hasPrevious()) {
int elem = lit.previous();
System.out.println(elem + " " + lit.nextIndex() + " " + lit.previousIndex()); // 1 0 -1
}
}
}
1.3. Collections工具类
- 关于集合操作的工具类,比如Arrays, Math
- 唯一的构造方法private,不允许在类的外部创建对象
- 提供了大量的static方法,可以通过类名直接调用
public class TestCollections {
public static void main(String[] args) {
List<Integer> list = new ArrayList();
Collections.addAll(list, 5,2,3,6,1);
System.out.println(list); // [5, 2, 3, 6, 1]
Collections.sort(list);
System.out.println(list); // [1, 2, 3, 5, 6]
int index = Collections.binarySearch(list, 5);
System.out.println(index); // 3
int max = Collections.max(list);
int min = Collections.min(list);
System.out.println(max + " " + min); // 6 1
Collections.fill(list, null);
System.out.println(list); // [null, null, null, null, null]
List list1 = new ArrayList();
Collections.addAll(list1, 10,20,30);
System.out.println(list1); // [10, 20, 30]
Collections.copy(list, list1);
System.out.println(list); // [10, 20, 30, null, null]
/*
* 在没有线程安全要求的情况下可以使用ArrayList,
* ArrayList线程不安全效率高,StringBuffer线程安全效率低,StringBuilder线程不安全效率高,Vector线程安全效率低
* 方案1:程序员手动将不安全的变成安全的
* 方案2:提供最新的线程安全并且性能高的集合类
* */
List list2 = new ArrayList();
Collections.addAll(list2, 100, 200, 300, 400);
System.out.println(list2); // [100, 200, 300, 400]
// 将list2转换成线程安全的集合类
list2 = Collections.synchronizedList(list2);
// 再操作线程就安全了
}
}
1.4. 旧的集合类
- Vector
- 实现原理和ArrayList相同,功能相同,都是长度可变的数组结构,很多情况下可以互用
- 两者的主要区别
- Vector是早期JDK接口,ArrayList是替代Vector的新接口
- Vector线程安全效率低,ArrayList重速度轻安全,线程不安全
- 长度需增长时,Vector默认增长一倍,ArrayList增长50%
- Hashtable类
- 实现原理和HashMap相同,功能相同,底层都是哈希表结构,查询速度快,很多情况下可以互用
- 两者的主要区别
- Hashtable是早期JDK提供,HashMap是新版JDK提供
- Hashtable继承Dictionary类,HashMap实现Map接口
- Hashtable线程安全,HashMap线程非安全
- Hashtable不允许有null值,HashMap允许null值
public class TestVector {
public static void main(String[] args) {
Vector<Integer> v = new Vector<Integer>();
v.addElement(1);
v.addElement(2);
Enumeration<Integer> en = v.elements();
while (en.hasMoreElements()) {
Integer elem = en.nextElement();
System.out.print(elem + " "); // 1 2
}
}
}
1.5. 新一代并发集合类
- 早期集合类Vector、Hashtable线程都是安全的,是使用了synchronized修饰方法
- 为了提高性能,使用ArrayList、HashMap替换,线程不安全性能好。使用Collections.synchronizedList(list)、Collections.synchronizedMap(m)解决,可以使ArrayList、HashMap线程安全,底层使用synchronized代码块锁
- 在大量并发的情况下,提供了新的线程同步集合类,位于java.util.concurrent包下,使用Lock锁
- ConcurrentHashMap
- CopyOnWriteArrayList
- CopyOnWriteArraySet
1.6. 集合常用概念辨析
- 集合和数组的比较
数组不是面向对象的,存在明显缺陷,集合弥补了数组的一些缺点,比数组灵活实用,大大提高了软件的开发效率,且不同的集合框架类可适用于不同场合- 数组容量固定且无法动态改变,集合类容量动态改变
- 数组可以存放基本数据类型和引用数据类型数据,集合类只能存放引用数据类型的数据
- 数组无法判断其中实际存有多少元素,length只表示了array容量,集合可以判断实际存有的元素数量,对总容量不关心
- 集合有多种数据结构(顺序表、链表、哈希表、树等)、多种特征(是否有序、是否唯一)、不同适应场合(查询快、便于删除、有序),数组仅采用顺序表方式
- 集合以类的形式存在,有封装继承多态等类的特性,通过简单的方法和属性调用即可实现各种复杂操作,大大提高了软件的开发效率
- ArrayList和LinkedList的联系和区别
- 联系
- 都实现了List接口
- 有序、不唯一
- ArrayList
- 特点:在内存中分配连续的空间,实现了长度可变的数组
- 优点:遍历元素和随机访问元素效率较高
- 缺点:添加和删除需大量移动元素,效率低,按照内容查询效率低
- LinkedList
- 特点:采用链表存储方式,底层是双向链表
- 优点:插入、删除效率较高(前提是必须先低效率的查询。如果插入删除发生在头尾可以减少查询次数)
- 缺点:遍历和随机访问元素效率低
- 联系
- 哈希表的原理(HashMap的底层原理)
- 哈希表的特征:快:查询快、添加快
- 哈希表的结构
- 最常用、最容易理解的结构(JDK1.7):数组+链表
- JDK1.8:数组+链表/红黑树(链表长度》=8)
- 哈希表添加原理
- 计算哈希码(hashCode())
- 计算存储位置(存储位置就是数组的索引)
- 存入指定位置(要处理冲突,可能重复。需借助equals进行比较)
- 哈希表查询原理:同哈希表添加原理
- 其他
- hashCode()和equals()的作用
- 如何减少冲突
- 如何产生不同数据类型的哈希码
- TreeMap的底层原理(红黑树的底层原理)
- 基本特征
- 二叉树、二叉查找树、二叉平衡树、红黑树
- 每个节点的结构
- 二叉树、二叉查找树、二叉平衡树、红黑树
- 添加原理
- 从根节点开始比较
- 添加过程就是构造二叉平衡树的过程,会自动平衡
- 平衡离不开比较:外部比较器优先,然后是内部比较器,否则出错
- 查询基本原理同添加
- 基本特征
- Collection和Collections的区别
- Collection是Java提供的集合接口,存储一组不唯一、无序的对象。他有两个子接口List和Set
- Java中还有一个Collecions类,专门用来操作集合类,它提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作
- Vector和ArrayList的联系和区别
- 实现原理和ArrayList相同,功能相同,都是长度可变的数组结构,很多情况下可以互用
- 两者的主要区别:
- Vector是早期JDK接口,ArrayList是替换Vector的新接口
- Vector线程安全效率低,ArraryList线程不安全速度高
- 长度需增长时,Vector默认增长一倍,ArrayList增长50%
- 各种集合类的特点
类型 | 存储结构 | 顺序 | 唯一性 | 查询效率 | 添加/删除效率 |
---|---|---|---|---|---|
ArrayList | 顺序表 | 有序(添加) | 不唯一 | 索引查询效率最高 | 效率低 |
LinkedList | 双向链表 | 有序(添加) | 不唯一 | 效率低 | 效率高 |
HashSet | 哈希表 | 无序 | 唯一 | 效率最高 | 效率最高 |
HashMap | 哈希表 | Key无序 | key唯一 | 效率最高 | 效率最高 |
LinkedHashSet | 哈希表+链表 | 有序(添加) | 唯一 | 效率最高 | 效率最高 |
LinkedHashMap | 哈希表+链表 | Key有序(添加) | key唯一 | 效率最高 | 效率最高 |
TreeSet | 红黑树 | 有序(自然) | 唯一 | 效率中等 | 效率中等 |
TreeMap | 红黑树 | 有序(自然) | key唯一 | 效率中等 | 效率中等 |
Collection | Map | ||
---|---|---|---|
无序 | 不唯一 | Collection | Map.values() |
唯一 | HashSet | HashMap keySet | |
有序 | 不唯一 | ArrayList | LinkedList |
唯一 | LinkedHashSet | TreeSet | |
LinkedHashMap keySet | TreeMap keySet |
2. 泛型
2.1. 为什么需要泛型
- 没有泛型之前
- 不安全:添加元素是无检查->宽进
- 繁琐:获取元素时需要强制类型转换->严出
- 采用泛型之后
- 安全:严进
- 简单:宽出
2.2. 什么是泛型generic
JDK 1.5引入泛型,即参数化类型
方法定义和调用:定义方法时有形参,然后调用此方法时传递实参
- 定义方法
public int add(int num1, int num2) {
return num1 + num2;
}
- 调用方法: add(10,20);
- 泛型类的定义和创建对象
public class ArrayList<T> implements List<T> {}
List<String> list = new ArrayList<String>();
2.3. 泛型内容
- 泛型接口
public interface GenericInterface<T> {
public void show(T t);
}
- 泛型类
public class GenericClass<T> implements GenericInterface<T> {
@Override
public void show(T t) {
}
}
public class GenericClass implements GenericInterface<String> {
@Override
public void show(String t) {
}
}
- 泛型方法
- 泛型类中方法即使使用了泛型,也不是泛型方法;泛型方法将泛型定义在方法上
- 静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定时,必须要将泛型定义在方法上
public class GenericTool {
public static <T> void method1(GenericClass<T> generic) {
}
}
- 上限和下限通配符
- <? extends E>:上限通配符,用来限制类型的上限
- <? super E>:下限通配符,用来限制类型的下限
- 注意:此处?是类型实参,而不是类型形参。可以解决当具体类型不确定的时候,这个通配符就是?
public class GenericTool {
public static <T> void method1(GenericClass<T> generic) {
}
public static <T> void method2(GenericClass<Student> generic) {
}
public static <T> void method3(GenericClass<? extends Student> generic) {
}
public static <T> void method4(GenericClass<? super Student> generic) {
}
public static void main(String[] args) {
method1(new GenericClass<Date>());
method2(new GenericClass<Student>());
// 实参可以是Student及其子类
method3(new GenericClass<MiddleSchoolStudent>());
method4(new GenericClass<Person>());
method4(new GenericClass<Object>());
}
}
class Person() {}
class Student extends Person {}
class MiddleSchoolStudent extends Student {}
2.4. 泛型注意事项
- 泛型提出的目的,其作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型相关信息擦出。成功编译后的class文件中,不包含泛型中的类型信息。泛型信息不会进入到运行时阶段
- 泛型类,是在实例化类时指明泛型的具体类型
- 泛型方法,是在调用方法时指明泛型的具体类型
3. IO流概述
3.1. IO流概述
在Java中,数据的输入/输入以流(stream)的方式进行;Java提供了各种各样流的类,用于获取不同种类的数据;程序中通过标准的方法输入输出数据;
Java流一般位于java.io包中
数据源data source,提供原始数据的媒介。常见:数据库、文件、其他程序、内存、网络连接、IO设备
流是一个抽象、动态的概念,是一连串连续动态的数据集合。
3.2. IO流分类
- 按流的方向分类
- 输入流:数据流向是数据源道程序(以InputStream、Reader结尾的流)
- 输出流:数据流向是程序到目的地(以OutputStream、Writer结尾的流)
- 输入输出流的划分是相对程序而言的,而不是相对数据源
- 按处理的数据单元分类
- 字节流:以字节为单位获取数据,命名上以Stream结尾,顶级类Input/OutputStream
- 字符流:以字符为单位获取数据,命名上以Reader/Writer结尾,顶级类FileReader/FileWriter
- 按处理对象不同分类
- 节点流:直接从数据源或目的地读写数据,如FileInputStream、FileReader等;处于IO操作的第一线,所有操作必须通过他们进行
- 处理流:不直接连接到数据源或目的地,是处理流的流。通过对其他流的处理提高程序的性能。如BufferedInputStream、BufferedReader等。处理流也叫包装流;可以对节点流进行包装,提高性能或提高程序灵活性
3.3. IO流体系结构
InputStream和OutputStream
- Java语言中最基本的两个字节输入输出类
- 其他所有字节输入输出流类都继承自这两个类
- 这两个类都是抽象类,不能创建他们的实例,只能使用他们的子类
Reader和Writer
- Java语言中最基本的两个字符输入输出类
- 其他所有字符输入输出流类都继承自这两个类
- 这两个类都是抽象类,不能直接实例化,只能使用他们的子类