几道最基础面试题

几道最基础面试题

一、方法的参数传递

   1、值传递与引用传递的理解

     个人认为:不存在引用传递,均是值传递

   2、方法传参是否会修改值

     如下图所示代码:
在这里插入图片描述
     对应的输出结果是:
在这里插入图片描述
    值传递: 传的是值的副本
    局部变量的作用域: 局部变量随着方法而产生,也随着方法而销毁
    引用类型的修改: 修改的是堆中的数据值
    String类型的存放: 存放于字符串常量池被final修饰
    String类型的修改: 先判断字符串常量池中,有没有修改后的值?有的话,直接指向原有的值;没有的话,再创建一个,指过去(其实就是修改了地址值

    基础类型的包装类: 方法传参中,具有不可变性

     解析代码:
       main方法中: age变量的 值是20
              personName变量的 值是地址值(“abc”在堆中的地址值)
              str变量的 值是地址值(“abc”在堆中的地址值)
       changeValue1方法中: age变量只是一个副本,原值是20,修改后是30 (基本类型)
                  但是在方法销毁后,这个age变量也被销毁,也就是这个副本被销毁
                  而main方法中的原件变量值并没有被修改
       changeValue2方法中: personName变量只是一个副本,修改时是修改了地址值所指向堆中的数据值 (引用类型)
                  堆中的原值是“abc”,修改后是“xxx”,但是其地址值没变
                  但是在方法销毁后,这个personName变量也被销毁,也就是这个副本被销毁
                  而main方法中的原件变量值并没有被修改,还是原来的地址值,此时该地址值指向的堆中数据却被修改了
       changeValue3方法中: str变量只是一个副本,修改时是修改了地址值 (String类型)
                  原值是“abc”在字符串常量池中的地址值,修改后是“xxx”在字符串常量池中的地址值
                  但是在方法销毁后,这个str变量也被销毁,也就是这个副本被销毁
                  而main方法中的原件变量值未被修改,还是那个地址值,此时该地址值指向字符串常量池中的数据未被修改

二、== 与 equals

   1、== 与 equals 的使用要求

     == : 可使用于基本类型,也能使用于引用类型
     equals : 只能使用于引用类型

   2、== 与 equals 的对比

     如图所示代码:
在这里插入图片描述
     对应的结果为:
在这里插入图片描述
    解析代码:
     == : 比较的就是 栈中变量的值 ;如果是 基本类型 ,就是比较数据值;如果是 引用类型 ,就是比较地址值
        凡是 new出来的对象栈中变量的值都是地址值;而 不同的new对象地址值是不一样的,所以 == 比较百分百是false
     equals: 属于Object类的方法;Object类中对其的实现就是用了 ==
          所以只要一个类 没有重写equals方法 ,那么就等同于 ==
          String类, 重写了equals方法 ,而且是 比较数据值的hashCode值
          String类,重写了hashCode方法只要数据值一样,hashCode值就一样
          所以String变量中,只要 数据值一样 ,用equals方法比较就肯定是true
     HashSet : 根据hashCode值比较是否是同一个值new出来的对象,hashCode值都不一样除非重写了hashCode方法
           String类,重写了hashCode方法只要数据值一样,hashCode值就一样

     所以上述代码中,结果如图所示

三、String 的各类比较

   1、String 的存放

     在 jdk1.8 之前,字符串常量池在方法区中;为了减小方法区内存溢出风险,在 jdk1.8 之后 就把常量池移到java 堆中

     String s = "abc":创建的对象,存储在字符串常量池中;在创建字符串对象之前,会先在 常量池中检查是否存在 abc 对象 ;存在,则直接返回常量池中 abc 对象的引用;不存在,会在常量池中创建该对象并将常量池中该对象的引用返回给对象 s
       简单来说,就是String s = ""创建的局部变量 s,其值是字符串常量池的地址值

     String s = new String("abc"):实际上 abc 本身就是字符串常量池中的一个对象,在运行 new String() 时,把常量池中的字符串 abc 复制到堆中;因此该方式 会在堆和常量池中都创建 abc 字符串对象最后把 java 堆中对象的引用返回给 s
       简单来说,就是String s = new String("")创建的局部变量 s,其值是堆的地址值

   2、String 的 intern 方法

     调用 String 的 intern 方法 时,先 判断字符串常量池中是否已经有对应的字符串(通过 String 的 equals 方法比较);有的话,则直接返回常量池中对应的地址值;没有的话,就先在常量池中创建,再返回常量池中对应的地址值
     所以:对于任意两个字符串 s 和 t ,s.intern() == t.intern()比较;仅当s.equals(t)为true时,才为true
       简单来说,就是String s = s.intern()创建的局部变量 s,其值是字符串常量池的地址值

   3、String 的各类比较

     如图所示代码:
在这里插入图片描述
在这里插入图片描述
     对应的结果为:
在这里插入图片描述
     解析: 上述代码中,栈中局部变量
       s1的值是 字符串常量池 的地址值,s2的值是 的地址值;所以两个地址值不一样
       s1和s5的 字符串值都不一样 了,字符串常量池中的地址值肯定也不一样
       s1和s6的 字符串值都不一样 了,字符串常量池中的地址值肯定也不一样
       s1和s6.intern()的 字符串值都不一样 了,字符串常量池中的地址值肯定也不一样
       s2的值是 的地址值,s2.intern()的值是 字符串常量池 的地址值;所以两个地址值不一样

四、代码块与构造方法的执行顺序

   1、普通代码块的概念与执行顺序

     如图所示代码:
在这里插入图片描述
     对应的结果为:
在这里插入图片描述
     普通代码块: 在方法或语句中出现的{...};就称为普通代码块;普通代码块可以多次执行

     普通代码块的执行顺序: 两部分普通代码块,执行顺序是“先出先执行”

   2、构造代码块与构造方法的执行顺序

     1)构造代码块的 概念
       直接在类中定义且 没有用static修饰、没有名字{...}代码块;称为构造代码块;构造代码块可以 多次执行

     2)构造代码块的 执行顺序
       两部分构造代码块,执行顺序是“先出先执行”

     3)构造代码块的 执行时机
       构造代码块在每次创建对象时都会被调用;是在构造方法方法中的隐式调用在隐式调用父类构造方法之后

     4)构造代码块与构造方法的优先级
       如图所示代码:
