java面试题


java面试题


1. 实现 String 字符串的反转

思路1:String的底层是字符数组,可以先将其转成数组再反转

public static void main(String[] args) {
	String str = "abcdefg";
	System.out.println(reverse(str));	
}

public static String reverse(String str) {
	char[] strArr = str.toCharArray();
	int x,y;
	char temp;
	for(x=0,y=strArr.length-1;x<y;x++,y--) {
		temp = strArr[x];
		strArr[x] = strArr[y];
		strArr[y] = temp;
	}
	return new String(strArr);
}

思路2:利用StringBuilder类的 reverse() 方法实现

public static String reverse(String str) {
	StringBuilder sb = new StringBuilder(str);
	StringBuilder bs = sb.reverse();
	return bs.toString();
}

思路3:利用StringBuilder类的 append() 方法实现

public static String reverse(String str) {
	StringBuilder sb = new StringBuilder();
	for(int i=str.length()-1;i>=0;i--) {
		sb.append(str.charAt(i));
	}
	return sb.toString();
}

思路4:利用StringBuffer类的 reverse() 方法实现

public static String reverse(String str) {
	StringBuffer sb = new StringBuffer(str);
	StringBuffer bs = sb.reverse();
	return bs.toString();
}

思路5:利用StringBuffer类的 append() 方法实现

public static String reverse(String str) {
	StringBuffer sb = new StringBuffer();
	for(int i=str.length()-1;i>=0;i--) {
		sb.append(str.charAt(i));
	}
	return sb.toString();
}

2. String,StringBuffer与StringBuilder的区别

String 是由 final 修饰的,值不可变,每次操作都会生成新的对象(浪费空间、效率低)。

而 StringBuffer 和 StringBuilder 类的对象能够被多次修改且不产生新的未使用对象。

StringBuilder 相比 StringBuffer 效率高,但 StringBuilder 不是线程安全的,StringBuffer 是线程安全的(其内部很多方法用了 synchronized 修饰)。


3. Array 与 ArrayList 的区别

Array是数组,声明后不可改变长度。
ArrayList是底层为数组的集合,长度可变(添加时若空间不足,会创建一个长度是原来1.5倍的新数组,然后将原数组元素拷贝过去)

Array既可以存储对象类型的数据,也可以存储基本数据类型的数据。
而ArrayList 只能存放对象数据类型 的数据,存储基本数据类型需要通过包装类来实现。

ArrayList在Array的基础之上做了很多功能增强,因此在效率上低于Array

当数据长度固定时,选择使用Array,当数据长度不固定时,选择使用ArrayList。
但如果需要频繁的增删元素,ArrayList也不太合适,参考:ArrayList和LinkedList的异同点


4. ArrayList 与 LinkedList 的异同点

两者都实现了List接口和Collection

ArrayList 是基于数组实现的,LinkedList是基于链表实现的。
ArrayList 随机查询速度快,LinkedList插入和删除速度快。

ArrayList内部是动态数组实现,在增加空间时会复制全部数据到新的容量大一些的数组中,耗时较长。
而LinkedList内部为双向链表,可以按需分配空间,扩展容量简单。


5. ArrayList 和 Vector 的区别?

ArrayList:线程不安全、效率高、常用
Vector:线程安全(synchronized 修饰)、效率低


6. List 和 Set 的异同?

List 有序 可重复
Set 无序 不可重复

List 主要有:ArrayList、LinkedList
Set 主要有:HashSet、TreeSet


7. 抽象类和接口的异同?

抽象类和接口都不能被实例化
一个类如果继承某个抽象类或实现某个接口都需要实现其所有的抽象方法,否则该类仍要被声明为抽象类。

抽象类中可以定义构造器,可以有抽象方法和具体方法。
接口中不能定义构造器,可以有抽象方法和默认方法。

抽象类中的成员可以是 private、默认、protected、public
接口中的成员全都是public

抽象类中可以定义成员变量,而接口中定义的成员变量实际上都是常量


8. 访问修饰符 public,private,protected, 以及不写(default)的区别

