基础面试题
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位置的元素会被后面一个元素覆盖