javaSe
1 == 和 equals区别
2 ArrayList和ListedList区别
3
一、ConcurrentHashMap 底层原理
ConcurrentHashMap 是线程安全的哈希表,它是 Java 并发包中提供的一种高效的并发 Map 实现。ConcurrentHashMap 底层采用了分段锁的机制,不同的段(Segment)可以被不同的线程同时访问,从而提高了并发性能。
ConcurrentHashMap 采用了数组 + 链表 + 红黑树的数据结构来实现哈希表。数组的每个元素都是一个段(Segment),每个段都是一个独立的哈希表,包含了若干个键值对。每个段都有自己的锁,不同的段可以被不同的线程同时访问,从而提高了并发性能。
ConcurrentHashMap 的 put 操作和 get 操作都是非常高效的,因为它们都可以并发进行,不需要对整个哈希表加锁。在进行 put 操作时,先根据 key 的哈希值找到对应的段,然后对该段加锁,再在该段中进行插入操作;在进行 get 操作时,也是先根据 key 的哈希值找到对应的段,然后对该段加锁,再在该段中进行查找操作。这样就可以实现高并发的插入和查找操作。
ConcurrentHashMap 的扩容过程也是非常高效的。当某个段的元素个数超过了阈值时,就会触发扩容操作。在进行扩容操作时,只需要对该段加锁,不需要对整个哈希表加锁。同时,扩容时只需要将旧的元素重新分配到新的段中即可,不需要像 HashMap 那样重新计算所有元素的哈希值,因此扩容的效率更高。
总之,ConcurrentHashMap 底层采用了分段锁的机制,不同的段可以被不同的线程同时访问,从而提高了并发性能。同时,它采用了数组 + 链表 + 红黑树的数据结构来实现哈希表,具有高效的插入、查找、扩容等操作。
二、ConcurrentHashMap在java8和java7它的实现一样吗?
Java 8 中的 ConcurrentHashMap 引入了新的实现方式,称为基于 CAS(Compare and Swap)的实现,相比于 Java 7 中的基于分段锁的实现,具有更好的性能表现。此外,Java 8 中的 ConcurrentHashMap 还引入了新的方法,如 forEach、reduce、search 等,使得对 ConcurrentHashMap 的操作更加方便和高效。但是,Java 7 中的 ConcurrentHashMap 仍然可以使用,并且在某些场景下,其性能表现可能更好。
总结:
java 8:CAS + 分段锁
java 7:分段锁
三、Java 8 ConcurrentHashMap 主要实现方式
Java 8 中的 ConcurrentHashMap 实现采用了基于 CAS 的方式,其主要实现逻辑如下:
ConcurrentHashMap 内部采用了一个数组来存储数据,每个数组元素称为一个“桶”,每个桶又是一个链表或红黑树,用于存储键值对。数组的大小会根据当前元素数量动态调整。
ConcurrentHashMap 中的每个桶都有一个链表或红黑树,用于存储键值对。当链表长度超过阈值(默认为 8)时,会将链表转换为红黑树,以提高查找效率。
ConcurrentHashMap 中的每个桶都有一个“基准计数”,用于记录该桶中键值对的数量。同时,每个线程都会维护一个“本地计数”,用于记录该线程向 ConcurrentHashMap 中插入的键值对数量。
ConcurrentHashMap 中的 put 操作会先根据 key 的哈希值找到对应的桶,然后对该桶进行加锁(采用了一种乐观锁的方式,即不断尝试 CAS 操作),如果加锁成功,则进行插入操作。插入操作包括两个步骤:首先将键值对插入到桶中,然后将“基准计数”加 1。如果加锁失败,则重试插入操作。
ConcurrentHashMap 中的 get 操作也会先根据 key 的哈希值找到对应的桶,然后对该桶进行加锁,如果加锁成功,则在该桶中查找对应的键值对。查找操作包括两个步骤:首先遍历链表或红黑树,查找对应的键值对;然后将“本地计数”加 1。如果加锁失败,则重试查找操作。
ConcurrentHashMap 中的扩容操作会将所有的桶都进行扩容,扩容时会对每个桶进行加锁,不同的桶可以被不同的线程同时扩容。扩容操作包括两个步骤:首先将旧桶中的键值对重新分配到新桶中,然后将“基准计数”更新为新桶中键值对的数量
4
5
6
6
7
8 面向对象
9
10 泛型
11
12
13
CopyOnWriteArrayList的底层原理
1、首先的话CopyOnWriteArrayList的底层也是通过数组来实现的,在向CopyOnWriteArrayList添加元素时,会复制一个新的数组并且将CopyOnWriteArrayList的数据存储数组指向它,写操作在新的数组上进行,读操作在原数组上进行。
//CopyOnWriteArrayList的底层数组 private transient volatile Object[] array;
2、写操作会加锁,防止出现并发写入丢失数据的问题。并且,写操作在结束之后会把元素组指向新的数组。
// 添加元素的操作 public boolean add(E var1) { // 加锁操作 ReentrantLock var2 = this.lock; var2.lock(); boolean var6; try { Object[] var3 = this.getArray(); int var4 = var3.length; //复制原来的数组到新数组,新数组长度是原数组长度 + 1 Object[] var5 = Arrays.copyOf(var3, var4 + 1); //将元素赋值给数组最后一个元素 var5[var4] = var1; //指向CopyOnWriteArrayList的底层数组 this.setArray(var5); var6 = true; } finally { //解锁 var2.unlock(); } return var6; }
// 获取元素 public E get(int index) { // 直接返回数据数组中该下标的元素 return this.get(this.getArray(), index); }
3、CopyOnWriteArrayList允许在写操作时来读取数据,大大提高了读的性能,因此适合读多写少的应用场景,但是CopyOnWriteArrayList会比较占用内存,同时读取到的数据不是实时最新的数据,所以不适合实时性要求很高的场景。
14
15 final
16 hashCode和equals
equals: 比较的是栈中的值(基本数据类型是栈中的值,引用类型则比较的是栈中的引用地址). 如果不去重写equals则默认使用Object中的equals方法
17
18
19 单向队列queue和双向队列deque
队列(queue)是一种常用的数据结构,可以将队列看做是一种特殊的线性表,该结构遵循的先进先出原则。Java中,LinkedList实现了Queue接口,因为LinkedList进行插入、删除操作效率较高
相关常用方法:
boolean offer(E e): 将元素追加到队列末尾,若添加成功则返回true。
E poll(): 从队首删除并返回该元素。
E peek(): 返回队首元素,但是不删除
双向队列(Deque),是Queue的一个子接口,双向队列是指该队列两端的元素既能入队(offer)也能出队(poll),如果将Deque限制为只能从一端入队和出队,则可实现栈的数据结构。对于栈而言,有入栈(push)和出栈(pop),遵循先进后出原则
常用方法如下:
void push(E e): 将给定元素”压入”栈中。存入的元素会在栈首。即:栈的第一个元素
E pop(): 将栈首元素删除并返回。
20 static关键字作用
static 关键字可以用来修饰:属性、方法、内部类、代码块;
static 修饰的资源属于类级别,是全体对象实例共享的资源;
使用 static 修饰的属性,静态属性是在类的加载期间初始化的,使用类名.属性访问
(1)static成员变量
首先我们要知道JAVA中有两种类型的变量:一种是用static修饰的静态变量,那么另外一种就是没有用static修饰的实例变量。
静态变量:静态变量属于类,在内存中只有一个复制,也就是说所有实例都指向一个地址,那么这些实例的值也就是相等了。
实例变量:属于对象,只有对象被创建后才会被分配地址空间,才能被使用。
(2)static成员方法
首先要注意的是static方法不能使用this和supper关键字,也不能调用非static方法,只能访问所属类的静态成员变量和成员方法。
static还有一个重要用途就是实现单例模式。单例模式的特点就是该类只能有一个实例,所以需要把类的构造函数用private修饰,那么外界创建该类的对象该怎么办呢?这就需要把创建对象的方法来声明成static来实现了。
(3)static代码块
静态代码块在类中独立于成员变量和成员方法,JVM在加载类时回执行static代码块,如果有多个就会按照顺序来执行,static代码块经常被用来初始化静态变量。
(4)static内部类
它可以不依赖于外部类实例对象而被实例化,只能访问外部类的静态成员变量和成员方法。
注意:
①多个对象共享同一个成员变量时用静态
②不能在成员方法内部定义static变量
③对于变量,如果使用static final修饰则表示一旦赋值就不可修改了哦!对于方法,用static final修饰就不能被覆盖了。
21. 字节流和字符流区别
0. 四大基类
InputStream和OutputStream 字节流
Reader和Writer 字符流
1.处理单元不同
字节流以字节为处理单元,而字符流以字符为处理单元。字节流主要用于处理二进制数据,而字符流主要用于处理文本数据。
2.处理速度不同
字节流读取和写入的速度比字符流快,因为字节流处理的是底层的字节数据,而字符流需要先将字节数据转换为字符数据,再进行处理。
3.数据表现形式不同
字节流以字节的形式读取和写入数据,可以处理所有类型的数据,包括图像、音频和视频等。而字符流则以字符的形式读取和写入数据,只能处理文本数据。
4.缓冲区大小不同
字节流的缓冲区大小一般比字符流大,因为字节流处理的数据一般比字符流大,需要更大的缓冲区来存储数据。