Java基础面试题

JAVA 基础 面试题

MySql 面试题

1.聚簇索引和非聚簇索引的概念

1.1聚簇索引
将数据存储与索引放到了一块,找到了索引也就找到了数据,当表有聚簇索引时,它的数据实际上存放在索引的叶子页上,也就是B+树的叶子节点上,因为数据行不能存在两个地方,所以一个表只能有一个聚簇索引,在InnoDB中通过主键聚集数据,如果没有定义主键,InnoDB会选择一个唯一的非空索引代替。如果没有这样的索引,InnoDB会隐式定义一个主键来作为聚簇索引。

InnoDB的的二级索引的叶子节点存放的是KEY字段加主键值。因此,通过二级索引查询首先查到是主键值,然后InnoDB再根据查到的主键值通过主键索引找到相应的数据块。

1.2非聚簇索引
将数据存储与索引分开,索引结构的叶子节点指向了数据的对应行,在非聚簇索引中,索引中的逻辑顺序并不等同于表中行的物理顺序,索引是指向表中行的位置的指针,这些指针本身是有序的,通过这些指针可以在表中快速定位

1.3补充
聚簇索引和非聚簇索引的根本区别是数据记录的排列顺序和索引顺序是否一致,聚簇索引表记录的排列顺序与索引的排列顺序一致,优点是查询速度快,缺点是对表进行修改速度比较慢,这是为了保持表中的记录的物理顺序与索引的顺序一致

非聚簇索引指定了表中记录的逻辑顺序,数据记录的物理顺序和索引的顺序不一致,聚集索引和非聚集索引都采用了B树的结构,但非聚簇索引的叶子层顺序与实际的数据叶并不相同。在对聚簇索引查询时,聚簇索引的速度一般要比非聚簇索引快。

1.4何时使用聚簇索引和非聚簇索引
当我们经常对表中的记录进行修改操作时,应该使用非聚簇索引,聚簇索引适用于列经常被分组排序、表中记录比较少、需要返回某范围内的数据

2.关于非聚簇索引和聚簇索引回表查询

2.1非聚簇索引一定会回表查询吗
不一定,当查询语句所要求的字段全部命中了索引,就不用再回表查询了

面试题

1、 请解释字符串比较之中“==”和equals()的区别?

  • ==:比较的是两个字符串内存地址(堆内存)的数值是否相等,属于数值比较;
  • equals():比较的是两个字符串的内容,属于内容比较。

2、判断arraylist是否为空

  • 如果想判断list是否为空,可以这么判断

if(null == list || list.size() ==0 ){

  //为空的情况

}else{

  //不为空的情况

}

  • list.isEmpty() 和 list.size()==0 有啥区别呢

    答案:没有区别 。isEmpty()判断有没有元素,而size()返回有几个元素, 如果判断一个集合有无元素 建议用isEmpty()方法.比较符合逻辑用法。

  • list!=null 跟 ! list.isEmpty()有什么区别?
这就相当与,你要要到商店买东西
 list!=null         首先判断是否有商店
!list.isEmpty()     没有判断商店是否存在,而是判断商店是否有东西
总结用法:如果连商店都没有,何来的的东西可卖
所以一般的判断是
if(list!=null && !list.isEmpty()){
   //不为空的情况
}else{
   //为空的情况
}

3、final关键字的用法

类:当用final修饰一个类时,表明这个类不能被继承;

方法:在使用final时,注意,只有在想明确禁止该方法在子类中被覆盖的情况下才将方法设置为final;

变量:final修饰变量只能被赋值一次,赋值后不再改变。final修饰成员变量,必须初始化方法。当函数的参数类型是final时,说明该参数是只读型,不能被更改;

4、java中那些类不能new

1:抽象类 abstract class
2:接口 interface
3:无公开的构造方法的类 private className() {}等
4:虚接口 abstract inerface

5、静态类

5、如何判断应该设计类、子类、抽象类或者接口?

简要提炼下上述的原文意思:
你如何知道自己需要创建一个(父)类、子类、抽象类或者接口呢?
1. 创建一个(父)类,当你的新类无法与其他任何类通过IS-A 测试(除了 Object 类,因为在 Java 中所有类都继承了 Object 类)
2. 创建一个子类,当你需要创建一个已经存在类的更加明确的版本的时候,此时需要你在这个子类中重写父类的方法或者添加一些新的行为
3. 创建一个抽象类,当你想要创建一组子类的模板的时候,在这个抽象类中,你至少需要实现一些该组子类通用的方法。并且关键的是,当你创建一个类为抽象类,这就意味着你不想要让这个类有任何的对象生成(仅仅用于规范一组子类的行为而已)。
4. 创建一个接口,当你需要定义一些类的某种角色的时候,无论这些类在继承树中哪个位置。

