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.缓冲区大小不同

  字节流的缓冲区大小一般比字符流大,因为字节流处理的数据一般比字符流大,需要更大的缓冲区来存储数据。

 

posted @ 2023-07-19 10:35  剑阁丶神灯  阅读(13)  评论(0编辑  收藏  举报