private 修饰的类、方法和属性只能被本类对象访问
default 默认只能在同一个包内访问
protected 修饰的类、方法和属性能在同一个包内访问,也能被其他包的子类访问
public 修饰的类、方法、属性可以跨类和跨包访问

在这里插入图片描述


9. 逻辑与&和短路与&&的区别

&运算符有两种用法:
(1)按位与;(2) 逻辑与

&&运算符是 短路与

逻辑与与短路与的异同:

二者都要求运算符左右两端的布尔值都是true整个表达式的值才是true
但如果短路与运算&&左边表达式的值为false时,右边表达式会被直接短路掉,不会进行运算。

例如在验证用户登录时判定用户名不是null且不是空字符串,应当写为:
username != null && !username.equals("")
二者的顺序不能交换,更不能用&运算符,因为第一个条件如果不成立,根本不能进行字符串的equals比较,否则会产生NullPointerException异常。

注:逻辑或运算符 | 和短路或运算符 || 的差别同理。


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

在Java 5以前,switch(expr)中,expr只能是byte、short、char、int
从Java 5开始,Java中引入了枚举类型,expr也可以是enum类型
从Java 7开始,expr还可以是字符串(String)
但是长整型(long)在目前所有的版本中都是不可以的


11. 在一个.java文件里面,可以有多个类吗,有什么限制?

一个.java的源文件中可以包含多个类
但是public类只能有一个,并且类名要和文件名相同


12. java如何获取字符串编码格式?

byte[] bytes = str.getBytes(“UTF-8”) 获取字符串在指定编码(UTF-8)下的byte数组表示

new String(bytes, “UTF-8”); 将byte数组通过指定编码(UTF-8)还原成字符串

这样将字符串先转成byte数组再还原,然后与原来的字符串比较,如果不等,说明该字符串不是UTF-8编码。

示例:

public static void main(String[] args) {
	String str = "你好";
	System.out.println(getEncoding(str));
}

public static String getEncoding(String str) {
	
	String encode = "ISO-8859-1";;
	try {
		if(str.equals(new String(str.getBytes(encode), encode))) {
			String s = encode;
			return s;
		}
	} catch (Exception exception) {
		
	}
			
	encode = "UTF-8";
	
	try {
		if (str.equals(new String(str.getBytes(encode), encode))) {
			String s1 = encode;
			return s1;
		}
	} catch (Exception exception1) {
	
	}
	
	encode = "GB2312";
	try {
		if (str.equals(new String(str.getBytes(encode), encode))) {
			String s2 = encode;
			return s2;
		}
	} catch (Exception exception2) {
	
	}
		
	encode = "GBK";
	try {
		if (str.equals(new String(str.getBytes(encode), encode))) {
			String s3 = encode;
			return s3;
		}
	} catch (Exception exception3) {
	
	}
	
	return "";
	
}

13. hashcode() 与equals() 的区别?

hashCode() 的作用是获取哈希码(哈希码的作用是确定该对象在哈希表中的索引位置)

equals它的作用是判断两个对象是否相等
如果对象重写了equals()方法,比较两个对象的内容是否相等;
如果没有重写,则是比较两个对象的地址是否相同,价于“==”

如果两个对象equals,则他们的hashcode相等。
如果两个对象hashcode相等,他们不一定equals。


14. ==与equals()区别

对于基本数据类型的变量,== 比较的是值是否相等
对于引用类型的变量,== 比较的是所指向的对象的地址是否相等

equals()是Object类中的方法,没有被重写时,equals()和 == 没有区别
有些类(比如String)重写了equals()方法,比较的是两个对象的值是否相同


15. HashSet 的存储原理?

HashSet 的底层采用的是 HashMap 来存储(其值作为 HashMap 的 key)

public boolean add(E e) {
	return map.put(e, PRESENT)==null;
}

16. HashMap 的底层存储原理?

HashMap 底层采用的是数组,在数组中存储数据时,考虑到唯一性问题,通过遍历的方式来判断效率低
采用 hash 算法,通过计算存储对象的hashcode跟数组长度-1做位运算,得到存储在数组的哪个下标下。
当存储数据过多,出现 哈希冲突(Aka哈希碰撞:不同对象计算出来的hash值相同),此时才需要通过 equals 比较。
如果哈希冲突且对象不相等,则新加入的对象指向原来位置的对象并取代其位置,组成链表。
所谓的哈希表本质是一个数组,数组中的元素是链表。

