面试题—开发篇

目录

一、Java 基础

二、容器

三、多线程

四、反射

五、对象拷贝

六、Java Web

七、异常

八、网络

九、设计模式

十、Spring/Spring MVC

十一、Spring Boot/Spring Cloud

十二、Hibernate

十三、Mybatis

十四、RabbitMQ

十五、Kafka

十六、Zookeeper

十七、MySql

十八、Redis

十九、JVM


一、Java 基础

1.JDK 和 JRE 有什么区别?

  • JRE:Java Runtime Environment,是Java运行环境,如果你不需要开发只需要运行Java程序,那么你可以安装JRE。例如程序员开发出的程序最终卖给了用户,用户不用开发,只需要运行程序,所以用户在电脑上安装JRE即可。
  • JDK:Java Development Kit,它是Java开发运行环境,在程序员的电脑上当然要安装JDK;JDK包含了JRE。

2.== 和 equals 的区别是什么?

  • ==:比较的对象引用,也就是内存地址是否相等
  • equals:比较的是值

2.Java重写equals()

  • 保证具有对称性、传递性、一致性(自反性和非空性自动满足)
public class ColorPoint{
  private Point point;
  private Color color;
  public ColorPoint(int x, int y, Color color){
    point = new Point(x, y);
    this.color = color;
  }
  //返回一个与该有色点在同一位置上的普通Point对象
  public Point asPoint(){
    return point;
  }
  public boolean equals(Object o){
    if(o == this)
     return true;
    if(!(o instanceof ColorPoint))
     return false;
    ColorPoint cp = (ColorPoint)o;
    return cp.point.equals(point)&&
             cp.color.equals(color);
  }
}

3.hashCode()方法的作用是什么?

  • Java Set中的元素是不可重复的,可两个元素是否重复应该依据什么来判断呢?这就是Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。   于是,Java采用了哈希表的原理。哈希算法将数据依特定算法直接指定到一个地址上。如果详细讲解哈希算法,那需要更多的文章篇幅,我在这里就不介绍了。初学者可以这样理解,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。  这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。  

3.两个对象的 hashCode()相同,则 equals()也一定为 tr      ue,对吗?

  • 不对,存在hash冲突

4.final 在 java 中有什么作用?

  • 终态,修饰类不能被继承、修饰函数不能被重写、修饰变量则变量只能读不能写;

5.java 中的 Math.round(-1.5) 等于多少?

  • Math.round()是四舍五入函数,返回值为int型

6.String 属于基础的数据类型吗?

  • 不是,是封装类型

7.java 中操作字符串都有哪些类?它们之间有什么区别?

  • String:不可变对象,不能修改
  • StringBuffer:长度可变,线程安全
  • StringBulider:长度可变,线程不安全

8.String str1="i"、String str2="i"与 String str3=new String(“i”)一样吗?

  • str1与str2一样,str1与str3不一样。
  • String str="i"; 因为String 是final类型的,所以“i”应该是在常量池。
  • new String("i");则是新建对象放到堆内存中。

9.如何将字符串反转?

  • StringBuilder(str).reverse().toString();

10.String 类的常用方法都有那些?

  • length()、toCharArray()、split(String)、contains(String)、startsWith(String)、endsWith(String)、toUpperCase()、replace(String,String)、substring(int,int)、trim()、charAt(int)、indexOf(String)

11.抽象类必须要有抽象方法吗?

  • 抽象类不一定有抽象方法;但是包含一个抽象方法的类一定是抽象类。(有抽象方法就是抽象类)
  • 在abstract class 中可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface中,只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在 interface中一般不定义数据成员),所有的成员方法都是abstract的。

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

  • 抽象类不能被实例化

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

  • 抽象方法不能用final修饰,因为子类需要重写抽象方法,如果被final修饰则无法重写

14.接口和抽象类有什么区别?

