土豆面试系列(三)

开门见山!

***************************************************************

1:如何最有效率地计算2*8?

很老的一道题了,直接左移三位,即2<<3;利用二进制,移位操作,这是最快的方式;移位可比普通的四则运算速度快。

2:请简单介绍下,JVM加载类的机制?

这个问的不多,但大点的公司对于底层要求还是比较严的,在此参考周志明的那本书介绍下。

大家知道,我们编写的Java或者其他语言编写生成的文件,编译成Class文件,而Class文件中的各种信息,只有加载到虚拟机之中后才能运行和使用,那么,虚拟机是怎么实现这个家在的呢?

简单来说,虚拟机吧描述类的数据从Class文件加载到内存,并且对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

这里,类从被加载到内存开始,到卸载出内存为止,是类的整个生命周期,包括:加载、验证、准备、解析、初始化、使用、卸载七个阶段,其中验证、准备、解析三个部分统称为连接。


(1)加载:在加载的过程中,虚拟机需要完成以下三件事情:

  1. 通过一个类的全限定名来获取定义此类的二进制字节流
  2. 将这个字节流所代表的的静态存储结构,转化为方法区的运行时数据结构
  3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

这里其实灵活性就很强了,就拿第一步来说吧,因为只说从一个Class文件中获取,并没有指定路径,所以可以从各种zip包中,可以从网络中获取,运行时的计算生成等等各种手段

(2)验证:验证是连接阶段的第一步,这一阶段的目的,是为了确保Class文件中的字节流内包含的信息,符合当前虚拟机的要求,并且不会危害虚拟机自身的安全,这个是毫无疑问的,name,验证阶段具体都会做什么检验操作呢?

  1. 文件格式验证:会验证字节流是否符合Class文件格式的规范,并且能够被当前版本的虚拟机处理。
  2. 元数据验证:这个阶段,对于字节码描述的信息进行语义分析,以保证其描述的信息,是否符合Java语言规范的要求
  3. 字节码验证:这是整个验证过程中最复杂的一个阶段,主要目的是通过数据流和控制流分析,确定程序的语义是合法的、符合逻辑的,这个阶段,会对类的方法体进行校验分析,保证类的方法在运行的过程中,不会做出危害虚拟机的行为
  4. 符号引用验证:这个阶段的校验,发生在虚拟机将符号引用转化为直接引用的时候。

(3)准备:是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。

(4)解析:这个阶段,是虚拟机将常量池内的符号引用替换为直接引用的过程。

(5)初始化:类的初始化阶段是类过程中的最后一步,到了这个阶段,才真正开始执行类中定义的Java程序代码。

3:谈谈你对JVM类加载器的了解

首先,从虚拟机的角度来说,只有两种不同的类加载器:

  1. 启动类加载器,这个类加载器是虚拟机自身的一部分。
  2. 另一种就是所有其他的类加载器,这些类加载器一定是是由Java语言实现的,独立于虚拟机外部,并且全部继承自抽象类:java.lang.ClassLoader。

而对于程序员来说,类加载器还可以划分地更加细致一些:

  1. 启动类加载器:这个类加载的目录,是<JAVA_HOME>\lib目录中的,或者是被-Xbootclasspath参数所指定的路径中的,必须是能够被虚拟机是别的类库加载到虚拟机内存中,简单说,就是虚拟机启动的时候就会加载这部分的类,或者说,启动类加载器是随着虚拟机启动就开始工作的;启动类加载器无法被Java程序直接引用。
  2. 扩展类加载器:这个加载器,用于加载<JAVA_HOME>\lib\ext目录中的类库,或者是为java.ext.dirs系统变量锁指定的路径中的所有类库,这是开发者可以直接使用的,换句话说,就是我们开发过程中使用到的第三方类库,基本就是用这个类加载器来加载的,所以叫做扩展类加载器。
  3. 应用程序类加载器:这个加载器呢?它是负责加载用户类路径所指定的类库的,开发者可以直接使用这个类加载器,如果应用程序中没有自己定义类加载器,一般情况下,就是用的这个程序中默认的类加载器。

至于类加载器的双亲委派模型这里就不多说了,大家可以自行百度;简单说就是,一个类加载器接收到类加载的请求后,不会直接自己尝试加载,而是将其为派给父类加载器去加载,一直到最顶层的加载器;只有当父加载器反馈无法加载后,子加载器才会尝试自己去加载。

这里,今天遇到了一个问题:如果我定义了一个类,全名是java.lang.String,那么,这个类能够正常加载吗?

本人亲自尝试过,是不行的,从理论上来说,就是因为父类已经加载了这个类,那么,再次加载,肯定会出问题,具体什么问题呢,参见下文:

 

这里报出的错是不存在主方法,因为实际加载的是JAVA_HOME目录下的String类,那个类里是没有main方法的。

