2024最新-javaSE面试题

java基础

  1. java是一种面向对象的编程语言,其特点如下:
    简单易学:Java的语法设计简单,易学易用,同时Java标准库提供了丰富的API,可以帮助程序员快速实现功能。
    平台无关性:Java程序可以在不同的操作系统和硬件平台上运行,这是由Java虚拟机(JVM)的存在实现的。这使得Java成为一种非常灵活的语言。
    面向对象:Java是一种纯面向对象的编程语言,所有的代码都必须写在类中,可以方便地封装、继承和多态等特性。
    安全性:Java程序运行在虚拟机上,严格的安全机制可以确保程序的安全性。
    多线程支持:Java提供了多线程支持,程序员可以利用多线程编写高效的程序。
    垃圾回收:Java内置垃圾回收机制,可以自动回收不再使用的内存空间,避免了手动管理内存的繁琐和容易出错的问题。
    总的来说,Java是一种非常强大的编程语言,有着广泛的应用领域,适合开发各种类型的应用程序。

  2. Final
    final表示最终的,不可变的
    final修饰的类无法被继承
    方法不能被覆盖重写
    final局部变量只能赋值一次
    final的实例变量必须手动赋值,系统不负责赋默认值

  3. 如何将字符串反转
    1.将对象封装到stringBuilder中,调用reverse方法反转
    2.使用字符串数组,实现从尾部开始逐个逆序放入字符串
    3.使用String的CharAt方法,使用String的CharAT方法取出字符串中的各个字符,然后插入到字符串中,调用StringBuilder的insert方法进行操作。
    4.使用递归的方法,实现字符串反转

  4. final、finally、finalize 有什么区别?
    final可以修饰类,变量,方法,修饰的类不能被继承,修饰的变量不能重新赋值,修饰的方法不能被重写
    finally用于抛异常,finally代码块内语句无论是否发生异常,都会在执行finally,常用于一些流的关闭。
    finalize方法用于垃圾回收。
    一般情况下不需要我们实现finalize,当对象被回收的时候需要释放一些资源,比如socket链接,在对象初始化时创建,整个生命周期内有效,那么需要实现finalize方法,关闭这个链接。
    但是当调用finalize方法后,并不意味着gc会立即回收该对象,所以有可能真正调用的时候,对象又不需要回收了,然后到了真正要回收的时候,因为之前调用过一次,这次又不会调用了,产生问题。所以,不推荐使用finalize方法。

  5. &和&&的区别?
    &&:逻辑与运算符。当运算符左右两边的表达式都为 true,才返回 true。同时具有短路性,如果第一个表达式为 false,则直接返回 false。
    &:逻辑与运算符、按位与运算符。
    按位与运算符:用于二进制的计算,只有对应的两个二进位均为1时,结果位才为1 ,否则为0。
    逻辑与运算符:& 在用于逻辑与时,和 && 的区别是不具有短路性。所以通常使用逻辑与运算符都会使用 &&,而 & 更多的适用于位运算。

  6. 两个对象的 hashCode() 相同,则 equals() 也一定为 true,对吗?
    不对。
    hashCode() 和 equals() 之间的关系如下:
    当有 a.equals(b) == true 时,则 a.hashCode() == b.hashCode() 必然成立,
    反过来,当 a.hashCode() == b.hashCode() 时,a.equals(b) 不一定为 true。
    如果两个对象相同,那么他们的hashCode值一定相同

  7. 深拷贝和浅拷贝区别是什么?
    数据分为基本数据类型和引用数据类型。
    基本数据类型:数据直接存储在栈中;
    引用数据类型:存储在栈中的是对象的引用地址,真实的对象数据存放在堆内存里。
    浅拷贝:
    对于基础数据类型:直接复制数据值;
    对于引用数据类型:只是复制了对象的引用地址,新旧对象指向同一个内存地址,修改其中一个对象的值,另一个对象的值随之改变。
    深拷贝:
    对于基础数据类型:直接复制数据值;
    对于引用数据类型:开辟新的内存空间,在新的内存空间里复制一个一模一样的对象,新老对象不共享内存,修改其中一个对象的值,不会影响另一个对象。
    深拷贝相比于浅拷贝速度较慢并且花销较大。

  8. 构造器是否可被重写?
    Constructor 不能被 override(重写),但是可以 overload(重载),所以你可以看到⼀个类中有多个构造函数的情况。在构造器中它的方法名与类名相同,在继承中会重写父类构造器,但是重写后又与父类名无法相同。

  9. 当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?
    值传递。Java 中只有值传递,对于对象参数,值的内容是对象的引用。

  10. 继承中构造方法的调用顺序
    子类的构造方法总是先调用父类的构造方法,如果子类的构造方法没有明显地指明使用父类的哪个构造方法,子类就调用父类不带参数的构造方法。
    而父类没有无参的构造函数,所以子类需要在自己的构造函数中显示的调用父类的构造函数。

  11. 堆,栈,方法区
    堆区:只存放类对象,线程共享
    方法区:又叫静态存储区,存放class文件和静态数据,线程共享
    栈区:存放方法局部变量,基本类型变量区,执行环境上下文,操作指令区,线程不共享。

  12. 面向对象三大特征是什么?
    封装:
    实现专业的分工
    减少代码耦合
    可以自由修改类的内部结构
    继承:
    java中是不支持多继承的,接口可以,一个接口可以继承多个其他接口
    也可以说子类是父类的实现,父类是子类的抽象
    继承往往用于抽象的概念
    例如:在数据库连接表a与表b时,可以对表a与表b来抽象它的父类,这里对获取数据库连接的方法进行抽象,这样只需关注数据库操作就行,关于数据库连接交给它的父类即可。
    多态:
    多态是三大特性中最重要的操作
    多态是同一个行为具有多个不同表现形式或形态的能力
    多态是一个接口,使用不同的实例而执行不同操作

  13. 静态和实例变量的区别
    语法区别:静态变量前要加static关键字,实例则不用
    隶属区别:实例变量隶属于某个对象的属性,而静态属于类
    根本区别:静态变量在JVM加载类自己创建,不可以被垃圾回收,实例变量在实例化对象时创建
    ,可以被垃圾回收

  14. 静态块,非静态块,构造函数的执行顺序
    1.静态优先
    2.父类优先
    3.非静态块优先于构造函数

  15. 接口与抽象类的不同:
    概念:
    抽象类:
    单继承。不能被实例化,只能被继承。可以包含属性与方法,方法中可以包含具体实现。子类继承抽象类必须实现抽象类的所有方法,否则继承的类还是抽象类。
    接口:
    多实现。不能包含属性(成员变量),能声明方法,但方法中不包含代码实现。实现类必须实现接口的所有方法
    功能:

  • 抽象类:如果我们要表示一种 is-a 的关系,并且是为了解决代码复用的问题;
  • 接口:如果我们要表示一种 has-a 关系,并且是为了解决解耦,提高代码的扩展性。
    层次:
  • 抽象类:自下而上的设计思路,先有子类的代码重复,然后再抽象成上层的父类(也就是抽象类)
  • 接口:自上而下的设计思路。我们在编程的时候,一般都是先设计接口,再考虑具体的实现
    使用:
    类与类,接口与接口是extends
    接口与类是implements

