java基础常见的面试题

1.overload与override的区别

override:方法覆写(子类与父类 接口与实现类)
在父类的方法不能满足子类需求时,需要覆写父类方法
方法签名一致
和返回值类型与修饰符有关
子类方法的权限修饰符不能低于父类方法的访问权限

overload:方法重载(在同一个类中)
方法名一致当形参列表不同
和返回值类型与修饰符无关

2.HashMap的put方法是怎么实现的?get方法是怎么实现的?

put方法:
         先根据key的hashCode计算hash值,再根据hash值得到该元素在数组中的位置,即索引,如果该位置上没有元素,则直接把该元素放在这个位置上,如果该位置上已经有元素,则在该位置上的元素将以链表形式存放,新加入的放在链表头,已经加入的放在链表尾。

get方法:
         先根据key的hashCode方法计算出hash值,然后再根据hash值找到数组中对应位置的某一元素,最后在根据key的equals方法逐一比较在对应位置的链表中找到该元素的值

3.HashMap与HashSet的区别

HashMap HashSet
实现了Map接口 实现了Set接口
存储键值对 存储对象
使用put方法将元素放入map中 使用add方法将元素放入set中
使用键对象计算hashCode值 使用成员对象计算hashCode值,可以使用equals方法判断两个对象的hashCode值是否相同
使用唯一的键来获取对象,效率比较快 效率慢

4.HashMap的数据结构线程安全么?说明原因

不安全,可能造成死循环。具体表现为链表的循环指向,应该使用ConcurrentHashMap。

5.HashMap的实现原理

        HashMap的主干是一个Entry数组,Entry是HashMap的基本单元,每一个Entry都有一个键值对。HashMap有数组和链表组成,数组是HashMap的主干,链表则是为了解决hash冲突而存在的,如果定位到的数组里面没有链表,则在查找、添加等操作时,效率很快,只需要一次寻址即可,如果定位到的数组里面有链表,对于添加操作来讲,其复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查询操作,也需要遍历链表,然后通过key对象的equals方法逐一比对查找,因此,从性能上考虑,链表越少,性能越好。

6.jdk1.8对HashMap的优化有哪些?

        对数据结构做了进一步优化,引入了红黑树,当链表长度过长(默认为8)时,链表就会转换为红黑树,利用红黑树快速增删查改的特点提高了HashMap的性能,其中会用到红黑树的增删查改等算法。

7.ArrayList和LinkedList的区别有哪些?

ArrayList LinkedList
查询快 增删快
底层基于数组 底层基于链表

8.ArrayList如何去重?

方法一:声明2个ArrayList,分别为listA与listB ,listA为待去重list ,listB 保存去重之后数据 。遍历listA ,然后判断listB中是否包含各个元素,若不包含,把此元素加入到listB中。
方法二:利用set集合进行去重

9.Set集合去重之后会存在哪些问题?

        set集合去重之后,列表的顺序变成无序,与原列表不相等,所以去重之后,列表要经过排序之后才能与原列表直接比较,这样就相等了。

10.HashMap和HashTable和ConcurrentMap的区别和联系

HashMap,Hashtable,ConcurrentHashMap的区别 
  a、HashMap是非线程安全的,HashTable是线程安全的。
  b、HashMap的键和值都允许有null值存在,而HashTable则不行。
  c、因为线程安全的问题,HashMap效率比HashTable的要高。
  HashMap:它根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。 HashMap最多只允许一条记录的键为null,允许多条记录的值为null。HashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap。
   Hashtable:Hashtable是遗留类,很多映射的常用功能与HashMap类似,不同的是它承自Dictionary类,并且是线程安全的,任一时间只有一个线程能写Hashtable,并发性不如ConcurrentHashMap,因为ConcurrentHashMap引入了分段锁.

11.解决hash碰撞

1、开防定址法
2、再哈希法
3、链地址法(Java的hashmap的解决办法就是这个)
4、建立一个公共溢出区

12.java中的队列都有哪些?

阻塞队列:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue等
普通队列:LinkedList、ArrayDequeue
非阻塞队列:ConcurrentLinkedQueue