6、请描述栈和堆的区别?同时列出栈和堆上分别存放什么东西?

堆中存放的是对象和数组。

栈中存放的是基本数据类型和堆中对象的引用。

另外栈是先进后出,堆是先进先出。

7、有几种方法可以释放对象的引用?

1、引用永久性的离开它的范围。

void go(){
    Life z = new Life();      //z会在方法结束时消失

}

2、引用被赋值到其他的对象上。

Life z = new Life();

z = new Life();   //第一个对象会在z被赋值到别处时挂掉。

3、直接将引用设定为null

Life z = new Life();

z = null;   //第一个对象会在z被赋值为null时击毙。

8、静态变量什么时候初始化?静态变量有几种初始化的方式,分别是什么?

加载过程

  1. 创建类的实例
  2. 调用类的静态方法
  3. 使用类的非 常量 静态字段
  4. 调用Java API中的某些反射方法
  5. 初始化某个类的子类
  6. 含有main()方法的类启动时

1、在静态变量的声明时初始化

static int i = 5;
static int j = 6;

2、在静态代码块中初始化

static int i;
static int j;
static{
    i = 5;
    j = 6;
}

9、变量定义static final String a="a"和static String a="a"有什么区别?在编译后生成的class里最大的区别是什么?

静态变量的初始化时机

  • 在类的生命周期内,静态变量只会被初始化一次。
  • 静态变量的初始化时机分为以下几种情况
    静态变量类型 初始化时机
    非final类型 类的初始时
    final类型—编译时可计算出取值 编译时
    final类型—编译时不可计算出取值 类初始化时
    静态变量的初始化时机与类的初始化时机紧密相关(final类型的静态变量除外,它编译时初始化)。在类的初始化阶段,java虚拟机执行类的初始化语句,为静态变量赋予初始值、执行静态代码块,所以静态变量的初始化时机即为类的初始化时机。

11、什么是泛型?请写一段使用ArrayList泛型的示例代码?

泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么也将类型由原来的具体的类型参数化,类似于方法中的变量参数,类型也定义成参数形式(称之为类型形参),然后在使用时传入具体的类型(类型实参)

如果数据类型不确定,可以使用泛型方法的方式,达到简化代码、提高代码重用性的目的

避免装箱拆箱

 
 
public class TestGeneric<T>//若要传入多个变量,在<>内添加类型即可
{                          //如public class TestGeneric<T,U,M> {} 
	private T first;
	private T second;
	
	public TestGeneric()
	{
		first = null;
		second = null;
	}
	
	public void setFirst(T newValue)
	{
		this.first = newValue;
	}
	
	public void setSecond(T newValue)
	{
		this.second = newValue;
	}
	
	public T getFirst()
	{
		return this.first;
	}
	
	public T getSecond()
	{
		return this.second;
	}
	
}
TestGeneric<Integer> p = new TestGeneric<>();//构造Integer类型的实例
p.setFirst(new Integer(11));                 
System.out.println(p.getFirst());
 
TestGeneric<String> p = new TestGeneric<>();//构造String类型的实例
p.setFirst(new String("hello"));                 
System.out.println(p.getFirst());
	public <T> void repeater(T content)
	{
		System.out.println(content);
	}

12、请写一段增强循环的示例代码?

for (int n : arr) {//变量n代表被遍历到的数组元素

    System.out.println(n);

}

注意:新for循环必须有被遍历的目标。目标只能是Collection或者是数组。

建议:遍历数组时,如果仅为遍历,可以使用增强for如果要对数组的元素进行 操作,使用老式for循环可以通过角标操作。

13、Swing的布局管理器有哪些?请详细介绍一下?

java共提供了五种布局管理器:流式布局管理器(FlowLayout)、边界布局管理器(BorderLayout)、网格布局管理器(GridLayout)、卡片布局管理器(CardLayout)、网格包布局管理器(GridBagLayout)。其中前三种是最常见的布局管理器。

14、在一个需要序列化的类中,是否可以存在不能被序列化的实例变量?如果可以,如何做到?如果不行,为什么?

另一个经常被问到的序列化面试问题。这也是一些时候也问, 如什么是瞬态 trasient 变量, 瞬态和静态变量会不会得到序列化等,所以,如果你不希望任何字段是对象的状态的一部分, 然后声明它静态或瞬态根据你的需要, 这样就不会是在 Java 序列化过程中被包含在内。

15、如果两个对象都有引用实例变量指向相同的对象会怎么样?例如两个Cat都有相同的Owner对象,那Owner会被存储两次吗?

17、如何启动新的线程?

  • 方式1:继承Thread类

步骤:

1):定义一个类A继承于Java.lang.Thread类.