相同点:

  • 都不能实例化对象
  • 都可以包含抽象方法,而且抽象方法必须被继承的类全部实现。

不同点:

  • 抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
  • 接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现
  • 接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量
  • 抽象方法只能申明,不能实现,接口是设计的结果 ,抽象类是重构的结果
  • 抽象类里可以没有抽象方法
  • 抽象方法要被实现,所以不能是静态的,也不能是私有的。
  • 接口可继承接口,并可多继承接口,但类只能单根继承。

15.java 中 IO 流分为几种?

  • BIO:同步阻塞I/O模式
  • NIO:同步非阻塞的I/O模型
  • AIO:异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。

二、容器

18.java 容器都有哪些?

  • ArrayList、LinkedList、Set、TreeSet、HashMap、TreeMap、Deque、PriorityQueue

19.Collection 和 Collections 有什么区别?

  • java.util.Collection 是一个集合接口;
  • java.util.Collections 是一个包装类。它包含有各种有关集合操作的静态方法(对集合的搜索、排序、线程安全化等),此类不能实例化,就像一个工具类,服务于Java的Collection框架。

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

  • List:有序集合
  • Set:不重复集合,LinkedHashSet按照插入排序,SortedSet可排序,HashSet无序
  • Map:键值对集合

21.HashMap 和 Hashtable 有什么区别?

  • Hashtable:底层数组+链表实现,无论key还是value都不能为null,线程安全
  • HashMap :底层数组+链表或数组加红黑树实现,可以存储null键和null值,线程不安全

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

  • 不需要排序使用HashMap,需要排序使用TreeMap

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

  • 用于存储Key-value集合的数据结构;底层用数组加链表或数组加红黑树实现,当链表长度超过8时转为红黑树;初识容量是16,扩容是2倍容量扩容;当数组大小使用率75%时触发扩容

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

  • HashSet是一个HashMap实例,都是一个存放链表的数组。HashSet中不允许有重复元素,这是因为HashSet是基于HashMap实现的,HashSet中的元素都存放在HashMap的key上面,而value中的值都是统一的一个固定对象private static final Object PRESENT = new Object();
  • HashSet中add方法调用的是底层HashMap中的put()方法,而如果是在HashMap中调用put,首先会判断key是否存在,如果key存在则修改value值,如果key不存在这插入这个key-value。而在set中,因为value值没有用,也就不存在修改value值的说法,因此往HashSet中添加元素,首先判断元素(也就是key)是否存在,如果不存在这插入,如果存在着不插入,这样HashSet中就不存在重复值。
  • 所以判断key是否存在就要重写元素的类的equals()和hashCode()方法,当向Set中添加对象时,首先调用此对象所在类的hashCode()方法,计算次对象的哈希值,此哈希值决定了此对象在Set中存放的位置;若此位置没有被存储对象则直接存储,若已有对象则通过对象所在类的equals()比较两个对象是否相同,相同则不能被添加。

25.ArrayList 和 LinkedList 的区别是什么?

  • ArrayList:底层是数组,查找快,增加删除慢
  • LinkedList:底层是链表,查找慢,增加删除快

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

  • 数组转 List ,使用 JDK 中 java.util.Arrays 工具类的 asList 方法
  • List 转数组,使用 List 的toArray方法

27.ArrayList 和 Vector 的区别是什么?

  • Vector:线程安全的动态数组,虽然是线程安全的,但性能较差,一般情况下使用ArrayList,除非特殊需求;
  • ArrayList:线程不安全

28.Array 和 ArrayList 有何区别?

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

29.在 Queue 中 poll()和 remove()有什么区别?

  • 异常处理方式不一样,offer()/peek()/poll()异常时返回null或false;add()/element()/remove()抛出异常30.哪些集合类是线程安全的?

31.迭代器 Iterator 是什么?

  • 用于顺序访问集合对象的元素,无需知道集合对象的底层实现。