在这里插入图片描述
       对应的结果为:
在这里插入图片描述
       解析: 构造代码块优先于构造方法

   3、静态代码块、构造代码块、构造方法的优先级

     1)静态代码块、构造代码块、构造方法的 优先级
       静态代码块 > 构造代码块 > 构造方法
       静态代码块只执行一次;构造代码块和构造方法可多次执行

     2)无继承情况例子说明
       如图所示代码:
在这里插入图片描述
在这里插入图片描述
       对应的结果为:
在这里插入图片描述
       解析:
         (1)在运行CodeBlock03的 main方法前,生成CodeBlock03的模板,此时运行了CodeBlock03的静态代码块
         (2)之后运行CodeBlock03的main方法
         (3)调用Code的构造方法,需要 先生成Code的模板,才能调用构造方法;此时优先运行了Code的静态代码块
         (4)运行完Code的静态代码块之后,需要先运行Code的构造代码块
         (5)运行完Code的构造代码块之后,才能运行Code的构造方法

         之后的调用,由于 静态代码块只执行一次 ,所以在原本的顺序下,踢出静态代码块即可

     3)有继承情况例子说明
       如图所示代码:
在这里插入图片描述
在这里插入图片描述
       对应的结果为:
在这里插入图片描述
       解析:
         (1)运行TestStaticSeq的main方法
         (2)调用子类的构造方法,需要 先生成父类的模板,才能生成子类的模板;此时优先运行了父类的静态代码块
         (3)生成父类的模板后,要先生成子类的模板;才能运行子类的构造方法,此时优先运行了子类的静态代码块
         (4)运行子类构造方法,先 调用父类构造方法(隐式);调用父类构造方法,需先运行父类构造代码块
         (5)运行了父类构造代码块之后,才会运行父类的构造方法
         (6)在子类构造方法中,调用完父类构造方法后(隐式),会调用子类的构造代码块
         (7)运行完子类的构造代码块之后,才会运行子类的构造方法

         之后的调用,由于 静态代码块只执行一次 ,所以在原本的顺序下,踢出静态代码块即可

     4)构造方法的 隐式三步
       在 调用一个类的构造方法时(无论是无参还是有参) ,都会有一个隐式三步
         (1)调用父类的构造方法(无参调无参,有参调有参)
         (2)给非静态成员变量赋予显示值
         (3)调用自身的构造代码块

       具体如下:下面代码中,1、2、3步就是隐式3步最后还有一个隐式 return 返回

     注意: 隐式三步的第二步和第三步是不固定的,哪个写前面就先哪个