2):在A类中覆盖Thread类中的run方法.

3):我们在run方法中编写需要执行的操作:run方法里的代码,线程执行体.

4):在main方法(线程)中,创建线程对象,并启动线程.

(1)创建线程类对象:

A类 a = new A类();

(2)调用线程对象的start方法:

a.start();//启动一个线程

注意:千万不要调用run方法,如果调用run方法好比是对象调用方法,依然还是只有一个线程,并没有开启新的线程.

线程只能启动一次!

创建启动线程实例:

//1):定义一个类A继承于java.lang.Thread类.  
class MusicThread extends Thread{  
    //2):在A类中覆盖Thread类中的run方法.  
    public void run() {  
        //3):在run方法中编写需要执行的操作  
        for(int i = 0; i < 50; i ++){  
            System.out.println("播放音乐"+i);  
        }  
    }  
}  
  
public class ExtendsThreadDemo {  
    public static void main(String[] args) {  
          
        for(int j = 0; j < 50; j ++){  
            System.out.println("运行游戏"+j);  
            if(j == 10){  
                //4):在main方法(线程)中,创建线程对象,并启动线程.  
                MusicThread music = new MusicThread();  
                music.start();  
            }  
        }  
    }  
  
}
  • 方式2:实现Runnable接口

步骤:

1):定义一个类A实现于java.lang.Runnable接口,注意A类不是线程类.

2):在A类中覆盖Runnable接口中的run方法.

3):我们在run方法中编写需要执行的操作:run方法里的,线程执行体.

4):在main方法(线程)中,创建线程对象,并启动线程.

(1)创建线程类对象:

Thread t = new Thread(new A());

(2)调用线程对象的start方法:

t.start();

//1):定义一个类A实现于java.lang.Runnable接口,注意A类不是线程类.  
class MusicImplements implements Runnable{  
    //2):在A类中覆盖Runnable接口中的run方法.  
    public void run() {  
        //3):在run方法中编写需要执行的操作  
        for(int i = 0; i < 50; i ++){  
            System.out.println("播放音乐"+i);  
        }  
          
    }  
}  
  
public class ImplementsRunnableDemo {  
    public static void main(String[] args) {  
        for(int j = 0; j < 50; j ++){  
            System.out.println("运行游戏"+j);  
            if(j == 10){  
                //4):在main方法(线程)中,创建线程对象,并启动线程  
                MusicImplements mi = new MusicImplements();  
                Thread t = new Thread(mi);  
                t.start();  
            }  
        }  
    }  
  
}

18、线程在执行时会有几种状态?这几种状态在什么时候会互相切换?

  1. 新建(NEW):新创建了一个线程对象。
  2. 可运行(RUNNABLE):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
  3. 运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
  4. 阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:

(一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
(二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
(三). 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。

  1. 死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。

线程的状态图

线程状态转换

20、如果对象有两个synchronized(同步化)的方法,两个线程是否能同时分别进入不同的同步化方法中?锁是在对象上还是在方法上?

不可以,锁是串行的 锁在对象上

22、Object的hashCode()方法与equals()分别有什么作用?

从性能方面看,重写的equals()里一般比较全面和复杂,这样效率就会比较低,而利用hashCode()进行对比,则只要生成一个hash值进行比较就可以了,效率很高。
从可靠性看,hashCode()并不是完全可靠的,有时候不同的对象他们生成的hashcode也会一样(生成hash值的公式可能存在问题),所以hashCode()只能说是大部分时候可靠。
因此,两个重要的结论:

equals()相等的两个对象他们的hashCode()肯定相等,也就是用equals对比是绝对可靠的。
hashCode()相等的两个对象他们的equals()不一定相等,也就是hashCode()不是绝对可靠的。

设计模式的六大原则

总原则:开闭原则(Open Close Principle)

开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类等,后面的具体设计中我们会提到这点。

  • 1、单一职责原则

不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,如若不然,就应该把类拆分。

  • 2、里氏替换原则(Liskov Substitution Principle)

里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科

历史替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。

  • 3、依赖倒转原则(Dependence Inversion Principle)

这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。

  • 4、接口隔离原则(Interface Segregation Principle)

这个原则的意思是:每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。

  • 5、迪米特法则(最少知道原则)(Demeter Principle)

就是说:一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。

最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。

29、请详细描述一下SQL语句的执行过程?

连接器: 身份认证和权限相关(登录 MySQL 的时候)。

查询缓存: 执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)。

分析器: 没有命中缓存的话,SQL 语句就会经过分析器,分析器说白了就是要先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。

优化器: 按照 MySQL 认为最优的方案去执行。

执行器: 执行语句,然后从存储引擎返回数据。

原文地址:https://www.cnblogs.com/chenn/p/14757716.html