面试准备-打卡第二天-Java篇

154 阅读8分钟

Java有哪些数据类型?

Java语言的数据类型分为两种:基本数据类型和引用数据类型。

image.png 1.基本数据类型包括 boolean(布尔型)、float(单精度浮点型)、char(字符型)、byte(字节型)、short(短整型)、int(整型)、long(长整型)和 double (双精度浮点型)共 8 种,如下表所示。

基本类型位数字节默认值
int3240
short1620
long6480L
byte810
char162'u0000'
float3240f
double6480d
boolean1false

对于 boolean,官方文档未明确定义,它依赖于 JVM 厂商的具体实现。逻辑上理解是占用 1 位,但是实际中会考虑计算机高效存储因素。

Java虚拟机规范讲到:在JVM中并没有提供boolean专用的字节码指令,而boolean类型数据在经过编译后在JVM中会通过int类型来表示,此时boolean数据4字节32位,而boolean数组将会被编码成Java虚拟机的byte数组,此时每个boolean数据1字节占8bit。

注意:

①. Java 里使用 long 类型的数据一定要在数值后面加上 L,否则将作为整型解析:

②. char a = 'h'char :单引号,String a = "hello" :双引号

switch是否能作用在byte上,是否能作用在long上,是否能作用在String上?

Java5 以前 switch(expr)中,expr 只能是 byte、short、char、int。

从 Java 5 开始,Java 中引入了枚举类型, expr 也可以是 enum 类型。

从 Java 7 开始,expr还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。 2.引用数据类型建立在基本数据类型的基础上,包括数组、类和接口。引用数据类型是由用户自定义,用来限制其他数据的类型。另外,Java 语言中不支持 C++中的指针类型、结构类型、联合类型和枚举类型。

访问修饰符public、private、protected、以及不写(默认)时的区别?

Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。

  • default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
  • private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
  • public : 对所有类可见。使用对象:类、接口、变量、方法
  • protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)

break、continue、return的区别及作用?

  • break 跳出总上一层循环,不再执行循环(结束当前的循环体)
  • continue 跳出本次循环,继续执行下次循环(结束正在执行的循环 进入下一个循环条件)
  • return 程序返回,不再执行下面的代码(结束当前的方法 直接返回)

常见的集合有哪些?

Java集合类主要由两个根接口Collection和Map派生出来的,Collection派生了三个子接口:List、Set、Queue,因此,Java集合大致也可分成List、Set、Queue、Map四种接口体系。

Java集合框架图如下:

image.png

image.png

图中List代表了有序可重复集合,可直接根据元素的索引来访问;Set代表无序不可重复集合,只能根据元素本身来访问;Queue是队列集合。

Map代表的是存储key-value对的集合,可根据元素的key来访问value

淡蓝色背景覆盖的是集合体系中常用的实现类,分别是ArrayList、LinkedList、ArrayQueue、HashSet、TreeSet、HashMap、TreeMap等实现类

为什么使用集合而不是数组?

  • 集合与数组的相似点:都可以存储多个对象,对外作为一个整体存在
  • 数组的缺点:长度必须在初始化时指定,且固定不变;数组采用连续存储空间,删除和添加效率低下;数组无法直接保存映射关系;数组缺乏封装,操作频繁

集合中有哪些是线程安全的?哪些是线程不安全的?

线程安全的:

  • Hashtable:比HashMap多了个线程安全
  • ConcurrentHashMap:是一种高效但是线程安全的集合
  • Vector:比ArrayList多了个同步化机制
  • Stack:栈,也是线程安全的,继承于Vector 线程不安全的:
  • HashMap
  • ArrayList
  • LinkedList
  • HashSet
  • TreeSet
  • TreeMap

ArrayList和LinkedList的区别有哪些?

  • ArrayList的底层数据结构是数组,支持下标访问,查询数据快,默认初始值大小为10,容量不足时会进行扩容
  • LinkedList的底层数据结构是链表,将元素添加到链表的末尾,无需扩容

ArrayList和LinkedList分别适用于什么场景?

对于随机index访问的get和set方法,ArrayList的速度要优先于LinkedList,因为ArrayList直接通过数组下标找到元素;LinkedList要移动指针遍历每个元素直到找到为止