3:问:给你二十五匹马,五条赛道,二十五匹马的速度各自不同,在没有什么计时工具的情况下,最短需要几场比赛能挑出其中最快的三匹马?

这个一道思考题,面一家大公司的时候碰到了,统一可以称为是跑马问题,通常会改变马匹的数量和赛道的数量,但解题的思维都是一致的。

解答:首先一场比赛,五匹马,A1,A2,A3,A4,A5,决出一匹最快的,比如说A1。

接下来四场比赛,决出每场比赛中最快的,比如说B1,C1,D1,E1。

目前进行了五场比赛,每场比赛都有一匹跑得最快的。

第六场比赛呢,让这五匹在各自队伍中最快的跑一场,决出二十五匹马中最快的,比如说是A1;这时候,最快的一匹出来了;比如说,这场比赛的名次是A1>B1>C1>D1>E1。

第七场呢,A1是不用参加了,因为其本身已经是最快的了;而我们要挑出最快的三匹马,D1和E1肯定不可能参与最后的角逐了,所以,最后一场让B1B2,C1,A2,A3跑一场,就可以决斗出剩余的两匹马了。

综上,最快七场比赛,可以决出最快的三匹马。

这个题目的关键,在于要利用第六场的赛果,去决断最后一场比赛的马匹。

4:实现一个栈,实现入栈,出栈,求栈中的最小值,时间复杂度都是O(1)。

前面两个操作,很容易实现,这就不说了。

重点与如何做到求取栈中的最小值的操作,一直保证时间复杂度是O(1)。

这里,代码不写了,简单介绍下这个问题的解决思路吧:

解答:毫无疑问,时间复杂度与空间复杂度是不能并存的,如果想要实现最小值获取的复杂度是O(1),那么,必须要创建一个空间来保存最小值,我们新建一个类,这里面有两个栈;一个主栈用于存储存入的元素,而一个副栈则是用于存储最小值。

那么,怎么利用这个副栈来达到我们的目的呢?

操作如下:

  1. 存入第一个元素的时候,主栈压栈;同时副栈压栈。
  2. 存入第二个元素的时候,主栈压栈;同时;该元素与副栈栈顶元素进行比较,如果比其要小,那么,将该元素压栈,否则,副栈不进行任何操作;综合来说,就是每存入一个元素,都要判断其是否是存入之后形成的一个新栈内所有元素的最小元素,如果是的,则压入副栈,或者称为最小值栈更好。
  3. 取元素的时候,普通操作不说了,这里说下如何取最小元素;因为存入的时候,最小值栈顶端元素,一定是现在主栈之中最小的元素,我们直接将该元素出栈即可。

5:一道今天刚遇到的面试题:简单介绍是这样的,给一个字符串,其中大小字母都有,而且大写字母出现的位置随机的,那么,设计一个算法,在不占用额外空间的情况下,把大写字母全部挪动到最右边,同时,保持小写字母的相对顺序不变;保证大写字母的相对顺序也不变:比如说abcdAghDefGij;操作之后的结果就是:abcdghefijADG。

解答:网上各种各样的解决方法大致相同,但写的不详细,这里给出我自己写的一个实现方式,但是,无法做到完全不占用任何额外空间,我仔细思考了,只要想做到移位和交换,实在是不可能完全不占用额外空间,我这里,占用了一个额外的空间,算法如下:

public class StringTransfer {
	public static void main(String[] args) {
		String text = "abcdAghDefGij";
		transferBig(text);

	}

	public static void transferBig(String text) {
		// 这里,之所以转了,是因为无法直接对字符串的某一个元素进行赋值操作
		char[] array = text.toCharArray();
		// 这里,索引是为了计算碰到了几个大写字母
		int index = 0;

		// 这里,为什么要根据index来判断呢?如果已经出现了一个大写字母,毫无疑问,就没有必要判断到最后一位的
		// 所以,index要用来判断具体要交换到哪个元素的。
		for (int i = array.length - 1; i >= 0; i--) {
			// 如果遇到了大写字母
			if (Character.isUpperCase(text.charAt(i))) {
				index++;
				for (int j = i; j < text.length() - index; j++) {
					char temp = array[j];
					array[j] = array[j + 1];
					array[j + 1] = temp;
				}
			}
		}
		System.out.println(new String(array));
	}
}

代码亲测是有效的。

6:简单介绍下Yarn中的几个组件呗

这个讲起来有些复杂,大家参照:Yarn

7:简单介绍下Hadoop安装过程中的重要的配置文件,以及配置文件的作用。

8:volatile关键字能够发挥什么作用?

9:Redis为什么那么快?内部使用什么机制存储的?

10:深入地讲解下HashMap的原理呗

11:Java 7的HashMap和Java 8的HashMap的区别在于哪儿?

这里先讲些简单的,Java7中的HashMap中,依靠的是数组和链表两种数据结构;而在Java8中的HashMap,扩容到一定程度的时候,会用红黑树来代替链表。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值