public class Father{

    private int id = 007;
    private String name = "零零发";
    private int age = 98;
    private float height = 1.78F;

    public Teacher() {
        // 1、super();  调用父类的构造方法(无参调无参,有参调有参)

		// 2、id = 007;  给非静态成员变量赋予显示值(在JVM内存程度上,真正的关联变量名和变量值)
			.
			.
			.
			
		// 3、{...}  调用自身的构造代码块

		...  // 自己手写的代码

		// return;  隐式返回
    }

五、linux的常用命令

   1、top:查看整体机器性能

     进程和程序,看看哪个进程占比较高
     %CPU:CPU使用占比
     %MEM:内存使用占比
     %Cpu(s):Cpu使用情况(在多核机器时,可通过连按6次1键;来查看每个CPU的使用情况
       id:idle,空闲率(越大越好)
     load average:负载的平均数;如果有三个数,就是 1分钟、5分钟、15分钟的系统平均负载量
       三个数相加,除以3,乘以百分百;如果高于60%,说明系统负载严重
     按键 q,退出

   2、uptime:简化版的查看整体机器性能

     一样有 load average

   3、free -m:查看内存情况

     free: 按字节展示,自己的换算
     free -g: 以 g 为单位展示,没小数;所以误差比较大
     所以:建议使用free -m

   4、df -h:查看硬盘情况

     df: 按字节展示,自己的换算
     所以:建议使用df -h

   5、vmstat -n 2 3:查看CPU情况,每2秒一次,显示3条记录

     看头的进程数,看尾的CPU使用情况

   6、iostat - xdk 2 3:查看磁盘io情况

     看最后的 %util ,说明磁盘IO高,可能需要进行SQL语句优化

   7、chmod:权限命令

   8、ifconfig:查看ip

六、Spring 的 bean作用域

   Spring 的 bean 有 4种 作用域,分别是:singleton、prototype、request、session
     1、singleton: 单例的作用域,默认值;在 初始化 IOC 容器时 创建对应的唯一的bean,之后再也不创建

     2、prototype: 原型的作用域;这里的原型指的是每一个 getBean() 方法都会创建自己的bean

     3、request: 每一次请求都会实例化一个bean

     4、session: 每一个会话都会实例化一个bean
   通过 scope 属性进行指定

七、Spring支持的常用数据库事务传播行为与隔离级别

   1、事务原理

     依赖于真正的数据库事务,使用AOP实现;以下提到的代理默认使用动态代理(AOP中)

     1)纯JDBC操作数据库,实现事务
       (1)获取连接 Connection con = DriverManager.getConnection()
       (2)开启事务con.setAutoCommit(true/false)
       (3)执行CRUD
       (4)提交事务/回滚事务 con.commit() / con.rollback()
       (5)关闭连接 conn.close()