抽象类中可以包含非抽象的方法,在 Java 7 之前接口中的所有方法都是抽象的,在 Java 8 之后,接口支持非抽象方法:default 方法、静态方法等。Java 9 支持私有方法、私有静态方法。
抽象类中的方法类型可以是任意修饰符,Java 8 之前接口中的方法只能是 public 类型,Java 9 支持 private 类型。

  1. 如何理解volatile关键字
    在并发领域中,存在三大特性:原子性,有序性,可见性。volatile关键字用来就是对象属性,在并发环境下可以保证这个属性的可见性,对于加了volatile关键字的属性,在对这个属性进行修改时,会直接将CPU高级缓存中的数据写回到主内存,对这个变量的读取也会直接从主内存中读取,从而保证了可见性,底层是通过操作系统的内存屏障来实现的,由于使用了内存屏障,所以会禁止指令重排,所以同时也就保证了有序性,在很多并发场景下,如果用好volatile关键字可以很好的提高执行效率。
    volatile对应的变量可能在你的程序本身不知道的情况下发生改变
    比如多线程的程序,共同访问的内存当中,多个程序都可以操纵这个变量
    你自己的程序,是无法判定何时这个变量会发生变化
    还比如,他和一个外部设备的某个状态对应,当外部设备发生操作的时候,通过驱动程序和中断事件,系统改变了这个变量的数值,而你的程序并不知道。
    对于volatile类型的变量,系统每次用到他的时候都是直接从对应的内存当中提取,**而不会利用cache当中的原有数值,**以适应它的未知何时会发生的变化,系统对这种变量的处理不会做优化——显然也是因为它的数值随时都可能变化的情况。

  2. 一个参数既可以是const还可以是volatile吗?解释为什么。
    是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

