Java基础面试题

第一部分Java基础

1、JDK和JRE有什么区别?

  • JDK:Java运行环境,Java开发工具包,提供了Java的开发和运行环境。
  • JRE:Java运行环境,为Java的运行提供可所需的环境。

2、== 和 equals 的区别?

== 解读

对于基本类型和引用类型,作用效果是不同的,如下所示:

  • 基本类型:比较的值是否相同
  • 引用类型:比较的地址是否相同
String x = "string";
String y = "string";
String z = new String("string");
System.out.println(x==y); // true
System.out.println(x==z); // false
System.out.println(x.equals(y)); // true
System.out.println(x.equals(z)); // true

​ 代码解读:因为x和y指向的是同一个引用,所以 == 也是 true,而new String()方法则重新开辟了内存空间,所以 == 的结果为true,而equals比较的一直是值,所以结果都为true。

equals解读

​ equals本质上就是 == ,只不过String和Integer等重写了equals方法,把他变成了值比较。

​ 总结: == 对基本类型来说是值比较,对于引用类型来说比较的是引用;而equals默认情况下是引用比较,只是很多类重写了equals方法。

3、两个对象的hashcode相等,则equals也一定为true,对吗?

不对,两个对象的hashcode相等,equals也不一定为true。

String str1 = "通话";
String str2 = "重地";
System.out.println(String.format("str1:%d | str2:%d",  str1.hashCode(),str2.hashCode()));
System.out.println(str1.equals(str2));

执行结果:

str1:1179395 | str2:1179395

false

代码解读:虽然二者hashcode的值相同,然而equals()比较却为false,因为在散列表中,hashcode相等即两个键值对的哈希值相等,然而哈希值相等,并不一定能得出键值对相等。

4、final在Java中的作用?

  • final修饰的类不能被继承
  • final修饰的方法不能被重写
  • final修饰的变量叫常量,常量必须初始化,初始化之后不能被修改

5、Java中Math.round(-1.5)等于多少?

等于 -1,因为在数轴上取值时,中间值0.5向右取整,所以正0.5是往上取整,-0.5是直接舍弃。

6、Strin属于基本类型吗?

不属于,基础数据类型有8种,byte、int、long、char、short、boolean、float、double。

7、操作字符串的类有哪些?有哪些区别?

String、StringBuffer、StringBuilder

String声明的是不可变的对象,每次操作都会生成新的string对象,然后将指针指向新的String对象,而StringBuffer和StringBuilder可以在原有的基础上进行操作,所以在经常改变字符串的情况下最好不要使用string。

StringBuffer和StringBuilder最大的区别在于,StringBuffer是线程安全的,而StringBuilder是线程不安全的。但是StringBuilder的性能却高于StringBuffer,所以在单线程的情况下建议使用StringBuilder,多线程的情况下使用StringBuffer。

8、String str = “i” 和 String str = new String(“i”) 一样吗?

不一样,因为内存的分配方式不一样。String str = "i"的方式,Java虚拟机会将其分配到常量池中,而String str = new String(“i”)则会被分配到堆内存中。

9、如何反转字符串?

使用StringBuffer或StringBuilder的reverse()方法。

10、string有哪些常用的方法?

11、抽象类必须有抽象方法吗?

不需要。抽象类不一定非要有抽象方法。

abstract class Cat {
    public static void sayHi() {
        System.out.println("hi~");
    }
}

12、抽象类和普通类有哪些区别?

  • 抽象类不能直接实例化,普通类可以直接被实例化
  • 普通类不能包含抽象的方法,抽象类可以包含抽象的方法

13、抽象类能使用final修饰吗?

不能,抽象类要被其他类继承,如果用final进行修饰,就不能被其他类继承了,这样就会产生矛盾。

14、接口和抽象类的区别

  • 实现:抽象类的子类使用extends来继承父类,而接口必须使用implements来实现。
  • 构造函数:抽象类可以有,但是接口不能有。
  • 实现数量:类可以实现多个接口,但是只能继承一个类。
  • 访问修饰符:接口中的方法默认使用public修饰,抽象类中的方法可以是任意修饰符。