JDK1.8的优化:随着元素的不断添加,链表可能会越来越长,因此会采用红黑树的方式。


17. 详解 ConcurrentHashMap?
  1. 线程不安全的HashMap (在多线程环境下,使用HashMap 进行put操作会引起死循环,导致CPU利用率接近100%)

  2. 效率低下的HashTable (HashTable 容器使用synchronized 来保证线程安全,但在线程竞争激烈的情况下HashTable 的效率非常低下)

  3. ConcurrentHashMap锁分段技术(将锁的粒度变小),兼顾了线程安全和性能。

锁分段技术:
map.put(key1, value1) 根据key1值hash一次得到所在的段,再hash一次得到在段里的位置。
此时map.put(key2, value2) 如果操作的是不同的段,则不用阻塞,也不会出现线程不安全的问题。


18. 如何设计一个双向链表的节点类?
class Node<T>{
	Node pre;
	Node next;
	T data;
}

19. 谈谈IO流的分类及选择?

按方向分:输入流、输出流

按读取单位分:
字节流、字符流(字节流读取二进制文件,用于拷贝文件,字符流读取文本文件,用于解析文件)

按处理方式分:节点流、处理流

IO流四大基类:InputStream、OutputStream、Reader、Writer


20. serialVersionUID的作用是什么?

当执行序列化,写对象到磁盘中时,会根据该类的结构生成一个版本号ID

当反序列化时,程序会比较磁盘中的序列化版本号ID与当前类结构生成的版本号ID是否一致,如果不一致则反序列化失败。

显示声明 serialVersionUID 可以避免对象不一致导致反序列化失败

private static final long serialVersionUID = 1L;

21. 如何根据 List 中的对象属性值将 List 排序?
Collections.sort(list, new Comparator<Info>() {

	@Override
	public int compare(Info o1, Info o2) {
		return o1.getDate().compareTo(o2.getDate());
	}
			
});

22. 常见的5个运行时异常和5个非运行时异常
  • 运行时异常:
    算数异常
    空指针异常
    类型转换异常
    数组越界异常
    非法参数异常

  • 非运行时异常:
    IOException
    SQLException
    FileNotFoundException
    NoSuchFileException
    NoSuchMethodException


23. 创建线程的三种方式

继承Thread类,重写run方法,创建对象,调用start方法启动线程

class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+":running.....");
    }
}

public static void main(String[] args){
        MyThread thread = new MyThread();
        thread.start();
}

实现Runable接口,实现run方法,根据该对象创建Thread对象,调用start方法启动线程

class MyTask implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+":running....");
    }
}

public static void main(String[] args){
	MyTask task = new MyTask();
    new Thread(task).start();
}

实现Callable接口,实现call方法,根据该对象创建Thread对象,调用start方法启动线程。

class MyTask2 implements Callable<Boolean>{

    @Override
    public Boolean call() throws Exception {
        return null;
    }
}

public static void main(String[] args){
	MyTask2 task2 = new MyTask2();
    new Thread(task2).start();
}

该种方式可以获取线程执行之后的返回值。


24. 线程的生命周期

在这里插入图片描述


25. Sleep和wait的区别
使目前正在执行的线程休眠1s
Thread.sleep(1000);

当前执行的线程等待1s,1s后自动醒来
wait(1000);

当前线程等待,直到被notify()notifyAll()方法唤醒
wait();

sleep方法定义在Thread上,wait方法定义在Object上
sleep不会释放锁,wait会释放锁
sleep可以使用在任何代码块,wait必须在同步方法或同步代码块执行

【补充】与wait配套使用的方法:

//唤醒在此对象监视器上等待的单个线程
void notify()

//唤醒在此对象监视器上等待的所有线程
void notifyAll()

26. JDK提供的线程池有哪些?如何使用?

27. 谈谈你对线程安全的理解,如何做到线程安全?

如果当多个线程同时访问某个对象时,不需要额外的同步控制,调用该对象的行为都可以获得正确的结果,那么该对象就是线程安全的。