13.多线程的线程安全问题怎么解决?有哪几种方法?synchronized和Lock都是互斥锁的原理,除了这个,还有什么java代码实现的方案可以解决线程安全的问题?

        线程安全问题解决方案:synchronized代码块、synchronized方法、Lock锁
其他解决线程安全问题:synchronized和lock都是悲观锁,还有乐观锁解决线程安全。乐观锁假设不会发生并发冲突,每次不加锁而是假设没有冲突而去完成某项操作,只在提交操作时检查是否违反数据完整性。如果因为冲突失败就重试,直到成功为止。乐观锁的实现有CAS算法。

14.对线程池的了解?线程池的创建方式?

创建方法有两种:
方式1:直接new ThreadPoolExecutor,Executor接口表示线程池,而 ThreadPoolExecutor为Executor接口最常用的实现类。
方式2:使用Executors线程池工具类创建。

方法名 方法描述
newFixedThreadPool(int nThreads) (常用)创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
在这里插入图片描述在这里插入图片描述
newWorkStealingPool(int parallelism) 在这里插入图片描述 创建持有足够线程的线程池来支持给定的并行级别,并通过使用多个队列,减少竞争,它需要穿一个并行级别的参数,如果不传,则被设定为默认的CPU数量。ForkJoinPool支持大任务分解成小任务的线程池
newSingleThreadExecutor()在这里插入图片描述 (常用)创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
newCachedThreadPool()在这里插入图片描述 (常用)创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。线程可以重复利用执行任务。默认为60s未使用就被终止和移除
newSingleThreadScheduledExecutor()在这里插入图片描述 创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
newScheduledThreadPool(int corePoolSize) 在这里插入图片描述 (常用)创建一个定长线程池,支持定时及周期性任务执行。

15.关于多线程中实现线程安全的几种方式?

Synchronized 同步代码块 Lock 和UnLock

Lock和Synchronized区别 :

  1. lock是一个接口,而synchronized是java的一个关键字,synchronized是内置的语言实现;
  2. synchronized在发生异常时候会自动释放占有的锁,因此不会出现死锁;而lock发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生。(所以最好将同步代码块用try catch包起来,finally中写入unlock,避免死锁的发生。)
  3. lock等待锁过程中可以用interrupt来中断等待,而synchronized只能等待锁的释放,不能响应中断;
  4. lock可以通过trylock来知道有没有获取锁,而synchronized不能;
  5. Lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离)

16.用过多线程吗,线程有哪些启动方式?线程有哪些状态?

创建并启动线程的6种方式:

  1. 继承Thread类创建线程
  2. 实现Runnable接口创建线程
  3. 使用Callable和FutureTask创建线程
  4. 使用线程池,例如用Executor框架
  5. Spring实现多线程(底层是线程池)
  6. 定时器Timer(底层封装了一个TimerThread对象)
    线程有几种状态 : 线程共有6种状态
    NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED
    新建、运行、阻塞、等待、带超时的等待、终止

17.java多线程的开发与项目的实际应用

1、后台任务,例如:定时向大量(100w以上)的用户发送邮件;
2、异步处理,例如:发微博、记录日志等;
3、分布式计算

18.对于JVM的CPU资源占用率过高的问题的排查

1.此时发现如果是Java的进程占用过高,并且一直下不来,则排查是什么线程导致占比过高。以图中进程举例,假如发现PID为31357的Java进程占CPU比一直很高,则记录下它的PID
2.查看Java进程里面的线程的占用情况
top -H -p PID端口号
说明:-H 指显示线程,-p 是指定进程
3.通过jstack命令获取占用资源异常的线程栈,可暂时保存到一个文件中查看 jstack 31357 > jstack.31357.log

以上能看到指定线程的堆栈信息。
如果想看到关于线程中的锁的附加信息,可以加一个-l参数
4.吐出的实际日志结果如下jstack -F “PID” > jstack.“PID”.txt
5.显然一直在跑的是19576这个线程,一直在执行EXCEL导出的相关方法,问题就出在这里,下面的任务就是排查这个地方的代码逻辑了。

