• 菜鸟网络一面(超详细)


    1.hashmap底层数据结构

    当你往 HashMap 中存储数据时,它会创建一个数组来保存这些数据。数组的每个位置都有一个索引,就像书架的每个格子一样。
    ​
    当你要取出数据时,HashMap 首先根据存储数据时设定的标识(也就是哈希值)找到对应的索引位置。如果多个数据的哈希值相同,它们会被放到同一个索引位置上,以链表(一条线)的形式连接起来。
    ​
    当链表上的元素过多时,为了提高查找效率,HashMap 会将链表转换为红黑树(一种特殊的二叉树结构),这样查找数据时会更快一些。
    ​
    所以,HashMap 的底层数据结构实际上是一个数组,数组中的每个位置都可以存储多个数据,通过链表或红黑树来解决哈希冲突问题。这种组合的方式使得 HashMap 具备了高效的插入、删除和查找数据的能力。

    2.hashmap存储元素流程

    ​
    HashMap 存储元素的流程大致如下:
    1.创建一个空的 HashMap 对象。
    2.将要存储的键值对(元素)作为输入。
    3.通过哈希函数计算键的哈希码(hash code),这个哈希码用于确定存储位置。
    4.使用键的哈希码,将键和值存储在 HashMap 的特定位置,通常是数组中的一个桶(bucket)。
    5.如果多个键具有相同的哈希码,称为哈希冲突,HashMap 使用链表或红黑树等数据结构来处理冲突。
    6.当需要查询或获取元素时,通过键的哈希码找到对应的桶,然后在链表或红黑树上搜索对应的键。
    7.如果找到了对应的键,返回相应的值;如果没有找到,表示该键不存在。
    需要注意的是,HashMap 的存储和检索过程都是基于键的哈希码进行的,因此元素的存储位置和检索效率都与哈希函数的选择以及哈希冲突的处理方式有关。

    3.hashmap用红黑树的优势

    HashMap 在处理哈希冲突时,可以使用链表或红黑树两种数据结构。而使用红黑树作为解决冲突的方式,具有以下优势:
    ​
    1. 时间复杂度更稳定:红黑树在平均情况下具有较低的搜索时间复杂度 O(log n),相比于链表的线性搜索时间复杂度 O(n),在大规模数据存储和查询时能够提供更稳定的性能。
    ​
    2. 更高效的插入和删除操作:红黑树的插入和删除操作相比于链表要高效许多。链表在插入和删除元素时可能需要遍历整个链表,时间复杂度为 O(n),而红黑树的插入和删除操作的时间复杂度为 O(log n)。
    ​
    3. 更均衡的树结构:红黑树能自动平衡,保证了树的高度较低且平衡,从而提供更快的搜索性能。而链表则没有这种自动平衡的机制,可能出现退化成类似于线性搜索的情况。
    ​
    需要注意的是,链表在元素数量较少时的存储和检索速度可能比红黑树更快,因为红黑树需要额外的空间来维护树结构。因此,HashMap 在元素数量较少且哈希冲突较少的情况下可能会使用链表而不是红黑树来解决冲突。

    4.链表什么时候转化为红黑树

    在 Java 8 的 HashMap 实现中,当链表中的元素数量达到一定阈值时,会将链表转化为红黑树,以优化查找的性能。具体来说,当链表长度超过 8 时,HashMap 会将链表转化为红黑树。
    ​
    这样做的原因是,虽然链表在元素数量较少时的存储和检索速度可能比红黑树更快,但当链表长度较长时,链表的线性搜索时间复杂度 O(n) 可能会增加查找的时间开销。而红黑树的平均搜索时间复杂度 O(log n) 相对较低,能够更加高效地进行元素的查找。
    ​
    需要注意的是,当红黑树中的元素数量减少到一定阈值以下时,HashMap 会将红黑树重新转化为链表,以减少额外的空间开销。因此,HashMap 会根据元素数量的变化动态地调整数据结构,以提供较好的性能。

    5.线程安全有了解吗?讲一下

    线程安全是指多个线程并发访问共享资源时,不会出现不正确的结果或不一致的状态。在多线程环境下,如果对共享资源的访问没有经过适当的同步措施,可能会导致数据竞争(data race)、不正确的计算结果、数据损坏等问题。
    ​
    在编程中,为了实现线程安全,可以采取以下几种常见的措施:
    ​
    1. 互斥锁(Mutex):使用互斥锁可以确保同一时间只有一个线程可以访问共享资源,其他线程需要等待锁的释放。互斥锁可以防止多线程同时修改共享资源,避免数据竞争。
    ​
    2. 原子操作(Atomic Operation):原子操作是指不可中断的操作,要么执行完全,要么不执行。原子操作可以保证对共享资源的操作是原子的,不会受到其他线程的干扰,确保数据的一致性。
    ​
    3. synchronized 关键字:synchronized 关键字可以用于修饰方法或代码块,确保同一时间只有一个线程可以执行被 synchronized 修饰的代码,从而保证共享资源的安全访问。
    ​
    4. 并发容器(Concurrent Containers):Java 提供了一些并发安全的容器类,如 ConcurrentHashMap、ConcurrentLinkedQueue 等,它们内部实现了线程安全的机制,能够在多线程环境下安全地访问共享数据。
    ​
    需要注意的是,并发编程涉及到复杂的线程调度和资源管理,正确处理线程安全问题需要了解并发编程的原理和相关的技术。同时,不要过度使用同步,尽量减少锁的粒度和锁的持有时间,以避免线程之间的阻塞和性能问题。

    6.锁

    锁(Lock)是一种同步机制,用于多线程环境中对共享资源的访问和修改控制。它提供了独占锁的功能,即在同一时间只有一个线程能够获得锁并执行临界区代码,其他线程需要等待。
    ​
    在 Java 中,主要有以下几种锁的实现方式:
    ​
    1. synchronized 关键字:synchronized 是 Java 内置的关键字,用于修饰方法或代码块。它在进入同步代码块之前自动获得锁,并在退出同步代码块时自动释放锁,确保同一时间只有一个线程可以执行被 synchronized 修饰的代码。
    ​
    2. ReentrantLock 类:ReentrantLock 是 java.util.concurrent.locks 包下提供的可重入锁实现类。与 synchronized 相比,ReentrantLock 提供了更丰富的功能,如公平锁和可中断锁等。它需要手动通过 lock() 方法获得锁,通过 unlock() 方法释放锁。
    ​
    3. ReadWriteLock 接口:ReadWriteLock 是用于读写分离的锁机制。它包括了一个写锁和多个读锁。在读多写少的场景中,多个线程可以同时获得读锁进行读取操作,而写操作时需要独占写锁。
    ​
    4. StampedLock 类:StampedLock 是 JDK 8 引入的锁机制,提供了乐观读锁、悲观读锁和写锁。乐观读锁不阻塞写锁,悲观读锁和写锁会阻塞其他线程的读写操作。StampedLock 的优势在于它的读锁乐观且无锁的特性,适用于读多写少的场景。
    ​
    使用锁的关键是要正确地申请和释放锁,以避免死锁和资源争用等问题。同时,要根据实际情况选择合适的锁实现,以获得最佳的性能和可靠性。

    7.cas有了解吗

    CAS(Compare and Swap)是一种并发编程中的原子操作,用于解决并发环境下的数据竞争问题。
    ​
    CAS 操作包括三个参数:内存位置 V、期望值 A 和新值 B。它的执行过程如下:
    ​
    1. 读取内存位置 V 的当前值。
    2. 如果当前值等于期望值 A,则将内存位置 V 的值更新为新值 B;否则不做任何操作。
    3. 返回操作前的内存位置 V 的当前值。
    ​
    整个 CAS 操作是原子的,不受其他线程的干扰。它利用底层硬件的特性,实现了乐观锁的机制。如果多个线程同时执行 CAS 操作,只有一个线程会成功执行更新,其他线程的操作不会造成影响。
    ​
    CAS 的优势在于避免了使用传统的互斥锁所带来的开销,不需要阻塞线程或进入内核态。相比于锁的机制,CAS 操作更轻量级,对于一些简单的操作可以提供更高的性能。
    ​
    然而,CAS 也存在一些限制和注意事项:
    - 只能保证一个共享变量的原子操作,对于多个变量的操作需要使用其他的同步机制。
    - 无法解决 ABA 问题,即如果期望值 A 在操作过程中被修改成其他值又恢复为 A,CAS 无法感知到这种变化。
    - 在高并发的情况下,CAS 的成功率可能较低,容易发生 CAS 操作失败的情况。
    ​
    在 Java 中,java.util.concurrent.atomic 包下提供了一系列基于 CAS 的原子类,如 AtomicBoolean、AtomicInteger 等,用于实现线程安全的原子操作。这些原子类利用了 CAS 操作的特性,避免了使用锁的开销,提供了一种高效且线程安全的编程方式。

    8.布隆过滤器原理

    布隆过滤器可以理解为一种查找表,但是它相比传统的查找表更省空间并且更快速。它的原理是通过多个哈希函数和一个位数组来判断一个元素是否在集合中存在。
    ​
    在初始化时,先创建一个很长的位数组,所有的位都被置为0。同时选择多个不同的哈希函数。
    ​
    当要插入一个元素时,使用这些哈希函数分别计算出对应的哈希值。然后将位数组中这些位置的位值设为1。
    ​
    当要判断一个元素是否存在时,同样使用这些哈希函数计算出哈希值。然后检查位数组中对应的位置的位值。
    - 如果任一位置的位值是0,那么可以确定该元素一定不存在。
    - 如果所有位置的位值都是1,那么该元素可能存在,但不能完全确定。
    ​
    布隆过滤器的优点在于占用的空间很小,并且查询速度非常快。它常用于判断某个数据是否存在的场景,比如判断一个网址是否已被访问过或者判断一个单词是否出现在词库中。但是需要注意的是,布隆过滤器也有一定的错误率,可能会把不存在的元素误判为存在,所以不适用于需要绝对精确判断的场景。

    9.布隆过滤器缺点

    布隆过滤器虽然有很多优点,但是也存在一些缺点,主要包括以下几点:
    ​
    1. 误判率(False Positive):布隆过滤器在判断一个元素可能存在时,并不能完全准确地告诉我们该元素是否真的存在。它只能给出一个可能存在的判断结果,但无法保证绝对的准确性。因为在插入元素时,相同的哈希值可能会影响到其他位置的位值,从而导致误判。
    ​
    2. 无法删除元素:一旦元素被插入到布隆过滤器中,就无法删除。因为删除一个元素意味着将对应的位值设为0,但这会影响到其他元素的判断结果。如果需要删除元素,必须使用其他方法来实现,或者重新构建布隆过滤器。
    ​
    3. 需要额外的空间:布隆过滤器需要使用一个位数组来存储位值,并且需要选择合适的哈希函数个数。这些都会消耗一定的内存空间。随着存储的元素数量增加,所需的空间也会增加,而且无法根据数据量自动调整大小。
    ​
    4. 哈希函数的选择和冲突:布隆过滤器需要选择多个不同的哈希函数来计算哈希值。如果选择的哈希函数不够好或者哈希函数之间产生过多的冲突,会导致误判率的增加。
    ​
    5. 不支持范围查询和部分匹配:布隆过滤器只能判断一个元素是否存在,无法支持范围查询和部分匹配操作。如果需要这些功能,需要结合其他数据结构来完成。
    ​
    综上所述,布隆过滤器在一些场景下可以提供高效的元素判断功能,但也需要在使用时考虑其可能存在的缺点和限制。

    10.string可以被继承吗

    在Java中,String类是被final修饰的,这意味着它是不可继承的。final关键字用于表示一个类不允许有子类。
    ​
    当我们使用Java编程时,如果尝试去继承String类是会发生编译错误的。这是由Java语言的设计决策所决定的,主要是出于安全性和不可变性的考虑。
    ​
    String类是不可变的,也就是说,一旦创建了String对象,它的值就不能被修改。这种设计有助于确保字符串的不可变性和线程安全性。
    ​
    如果我们想要拓展String的功能或者自定义一个可变的字符串类,我们可以使用其他方法,例如组合String对象或者创建自定义的类来实现。但直接继承String类是不被允许的。

    11.为什么string要定义成被final修饰的

    String类在Java中被定义为final的主要原因有几点:
    ​
    1. 不可变性:String对象在Java中是不可变的,也就是说一旦创建了String对象,它的值就不可被修改。这种不可变性带来了很多好处,例如线程安全性、安全性和缓存优化等。如果String类可以被继承,可能会导致子类对字符串值的意外修改,违背了不可变性的设计原则。
    ​
    2. 安全性:字符串在Java中被广泛使用,并且经常作为参数传递给许多核心类和方法。通过将String类定义为final,可以确保在这些场景中,字符串的值不会被意外修改,提高了程序的安全性和稳定性。
    ​
    3. 性能优化:由于String对象的不可变性,可以在编译时或运行时对字符串进行缓存和共享,以提高性能和节省内存。如果String类可以被继承并修改值,这种缓存和共享的机制将变得更加复杂,可能导致性能下降。
    ​
    总结来说,通过将String类定义为final,Java语言可以保证字符串的不可变性、安全性和性能优化。这也是为什么String类在Java中不可被继承的重要原因之一。

    12.redis 持久化 redis

    Redis是一种内存数据存储系统,它通常被用作缓存或键值存储。为了确保数据不会在服务器停机或崩溃时丢失,Redis提供了两种持久化机制:RDB持久化和AOF持久化。
    ​
    1. RDB持久化:RDB持久化通过将Redis数据集快照保存到磁盘上的二进制文件中实现持久化。该文件包含了在某个时间点上数据集的快照。可以通过配置Redis服务器定期进行自动快照,或者手动执行SAVE或BGSAVE命令进行快照。RDB持久化适用于备份和恢复数据以及集群迁移等场景。
    ​
    2. AOF持久化:AOF(Append-Only File)持久化通过将Redis的操作日志以追加的方式写入到磁盘上的文件中来实现持久化。每个写操作都被追加到AOF文件末尾,保证了所有操作的顺序性。当Redis服务器重启时,将重新执行AOF文件中的命令来恢复数据。AOF持久化支持三种方式:无持久化、每秒持久化、每写操作持久化。配置的持久化方式会影响性能和数据安全性。
    ​
    可以在Redis的配置文件中启用和配置持久化机制,根据实际需求选择适合的持久化方式。另外,也可以同时启用RDB持久化和AOF持久化,以提供更高的数据安全性和灵活性。

    13.RabbitMQ消息可靠性

    RabbitMQ是一个可靠的消息代理系统,它提供了多种机制来确保消息的可靠性传递。以下是RabbitMQ提供的一些关键特性来确保消息的可靠性:
    ​
    1. 持久化队列:可以声明一个持久化队列,这样即使在RabbitMQ服务器断电或重启后,队列和其中的消息也能够得到保留。
    ​
    2. 持久化消息:可以将消息标记为持久化,这样它们将被写入磁盘而不是只存在于内存中。这样即使在RabbitMQ服务器断电或重启后,消息也能够恢复。
    ​
    3. 事务(Transactions):可以使用RabbitMQ提供的事务机制来确保消息的原子性传递,即要么一起成功,要么一起失败。但是,使用事务会对性能产生一定影响,因此在需要更高吞吐量的情况下,可以选择使用发布确认模式(Publish Confirm)来代替。
    ​
    4. 发布确认模式(Publish Confirm):这是一种轻量级的、异步的模式,它通过将通道(Channel)设置为“confirm”模式,然后在发送消息后通过等待RabbitMQ的确认来确保消息的成功传递。通过该模式可以有选择地确认每条单独的消息,或者以批量方式确认已发布的一组消息,从而提供更高的性能。
    ​
    5. ACK机制:在RabbitMQ中,消费者在处理完一条消息后可以发送一个ACK(Acknowledgement)给RabbitMQ,表示该消息已经被消费。当RabbitMQ收到ACK后,就会将该消息从队列中移除。消费者在处理消息时,如果发生异常或处理失败,可以选择不发送ACK,这样RabbitMQ将会把该消息重新投递给其他消费者,确保消息不会丢失。
    ​
    需要注意的是,尽管RabbitMQ提供了这些可靠性机制,但在实际使用中,要根据具体场景和需求来选择合适的机制,权衡可靠性和性能之间的平衡。同时,正确处理异常和重试机制也是确保消息可靠性的重要部分。

    14.说一下mysql索引

    MySQL索引是一种用于提高数据库查询性能的数据结构。它通过在数据库表中的一个或多个列上创建索引,以便更高效地检索数据。索引可以加快查询速度,减少数据库的IO操作,并有效地支持数据的快速查找、排序和过滤。
    ​
    以下是关于MySQL索引的一些重要信息:
    ​
    1. 索引类型:MySQL提供多种索引类型,包括B-tree索引、哈希索引、全文索引等。其中,B-tree索引是最常用的类型,适用于大多数查询场景。
    ​
    2. B-tree索引:B-tree是一种平衡树结构,在MySQL中常用于创建索引。它通过将索引键的值和对应的行位置存储在一棵树中,以支持高效的数据查找。
    ​
    3. 索引列选择:选择适当的索引列是提高查询性能的关键。通常,较频繁用于查询条件的列或经常用于排序和分组的列是较好的选择。避免在过长的文本列上创建索引,以免占用过多空间和降低性能。
    ​
    4. 聚簇索引:在InnoDB存储引擎中,表的主键列默认会创建一个聚簇索引。聚簇索引决定了数据在磁盘上的物理存储顺序,因此可以提高范围查询和排序性能。
    ​
    5. 覆盖索引:通过在索引中包含查询所需的所有列,可以避免访问表的主体数据,从而提高查询性能。这种索引称为覆盖索引。
    ​
    6. 索引的维护和使用:索引的增删改操作会对性能产生影响,因为MySQL需要更新索引以保持数据的一致性。因此,需要谨慎地选择需要创建索引的列,并定期检查和维护索引的健康状态。
    ​
    需要注意的是,虽然索引可以提高查询性能,但过多或不正确使用索引可能会导致性能下降。因此,在设计和使用索引时,需要考虑查询的频率、表的大小、索引的选择性以及对写操作的影响等因素,并进行权衡和优化。

    15.什么样的表需要建索引

    一般来说,以下情况下建议在表上创建索引:
    ​
    1. 主键或唯一标识:表中的主键或唯一标识列通常应该建立索引。这些列是用于保证数据的唯一性,并且在查询和连接操作中经常被使用。
    ​
    2. 经常被搜索的列:如果在查询中经常使用某个列进行搜索操作,那么该列应该被索引。索引可以大幅提高搜索的速度,尤其是对于经常进行范围查询的列。
    ​
    3. 经常被排序或分组的列:如果在查询中经常使用某个列进行排序或分组操作,那么该列也应该被索引。索引可以显著提高这类操作的性能。
    ​
    4. 外键列:在涉及外键关系的表之间进行连接查询时,外键列通常建议被索引。这样可以提高连接操作的性能。
    ​
    5. 查询性能需要提升的列:对于查询性能较差的列,可以考虑建立索引来提高查询效率。然而,过多的索引也会造成额外的开销,需要权衡索引的数量与查询性能的需求。
    ​
    需要注意的是,并不是所有的列都适合建立索引。对于以下情况,可能不适合或没有必要建立索引:
    ​
    1. 非唯一性的低基数列:如果某个列的取值范围较大,而且重复值较多(例如性别列),则该列的索引效果会较差。
    ​
    2. 经常进行更新的列:对于经常进行更新的列,索引的更新操作也会产生额外的开销。在这种情况下,需要仔细权衡索引的使用。
    ​
    3. 大文本或二进制列:对于大文本或二进制列,索引会占用大量的空间,并且不一定能够提高查询性能。因此,通常情况下不建议对这类列进行索引。
    ​
    综上所述,在设计表的时候,需要根据具体的查询需求和性能优化目标来选择哪些列需要建立索引,以达到最佳的查询性能。

    16.了解聚簇索引和非聚簇索引吗

    聚簇索引和非聚簇索引是数据库中常见的两种索引类型,它们有不同的工作原理和应用场景。
    ​
    1. 聚簇索引(Clustered Index):聚簇索引是根据索引的键值对表中的数据行进行物理排序。换句话说,聚簇索引决定了数据在磁盘上的物理存储顺序。在聚簇索引的组织下,表的数据行实际上是按照索引的顺序存储的。因此,一个表只能拥有一个聚簇索引。聚簇索引的优势是在范围查询、排序和分组等操作时能提供更好的性能,但是对于插入和更新操作的性能会稍低。
    ​
    2. 非聚簇索引(Non-Clustered Index):非聚簇索引是独立于表中数据行的物理顺序的。它使用一个独立的结构来存储索引的键值和指向数据行的引用。一个表可以拥有多个非聚簇索引。非聚簇索引的优势是可以提高查找操作的性能,尤其是等值查询。此外,非聚簇索引适用于频繁进行插入和更新操作的表,因为插入和更新数据不会影响到索引的物理存储顺序。
    ​
    需要注意的是,聚簇索引和非聚簇索引并非绝对优劣,而是根据具体的应用场景来进行选择。在设计数据库表时,需要根据查询和更新操作的需求,以及对性能的要求做出权衡,合理选择使用聚簇索引或非聚簇索引来优化数据库的性能。

    项目

    1.书籍怎么存到es里?

    2.

    自我介绍 熟悉的技术 技术栈

    最近在学什么Java技术

    项目分为PC端和小程序端 PC端负责线下书店管理平台

  • 相关阅读:
    WEB区块链开发组件 - KLineChart
    服务器操作系统到底用win还是linux好?
    有趣的BUG之Stack Overflow
    合成复用原则~
    【ARMv8 SIMD和浮点指令编程】寄存器
    java.lang.ClassNotFoundException: rx.Single(hystrix)
    GIS数据漫谈(五)— 地理坐标系统
    享元模式 & 基于享元模式的对象池设计与开发应用(设计模式与开发实践 P12)
    CSS 3D变换 transform3D
    详解 React 18 更新后的特性,一文即懂
  • 原文地址:https://blog.csdn.net/weixin_52223673/article/details/132634290