2022年10月26日 16:46 周三转载自https://github.com/Snailclimb/JavaGuide (添加小部分笔记)感谢作者!
2022年10月26日 14:17 周三转载自https://github.com/Snailclimb/JavaGuide (添加小部分笔记)感谢作者!
https://zhuanlan.zhihu.com/p/360878783 IO多路复用讲解,这是一个与系统底层有关的知识点,需要一些操作系统调用代码才知道IO多路复用省的时间。
I/O
#
何为I/O
#
- I/O(Input/Output),即输入/输出
从计算机结构的角度来解读一下I/O,根据冯诺依曼结构,计算机结构分为5大部分:运算器、控制器、存储器、输入设备、输出设备
其中,输入设备:键盘;输出设备:显示器
网卡、硬盘既属于输入设备也属于输出设备 - 输入设备向计算机输入(内存)数据,输出设备接收计算机(内存)输出的数据,即I/O描述了计算机系统与外部设备之间通信的过程
- 从应用程序的角度解读I/O
- 为了保证系统稳定性和安全性,一个进程的地址空间划分为用户空间User space和内核空间Kernel space
kernel 英[ˈkɜːnl]
- 平常运行的应用程序都运行在用户空间,只有内核空间才能进行系统态级别的资源有关操作—文件管理、进程通信、内存管理
- 如果要进行IO操作,就得依赖内核空间的能力,用户空间的程序不能直接访问内核空间
- 用户进程要想执行IO操作,必须通过系统调用来间接访问内核空间
- 对于磁盘IO(读写文件)和网络IO(网络请求和响应),从应用程序视角来看,应用程序对操作系统的内核发起IO调用(系统调用),操作系统负责的内核执行具体IO操作
- 应用程序只是发起了IO操作调用,而具体的IO执行则由操作系统内核完成
- 应用程序发起I/O后,经历两个步骤
- 内核等待I/O设备准备好数据
- 内核将数据从内核空间拷贝到用户空间
有哪些常见的IO模型
#
UNIX系统下,包括5种:同步阻塞I/O,同步非阻塞I/O,I/O多路复用、信号驱动I/O和异步I/O
Java中3中常见I/O模型
#
BIO (Blocking I/O )
#
- 应用程序发起read调用后,会一直阻塞,直到内核把数据拷贝到用户空间

NIO (Non-blocking/New I/O)
#
- 对于java.nio包,提供了Channel、Selector、Buffer等抽象概念,对于高负载高并发,应使用NIO
- NIO是I/O多路复用模型,属于同步非阻塞IO模型
一般的同步非阻塞 IO 模型中,应用程序会一直发起 read 调用。
等待数据从内核空间拷贝到用户空间的这段时间里,线程依然是阻塞的**,**直到在内核把数据拷贝到用户空间。
相比于同步阻塞 IO 模型,同步非阻塞 IO 模型确实有了很大改进。通过轮询操作,避免了一直阻塞。
但是,这种 IO 模型同样存在问题:应用程序不断进行 I/O 系统调用轮询数据是否已经准备好的过程是十分消耗 CPU 资源的。
★★ 也就是说,【准备数据,数据就绪】是不阻塞的。而【拷贝数据】是阻塞的

...
2022年10月24日 23:40 周一转载自https://github.com/Snailclimb/JavaGuide (添加小部分笔记)感谢作者!
装饰器模式
#
类图:

装饰器,Decorator,装饰器模式可以在不改变原有对象的情况下拓展其功能
★装饰器模式,通过组合替代继承来扩展原始类功能,在一些继承关系较复杂的场景(IO这一场景各种类的继承关系就比较复杂)下更加实用
对于字节流,FilterInputStream(对应输入流)和FilterOutputStream(对应输出流)是装饰器模式的核心,分别用于增强(继承了)InputStream和OutputStream子类对象的功能
Filter (过滤的意思),中间(Closeable)下面这两条虚线代表实现;最下面的实线代表继承