采用synchronized关键字给代码块或方法加锁来实现线程安全,比如StringBuffer


28. 谈谈你对ThreadLocal的理解

29. 谈谈类的加载机制

30. 并发和并行的区别

并发:同一个CPU执行多个任务,按细分的时间片交替执行
并行:在多个CPU上同时处理多个任务


31. synchronized 和 volatile 的区别

synchronized 修饰方法和代码块

synchronized 修饰静态方法时,锁定的是类
synchronized 修饰非静态方法时,锁定的是方法的调用者
synchronized 修饰代码块时,锁定的是传入的对象

volatile 修饰变量

synchronized,可以保证变量修改的可见性及原子性,可能会造成线程的阻塞
volatile仅能实现变量修改的可见性,但无法保证原子性,不会造成线程的阻塞


32. synchronized 和 lock 的区别

synchronized 可以修饰方法和代码块
lock 只能修饰代码块

synchronized 无需手动获取和释放锁,发生异常会自动解锁,不会出现死锁
lock 需要手动 加锁 lock()释放锁 unlock(),如果忘记 unlock() 则会出现死锁


33. TCP和UDP的区别

两者都是传输层的协议
TCP提供可靠的传输协议,传输前需要建立连接,面向字节流,传输慢
UDP无法保证传输的可靠性,无需创建连接,以报文的方式传输,效率高


34. 常见的响应状态码

200:表示服务器已经成功接受请求,并将返回客户端所请求的最终结果

400:(错误请求) 服务器不理解请求的语法
403:(禁止) 服务器拒绝请求
404:(未找到) 客户端请求的资源没有找到或者是不存在
405:(方法禁用) 禁用请求中指定的方法

500:(服务器内部错误) 服务器遇到错误,无法完成请求
503:(服务不可用) 服务器目前无法使用(由于超载或停机维护)。通常这只是暂时状态。


35. 谈谈什么是TCP的三次握手,什么是TCP的四次挥手

三次握手:
在这里插入图片描述
客户端发送一个带SYN标志的TCP报文到服务器。(请求连接)

服务器端回应客户端的报文中同时带ACK标志和SYN标志。ACK表示对刚才客户端SYN报文的回应(我收到了你的请求);同时又标志SYN给客户端( 你是否准备好进行数据通讯)

客户必须再次回应服务段一个ACK报文(我准备好通讯了)

四次挥手:
在这里插入图片描述
第一次挥手:
客户端发送关闭连接请求,表示客户端已经没有数据再需要发送给服务端了。

第二次挥手:
服务端发送确认报文,此时客户端向服务器的方向就释放了。
客户端收到服务端的确认后,继续等待服务端发送连接关闭报文。

第三次挥手:
服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,并等待客户端的确认。

第四次挥手:
客户端收到服务器的连接释放报文后,发出确认报文。


36. 死锁的产生以及如何避免?

死锁的产生

线程A持有独占锁资源1,并尝试获取独占锁资源2
同时线程B持有独占锁资源2,并尝试获取独占锁资源1
线程A、B相互持有对方需要的锁,从而发生阻塞,最终变成死锁。

示例代码

public class MyTest {
	
	private static final Object a = new Object();
	private static final Object b = new Object();
	
	public static void main(String[] args) {
		new Thread(new Task(true)).start();
		new Thread(new Task(false)).start();
	}
	
	static class Task implements Runnable{
		private boolean flag;
		
		public Task(boolean flag) {
			this.flag = flag;
		}
		
		@Override
		public void run() {
			if(flag) {
				synchronized (a) {
					System.out.println(Thread.currentThread().getName()+ "获取到了a资源");
					synchronized (b) {
						System.out.println(Thread.currentThread().getName()+ "获取到了b资源");
					}
				}
			}else {
				synchronized (b) {
					System.out.println(Thread.currentThread().getName()+ "获取到了b资源");
					synchronized (a) {
						System.out.println(Thread.currentThread().getName()+ "获取到了a资源");
					}
				}
			}
		}
		
	}
	
}

如何防止死锁

  1. 减少同步代码块的嵌套
  2. 降低锁的使用粒度
  3. 使用 tryLock(timeout) 的方式,设置超时时间,超时后主动退出

