java常见面试题

面试题:
(基础题)

  • 静态变量有什么作用?
    静态变量可以被类的所有实例共享。无论一个类创建了多少个对象,它们都共享同一份静态变量。
    通常情况下,静态变量会被 final 关键字修饰成为常量。

  • 静态方法为什么不能调用非静态成员?
    这个需要结合 JVM 的相关知识,主要原因如下:
    静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访 问。而非静态成员属于实例对象,只有在对象实例化之后才存在,需要通过类的实例对象去访问。
    在类的非静态成员不存在的时候静态成员就已经存在了,此时调用在内存中还不存在的非静态成员,属于非法操作。

  • 对象的相等和引用相等的区别?

​ 对象的相等一般比较的是内存中存放的内容是否相等。
​ 引用相等一般比较的是他们指向的内存地址是否相等。

  • 深拷贝和浅拷贝区别了解吗?什么是引用拷贝?

​ 浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如 果原对象内部的属性是引用类型的话,
浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。
​ 深拷贝 :深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。

  • Java 9 为何要将 String 的底层实现由 char[] 改成了 byte[] ?

​ 新版的 String 其实支持两个编码方案: Latin-1 和 UTF-16。如果字符串中包含的汉字没有超过 Latin-1 可表示范围内的字符,
那就会使用 Latin-1 作为编码方案。Latin-1 编码方案下,byte 占一个字节(8 位),char 占用 2 个字节(16),byte 相较 char 节省一半的内存空间。

  • 字符串拼接用“+” 还是 StringBuilder?
    可以看出,字符串对象通过“+”的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。
    不过,在循环内使用“+”进行字符串的拼接的话,存在比较明显的缺陷:编译器不会创建单个 StringBuilder 以复用,会导致创建过多的 StringBuilder 对象。
    如果直接使用 StringBuilder 对象进行字符串拼接的话,就不会存在这个问题了。

  • 字符串常量池的作用了解吗?
    字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。

  • 动态代理
    Spring 5.x 中 AOP 默认依旧使用 JDK 动态代理。
    SpringBoot 2.x 开始,为了解决使用 JDK 动态代理可能导致的类型转化异常而默认使用 CGLIB。
    在 SpringBoot 2.x 中,如果需要默认使用 JDK 动态代理可以通过配置项spring.aop.proxy-target-class=false来进行修改,proxyTargetClass配置已无效。

  • new String(“abc”)到底创建了几个对象
    如果 abc 这个字符串常量不存在,则创建两个对象,分别是 abc 这个字符串常量, 以及 new String 这个实例对象。 如果 abc 这字符串常量存在,则只会创建一个对象

  • 自动装箱与拆箱
    对象相等比较
    public static void main(String[] args) {
    Integer a = 1000;
    Integer b = 1000;
    Integer c = 100;
    Integer d = 100;
    System.out.println("a == b is " + (a == b));
    System.out.println(("c == d is " + (c == d)));
    }
    输出结果:
    a == b is false
    c == d is true
    在 Java 5 中,在 Integer 的操作上引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。

    适用于整数值区间-128 至 +127。

    只适用于自动装箱。使用构造函数创建对象不适用。

  • 说说 List, Set, Queue, Map 四者的区别?
    List(对付顺序的好帮手): 存储的元素是有序的、可重复的。
    Set(注重独一无二的性质): 存储的元素是无序的、不可重复的。
    Queue(实现排队功能的叫号机): 按特定的排队规则来确定先后顺序,存储的元素是有序的、可重复的。
    Map(用 key 来搜索的专家): 使用键值对(key-value)存储,类似于数学上的函数 y=f(x),“x” 代表 key,“y” 代表 value,key 是无序的、不可重复的,
    value 是无序的、可重复的,每个键最多映射到一个值。

  • ArrayList自动扩容?
    每当向数组中添加元素时,都要去检查添加后元素的个数是否会超出当前数组的长度,如果超出,数组将会进行扩容,以满足添加数据的需求。
    数组扩容通过ensureCapacity(int minCapacity)方法来实现。在实际添加大量元素前,我也可以使用ensureCapacity来手动增加ArrayList实例的容量,以减少递增式再分配的数量。
    数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的1.5倍。这种操作的代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。
    当我们可预知要保存的元素的多少时,要在构造ArrayList实例时,就指定其容量,以避免数组扩容的发生。或者根据实际需求,通过调用ensureCapacity方法来手动增加ArrayList实例的容量。

  • ArrayList的Fail-Fast机制?
    ArrayList也采用了快速失败的机制,通过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险
    替换为
    private static List list = new CopyOnWriteArrayList();

  • ArrayList 与 LinkedList 区别?
    是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
    底层数据结构: ArrayList 底层使用的是 Object 数组;LinkedList 底层使用的是 双向链表 数据结构
    是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。
    ArrayList 实现了 RandomAccess 接口,就表明了他具有快速随机访问功能

  • 比较 HashSet、LinkedHashSet 和 TreeSet 三者的异同
    HashSet、LinkedHashSet 和 TreeSet 都是 Set 接口的实现类,都能保证元素唯一,并且都不是线程安全的。
    HashSet、LinkedHashSet 和 TreeSet 的主要区别在于底层数据结构不同。HashSet 的底层数据结构是哈希表(基于 HashMap 实现)。
    LinkedHashSet 的底层数据结构是链表和哈希表,元素的插入和取出顺序满足 FIFO。TreeSet 底层数据结构是红黑树,元素是有序的,排序的方式有自然排序和定制排序。
    底层数据结构不同又导致这三者的应用场景不同。HashSet 用于不需要保证元素插入和取出顺序的场景,
    LinkedHashSet 用于保证元素的插入和取出顺序满足 FIFO 的场景,TreeSet 用于支持对元素自定义排序规则的场景。

  • HashMap:
    相比于之前的版本, JDK1.8 之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)
    (将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。

  • HashMap的遍历:
    EntrySet 之所以比 KeySet 的性能高是因为,KeySet 在循环时使用了 map.get(key),而 map.get(key) 相当于又遍历了一遍 Map 集合去查询 key 所对应的值。
    为什么要用“又”这个词?那是因为在使用迭代器或者 for 循环时,其实已经遍历了一遍 Map 集合了,因此再使用 map.get(key) 查询时,相当于遍历了两遍。
    而 EntrySet 只遍历了一遍 Map 集合,之后通过代码“Entry<Integer, String> entry = iterator.next()”把对象的 key 和 value 值都放入到了 Entry 对象中,
    因此再获取 key 和 value 值时就无需再遍历 Map 集合,只需要从 Entry 对象中取值就可以了。
    所以,EntrySet 的性能比 KeySet 的性能高出了一倍,因为 KeySet 相当于循环了两遍 Map 集合,而 EntrySet 只循环了一遍

  • HashMap 和 Hashtable 的区别
    (相比于HashMap来说 TreeMap 主要多了对集合中的元素根据键排序的能力以及对集合内元素的搜索的能力)
    线程是否安全: HashMap 是非线程安全的,Hashtable 是线程安全的,因为 Hashtable 内部的方法基本都经过synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!);
    效率: 因为线程安全的问题,HashMap 要比 Hashtable 效率高一点。另外,Hashtable 基本被淘汰,不要在代码中使用它;
    对 Null key 和 Null value 的支持: HashMap 可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个;
    Hashtable 不允许有 null 键和 null 值,否则会抛出 NullPointerException。
    初始容量大小和每次扩充容量大小的不同 : ① 创建时如果不指定容量初始值,Hashtable 默认的初始大小为 11,之后每次扩充,容量变为原来的 2n+1。HashMap 默认的初始化大小为 16。 之后每次扩充,容量变为原来的 2 倍。② 创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小, 而 HashMap 会将其扩充为 2 的幂次方大小(HashMap 中的tableSizeFor()方法保证,下面给出了源代码)。也就是说 HashMap 总是使用 2 的幂作为哈希表的大小,后面会介绍到为什么是 2 的幂次方。
    底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时, 将链表转化为红黑树(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树), 以减少搜索时间(后文中我会结合源码对这一过程进行分析)。Hashtable 没有这样的机制。

  • HashMap 的长度为什么是 2 的幂次方
    得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算方法是“ (n - 1) & hash”。(n 代表数组长度)。这也就解释了 HashMap 的长度为什么是 2 的幂次方

  • HashMap 中的 hash 方法为什么要右移 16 位异或?
    之所以要对 hashCode 无符号右移 16 位并且异或,核心目的是为了让 hash 值 的散列度更高,尽可能减少 hash 表的 hash 冲突,从而提升数据查找的性能。

  • HashMap 是怎么解决哈希冲突的?
    链式寻址法,简单理解就是把存在 hash 冲突的 key, 以单向链表的方式来存储,比如 HashMap 就是采用链式寻址法来实现的。 向这样一种情况,存在冲突的 key 直接以单向链表的方式进行存储。
    再 hash 法,就是当通过某个 hash 函数计算的 key 存在冲突时,再用另外一个 hash 函数对这个 key 做 hash,一直运算直到不再产生冲突。这种方式会增加计 算时间,性能影响较大。
    建立公共溢出区,就是把 hash 表分为基本表和溢出表两个部分,凡事存在冲突 的元素,一律放入到溢出表中。 HashMap 在 JDK1.8 版本中,通过链式寻址法+红黑树的方式来解决 hash 冲突 问题,
    其中红黑树是为了优化 Hash 表链表过长导致时间复杂度增加的问题。当 链表长度大于 8 并且 hash 表的容量大于 64 的时候,再向链表中添加元素就会 触发转化.

  • List:
    List myList = Arrays.asList(1, 2, 3);
    myList.add(4);//运行时报错:UnsupportedOperationException
    myList.remove(1);//运行时报错:UnsupportedOperationException
    myList.clear();//运行时报错:UnsupportedOperationException
    Arrays.asList() 方法返回的并不是 java.util.ArrayList ,而是 java.util.Arrays 的一个内部类,这个内部类并没有实现集合的修改方法或者说并没有重写这些方法。

  • CopyOnWriteArrayList有何缺陷,说说其应用场景?
    CopyOnWriteArrayList 有几个缺点: 由于写操作的时候,需要拷贝数组,会消耗内存,如果原数组的内容比较多的情况下,可能导致young gc或者full gc 不能用于实时读的场景, 像拷贝数组、新增元素都需要时间,所以调用一个set操作后,读取到数据可能还是旧的,虽然CopyOnWriteArrayList 能做到最终一致性,
    但是还是没法满足实时性要求;

  • 为什么HashTable慢? 它的并发度是什么? 那么ConcurrentHashMap并发度是什么?
    Hashtable之所以效率低下主要是因为其实现使用了synchronized关键字对put等操作进行加锁,而synchronized关键字加锁是对整个对象进行加锁,也就是说在进行put等修改Hash表的操作时,
    锁住了整个Hash表,从而使得其表现的效率低下。
    ConcurrentHashMap在JDK1.7和JDK1.8中实现有什么差别? JDK1.8解決了JDK1.7中什么问题
    HashTable : 使用了synchronized关键字对put等操作进行加锁;
    ConcurrentHashMap JDK1.7: 使用分段锁机制实现;
    ConcurrentHashMap JDK1.8: 则使用数组+链表+红黑树数据结构和CAS原子操作实现;

  • 如何理解Java中的泛型是伪泛型?
    泛型中类型擦除 Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了“伪泛型”的策略,
    即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),
    就像完全没有泛型一样。

  • IO流:
    一般我们是不会直接单独使用 FileInputStream ,通常会配合 BufferedInputStream(字节缓冲输入流,后文会讲到)来使用。
    像下面这段代码在我们的项目中就比较常见,我们通过 readAllBytes() 读取输入流所有字节并将其直接赋值给一个 String 对象。
    // 新建一个 BufferedInputStream 对象
    BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(“input.txt”));
    // 读取文件的内容并复制到 String 对象中
    String result = new String(bufferedInputStream.readAllBytes());
    System.out.println(result);

  • 字节缓冲流
    IO 操作是很消耗性能的,缓冲流将数据加载至缓冲区,一次性读取/写入多个字节,从而避免频繁的 IO 操作,提高流的传输效率。
    字节缓冲流这里采用了装饰器模式来增强 InputStream 和OutputStream子类对象的功能。
    举个例子,我们可以通过 BufferedInputStream(字节缓冲输入流)来增强 FileInputStream 的功能。

  • 装饰器模式
    装饰器(Decorator)模式 可以在不改变原有对象的情况下拓展其功能。
    装饰器模式通过组合替代继承来扩展原始类的功能,在一些继承关系比较复杂的场景(IO 这一场景各种类的继承关系就比较复杂)更加实用。
    举个例子,我们可以通过 BufferedInputStream(字节缓冲输入流)来增强 FileInputStream 的功能。

  • 适配器模式
    适配器(Adapter Pattern)模式 主要用于接口互不兼容的类的协调工作,你可以将其联想到我们日常经常使用的电源适配器。
    IO 流中的字符流和字节流的接口不同,它们之间可以协调工作就是基于适配器模式来做的,更准确点来说是对象适配器。
    通过适配器,我们可以将字节流对象适配成一个字符流对象,这样我们可以直接通过字节流对象来读取或者写入字符数据。
    InputStreamReader 和 OutputStreamWriter 就是两个适配器(Adapter), 同时,它们两个也是字节流和字符流之间的桥梁。
    InputStreamReader 使用 StreamDecoder (流解码器)对字节进行解码,实现字节流到字符流的转换,
    OutputStreamWriter 使用StreamEncoder(流编码器)对字符进行编码,实现字符流到字节流的转换。

  • volatile关键字的作用是什么? 防重排序
    我们从一个最经典的例子来分析重排序问题。大家应该都很熟悉单例模式的实现,而在并发环境下的单例实现方式,我们通常可以采用双重检查加锁(DCL)的方式来实现。

  • 为什么要用线程池?
    池化技术想必大家已经屡见不鲜了,线程池、数据库连接池、Http 连接池等等都是对这个思想的应用。池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。
    降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
    提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
    提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

  • Synchronized由什么样的缺陷?
    Java Lock是怎么弥补这些缺陷的? synchronized的缺陷 效率低:锁的释放情况少,只有代码执行完毕或者异常结束才会释放锁;试图获取锁的时候不能设定超时,不能中断一个正在使用锁的线程,
    相对而言,Lock可以中断和设置超时 不够灵活:加锁和释放的时机单一,每个锁仅有一个单一的条件(某个对象),相对而言,读写锁更加灵活 无法知道是否成功获得锁,相对而言,
    Lock可以拿到状态 Lock解决相应问题 Lock类这里不做过多解释,主要看里面的4个方法: lock(): 加锁 unlock(): 解锁 tryLock(): 尝试获取锁,返回一个boolean值 tryLock(long,TimeUtil): 尝
    试获取锁,可以设置超时 Synchronized只有锁只与一个条件(是否获取锁)相关联,不灵活,后来Condition与Lock的结合解决了这个问题。

  • 实现 Runnable 接口和 Callable 接口的区别
    Runnable 接口 不会返回结果或抛出检查异常,但是 Callable 接口 可以。所以,如果任务不需要返回结果或抛出异常推荐使用 Runnable 接口 ,这样代码看起来会更加简洁。

  • 执行 execute()方法和 submit()方法的区别是什么呢?
    execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;
    submit()方法用于提交需要返回值的任务。线程池会返回一个 Future 类型的对象,通过这个 Future 对象可以判断任务是否执行成功,并且可以通过 Future 的 get()方法来获取返回值,
    get()方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。

  • 线程池参数:
    ThreadPoolExecutor 3 个最重要的参数:
    corePoolSize : 核心线程数定义了最小可以同时运行的线程数量。(java核心线程池的回收由allowCoreThreadTimeOut参数控制,默认为false,若开启为true)
    maximumPoolSize : 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。
    workQueue: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。

    ThreadPoolExecutor其他常见参数:
    keepAliveTime:当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁;
    unit : keepAliveTime 参数的时间单位。
    threadFactory :executor 创建新线程的时候会用到。
    handler : AbortPolicy, 默认
    该策略是线程池的默认策略。使用该策略时,如果线程池队列满了丢掉这个任务并且抛出RejectedExecutionException异常。 源码如下:
    DiscardPolicy
    这个策略和AbortPolicy的slient版本,如果线程池队列满了,会直接丢掉这个任务并且不会有任何异常。 源码如下:
    DiscardOldestPolicy 这个策略从字面上也很好理解,丢弃最老的。也就是说如果队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列。
    因为队列是队尾进,队头出,所以队头元素是最老的,因此每次都是移除对头元素后再尝试入队。 源码如下
    CallerRunsPolicy
    使用此策略,如果添加到线程池失败,那么主线程会自己去执行该任务,不会等待线程池中的线程去执行。就像是个急脾气的人,我等不到别人来做这件事就干脆自己干。 源码如下:


​ 问题一:线程池被创建后里面有线程吗?如果没有的话,你知道有什么方法对线程池进行预热吗?
​ 线程池被创建后如果没有任务过来,里面是不会有线程的。如果需要预热的话可以调用下面的两个方法:

  • ThreadLocal 是什么?它的实现原理呢?
    ThreadLocal 是一种线程隔离机制,它提供了多线程环境下对于共享变量访问的 安全性。 在多线程访问共享变量的场景中,一般的解决办法是对共享变量加锁, 从而保证 在同一时刻只有一个线程能够对共享变量进行更新,并且基于 Happens-Before 规则里面的监视器锁规则,又保证了数据修改后对其他线程的可见性。但是加锁会带来性能的下降,所以 ThreadLocal 用了一种空间换时间的设计思想, 也就是说在每个线程里面,都有一个容器来存储共享变量的副本,然后每个线程 只对自己的变量副本来做更新操作,这样既解决了线程安全问题,又避免了多线 程竞争加锁的开销。
    ThreadLocal 的 具 体 实 现 原 理 是 , 在 Thread 类 里 面 有 一 个 成 员 变 量 ThreadLocalMap,它专门来存储当前线程的共享变量副本,后续这个线程对于 共享变量的操作,都是从这个 ThreadLocalMap 里面进行变更,不会影响全局共 享变量的值.

  • 什么是守护线程,它有什么特点
    守护线程,它是一种专门为用户线程提供服务的线程,它的生命周期依赖于用户 线程。 只有 JVM 中仍然还存在用户线程正在运行的情况下,守护线程才会有存在的意 义。否则,一旦 JVM 进程结束,
    那守护线程也会随之结束。 也就是说,守护线程不会阻止 JVM 的退出。但是用户线程会!守护线程和用户线程的创建方式是完全相同的,我们只需要调用用户线程里面的 setDaemon 方法并且设置成 true,
    就表示这个线程是守护线程。

  • 如何判断是 CPU 密集任务还是 IO 密集任务?
    CPU 密集型简单理解就是利用 CPU 计算能力的任务比如你在内存中对大量数据进行排序。
    但凡涉及到网络读取,文件读取这类都是 IO 密集型,这类任务的特点是 CPU 计算耗费时间相比于等待 IO 操作完成的时间来说很少,大部分时间都花在了等待 IO 操作完成上。

  • IO 和 NIO 的区别
    站在网络 IO 的视角来说,前者是阻塞 IO, 后者是非阻塞 IO。
    NIO主要用到的是块,所以NIO的效率要比IO高很多
    IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,因为它们没有被缓存在任何地方,所以,它不能前后移动流中的数据。
    如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。
    这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。

  • Java 常见并发容器总结
    JDK 提供的这些容器大部分在 java.util.concurrent 包中。
    ConcurrentHashMap : 线程安全的 HashMap
    CopyOnWriteArrayList : 线程安全的 List,在读多写少的场合性能非常好,远远好于 Vector。
    ConcurrentLinkedQueue : 高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList,这是一个非阻塞队列。
    BlockingQueue : 这是一个接口,JDK 内部通过链表、数组等方式实现了这个接口。表示阻塞队列,非常适合用于作为数据共享的通道。
    ConcurrentSkipListMap : 跳表的实现。这是一个 Map,使用跳表的数据结构进行快速查找。

  • Java8 新特性实战
    新 interface 的方法可以用default 或 static修饰,这样就可以有方法体,实现类也不必重写此方法。
    functional interface 函数式接口
    Optional:建议使用 Optional 解决 NPE(java.lang.NullPointerException)问题,它就是为 NPE 而生的,其中可以包含空值或非空值。
    Lambda 表达式
    Java 8 允许使用 :: 关键字来传递方法或者构造函数引用,无论如何,表达式返回的类型必须是 functional-interface。
    Stream
    java.time 主要类
    java.util.Date 既包含日期又包含时间,而 java.time 把它们进行了分离
    LocalDateTime.class //日期+时间 format: yyyy-MM-ddTHH:mm:ss.SSS
    LocalDate.class //日期 format: yyyy-MM-dd
    LocalTime.class //时间 format: HH:mm:ss

  • Mysql(数据库)
    作者建议当存储IPv4地址时,应该使用32位的无符号整数(UNSIGNED INT)来存储IP地址,而不是使用字符串。

  • SQL优化手段有哪些
    1、查询语句中不要使用select *
    2、尽量减少子查询,使用关联查询(left join,right join,inner join)替代
    3、减少使用IN或者NOT IN ,使用exists,not exists或者关联查询语句替代
    4、or 的查询尽量用 union或者union all 代替(在确认没有重复数据或者不用剔除重复数据时,
    union all会更好) 5、应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
    6、应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表
    扫描,如: select id from t where num is null 可以在num上设置默认值0,确保表中num列没有
    null值,然后这样查询: select id from t where num=0

    int(1) 和 int(10) 有什么区别?
    int后面的数字不能表示字段的长度,int(num)一般加上zerofill,才有效果。

  • 面试官问:select…for update会锁表还是锁行?
    如果查询条件用了索引/主键,那么select … for update就会进行行锁。
    如果是普通字段(没有索引/主键),那么select … for update就会进行锁表。

  • 数据库范式了解吗?
    – 1NF, 第一范式
    字段不能再分,就满足第一范式。
    – 2NF, 第二范式
    满足第一范式的前提下,不能出现部分依赖。
    消除复合主键就可以避免部分依赖。增加单列关键字。
    – 3NF, 第三范式
    满足第二范式的前提下,不能出现传递依赖。
    某个字段依赖于主键,而有其他字段依赖于该字段。这就是传递依赖。
    将一个实体信息的数据放在一个表内实现。

  • 存储引擎
    MySQL 5.5.5 之前,MyISAM 是 MySQL 的默认存储引擎。5.5.5 版本之后,InnoDB 是 MySQL 的默认存储引擎。

  • MySQL 存储引擎架构了解吗?
    MySQL 存储引擎采用的是插件式架构,支持多种存储引擎,我们甚至可以为不同的数据库表设置不同的存储引擎以适应不同场景的需要。存储引擎是基于表的,而不是数据库。

  • MyISAM 和 InnoDB 的区别是什么?
    MyISAM 只有表级锁(table-level locking),而 InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁。而且最大的缺陷就是崩溃后无法安全恢复。MyISAM 不提供事务支持。InnoDB 提供事务支持,实现了 SQL 标准定义了四个隔离级别,具有提交(commit)和回滚(rollback)事务的能力。并且,InnoDB 默认使用的 REPEATABLE-READ(可重读)隔离级别是可以解决幻读问题发生的(基于 MVCC 和 Next-Key Lock)
    MyISAM 不支持外键,而 InnoDB 支持外键。
    MyISAM 不支持数据库异常崩溃后的安全恢复,而 InnoDB 支持。使用 InnoDB 的数据库在异常崩溃后,数据库重新启动的时候会保证数据库恢复到崩溃前的状态。这个恢复的过程依赖于 redo log
    MyISAM 不支持MVCC,而 InnoDB 支持。
    InnoDB 引擎中,其数据文件本身就是索引文件。相比 MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按 B+Tree 组织的一个索引结构,树的叶节点 data 域保存了完整的数据记录。

  • ACID 特性:
    原子性(Atomicity) : 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
    一致性(Consistency): 执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;
    隔离性(Isolation): 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
    持久性(Durabilily): 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

  • 并发事务带来了哪些问题?
    脏读(Dirty read): 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
    脏写(Lost to modify): 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务 1 读取某表中的数据 A=20,事务 2 也读取 A=20,事务 1 修改 A=A-1,事务 2 也修改 A=A-1,最终结果 A=19,事务 1 的修改被丢失
    不可重复读(Unrepeatable read): 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间, 由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
    幻读(Phantom read): 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中, 第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

  • 行级锁的使用有什么注意事项?
    InnoDB 的行锁是针对索引字段加的锁,表级锁是针对非索引字段加的锁。当我们执行 UPDATE、DELETE 语句时,如果 WHERE条件中字段没有命中唯一索引或者索引失效的话,
    就会导致扫描全表对表中的所有行记录进行加锁。这个在我们日常工作开发中经常会遇到,一定要多多注意!!!

  • 共享锁和排他锁呢
    不论是表级锁还是行级锁,都存在共享锁(Share Lock,S 锁)和排他锁(Exclusive Lock,X 锁)这两类:
    共享锁(S 锁) :又称读锁,事务在读取记录的时候获取共享锁,允许多个事务同时获取(锁兼容)。
    排他锁(X 锁) :又称写锁/独占锁,事务在修改记录的时候获取排他锁,不允许多个事务同时获取。如果一个记录已经被加了排他锁,那其他事务不能再对这条事务加任何类型的锁(锁不兼容)。
    排他锁与任何的锁都不兼容,共享锁仅和共享锁兼容。

    由于 MVCC 的存在,对于一般的 SELECT 语句,InnoDB 不会加任何锁。不过, 你可以通过以下语句显式加共享锁或排他锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

~有思想的码农

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

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

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

打赏作者

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

抵扣说明:

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

余额充值