枚举

  1. 枚举允许继承类吗?
    枚举不允许继承类。Jvm在生成枚举时已经继承了Enum类,由于Java语言是单继承,不支持再继承额外的类(唯一的继承名额被Jvm用了)。

  2. 枚举可以用等号比较吗?
    枚举可以用等号比较。Jvm会为每个枚举实例对应生成一个类对象,这个类对象是用public static final修饰的,在static代码块中初始化,是一个单例。

  3. 枚举可以被人家继承吗?
    不可以继承枚举。因为Jvm在生成枚举类时,将它声明为final。

集合

  1. 说说你了解的集合
  • 集合从大的方向分有两个,一是Collection集合,二是Map集合。
  • Collection集合下有List、Set、Queue。Map集合下有HashMap、LinkedHashMap、TreeMap、HashTable、ConcurrentHashMap。
  • List集合下有ArrayList、LinkedList、Vector、CopyOnWriteArrayList。Set集合下有HashSet、LinkedHashSet、TreeSet、CopyOnWriteArraySet
  1. 说说对ArrayList的理解
  • ArrayList是List集合的一个实现类,其底层实现是数组transient Object[] elementData;,数组的查询是直接通过索引,查询速度比较快,时间复杂度是O(1),增删的话,根据增删的位置,时间复杂度有所不同,如果是中间第i个位置,时间复杂度就是O(n-i),简单理解其时间复杂度是O(n)
  • 扩容机制:当构造方法中没有指定数组的大小时,其默认初始容量是10。当超过这个默认值的时候,一定有一个扩容机制,其扩容机制是,当集合中元素的个数大于集合容量的时候,也就是add的时候集合放不下了,就会触发扩容机制,扩容后的新集合容量是旧集合容量的1.5倍,源码:int newCapacity = oldCapacity + (oldCapacity >> 1);。
  • 线程问题:ArrayList是线程不安全的,在add()方法的时候,首先会检查一下数组的容量是否够用,如果够用,那么就会执行elementData[size++] = e;方法,该语句执行了两大步,第一大步是,将e放到elementData缓冲区,第二大步是,将size的大小进行加1操作,也就是说,这个操作并非原子性操作。当在并发的情况时,就会出现问题。
  1. 说说对LinkedList的理解
  • LinkedList也是List的一个实现类,其底层是双向链表,其内部有一个next指针指向下一个节点,一个prev指针,指向上一个节点,由于是链表的数据结构,所以在查询的时候相对就比较慢了,时间复杂度是O(n),因为当我们需要查询某个元素的时候,需要从第一个节点开始遍历,直到查询结束。而他的增删就比较快了,如果增加一个N节点,直接将后一个节点的prev指向N节点,N节点的next指向后一个节点,前一个节点的next指向N节点,N节点的prev指针指向前一个节点即可,时间复杂度为O(1),空间复杂度一般比ArrayList大,因为每个节点都要存储两个指针。
  • 线程问题:LinkedList也是线程不安全的,其添加元素的操作,通过linkLast方法在尾部进行添加的,添加完之后,并把size的大小加1。其他的不说,单单一个size++就不是原子性了。简单的a加1操作会执行三步:1:把a的值加载到内存、2:将内存中的值,存储到变量中、3:然后进行加1操作。
  1. 说说对Vector的理解
    Vector也是List的一个实现类,其底层也是一个数组protected Object[] elementData;,底层ArrayList差不多,也就是加了synchronized的ArrayList,线程是安全的,效率没有ArrayList高,一般不建议使用。
  2. 如果不使用Vector来解决ArrayList的线程安全问题,还有其他的解决方案吗?
  • 既然不建议使用Vector,还有一个包java.util.concurrent(JUC)包,它下面有一个类CopyOnWriteArrayList也是线程安全的。CopyOnWriteArrayList也是List的一个实现类。
  • add方法用Lock锁来解决并发问题,其中在进行添加数据的时候,用了copyOf方法,也就是复制了一份,然后再set进去。
  • CopyOnWriteArrayList底层也是用的数组,但是它的数组是用volatile修饰了,主要是保证了数据的可见性。get操作时,并没有加锁,因为volatile保证了数据的可见性,当数据被修改的时候,读操作能立刻知道。
  1. 说说List和Set的区别
  • List的存储顺序是按照存入的顺序来的,而Set是根据哈希值来的
  • List可以存储相同的元素,Set不可以存储相同的元素
  1. 说说对HashSet的理解
  • HashSet是Set集合的一个实现类,其底层实现是HashMap的key,初始化容量是16,负载因子是0.75,扩容机制,是变为原来的2倍。
  • HashSet存储元素的顺序并不是按照存入时的顺序(和List不同)而是按照哈希值来存的所以取数据也是按照哈希值取得。元素的哈希值是通过元素的hashcode方法来获取的, HashSet首先判断两个元素的哈希值,如果哈希值一样,接着会比较equals方法 如果 equals结果为true ,HashSet就视为同一个元素。如果equals 为false就不是同一个元素。 哈希值相同equals为false的元素是怎么存储呢,就是在同样的哈希值下顺延(可以认为哈希值相同的元素放在一个哈希桶中)。也就是哈希一样的存一列。
  1. 说说对LinkedHashSet的理解
  • LinkedHashSet是对在HashSet的基础上维护了一个双向链表,使得LinkedHashSet存取有序。
  1. 说说对TreeSet的理解
  • TreeSet()是使用二叉树的原理对新add()的对象按照指定的顺序排序(升序、降序),每增加一个对象都会进行排序,将对象插入的二叉树指定的位置。
  1. 如果想要对HashSet进行线程安全处理,应该怎么办?
  • 可以通过CopyOnWriteArraySet来实现。
  1. queue是什么呢
  • queue是一个队列,先进先出(FIFO)的数据结构
  1. 聊聊对HashMap的理解(重要)
  • 底层是JDK1.7是通过数组+链表JDK1.8是通过数组+链表+红黑树组成。所有的数据都是通过一个Node节点进行封装,其中Node节点中封装了hash值,key,value,和next指针。hash是通过key计算出的hashCode值进行对数组容量减一求余得到的(官方的求余方式是通过&运算进行的)。不同的key计算出来的hash值可能相同,解决冲突是通过拉链法(链表和红黑树)进行处理。正是因为这种存储形势,所以HashMap的存取顺序是无序的。
  • 懒加载机制,在put值的时候会判断数组是否为空,如果是就初始化数组,而不是new的时候就初始化。
  • HashMap是Map的一个实现类,其默认初始化容量大小是16。扩容机制是根据扩容因子来扩容的,当容量的使用量达到总容量的0.75时,就会触发扩容,举例说就是,当总容量是16时,使用量达到12,就会触发扩容机制。
  • 当我们put一个值的时候,通过key来计算出hash值,计算出来的hash值做为数组的索引,Node节点中封装了hash值,key,value和next。当链表的长度小于8的时候,处理冲突的方式是链表。大于等于8的时候,就会触发红黑树方式存储。
  • 当元素个数小于等于6的时候,会触发红黑树转化为链表的形式,为什么不是小于等于7,是因为给一个过度,也就是防止添加一个刚好为8,删除一个刚好为7,这样来回转化。
  1. 说说对LinkedHashMap的理解
  • LinkedHashMap解决了HashMap不能保证存取顺序的问题。内部增加了一个链表用于维护元素存取顺序。
  1. 说说对TreeMap的理解
  • TreeMap实现SortedMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序。
  1. 如果想要保证HashMap的线程安全,应该怎么办?
  • 可以通过HashTable,该类的出现主要是解决了HashMap的线程安全问题,直接用了synchronized锁,所以效率上不高,不建议使用(发现JDK1.0的线程问题,解决都很暴力)。
  • ConcurrentHashMap是java.util.concurrent包下的,并发包下的。他就是对HashMap进行了一个扩展,也就是解决了他的线程安全问题。ConcurrentHashMap用了大量的CAS来进行优化。
  1. 什么是CAS呢?
  • CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术。简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值。
  1. Collection 和 Collections 有什么区别?
  • Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。
  • Collections则是集合类的一个工具类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。
  1. ArrayList和LinkedList 的区别是什么?
  • ArrayList底层的数据结构是数组,支持随机访问,而 LinkedList 的底层数据结构是双向循环链表,不支持随机访问。使用下标访问一个元素,ArrayList 的时间复杂度是 O(1),而 LinkedList 是 O(n)。
  1. ArrayList和Vector 的区别是什么?
  • Vector使用了synchronized来实现线程同步,是线程安全的,而ArrayList是非线程安全的。
  • Vector扩容每次会增加1倍,而ArrayList只会增加0.5倍。
  1. HashMap和HashTable的区别?
  • HashMap允许空键值,HashTable不允许。
  • HashMap线程不安全(效率高),HashTable线程安全。
  1. 总结一下HashMap和Hashtable的区别:
  • HashMap是Hashtable的轻量级实现,它们都实现了Map接口,主要区别在于HashMap允许空(null)键值(key),而Hashtable不允许。
  • HashMap没有contains方法,而是containsValue和containsKey。
  • Hashtable的方法是线程安全的,而HashMap不是线程安全的。
  • HashMap使用Iterator,Hashtable使用Enumeration。
  • HashMap和Hashtable采用的hash/rehash算法都几乎一样,所以性能不会有很大的差异。
  • 在Hashtable中,hash数组默认大小是11,增加的方式是old*2+1。在Has和Map中,hash数组的默认大小是16,而且一定是2的倍数。
  • hash值的使用不同,Hashtable直接使用对象的hashCode。
  1. 集合(Set)和数组(Array)的区别
    数据类型不同:数组是由一组相同数据类型的元素组成,而集合是由一组唯一的元素组成,这些元素可以是不同类型的。
    存储方式不同:数组是使用连续的内存地址存储,所以访问元素时可以通过下标实现快速访问。而集合存储元素的方式与具体实现有关,一般是使用哈希表或二叉树的方式,所以访问元素的效率与数据量成正比。
    增删操作效率不同:数组的插入和删除操作不如集合高效,因为当需要插入或删除一个元素时,数组需要移动其他元素来填补空隙,而集合通常只需要修改相关指针或哈希值即可。
    元素唯一性不同:集合中的元素是唯一的,不能重复,而数组中可以存在相同的元素。
    功能不同:集合一般提供集合运算操作(求并集、交集等),查找、插入、删除元素等操作,而数组主要用于存储一组相同类型的元素,提供访问、插入、删除等操作。

  2. HashMap
    Java中的HashMap是一种基于哈希表的数据结构,它实现了Map接口,提供了键(Key)和值(Value) 的映射关系。在HashMap中,键和值都可以为null。
    在HashMap内部,它的核心是一个Entry数组,每个Entry对应一个键值对。每个Entry包含四个域,分别是:

  3. int类型的hash值,用于快速定位到这个键值对所在的位置。

  4. K类型的Key,用来唯一标识这个键值对。

  5. V类型的Value,表示键所对应的值。

  6. 一个指向下一个Entry的引用,因为在HashMap中同一个桶可能会存放多个元素,这些元素都存在一个链表中,使用next指针指向下一个Entry。
    在使用HashMap时,当我们插入一个键值对时,首先需要根据Key的hash值计算得到这个键值对在数组中的位置,然后在这个位置上查找是否已经存在同样的Key,若存在则用新的Value代替旧的Value,若不存在则新创建一个Entry并插入到对应的数组位置。在查询一个Value时,也是先计算Key的hash值,然后根据hash值定位到相应的位置,再依次遍历对应链表上的每个Entry,直到找到对应的Key为止。
    HashMap在插入、查询等操作时具备较高的性能,但当哈希算法设计不够合理或者哈希冲突发生概率较高时,性能也会受到影响。同时,在多线程环境下,需要使用ConcurrentHashMap等线程安全的Map实现来避免并发导致的问题。

  7. HashMap的put方法:

  8. 对Key进行Hash运算,得到它的HashCode。如果Key为null,则HashCode为0。

  9. 将HashCode通过Hash算法计算得到它在Entry数组中的位置(注意:这个位置可能已经有其它的元素了)。

  10. 遍历相同HashCode的元素,查看它们是否与当前要添加的Key相等。

  11. 如果想等,则覆盖新的Value,否则将新元素添加到链表中的头部。

  12. 如果链表长度达到8,需要将链表转换为红黑树,以提高查找效率。

  13. 如果HashMap中的元素个数size超过了负载因子(load factor)阈值(threshold),则需要进行扩容,来保证HashMap的效率。
    整个流程中,最耗时的操作其实是第3步——查找相同HashCode的元素,这个步骤的时间复杂度在最坏情况下甚至会达到O(N)(N为链表长度),所以保证hashCode的分布均匀性非常重要,只有hashCode的分布均匀,才能够保证相同HashCode的元素数量足够小。因此在设计hashCode时需要考虑到这一点。

  14. HashMap的扩容机制
    1.7版本
    先生成新数组
    遍历老数组中的每个位置上的链表上的元素
    获取每个元素的key,并基于新数组长度,计算出每个元素在新数组中的下标
    将元素添加到新数组中去
    所有元素转移完了之后,将新数组赋值给HashMap对象的table属性