37. 什么是悲观锁,什么是乐观锁?

悲观锁是利用数据库本身的锁机制来实现,会锁记录。
实现的方式为:select * from t_table where id = 1 for update

乐观锁是一种不锁记录的实现方式,采用version字段来作为判断依据。
每次对数据的更新操作,都会对version+1,这样提交更新操作时,如果version的值已被更改,则更新失败。


38. 如何实现将两个集合A、B中重复的元素放入集合C中

暴力遍历,时间复杂度为O(n²):

List<String> list1 = Arrays.asList("a","b","c");
List<String> list2 = Arrays.asList("a","b");
List<String> list3 = new ArrayList<>();
for (String a : list1) {
	if(list2.contains(a)){
		list3.add(a);
	}
}
System.out.println(list3);

通过 set 集合优化,时间复杂度为O(n)

List<String> list1 = Arrays.asList("a","b","c");
List<String> list2 = Arrays.asList("a","b");
List<String> list3 = new ArrayList<>();
Set<String> set = new HashSet<>();
for (String a : list1) {
	set.add(a);
}
for (String b : list2) {
	if(set.contains(b)){
		list3.add(b);
	}
}
System.out.println(list3);

39. 如何实现线程同步

使用synchronized 关键字同步方法
使用synchronized 关键字同步代码块
使用特殊域变量(volatile)实现线程同步
使用重入锁实现线程同步
使用局部变量ThreadLocal实现线程同步


40. 谈谈SpringBoot的工作原理
  1. 我们使用SpringBoot时,由于父工程有对版本的统一控制,所以大部分第三方包,我们无需关注版本,个别没有纳入SpringBoot管理的,才需设置版本号

  2. SpringBoot将所有的常见开发功能,分成了一个个场景启动器(starter),这样我们需要开发什么功能,就导入什么场景启动器依赖即可。
    比如,我们现在要开发web项目,所以我们导入了spring-boot-starter-web
    我们需要开发模板页的功能,那么引入spring-boot-starter-thymeleaf
    我们需要整合redis,那么引入spring-boot-starter-data-redis
    我们需要整合amqp,实现异步消息通信机制,那么引入spring-boot-starter-amqp

  3. 为什么我们不需要配置?
    SpringBoot启动类上有一个注解:@SpringBootApplication
    该注解是一个复合注解,包含了很多的信息:@SpringBootConfiguration、@EnableAutoConfiguration
    @SpringBootConfiguration 本质上就是 @Configuration
    @EnableAutoConfiguration 的作用就是告诉SpringBoot开启自动配置功能
    其内部包含了 @AutoConfigurationPackage 和 @Import(AutoConfigurationImportSelector.class)
    @AutoConfigurationPackage 内部为 @Import(AutoConfigurationPackages.Registrar.class),给容器导入了一个Registrar组件,该组件的作用是加载启动类所在包下面的所有类
    @Import(AutoConfigurationImportSelector.class) 默认加载了好多的自动配置类


41. 用户反映你开发的网站访问很慢可能会是什么原因?如何解决?

可能原因:服务器带宽不够
解决办法:加带宽

可能原因:服务器负载过高,内存不够
解决办法:优化服务器,做服务器集群

可能原因:网站的开发代码没写好
解决办法:优化代码,优化sql语句(通过mysql的慢查询日志查找问题sql)

可能原因:数据库性能瓶颈
解决办法:优化数据库(比如分库分表、读写分离)

其他办法:
使用非关系型数据库的缓存机制
使用CDN(内容分发网络),根据用户的IP分配不同地区的静态资源服务器


42. 什么是进程?什么是线程?

进程指运行中的应用程序,每个进程的启动,操作系统都会为其分配独立的地址空间。
线程由进程创建,是被系统独立调度和分派的基本单位,
一个进程中可以包含多个线程
线程可与同属一个进程的其它线程共享进程所拥有的全部资源


43. Nginx 配置负载均衡有哪些策略

默认轮询、根据权重分配、根据访问ip的hash分配(可解决session问题)、根据后端服务器的响应时间来分配

https://blucoding.blog.csdn.net/article/details/109026675


已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页