19.Synchronized 和 Static Synchronized的区别

        Synchronized是对类的当前实例进行加锁,防止其他线程同时访问该类的该实例的所有synchronized块, 类的两个不同实例就没有这种约束了。那么static synchronized恰好就是要控制类的所有实例的访问了,static synchronized是限制线程同时访问jvm中该类的所有实例同时访问对应的代码快。实际上,在类中某方法或某代码块中有 synchronized,那么在生成一个该类实例后,改类也就有一个监视块,防止线程并发访问改实例synchronized保护快,而static synchronized则是所有该类的实例公用一个监视快了.

20.一个主线程,开5个线程同时执行,5个线程都结束主线程才开始执行,怎么做?

例如:CountDownLatch latch = new CountDownLatch(5) //声明计数器为5个
	Thread t = new Thread() {
	public void run() {
	try {
	//TODO 你的应用
	} catch (Exception e) {
	//TODO 异常处理
	}
	finally {
	latch.countDown(); //这句是关键
	}
	}
	};
	t.start();
	System.out.println("ok"); //5个线程都跑完后输出

        然后让以上操作循环五次(就是说同时开5个线程),那么这个"ok"就会在等到这5个线程都ok后才会被输出一次。

21.得到线程执行结果的方法有哪些?

1、根据反射原理实现
        很多人都习惯在调用线程的时候,通过构造方法给线程传递参数,这里我们在构造方法里传两个参数,一个是类 callback,一个是方法名 method。这样我们在线程的run方法最后 执行callback.getMethod(method).invoke(null); 这样就可能执行你指定的某个类下的某个方法了!
2、Callable+ScheduledThreadPoolExecutor实现
        这个是在Java1.5以后,添加了ScheduledThreadPoolExecutor和callable两个组件(接口),ScheduledThreadPoolExecutor大家可能比较熟悉,它的优点就不说了;它用来执行线程进行调度的时候,有一个方法.schedule(Callable callable, long delay, TimeUnit unit),注意:这里的参数是Callable而不是Runnable,大家千万不要被它唬住了,其实它和Runnable差不多,都能实现某个线程,只不过Callable可以添加返回值!这正是我们想要的!所以我们在用SclExecutor调度线程得到返回值 ScheduledFuture后执行get()方法,就可以得到返回值了

22.对网络编程有没有了解过?清楚阻塞IO和非阻塞IO的区别吗?听说过netty框架吗?

        进程(线程)IO调用会不会阻塞进程自己。所以这里两个概念是相对调用进程本身状态来讲的。
        在阻塞模式下,若从网络流中读取不到指定大小的数据量,阻塞IO就在那里阻塞着。比如,已知后面会有10个字节的数据发过来,但是我现在只收到8个字节,那么当前线程就在那傻傻地等到下一个字节的到来,对,就在那等着,啥事也不做,直到把这10个字节读取完,这才将阻塞放开通行。