1.8版本
先生成新数组
遍历老数组中的每个位置上的链表或红黑树
如果是链表,则直接将链表中的每个元素重新计算下标,并添加到新数组中去
如果是红黑树,则先遍历红黑树,先计算出红黑树中每个元素对应在新数组中的下标位置

  • 统计每个下标位置的元素个数
  • 如果该位置下的元素个数超过了8,则生成一个新的红黑树,并将根节点的添加到新数组的对应位置
  • 如果该位置下的元素个数没有超过8,则生成一个链表,并将链表的头节点添加到新数组的对应位置
    所有元素转移完了之后,将数组赋值给HashMap对象的table属性。
  1. ConcurrentHashMap的扩容机制
    1.7版本
    ConcurrentHashMap是基于segment分段实现的
    每个segment相对于一个小型的HashMap
    每个segment内部会进行扩容,和HashMap的扩容逻辑类似
    先生成新的数组,然后转移元素到新数组中
    扩容的判断也是每个segment内部单独判断的,判断是否超过阈值

1.8版本
ConcurrentHashMap是不在基于segment实现的
当某个线程进行put时,如果发现ConcurrentHashMap正在进行扩容那么该线程一起进行扩容
如果某个线程put时,发现没有正在进行扩容,则将key-value添加到ConcurrentHashMap中,然后判断是否超过阈值,超过了则进行扩容
ConcurrentHashMap是支持多个线程同时扩容的
扩容之前也先生成一个新的数组
在转移元素时,先将原数组分组,将每组分给不同的线程来进行元素转移工作

  1. map:HashMap、TreeMap和HashTable
    线程安全
    HshaMap线程不安全
    TreeMap线程不安全
    HashTable线程安全
    空值
    HashMap一个null key,多个null value
    TreeMap不能null key,多个null value
    HashTable都不能有null
    继承和接口
    HashMap继承AbstractMap,实现接口Map
    TreeMap继承AbstractMap,实现接口NavigableMap(SortMap的一种)
    HashTable继承Dictionary,实现接口Map
    顺序
    HashMap中key是无序的
    TreeMap是有序的
    HashTable是无序的
    构造函数
    HashMap有调优初始容量和负载因子
    TreeMap没有
    HashTable有
    数据结构
    HashMap是链表+数组+红黑树
    TreeMap是红黑树
    HashTable是链表+数组
  • 24
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏日一凉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值