15、Java中的IO流分为几种?

  • 按功能来分:输入流和输出流
  • 按类型来分:字节流和字符流

字节流和字符流最大的区别是:字节流按照8位字节进行传输,字符流按照16字节进行传输。

16、BIO、NIO、AIO有什么区别?

  • BIO:Block IO 同步阻塞式IO,就是我们平常使用的IO,特点是简单方便,并发处理能力低。
  • NIO:New IO 同步非阻塞式IO,是传统IO的升级,客户端和服务器通过信道Channel通讯,实现了多路复用。
  • AIO:Asynchronous IO是NIO的升级,也叫NIO2,实现了异步非阻塞IO,异步IO的操作都是基于事件和毁掉机制。

17、Files的常用方法有哪些?

  • Files.exists():检测文件路径是否存在。
  • Files.createFile():创建文件。
  • Files.createDirectory():创建文件夹。
  • Files.delete():删除一个文件或目录。
  • Files.copy():复制文件。
  • Files.move():移动文件。
  • Files.size():查看文件个数。
  • Files.read():读取文件。
  • Files.write():写入文件。

18、Java常用容器

图略

19、Collection和Collections的区别是什么?

  • java.util.Collection是一个集合接口。他提供了对集合对象的基本操作方法。Collection接口在Java中有很多实现。继承自他的有List和Set接口。
  • Collectios则是集合类的一个工具类,其中提供了一些列的静态方法,用于对集合进行元素的排序,搜索及线程安全的各种操作。

20、List、Set、Map之间的区别是什么?

图略

21、HashMap和HashTable的区别是什么?

  • HashMap 的初始容量为:16,Hashtable 初始容量为:11,两者的负载因子默认都是:0.75。
  • HashTable是线程同步的,而HashMap是线程非同步的,但是效率更高一些。
  • HashMap允许非空键值,而HashTable不允许。

22、如何决定使用HashMap还是TreeMap?

对于在map中插入、删除和定位元素这类操作、hashmap是最好的选择。然而,假如你要对一个有序的key集合进行遍历,Treemap是更好的选择。

23、说一下HashMap的实现原理?

HashMap概述: HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

HashMap的数据结构: 在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。

当我们往Hashmap中put元素时,首先根据key的hashcode重新计算hash值,根绝hash值得到这个元素在数组中的位置(下标),如果该数组在该位置上已经存放了其他元素,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放入链尾.如果数组中该位置没有元素,就直接将该元素放到数组的该位置上。

需要注意Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn)。

24、说一下HashSet的实现原理?

  • HashSet底层由HashMap实现
  • HashSet的值存放于HashMap的key上
  • HashMap的value统一为PERSENT

25、ArrayList和LinkList的区别?

最明显的是实现方式不同,ArrayList底层结构是数组,支持随机访问,而LinkList的底层实现是双向循环链表,不支持随机访问。使用下表访问一个元素,ArrayList的时间复杂度是O(1),而LinkList是O(n)。

26、如何实现数组和List之间的转换

  • List转数组:调用ArrayList的toArray()方法
  • 数组转List:调用Array的asList方法

27、Array和ArrayList的区别?

  • Array可以容纳基本类型和对象,而ArrayList只能容纳对象。
  • Array是指定大小的,而ArrayList大小是固定的。
  • Array没有提供ArrayList那么多功能,比如addAll、removeAll和iterator等。

28、在Queue中poll()和remove()方法有什么区别?

poll() 和 remove() 都是从队列中取出一个元素,但是 poll() 在获取元素失败的时候会返回空,但是 remove() 失败的时候会抛出异常。

29、迭代器Iterator是什么?

迭代器是一种设计模式,他是一个 对象,他可以遍历并选择序列中的对象。

30、Iterator 和 ListIterator 有什么区别?

  • Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。
  • Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。
  • ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。

31、Iterator 怎么使用?有什么特点?

Java中的Iterator功能比较简单,并且只能单向移动:

(1) 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。注意:iterator()方法是java.lang.Iterable接口,被Collection继承。

(2) 使用next()获得序列中的下一个元素。

(3) 使用hasNext()检查序列中是否还有元素。

(4) 使用remove()将迭代器新返回的元素删除。

Iterator是Java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素。

32、预留位置

33、预留位置

35、并行和并发有什么区别?

  • 并行是指两个是或多个事件同时发生;并发是指两个或多个事件在同一时间间隔发生
  • 并行是在不同实体上的多个事件,并发是在同一个实体上的同一事件
  • 并发编程的目标是充分利用处理器的性能,发挥每一个核的性能

36、线程和进程的区别?
进程是程序运行和资源分配的最小单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,多个线程共享内存资源,减少切换次数,从而提高效率。线程是进程的一个实体,是cup调度和分派的基本单位,是比程序更小的独立运行的基本单位。同一进程中多个线程之间可以并发执行。

37、守护线程是什么?

守护线程是个服务线程,准去来说就是服务其他线程的线程

38、创建线程有几种方式?

  • 继承Thread类
    • 定义Thread类的子类,并重写该类的run()方法,该run()方法代表了线程要完成的任务,因此把run()方法称为执行体
    • 创建Thread子类的实例,即创建了线程对象
    • 调用线程对象的start()方法来启动线程
  • 通过Runnable接口创建线程
    • 定义Runnable接口的实现类,并重写该类的run()方法,该方法同样是线程的执行体
    • 创建Runnable实现类的实例,并以此实例作为thread的target来创建thread对象,该thread对象才是真正的对象
    • 调用线程对象的start()方法来启动线程
  • 通过Callable和Futrue创建线程
    • 创建Callable接口的实现类,并实现call方法,该call方法作为线程的执行体,并且有返回值
    • 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值
    • 调用FutureTask对象作为Thread对象的target创建并启动线程
    • 调用FutureTask对象的get()方法来获得子线程执行结束后的结果

39、说一下runnable和callable的区别

  • Runnable接口中的方法返回值是void,他只是单纯地去执行run()方法中的代码
  • Callable中的call()方法是有返回值的,是一个泛型,和Future和FutureTask配合可以用来获取异步执行的结果

40、线程的状态有哪些?

  • 创建:在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
  • 就绪:当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
  • 运行:线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
  • 阻塞:线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。
  • 死亡:如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪

41、sleep()和wait()的区别?

  • sleep()方法是Thread类的方法,线程通过调用该方法,进入休眠状态主动让出CPU,从而CPU可以执行其他的线程。经过sleep指定的时间后,线程进入就绪状态和其他线程一起竞争cpu的使用权。如果当前线程进入了同步锁,sleep()方法并不会释放锁。即使当前线程使用sleep方法让出了cpu,但其他被同步锁挡住了的线程也无法得到执行。
  • wait()方法可以中断线程的运行,使本线程等待,暂时让出CPU的使用权,并允许其他线程使用这个同步方法。其他线程如果在使用这个同步方法时不需要等待,那么它使用完这个方法的同时,应该用notifyAll()方法通知所有由于使用了这个同步方法而处于等待的线程结束等待,曾中断的线程就会从刚才中断处继续执行这个同步方法(并不是立马执行,而是结束等待),并遵循“先中断先继续”的原则。

42、notify()和 notifyAll()有什么区别?

  • 如果线程调用了wait()方法,那么线程便会错处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁
  • 当线程调用了对象的notifyAll()方法,被唤醒的线程便会进入该对象的锁池中,锁池中的线程会竞争该对象的锁。也就是说调用这个方法之后,所有线程会由等待池进入锁池中,等待锁竞争。
  • 优先级高的线程竞争到对象锁的概率大。