新增和删除元素,LinkedList的速度要优于ArrayList,因为ArrayList在新增和删除元素时,可能扩容和复制数组,而LinkedList的新增和删除操作只需要修改指针即可。

因此,ArrayList适用于查询多,增删少的场景,而LinkedList适用于查询少,增删多的场景

ArrayList和Vector的区别?

  • Vector是线程安全的,ArrayList不是线程安全的。其中,Vector在关键性的方法前面都加了synchronized关键字,来保证线程的安全性。如果有多个线程会访问到集合,那最好是使用 Vector,因为不需要我们自己再去考虑和编写线程安全的代码。
  • ArrayList在底层数组不够用时在原来的基础上扩展0.5倍,Vector是扩展1倍,这样ArrayList就有利于节约内存空间。

讲一下ArrayList的扩容机制?

ArrayList实现了List的接口,继承于AbstractList,从源码可以看到,在grow方法里面进行扩容,将数组容量扩大为原来的1.5倍,举个例子,如果初始化的值为8,当添加第九个元素时,发现数组空间不够,就会进行扩容。扩容后的容量为12,扩容后会调用Arrays.copyOf()方法对数组进行拷贝,把原数组的数据,原封不动的复制到新数组中,然后把ArrayList的地址指向新数组

以JDK1.8为例说明:

public boolean add(E e) {
    //判断是否可以容纳e,若能,则直接添加在末尾;若不能,则进行扩容,然后再把e添加在末尾
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //将e添加到数组末尾
    elementData[size++] = e;
    return true;
    }

// 每次在add()一个元素时,arraylist都需要对这个list的容量进行一个判断。通过ensureCapacityInternal()方法确保当前ArrayList维护的数组具有存储新元素的能力,经过处理之后将元素存储在数组elementData的尾部

private void ensureCapacityInternal(int minCapacity) {
      ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
        //如果传入的是个空数组则最小容量取默认容量与minCapacity之间的最大值
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    
  private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // 若ArrayList已有的存储能力满足最低存储要求,则返回add直接添加元素;如果最低要求的存储能力>ArrayList已有的存储能力,这就表示ArrayList的存储能力不足,因此需要调用 grow();方法进行扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }


private void grow(int minCapacity) {
        // 获取elementData数组的内存空间长度
        int oldCapacity = elementData.length;
        // 扩容至原来的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //校验容量是否够
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //若预设值大于默认的最大值,检查是否溢出
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 调用Arrays.copyOf方法将elementData数组指向新的内存空间
         //并将elementData的数据复制到新的内存空间
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

Array和ArrayList有什么区别?

  • Array 可以包含基本类型和对象类型,ArrayList 只能包含对象类型。
  • Array 大小是固定的,ArrayList 的大小是动态变化的。
  • ArrayList 提供了更多的方法和特性,比如:addAll(),removeAll(),iterator() 等等。

Set和List有什么区别?

List是以索引来存取元素,有序的,元素是允许重复的,可以插入多个null;Set不能存放重复元素,无序的,只允许插入一个null

List底层实现有数组、链表两种方式;Set基于Map实现,Set里的元素值就是Map的数值

还知道哪些线程安全的List?

可以使用Collections.synchronizedList()方法返回一个线程安全的List

还有另一种方式,使用CopyOnWriteArrayList

请详细讲下CopyOnWriteArrayList的原理?

CopyOnWriteArrayList是一个线程安全的List,底层是通过复制数组的方式来实现的,所谓的CopyOnWrite就是写时复制,当我们的容器添加元素时,不直接往容器添加,而是先将当前容器进行复制,复制出一个新的容器,然后往新的容器添加元素,添加完元素之后,再将原容器的引用指向新容器,这样做的好处就是可以对CopyOnWrite容器进行并发的读而不需要加锁,因为当前容器不会被修改

CopyOnWriteArrayList有什么缺点吗?

内存占用问题:由于CopyOnWrite写时复制机制,在进行写操作时,内存里会同时驻扎两个对象的内存

CopyOnWrite容器不能保证数据的实时一致性,可能读取到旧数据