高频面试题-JDK集合源码篇(String,ArrayList)

简介: 都是List的子集合,LinkedList继承与Dqueue双端队列,看名字就能看出来前者是基于数组实现,底层采用Object[]存储元素,数组中的元素要求内存分配连续,可以使用索引进行访问,它的优势是随机访问快,但是由于要保证内存的连续性,如果删除了元素,或者从中间位置增加了元素,会设计到元素移位的操作,所以增删比较慢。

基础面试题

1.String 和 StringBuilder 和 StringBuffer

  • String 和 StringBuilder 和 StringBuffer的区别
    区别就是String是不可变的,每次创建一个字符串,不管是用New的方式,还是使用“”引号声明,亦或是“”+“”拼接字符串都会在内存中开辟新的内存空间。而后2者都是可变的它们都继承与AbstractStringBuilder 。在JDK8及以前,他们都是使用char[] 存储数据,在JDK9往后为了节约内存采用了byte[]存储数据。
  • String为什么不可变StringBuilder为什么可变
    因为String底层的 char[] 前面加了一个final 修饰符,我们知道加了final修饰的变量是不可被重新赋值的,所以它是不可变的,来看一下源码
    String源码 : char数组前面有final
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
  • StringBuilder和StringBuffer源码
public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
    /** use serialVersionUID for interoperability */
    static final long serialVersionUID = 4383685877147921099L;
    /**
     * Constructs a string builder with no characters in it and an
     * initial capacity of 16 characters.
     */
    public StringBuilder() {
        super(16);  //初始容量16
    } 
    ...省略...
}
  • AbstractStringBuilder源码 : char数组前面没有final
abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     * 存储元素的char数组
     */
    char[] value;
    /**
     * The count is the number of characters used.
     */
    int count;
    /**
     * This no-arg constructor is necessary for serialization of subclasses.
     */
    AbstractStringBuilder() {
    }
    /**
     * Creates an AbstractStringBuilder of the specified capacity.
     */
    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }
  • StringBuilder为什么线程不安全StringBuffer为什么安全
    要安全就是要加锁,所以SpringBuffer暴露给外界的方法是加了synchronized同步锁的所以它安全,而StringBuilder并没有加,所以它不安全。这就导致了另外一个区别,就是加了锁安全,但是性能变低了,没加锁不安全,但是性能高。
    StringBuffer源码:比如append方法加了synchronized
@Override
public synchronized StringBuffer append(Object obj) {
    toStringCache = null;
    super.append(String.valueOf(obj));
    return this;
}
@Override
public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}
  • StringBuilder源码:append方法并没有加synchronized
@Override
public StringBuilder append(Object obj) {
    return append(String.valueOf(obj));
}
@Override
public StringBuilder append(String str) {
    super.append(str);
    return this;
}
  • 什么场景下使用StringBuilder什么场景使用StringBuffer
    因为StringBuilder和StringBuffer是可变的,所以如果要拼接字符串尽量用这两个家伙,由于StringBuidler是线程不安全的,所以在多线程环境中,要使用StringBuffer保证线程安全,如果不是在多线程情况下,使用StringBuilder就好了,性能更高。

2.ArrayList和LinkedList的区别

都是List的子集合,LinkedList继承与Dqueue双端队列,看名字就能看出来前者是基于数组实现,底层采用Object[]存储元素,数组中的元素要求内存分配连续,可以使用索引进行访问,它的优势是随机访问快,但是由于要保证内存的连续性,如果删除了元素,或者从中间位置增加了元素,会设计到元素移位的操作,所以增删比较慢

ArrayList源码如下:

public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
      {
        private static final long serialVersionUID = 8683452581122892189L;
        /**容量
         * Default initial capacity.
         */
        private static final int DEFAULT_CAPACITY = 10;
          //存储元素的对象数组
      transient Object[] elementData; // non-private to simplify nested class access
      //当前元素数量
       private int size;
      ...省略...
      //可手动指定初始容量
      public ArrayList(int initialCapacity) {
          if (initialCapacity > 0) {
              this.elementData = new Object[initialCapacity];
          } else if (initialCapacity == 0) {
              this.elementData = EMPTY_ELEMENTDATA;
          } else {
              throw new IllegalArgumentException("Illegal Capacity: "+
                                                 initialCapacity);
          }
      }

而LinkedList是基于双向链表的存储结构,链表结构不要求元素的内存分配连续性,不支持索引访问,但是它的每个元素持有下一个元素的指针,或持有上一个元素的指针,所以它的访问方式是从链表头一个一个往后面取元素,或者从链表尾一个一个往前面取元素(双向链表),它的缺点是查找元素慢,因为要从前往后一个一个遍历,如果是查找第一个和最后一个元素一次查找就拿到了。它的有点就是增删了元素只需要改变元素之间的指针指向即可,不涉及到移位操作,所以增删快

LinkedList源码如下:

public class LinkedList<E>
      extends AbstractSequentialList<E>
      implements List<E>, Deque<E>, Cloneable, java.io.Serializable
  {
      transient int size = 0;
      /**
       * Pointer to first node.
       * Invariant: (first == null && last == null) ||
       *            (first.prev == null && first.item != null)
       */
      transient Node<E> first;  //第一个节点
      /**
       * Pointer to last node.
       * Invariant: (first == null && last == null) ||
       *            (last.next == null && last.item != null)
       */
      transient Node<E> last; //最后一个节点
    //一个节点对象
    private static class Node<E> {
        E item; //存储节点数据
        Node<E> next; //下一个节点
        Node<E> prev; //上一个节点
        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

当然如果是ArrayList和LinkedList都进行遍历,其实性能也就差不多。

3.ArrayList的扩容机制

ArrayList默认初始容量为 10 也可以在 new ArrayList(容量) 指定容量大小,当存储新的元素的时候,ArrayList会判断容量来决定是否扩容, 新的容量 = 老的容量 + 老容量/2 , 成1.5倍扩容。其实就是以新的容量创建一个新的数组,然后把老的数组中的元素copy到新的数组中。

增加元素到ArrayList末尾

public boolean add(E e) {
  //判断是否需要扩容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //把新元素放到数组的 seize+1 位置
    elementData[size++] = e;
    return true;
}
//扩容方法
 private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        //数组拷贝操作
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

这就是扩容长度:int newCapacity = oldCapacity + (oldCapacity >> 1);新的容量 = 老的容量 + 老容量/2 , 而 elementData = Arrays.copyOf(elementData, newCapacity); 就是以新的容量创建一个新的数组,然后把老的数组中的元素copy到新的数组中。

4.ArrayList如何删除元素呢

删除元素和新增元素一样,都要涉及到移位操作,使用的都是数组拷贝的方式。

ArrayList#remove(index) 源码:

/**删除此列表中指定位置的元素。 将所有后续元素向左移动(从其索引中减去一个)。
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).
     *
     * @param index the index of the element to be removed
     * @return the element that was removed from the list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
        rangeCheck(index);
        modCount++;
        //要删除的索引位置的老的元素
        E oldValue = elementData(index);
        int numMoved = size - index - 1;
        if (numMoved > 0)
          //拷贝数组:把index位置后面的往前移动
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //size减少1
        elementData[--size] = null; // clear to let GC do its work
        return oldValue;
    }

拷贝数组:把index位置后面的往前移动,index位置的元素会被后面一个元素覆盖

目录
相关文章
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
102 2
|
19天前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
1月前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
57 2
|
2月前
|
存储 安全 Java
美团面试:String 为什么 不可变 ?(90%答错了,尼恩来一个绝世答案)
45岁老架构师尼恩分享Java面试心得,涵盖String不可变性、字符串常量池、面试技巧等内容。尼恩强调,掌握深层技术原理,如String不可变性的真正原因,可在面试中脱颖而出,赢得高薪Offer。此外,尼恩还提供了大量技术资源和面试指导,帮助求职者提升技术水平,顺利通过大厂面试。
|
3月前
|
存储 安全 算法
Java面试题之Java集合面试题 50道(带答案)
这篇文章提供了50道Java集合框架的面试题及其答案,涵盖了集合的基础知识、底层数据结构、不同集合类的特点和用法,以及一些高级主题如并发集合的使用。
139 1
Java面试题之Java集合面试题 50道(带答案)
|
4月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
508 37
|
3月前
|
存储 分布式计算 NoSQL
大数据-40 Redis 类型集合 string list set sorted hash 指令列表 执行结果 附截图
大数据-40 Redis 类型集合 string list set sorted hash 指令列表 执行结果 附截图
35 3
|
4月前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
|
2月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
69 2
|
3月前
|
Java
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
本文深入探讨了Java中方法参数的传递机制,包括值传递和引用传递的区别,以及String类对象的不可变性。通过详细讲解和示例代码,帮助读者理解参数传递的内部原理,并掌握在实际编程中正确处理参数传递的方法。关键词:Java, 方法参数传递, 值传递, 引用传递, String不可变性。
77 1
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性