43、线程的run()方法和strat()方法有什么区别?

  • start()方法来启动一个线程,真正实心了多线程运行。这时无需等待run()方法体代码执行完毕,可以继续执行下面的代码,这时线程处于就绪状态,并没有运行。然后通过run()方法来完成其运行状态,这里run()方法称为线程体,他包含了要执行的的这个线程的内容,run()方法执行结束,此线程终止,然后CPU再调度其他线程。
  • run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。如果直接调用run()方法,其实就是调用了一个普通函数而已,直接调用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是一条,根本没有多线程的特征。所以在多线程执行时要调用start()方法而不是run()方法。

总结: 系统通过调用线程类的start()方法来启动一个线程、此时该线程处于就绪状态,等待调度。在调度过程中,JVM底层通过调用线程类的run()方法来完成实际的操作,当run()方法结束后,此线程就会终止。

如果直接调用线程类的run()方法,此时run()方法仅仅被当做一个普通的函数调用,程序中仍然只有主线程这一个线程

结论: start()方法能够异步的调用run()方法,但是直接调用run()方法却是同步的。
44、创建线程池有几种方式

  • newFixedThreadPool(int i)

    创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这是线程池规模将不再变化,当线程发生未知错误而结束时,线程池会补充创建一个新的线程

  • newCachedThreadPoll()

    创建一个可缓存的线程池,如果线程池的规模超出了处理的需求,将自动回收空闲线程,当需求增加时,可以添加新的线程,线程池的规模不存在任何限制

  • newSingleThreadExecutor()

    这是一个单线程的Executor,他创建单个线程来执行任务,如果这个线程异常结束,会创建一个新的来代替他,他的特点是能确保依照任务在队列中的顺序来串行执行

  • newScheduledThreadPool(int corePoolSize)

    创建一个固定长度的线程池,而且以延时或定时的方式来执行任务

45、线程池有哪些状态

Running,ShutDown,Stop,Tidying,Terminated

转换状态图略

46、线程池的submit()和execute()方法有什么区别?

  • 接受参数不一样
  • submit()有返回值而execute()没有
  • submit()方便异常处理

47、在Java程序中怎么保证多线程的安全?

  • 原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作
  • 可见性:一个线程对主内存的修改可以及时地被其他线程看到
  • 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察解果一班杂乱无序

48、多线程锁的升级原理是什么?

在Java中,锁有四种状态,级别从低到高依次为:无状态锁、偏向锁、轻量级锁和重量级锁,这几个状态会随着竞争情况逐渐升级,锁可以升级但不能降级

49、什么是死锁?

死锁是两个或两个以上的进程在执行过程中,由于竞争资源或者彼此通信不畅而造成的一种阻塞现象,若无外力作用,他们都将无法执行下去。

50、怎么防止死锁?

死锁的四个必要条件

  • 互斥条件:进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源
  • 请求和保持条件:进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此时请求阻塞,但又对自己获得的资源保持不放
  • 不可剥夺条件:是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放
  • 环路等待条件:是指进程发生死锁后,若干进程之间形成一种头尾相接的循环等待资源关系

51、ThreadLocal是什么?有什么应用场景?
线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。

public class ThreadLocalDemo {
    static ThreadLocal<Object> tl = new ThreadLocal<>();