     2)spring操作数据库,实现事务:无需上述的(2)、(4)两步,由spring自动完成
       (1)配置文件开启注解驱动,在相关的类和方法上通过注解@Transactional标识
       (2)spring 在启动的时候会去解析生成相关的bean,这时候会查看拥有相关注解的类和方法,并且为这些类和方法生成代理,并根据@Transaction的相关参数进行相关配置注入,这样就在代理中为我们把相关的事务处理掉了(开启正常提交事务,异常回滚事务)
       (3)真正的数据库层的事务提交和回滚是通过 binlog 或者 redo log 实现的

   2、传播行为

     1)传播行为的理解: 当一个方法调用另一个方法时,两个方法均有事务时,对多个事务的处理机制

     2)传播行为的类型:7种 传播行为,但是 常用的就两种required、requires_new

     3)传播行为的设置: 通过 propagation 属性进行指定

   3、数据库事务并发可能出现的问题

     1)脏读: 线程A在修改数据库,线程B读取了修改后的值进行后续操作;线程A却发生了 回滚 ,线程B读取的数据就是脏读

     2)不可重复读: 线程A读取了一次数据,线程B修改了这个数据,线程A再次读取时,出现两次读取不一致

     3)幻读: 线程A读取了数据行,线程B增加或删除了n行,线程A再次读取数据行时,出现两次读取行数不一致

   3、隔离级别

     1)隔离级别的理解: 针对 第2点的三种问题 的处理级别

     2)隔离级别的类型:4种 传播行为
       (1)read uncommitted:读未提交
       (2)read committed:读已提交
       (3)repeatable read:可重复读
       (4)serializable:串行化
     由于 隔离级别越高数据一致性越好,但并发性越差
     所以 常用的就两种read committed、repeatable read

     3)隔离级别的设置: 通过 isolation 属性进行指定

     4)Oracle 和 Mysql 对隔离级别的支持情况:
在这里插入图片描述

八、SpringMVC 大概的工作流程

在这里插入图片描述

九、Mybatis 中实体类属性名和数据表字段名不一致的解决办法

   1、sql 语句中对字段名进行起别名

   2、在 Mybatis 的全局配置文件中开启驼峰形式

   3、在 Mapper 映射文件中使用 resultMap 来自定义映射规则

十、Redis持久化的基础

   1、RDB 持久化

     在 指定的时间间隔 内,将内存中的数据集快照写入磁盘
     恢复时,直接将快照文件读到内存中
在这里插入图片描述
     优点: 节省磁盘空间;恢复速度快
     缺点: 数据庞大时比较消耗性能;有可能会丢失最后一次快照之后的数据

   2、AOF 持久化

     以日志的方式来记录每个写操作,只追加文件不改写文件
     Redis启动之初会读取该文件重新构建数据;也就是Redis重启时,根据日志文件内容从前至后执行一次,已完成恢复工作
     优点: 备份机制更稳健;丢失数据概率更低;可以处理误操作
     缺点: 比RDB占用更多的磁盘空间;恢复速度慢;每次读写都同步的话,有一定性能压力;存在个别bug,没办法恢复

十一、Mysql 的索引创建时机

   1、索引的本质

在这里插入图片描述

   2、哪些情况需要创建索引

     1)主键自动创建唯一索引

     2)频繁作为查询条件的字段

     3)与其他表关联的字段(外键关系)

     4)查询中需要统计或者分组的字段

     5)查询中需要排序的字段

     6)单键索引和组合索引,组合索引的性价比更高

   3、哪些情况不要创建索引

     1)表记录太少

     2)经常增删改的表或者字段

     3)查询条件中用不到的字段

     4)过滤性不好的字段

十二、JVM 垃圾回收机制的基础

   1、发生在哪个部分:堆

   2、gc 有几种:minor gc;full gc

   3、gc 的算法:

     1)引用计数算法:
在这里插入图片描述

     2)复制算法: 主要是 minor gc 在使用