在非阻塞模式下,若从网络流中读取不到指定大小的数据量,非阻塞IO就立即通行。比如,已知后面会有10个字节的数据发过来,但是我现在只收到8个字节,那么当前线程就读取这8个字节的数据,读完后就立即返回,等另外两个字节再来的时候再去读取。
netty框架是一个基于 JAVA NIO 类库的异步非阻塞通信框架。
(https://blog.csdn.net/mn_kw/article/details/72780507)

23.tcp和udp的区别和应用场景

1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的
UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
5、TCP首部开销20字节;UDP的首部开销小,只有8个字节
6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道
应用场景:TCP 协议(如文件传输、重要状态的更新等);反之,则使用 UDP 协议(如视频传输、实时通信等)。

24.有没有了解JVM的技术原理?平时工作有没有接触过

JVM工作原理:
      JVM最重要的一个特点就是跨平台。java源文件通过javac命令启动编译器,将java源文件编译成class字节码文件,class字节码文件通过java命令启动JVM虚拟机加载class字节码文件。
内存管理:
内存分为两类:线程共享和线程私有内存
线程共享内存:堆、方法区、常量池
线程私有内存:java栈、本地方法栈、pc寄存器
执行流程:类加载机制:父类委托模式
垃圾回收:大部分的GC都是采用分代`收集算法的
工作中的使用:jvm调优
有用过就说有用过,没有就说没有,但是有了解过可以怎么调优。
最经典的就是GC调优。(GC调优https://blog.csdn.net/hellozhxy/article/details/80637199)

25.GC垃圾回收的几种算法

1.标记-清除算法
      该算法先标记,后清除,将所有需要回收的算法进行标记,然后清除;这种算法的缺点是:效率比较低;标记清除后会出现大量不连续的内存碎片,这些碎片太多可能会使存储大对象会触发GC回收,造成内存浪费以及时间的消耗。
2.复制算法
      复制算法将可用的内存分成两份,每次使用其中一块,当这块回收之后把未回收的复制到另一块内存中,然后把使用的清除。这种算法运行简单,解决了标记-清除算法的碎片问题,但是这种算法代价过高,需要将可用内存缩小一半,对象存活率较高时,需要持续的复制工作,效率比较低。
3.标记整理算法
      标记整理算法是针对复制算法在对象存活率较高时持续复制导致效率较低的缺点进行改进的,该算法是在标记-清除算法基础上,不直接清理,而是使存活对象往一端游走,然后清除一端边界以外的内存,这样既可以避免不连续空间出现,还可以避免对象存活率较高时的持续复制。这种算法可以避免100%对象存活的极端状况,因此老年代不能直接使用该算法。
4.分代收集算法
      分代收集算法就是目前虚拟机使用的回收算法,它解决了标记整理不适用于老年代的问题,将内存分为各个年代,在不同年代使用不同的算法,从而使用最合适的算法,新生代存活率低,可以使用复制算法。而老年代对象存活率高,没有额外空间对它进行分配担保,所以只能使用标记清除或者标记整理算法。

26.jdk1.8的JVM有哪几个区域?

在这里插入图片描述
      1.8同1.7比,最大的差别就是:元数据区取代了永久代。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中,而是使用本地内存。

27.写一段栈溢出和堆溢出的代码

栈溢出:

public static void main(String[] args){
			main(args);
} 

堆溢出:

public static void main(String[] args) {
    List<byte[]> list = new ArrayList<>();
    int i=0;
    while(true){
        list.add(new byte[5*1024*1024]);
        System.out.println("分配次数:"+(++i));
    }
}

28.java内存泄漏与内存溢出的区别,什么情况下会造成内存溢出?

内存泄漏memory leak :
是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
内存溢出 out of memory :
指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。
两者的区别:
内存泄漏的堆积最终会导致内存溢出
内存溢出就是你要的内存空间超过了系统实际分配给你的空间,此时系统相当于没法满足你的需求,就会报内存溢出的错误。
内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序。就相当于你租了个带钥匙的柜子,你存完东西之后把柜子锁上之后,把钥匙丢了或者没有将钥匙还回去,那么结果就是这个柜子将无法供给任何人使用,也无法被垃圾回收器回收,因为找不到他的任何信息。
内存溢出:一个盘子用尽各种方法只能装4个果子,你装了5个,结果掉倒地上不能吃了。这就是溢出。比方说栈,栈满时再做进栈必定产生空间溢出,叫上溢,栈空时再做退栈也产生空间溢出,称为下溢。就是分配的内存不足以放下数据项序列,称为内存溢出。说白了就是我承受不了那么多,那我就报错。
内存溢出的原因:
内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
代码中存在死循环或循环产生过多重复的对象实体;
使用的第三方软件中的BUG;
启动参数内存值设定的过小

29.常见的排序

我常用的就是 冒泡排序
最简单的一种排序算法。先从数组中找到最大值(或最小值)并放到数组最左端(或最右端),然后在剩下的数字中找到次大值(或次小值),以此类推,直到数组有序排列。
算法的时间复杂度为O(n^2)。

30.了解过二叉树吗?哪些地方用到过它,能聊聊吗?

      二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”和“右子树”。二叉树常被用于实现二叉查找树和二叉堆。一种属树形结构的数据结构
其中的索引其中就包含一种b-tree就是一种树形结构二叉树在排序、查找、大规模数据索引方面有很多很多应用。

31.B-tree和B+tree的区别,谈谈平衡二叉树和红黑树

B-tree和B+tree的区别:
B+tree是B-tree的变体,也是一种多路搜索树,其定义基本与B-tree同,区别:
1.非叶子结点的子树指针与关键字个数相同;
2.非叶子结点的子树指针P[i],指向关键字值属于[K[i],K[i+1])的子树(B-tree是开区间);
3.为所有叶子结点增加一个链指针;在B+Tree的每个叶子节点增加一个指向相邻叶子节点的指针,就形成了带有顺序访问指针的B+Tree;目的:是为了提高区间访问的性能
4.所有关键字都在叶子结点出现
5.B+Tree中叶节点和内节点一般大小不同;虽然B-Tree中不同节点存放的key和指针可能数量不一致,但是每个节点的域和上限是一致的,所以在实现中B-Tree往往对每个节点申请同等大小的空间

平衡二叉树:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。构造与调整方法 平衡二叉树的常用算法有红黑树、AVL、Treap等。 最小二叉平衡树的节点的公式如下 F(n)=F(n-1)+F(n-2)+1 这个类似于一个递归的数列,可以参考Fibonacci数列,1是根节点,F(n-1)是左子树的节点数量,F(n-2)是右子树的节点数量。

红黑树在原有的排序二叉树增加了如下几个要求:

性质 1:每个节点要么是红色,要么是黑色。
性质 2:根节点永远是黑色的。
性质 3:所有的叶节点都是空节点(即 null),并且是黑色的。
性质 4:每个红色节点的两个子节点都是黑色。(从每个叶子到根的路径上不会有两个连续的红色节点)
性质 5:从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点。

32.一个非接口的类a和类b,类c怎么能够全部使用到a,b的功能?

这个需求有两个方式:
继承关系:使用多重继承,c继承a,a继承b,通过继承可以将功能继承过来
组合关系:c中包含a和b的对象作为c中的字段。
注意:一般我们都使用组合关系来实现,只有a,b,c三者之间有从属关系的时候我们才使用继承关系。

33.抽象类和接口的区别?抽象类能继承实体类吗?

抽象类 接口
默认的方法实现 它可以有默认的方法实现 接口完全是抽象的。它根本不存在方法的实现
实现 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现
构造器 可以有 不能有
与正常java类的区别 除了你不能实例化抽象类之外,它和普通Java类没有任何区别 接口是完全不同的类型
访问修饰符 抽象方法可以有public、protected和default这些修饰符 接口方法默认修饰符是public。你不可以使用其它修饰符。
main方法 抽象方法可以有能够执行的main方法 接口没有main方法
多继承 抽象方法可以继承一个类和实现多个接口 接口只可以继承一个或多个其它接口
速度 比接口速度快 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。
添加新方法 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 如果你往接口中添加方法,那么你必须改变实现该接口的类。

抽象类能继承实体类, 但是抽象类不能直接被实例化对象

34.现再有一字符串abcgfexyz*,请写一个方法,将*号移到首部,并不改变其他字母的顺序,即结果为 *******abcgfexyz

 static void strMove(char[] str) {
        int i = str.length - 1; // 遍历的次数
        int j = i; // j遍历字符串中的非*字符
        while (j > 0 && str[j] != '*') // 跳过最后面的非'*'字符
        {
            j--;
        }
        while (j >= 0 && i >= 0) {
            while (str[i] != '*' && i >= 0) {
                i--;
            }
            while (str[j] == '*' && j >0) {//这里的j不能为0
                j--;
            }
            // 交换j.i处字符
            str[i--] = str[j]; // *前面的字符后移放到*的位置
            str[j--] = '*'; // 将*前移
        }
        System.out.println(str);
    }
    @Test
    public void test(){
        char[] chr={'*','a','b','c','*','*','g','f','e','*','*','*','x','y','z','*'};
        strMove(chr);
    }

35.用java代码怎样实现乐观锁的方案?

      表设计时,需要往表里加一个version字段。每次查询时,查出带有version的数据记录,更新数据时,判断数据库里对应id的记录的version是否和查出的version相同。若相同,则更新数据并把版本号+1;若不同,则说明,该数据发送并发,被别的线程使用了,进行递归操作,再次执行递归方法,知道成功更新数据为止。

发布了18 篇原创文章 · 获赞 1 · 访问量 417

猜你喜欢

转载自blog.csdn.net/weixin_43714592/article/details/104040400