    public static void main(String[] args) {

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 在此赋值
            tl.set(1);
        }).start();
        
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 在此取值
            System.out.println(tl.get());
        }).start();
    }
}
  • 主要方法
    • set()

      public void set(T value) {
      		// 获得当前线程
              Thread t = Thread.currentThread();
              // 获得当前线程的私有map
              ThreadLocalMap map = getMap(t);
              if (map != null)
             		// 给线程私有的ThreadLocalMap里面存放数据,key为this,
             		// 也就是当前的ThreadLocal类,value为传传入的值
                  map.set(this, value);
              else
                  createMap(t, value);
          }
      
      // getMap方法的实现
      ThreadLocalMap getMap(Thread t) {
              return t.threadLocals;
          }
      
      // 这里的threadLocals属于Thread类,类型为map
      ThreadLocal.ThreadLocalMap threadLocals = null;
      
      // 这是set操作的代码
      private void set(ThreadLocal<?> key, Object value) {
          Entry[] tab = table;
          int len = tab.length;
          int i = key.threadLocalHashCode & (len-1);
      
          for (Entry e = tab[i];
               e != null;
               e = tab[i = nextIndex(i, len)]) {
              ThreadLocal<?> k = e.get();
      
              if (k == key) {
                  e.value = value;
                  return;
              }
      
              if (k == null) {
                  replaceStaleEntry(key, value, i);
                  return;
              }
          }
      
          tab[i] = new Entry(key, value);
          int sz = ++size;
          if (!cleanSomeSlots(i, sz) && sz >= threshold)
              rehash();
      }
      
    • get()

      public T get() {
      	// 获取当前线程对象
          Thread t = Thread.currentThread();
          // 获取此线程对象维护的threadLocalMap
          ThreadLocalMap map = getMap(t);
          if (map != null) {
          	// 以当前的threadLocal为key获取对应的存储实体
              ThreadLocalMap.Entry e = map.getEntry(this);
              if (e != null) {
                  @SuppressWarnings("unchecked")
                  // 获取对应实体对应的value
                  T result = (T)e.value;
                  return result;
              }
          }
          return setInitialValue();
      }
      
      // 此方法可以被子类重写。否则默认返回null
      private T setInitialValue() {
              T value = initialValue();
              Thread t = Thread.currentThread();
              ThreadLocalMap map = getMap(t);
              if (map != null)
                  map.set(this, value);
              else
                  createMap(t, value);
              return value;
          }
      
    • 存在的问题:内存泄漏
      解决方法:在线程的threadLocalMap中的key是弱引用类型,保证了GC的时候可以回收掉。对于threadLocalMap中的value,需要收工调用remove()方法删除掉,这样就结局了内存泄漏问题。

    • 如何解决hash冲突
      在根据key计算index的时候,定义了一个AutomicInteger类型的值,每次获取当前值并加上HASH_INCREMENT,HASH_INCREMENT=0x61c88647(这个值跟斐波那契数列有关),主要目的是为了让hash值均匀分布在2的n次方的数组里,尽量避免hash冲突。

    • threadLocalMap是什么时候初始化的
      -第一次调用get()或者set()方法的时候,如果没有的话会执行initValue()方法进行初始化。

    • threadLocalMap什么时候扩容
      threadLocalMap的默认容量为16,阈值为0.75,当数量超过0.75的时候会进行remove()操作,如果remove之后仍然超过阈值,则进行扩容。

    • 如何进行扩容
      首先会创建新的数组,长度是当前的两倍,然后迭代老的数组,将其中的数据重新按照hash算法放入新的数组里面,然后更新threadLocalMap对象的这个散列表的引用,指向新的数组。

52、说说synchronized的底层原理

synchronized 可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时他还保证共享变量的内存可见性

Java中每个对象都可以作为锁,这是synchronized实现同步的基础:

  • 普通同步方法,锁是当前实例对象
  • 静态同步方法,锁是当前类的class对象
  • 同步方法块,锁是括号里的对象

53、synchronized和volatile 的区别是什么?

  • volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
  • volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。
  • volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性。
  • volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
  • volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。

54、synchronized和lock有什么区别?

  • 首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
  • synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
  • synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
  • 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
  • synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可);
  • Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

55、synchronized 和 ReentrantLock 区别是什么?

synchronized是和if、else、for、while一样的关键字,ReentrantLock是类,这是二者的本质区别。既然ReentrantLock是类,那么它就提供了比synchronized更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量,ReentrantLock比synchronized的扩展性体现在几点上:

  • ReentrantLock可以对获取锁的等待时间进行设置,这样就避免了死锁
  • ReentrantLock可以获取各种锁的信息
  • ReentrantLock可以灵活地实现多路通知

56、说一下 atomic 的原理?

Atomic包中的类基本的特性就是在多线程环境下,当有多个线程同时对单个(包括基本类型及引用类型)变量进行操作时,具有排他性,即当多个线程同时对该变量的值进行更新时,仅有一个线程能成功,而未成功的线程可以向自旋锁一样,继续尝试,一直等到执行成功。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值