在这里插入图片描述

     3)标记清除算法: 主要是 full gc 在使用
在这里插入图片描述

     4)标记压缩算法: 主要是 full gc 在使用
在这里插入图片描述

     5)标记清除压缩算法: 主要是 full gc 在使用; 3)和4)的结合 ;先标记、后清除;等到内存碎片较多时,进行压缩

十三、Redis在项目中的使用场景

在这里插入图片描述

十四、几种常用的设计模式

   1、单例设计模式应用场景

  • 网站的计数器:一般也是单例模式实现,否则难以同步
  • 应用程序的日志应用:一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加
  • 数据库连接池的设计:一般也是采用单例模式,因为数据库连接是一种数据库资源
  • 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取
  • Application:也是单例的典型应用
  • Windows的Task Manager (任务管理器):就是很典型的单例模式
  • Windows的Recycle Bin (回收站):也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例

   2、模板方法设计模式应用场景

abstract class Template {
	public final void getTime() {
		long start = System.currentTimeMillis();
		code();
		long end = System.currentTimeMillis();
		System.out.println("执行时间是:" + (end - start));
	}
	
	public abstract void code();
}

class SubTemplate extends Template {
	public void code() {
		for (int i = 0; i < 10000; i++) {
			System.out.println(i);
		}
	}
}
  • 数据库访问的封装
  • Junit单元测试
  • Hibernate中模板程序
  • JavaWeb的Servlet中关于 doGet / doPost 方法调用
  • Spring中JDBCTemlate、HibernateTemplate等

   3、代理模式设计模式应用场景

interface Network {
	public void browse();
}

// 被代理类
class RealServer implements Network { @Override
	public void browse() {
		System.out.println("真实服务器上网浏览信息");
	}
}

// 代理类
class ProxyServer implements Network {
	private Network network;
	public ProxyServer(Network network) {
		this.network = network;
	}
	public void check() {
		System.out.println("检查网络连接等操作");
	}
	public void browse() {
		check();
		network.browse();
	}
}

public class ProxyDemo {
	public static void main(String[] args) {
		Network net = new ProxyServer(newRealServer());
		net.browse();
	}
}

应用场景:

  • 安全代理:屏蔽对真实角色的直接访问
  • 远程代理:通过代理类处理远程方法调用(RMI)
  • 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象
       比如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有100MB,在打开文件时,不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用proxy来进行大图片的打开

分类:
   静态代理(静态定义代理类)
   动态代理(动态生成代理类)
     JDK自带的动态代理,需要反射等知识

十五、几道网红题

   1、看似考察参数传递,实则不是

在这里插入图片描述
     解决办法:
       1)运行 method 方法的打印,不运行 main 的打印
在这里插入图片描述
       2)捕获打印流,重写打印流
在这里插入图片描述

   2、数组除以首元素后遍历

定义一个int型的数组:int[] arr = new int[]{12,3,3,34,56,77,432};让数组的每个位置上的值去除以首位置的元素,得到的结果,作为该位置上的新值;遍历新的数组
在这里插入图片描述

   3、数组输出值问题

在这里插入图片描述
     前者是地址值,后者是数据值
在这里插入图片描述

   4、多态性问题

class Base {
	int count = 10;
	public void display() {
		System.out.println(this.count);
	}
}

class Sub extends Base {
	int count = 20;
	public void display() {
		System.out.println(this.count);
	}
}

public class FieldMethodTest {
	public static void main(String[] args){
		Sub s = new Sub();	// 此步,new 对象时,堆中的 new Sub() 对象中,已经包含父类的成员变量和方法(方法被覆盖)
		System.out.println(s.count);	// 20
		s.display();	// 20
		Base b = s;	// 向上转型,多态性;赋值的是地址值
		System.out.println(b == s);	// true(==:在引用类型时,比较的是地址值)
		System.out.println(b.count);	// 10(向上转型的多态性,对于成员变量是无效的;b 调就是 b 的成员变量)
		b.display();	// 20(向上转型的多态性,对方法生效;父类方法被子类覆盖)
	}
}

   5、包装类问题