32.Iterator 怎么使用?有什么特点?

  • 使用方法iterator()要求容器返回一个Iterator。Iterator将准备好返回序列的第一个元素。
  • 使用next()获得序列中的下一个元素
  • 使用hasNext()检查序列中是否还有元素。
  • 使用remove()将迭代器新近返回的元素删除。

32.List如何一边遍历,一边删除?

  • 使用迭代器的remove()方法
  • 使用for正循环遍历,每次删除时修改下标i=i-1
public static void main(String[] args) {
    List<String> platformList = new ArrayList<>();
    platformList.add("博客园");
    platformList.add("优快云");
    platformList.add("掘金");

    Iterator<String> iterator = platformList.iterator();
    while (iterator.hasNext()) {
        String platform = iterator.next();
        if (platform.equals("博客园")) {
            iterator.remove();
        }
    }

    System.out.println(platformList);
}

33.Iterator 和 ListIterator 有什么区别?

  • ListIterator 继承 Iterator
  • ListIterator 比 Iterator多方法
  • 使用范围不同,Iterator可以迭代所有集合;ListIterator 只能用于List及其子类

34.怎么确保一个集合不能被修改?

  • 首先我们要清楚,集合(map,set,list…)都是引用类型,所以我们如果用final修饰的话,集合里面的内容还是可以修改的。可以采用Collections包下的unmodifiableMap方法,通过这个方法返回的map,是不可以修改的

35.unmodifiableMap不能修改的原理:

  • 当调用其读相关方法(size、isEmpty、get)时,间接调用的是成员属性m的对应方法。
  • 当调用其修改相关方法(put、remove、clear)时,直接抛出UnsupportedOperationException异常,不让修改。

三、多线程

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

  • 并行:进程并行执行,多核cpu并行执行多个进程
  • 并发:一个进程的多个线程并发,并发只是看起来像同时执行,其实是轮流使用cpu的

36.线程和进程的区别?

1)功能不同

  • 进程就是运行中的程序,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
  • 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。

2)工作原理不同

  • 进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
  • 线程是独立调度和分派的基本单位。线程可以为操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。

3)作用不同

  • 进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。
  • 通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。

36.python协程是什么?与线程有什么区别?

  • 进程上的一把锁,python程序运行时,只有一个线程可以获取这把锁,这也是python为什么是伪并发的原因

37.守护线程是什么?

  • java里线程分2种,守护线程、用户线程。守护线程是专门用于服务其他的线程,比如垃圾回收线程,就是最典型的守护线程。
  • 如果其他的线程(即用户自定义线程)都执行完毕,连main线程也执行完毕,那么jvm就会退出(即停止运行)——此时,连jvm都停止运行了,守护线程当然也就停止执行了。
  • 当线程只剩下守护线程的时候,JVM就会退出;补充一点如果还有其他的任意一个用户线程还在,JVM就不会退出。

37.使用守护线程需要注意什么?

  • thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
  • 在Daemon线程中产生的新线程也是Daemon的。
  • 守护线程不能用于去访问固有资源,比如读写操作或者计算逻辑。因为它会在任何时候甚至在一个操作的中间发生中断。
  • Java自带的多线程框架,比如ExecutorService,会将守护线程转换为用户线程,所以如果要使用后台线程就不能用Java的线程池。

38.创建线程有哪几种方式?

  • 两种,继承Thread类或者实现Runnable接口,因为Java不允许多继承,但允许实现多个接口,所以推荐使用实现Runnable接口的方式

39.说一下 runnable 和 callable 有什么区别?

  • 两者最大的不同点是:实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;
  • Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;

39.callable创建线程的demo