其中BufferedInputStream(字节缓冲输入流)、DataInputStream等等都是FilterInputStream的子类,对应的BufferedOutputStream和DataOutputStream都是FilterOutputStream的子类
例子,使用BufferedInputStream(字节缓冲输入流)来增强FileInputStream功能
ZipInputStream和ZipOutputStream还可以用来增强BufferedInputStream和BufferedOutputStream的能力
...
2022年10月23日 12:21 周日转载自https://github.com/Snailclimb/JavaGuide (添加小部分笔记)感谢作者!
简介
#
字节流
#
2022年10月22日 18:26 周六转载自https://github.com/Snailclimb/JavaGuide (添加小部分笔记)感谢作者!
总结
#
Java7 中 ConcurrentHashMap
使用的分段锁,也就是每一个 Segment 上同时只有一个线程可以操作,每一个 Segment
都是一个类似 HashMap
数组的结构,每一个HashMap可以扩容,它的冲突会转化为链表。但是 Segment
的个数一但初始化就不能改变。
Java8 中的 ConcurrentHashMap
使用的 Synchronized
锁加 CAS 的机制。结构也由 Java7 中的 Segment
数组 + HashEntry
数组 + 链表 进化成了 Node 数组 + 链表 / 红黑树,Node 是类似于一个 HashEntry 的结构。它的冲突再达到一定大小时会转化成红黑树,在冲突小于一定数量时又退回链表。
源码 (略过)
#
ConcurrentHashMap1.7
#
- 存储结构
- Segment数组(该数组用来加锁,每个数组元素是一个HashEntry数组(该数组可能包含链表)
- 如图,ConcurrentHashMap由多个Segment组合,每一个Segment是一个类似HashMap的结构,每一个HashMap内部可以扩容,但是Segment个数初始化后不能改变,默认16个(即默认支持16个线程并发)

ConcurrentHashMap1.8
#
2022年10月21日 15:30 周五转载自https://github.com/Snailclimb/JavaGuide (添加小部分笔记)感谢作者!
HashMap简介
#
- HashMap用来存放键值对,基于哈希表的Map接口实现,是非线程安全的
- 可以存储null的key和value,但null作为键只能有一个
- JDK8之前,HashMap由数组和链表组成,链表是为了解决哈希冲突而存在;JDK8之后,当链表大于阈值(默认8),则会选择转为红黑树(当数组长度大于64则进行转换,否则只是扩容),以减少搜索时间
- HashMap默认初始化大小为16,每次扩容为原容量2倍,且总是使用2的幂作为哈希表的大小
底层数据结构分析
#
JDK8之前,HashMap底层是数组和链表,即链表散列;通过key的hashCode,经过扰动函数,获得hash值,然后再通过(n-1) & hash 判断当前元素存放位置(n指的是数组长度),如果当前位置存在元素,就判断元素与要存入的元素的hash值以及key是否相同,相同则覆盖,否则通过拉链法解决

扰动函数,即hash(Object key)方法
//JDK1.8
static final int hash(Object key) {
int h;
// key.hashCode():返回散列值也就是hashcode
// ^ :按位异或
// >>>:无符号右移,忽略符号位,空位都以0补齐
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
JDK1.7
//JDK1.7 , 则扰动了4次,性能较差
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
JDK1.8之后,当链表长度大于阈值(默认为 8)时,会首先调用 treeifyBin()
方法。这个方法会根据 HashMap 数组来决定是否转换为红黑树。只有当数组长度大于或者等于 64 的情况下,才会执行转换红黑树操作,以减少搜索时间。否则,就是只是执行 resize()
方法对数组扩容。相关源码这里就不贴了,重点关注 treeifyBin()
方法即可!
...
2022年10月20日 17:01 周四转载自https://github.com/Snailclimb/JavaGuide (添加小部分笔记)感谢作者!
简介
#
3. 扩容机制分析 ( JDK8 )
#
ArrayList的构造函数
- 三种方式初始化,构造方法源码
- 空参,指定大小,指定集合 (如果集合类型非Object[].class,则使用Arrays.copyOf转为Object[].class)
- 以无参构造方式创建ArrayList时,实际上初始化赋值的是空数组;当真正操作时才分配容量,即添加第一个元素时扩容为10
/**
* 默认初始容量大小
*/
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
*默认构造函数,使用初始容量10构造一个空列表(无参数构造)
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 带初始容量参数的构造函数。(用户自己指定容量)
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {//初始容量大于0
//创建initialCapacity大小的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {//初始容量等于0
//创建空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {//初始容量小于0,抛出异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
*构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返回
*如果指定的集合为null,throws NullPointerException。
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
以无参构造参数函数为例
先看下面的 add()方法扩容
...
2022年10月19日 17:26 周三转载自https://github.com/Snailclimb/JavaGuide (添加小部分笔记)感谢作者!
集合判空
#
//阿里巴巴开发手册
判断所有集合内部的元素是否为空,使用 isEmpty()
方法,而不是 size()==0
的方式。
集合转Map
#
//阿里巴巴开发手册
...
2022年10月18日 08:54 周二转载自https://github.com/Snailclimb/JavaGuide (添加小部分笔记)感谢作者!
Map
#
HashMap和Hashtable的区别
HashMap是非线程安全的,Hashtable是线程安全的,因为Hashtable内部方法都经过synchronized修饰(不过要保证线程安全一般用ConcurrentHashMap)
由于加了synchronized修饰,HashTable效率没有HashMap高
HashMap可以存储null的key和value,但null作为键只能有一个**;HashTable不允许有null键和null值**
初始容量及每次扩容
- Hashtable默认初始大小11,之后扩容为2n+1;HashMap初始大小16,之后扩容变为原来的2倍
- 如果指定初始大小,HashTable直接使用初始大小
而HashMap
会将其扩充为 2 的幂次方大小(HashMap
中的**tableSizeFor()
**方法保证,下面给出了源代码)。也就是说 HashMap
总是使用 2 的幂作为哈希表的大小,后面会介绍到为什么是 2 的幂次方
底层数据结构
- JDK1.8之后HashMap解决哈希冲突时,当链表大于阈值(默认8)时,将链表转为红黑树(转换前判断,如果当前数组长度小于64,则先进行数组扩容,而不转成红黑树),以减少搜索时间。
- Hashtable没有上面的机制
/**
HashMap 中带有初始容量的构造函数:
*/
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/*下面这个方法保证了 HashMap 总是使用 2 的幂作为哈希表的大小。*/
/**
* Returns a power of two size for the given target capacity.
*/
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
HashMap和hashSet区别
...
2022年10月17日 08:55 周一转载自https://github.com/Snailclimb/JavaGuide (添加小部分笔记)感谢作者!
集合包括Collection
和Map
,Collection 存放单一元素。Map 存放键值对
#

List,Set,Queue,Map区别
#
List
(对付顺序的好帮手): 存储的元素是有序的、可重复的。Set
(注重独一无二的性质): 存储的元素是无序的、不可重复的。Queue
(实现排队功能的叫号机): 按特定的排队规则来确定先后顺序,存储的元素是有序的、可重复的。Map
(用 key 来搜索的专家): 使用键值对(key-value)存储,类似于数学上的函数 y=f(x),“x” 代表 key,“y” 代表 value,key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值。
各种集合框架–底层数据结构
#
- List
- ArrayList、Vector —-> Object[] 数组
- LinkedList 双向链表 (jdk 1.6 之前为循环链表, 1.7 取消了循环)
- Set
- HashSet (无序,唯一),且基于HashMap
- LinkedHashSet 是HashSet的子类,基于LinkedHashMap
(LinkedHashMap内部基于HashMap实现)
- TreeSet(有序,唯一) :红黑树(自平衡的排序二叉树)
- Queue (队列)
- PriorityQueue:Object[] 数组来实现二叉堆
- ArrayQueue:Object[] 数组+ 双指针
- Map