// 三元运算符:后两者的值应是同类型,所以此处会将 Integer 转换成Double
Object o1 = true ? new Integer(1) : new Double(2.0);
System.out.println(o1); // 输出:1.0
Object o2;
if (true)
	o2 = new Integer(1);
else
	o2 = new Double(2.0);
System.out.println(o2); // 输出:1

   6、多线程问题

在这里插入图片描述
sleep() 和 wait() 有什么区别?

  • 定义位置不一样: sleep()方法定义在Thread中;wait()方法定义在Object中
  • 使用要求不一样: sleep()方法可以随时随地进行调用,wait()方法只能在同步代码块或同步方法中调用
  • 唤醒方式不一样: sleep()方法只要时间到了就自动唤醒,wait()方法需要使用notify()或notifyAll()方法进行唤醒
  • 是否存在异常不一样: sleep()方法的使用需要处理异常,wait()方法的使用不用处理异常
  • 是否释放对象锁资源不一样: sleep()方法不会释放对象锁资源,wait()方法会释放对象锁资源

启动一个线程是用run()还是start()?
   启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行。这并不意味着线程就会立即运行。run()方法就是正常的对象调用方法的执行,并不是使用分线程来执行的

synchronized和Lock的异同?
   主要相同点: Lock能完成synchronized所实现的所有功能
   主要不同点: Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放

   7、String、StringBuffer、StringBuilder问题

在这里插入图片描述
结论:

  • 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量
  • 只要其中有一个是变量,结果就在堆中
  • 如果拼接的结果调用intern()方法,返回值就在常量池中
  • 使用final修饰String时,其定义也是在常量池中


对比String、StringBuffer、StringBuilder

  • String(JDK1.0):不可变字符序列;char[]数组长度默认为0,不可变
  • StringBuffer(JDK1.0):可变字符序列、效率低、线程安全;char[]数组长度默认为16,可扩容(原来的2倍 + 2)
  • StringBuilder(JDK 5.0):可变字符序列、效率高、线程不安全;char[]数组长度默认为16,可扩容(原来的2倍 + 2)
    注意:作为参数传递的话,方法内部String不会改变其值,StringBuffer和StringBuilder
    会改变其值



String str = null;
StringBuffer sb = new StringBuffer();
sb.append(str);
System.out.println(sb.length());// 4
System.out.println(sb);// null ; 在此处,这是一个字符串,不是关键词
StringBuffer sb1 = new StringBuffer(str);
System.out.println(sb1);// 运行到此处时,报错

   8、集合问题

@Test
public void testListRemove() {
	List list = new ArrayList();
	list.add(1);  // List中不能放基本类型,会进行自动装箱
	list.add(2);
	list.add(3);
	list.add(4);
	updateList(list);
}
private static void updateList(List list) {
	// 第一种:
	list.remove(2);  // 2的含义,有可能是索引,也有可能是值;但是值的话是要进行自动装箱,比较麻烦;所以会被识别为索引
	System.out.println(list);  // 1、2、4
	
	// 第二种:
	list.remove(new Integer(2));  // 2的含义,只能是值;索引是基本类型的int
	System.out.println(list);  // 1、4
}
// 1:表示Person重写了hashCode()和equals()方法
// 2:表示Person没有重写hashCode()和equals()方法

HashSet set = new HashSet();
Person p1 = new Person(1001,"AA");
Person p2 = new Person(1002,"BB");

set.add(p1);
set.add(p2);

p1.name = "CC";
set.remove(p1);
System.out.println(set);  // 1:输出2个;	2:输出1个

set.add(new Person(1001,"CC"));
System.out.println(set);  // 1:输出3个;	2:输出2个

set.add(new Person(1001,"AA"));
System.out.println(set);  // 1:输出4个;	2:输出3个
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值