public class CallableImpl implements Callable<String> {
    private String acceptStr;
    public CallableImpl(String acceptStr) {
        this.acceptStr = acceptStr;
    }
    @Override
    public String call() throws Exception {
        // 任务阻塞 1 秒
        Thread.sleep(1000);
        return this.acceptStr + " append some chars and return it!";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<String> callable = new CallableImpl("my callable test!");
        FutureTask<String> task = new FutureTask<>(callable);
        long beginTime = System.currentTimeMillis();
        // 创建线程
        new Thread(task).start();
        // 调用get()阻塞主线程,反之,main线程不会阻塞
        String result = task.get();
        long endTime = System.currentTimeMillis();
        System.out.println("hello : " + result);
        System.out.println("cast : " + (endTime - beginTime) / 1000 + " second!");
    }
}

40.线程有哪些状态?

  • 创建(new)、就绪(runnable)、运行(running)、阻塞(blocked)、time waiting、waiting、消亡(dead)

41.sleep() 和 wait() 有什么区别?

  • wait、notify/notifyAll是Object的本地final方法,wait使当前线程阻塞,前提是必须先获得锁,所以只能在synchronized锁范围内里使用wait、notify/notifyAll方法,
  • sleep方法是Thead类的静态方法,sleep可以在任何地方使用,
  • sleep() 和 wait() 都会释放cpu资源,但sleep()不会释放锁,wait()会释放锁

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

  • notify():唤醒等待线程
  • notifyAll():唤醒所有等待线程,但最终也只有一个线程能够获得锁

42.选择使用notify还是notifyAll()?

  • notify方法和notifyAll()方法两者非常相似,到底该用哪一个,老实说,这个选择有点困难。选择notify的话,因为要唤醒的线程比较少(only one),程序的处理速度当然比notifyAll略胜一筹。但是选择notify时,若这部分处理不好,可能会出现程序挂掉的危险。一般说来,选择notifyAll所写出来的程序代码会比notify可靠。除非你能确定程序员对程序代码的意义和能力限度一清二楚,否则选择notifyAll应该是比较稳扎稳打。

43.线程的 run()和 start()有什么区别?

  •  start() 方法是用来启动线程的,轮到该线程执行时,会自动调用 run();
  • 直接调用 run() 方法,无法达到启动多线程的目的,相当于主线程线性执行 Thread 对象的 run() 方法。
  • 一个线程对线的 start() 方法只能调用一次,多次调用会抛出 java.lang.IllegalThreadStateException 异常;run() 方法没有限制。

44.创建线程池有哪几种方式?

  • 通过Executors工厂方法创建
  • 通过new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)自定义创建
  • 推荐:使用ThreadPoolExecutor方式创建线程池(阿里不允许使用Executors,使用ThreadPoolExecutor可以避免资源耗尽的风险)
//创建使用单个线程的线程池
ExecutorService es1 = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
    es1.submit(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "正在执行任务");
        }
    });
}
//创建使用固定线程数的线程池
ExecutorService es2 = Executors.newFixedThreadPool(3);
//创建一个会根据需要创建新线程的线程池
ExecutorService es3 = Executors.newCachedThreadPool();
//创建拥有固定线程数量的定时线程任务的线程池
ScheduledExecutorService es4 = Executors.newScheduledThreadPool(2);
//创建只有一个线程的定时线程任务的线程池
ScheduledExecutorService es5 = Executors.newSingleThreadScheduledExecutor();

ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 20, 2, TimeUnit.SECONDS, new LinkedBlockingQueue<>(5));
for (int i = 0; i < size; i++) {
    poolExecutor.execute(new DemoTask(i));
    Console.log("poolSize:" + poolExecutor.getPoolSize());
}

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

  • 参数不同:execute() 参数 Runnable ;submit() 参数 (Runnable) 或 (Runnable 和 结果 T) 或 (Callable)
  • 返回值不同:execute() 没有返回值;而 submit() 有返回值,submit() 的返回值 Future 调用get方法时,可以捕获处理异常
  • 总结:推荐使用submit()

45.线程池都有哪些状态?

  • RUNNING
  • SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。调用shutdown()方法时变为这个状态
  • STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。调用shutdownNow()方法时变为
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值