阿里-应届生面试求职基础题以及答案(1)

这是转自一个博客的题目,随后我将总结出这些题的答案,而答案也是我从各大博客中收集整理的

基础能力
1.什么是值传递和引用传递
2.线程状态有哪些,它们之间是如何转换的
3.线程与进程的区别
4.索引的作用及代价?如何建好索引?索引的原理
5.Hash算法
6.锁:乐观锁和悲观锁
7.什么情况下会产生死锁?如何避免死锁?
8.JVM GC:介绍垃圾回收机制,垃圾回收算法。
9.JAVA的反射机制
10.Java集合类有哪些,分别在哪些场景使用
11.简述一次HTTP请求的基本流程
12.什么是JDBC,为什么需要什么是JDBC?实现原理是什么?
13.Get和Post的区别
14.Cookie和Session的区别,分别用于什么场景
15.为什么需要编码?UTF-8和GBK是如何进行编码的
16.分别介绍下JDK,JRE和JVM

1.值传递就是基本类型数据的赋值
例如 方法中有个参数int a 方法体里面的有个int b 将 a = b (b的值赋给a)这就是值传递 这样不会改变a中的值
引用传递和值传递类似,只不过是对象之间的赋值罢。

2.线程状态有5个状态
(1).新状态(还未调用start方法)
(2).就绪状态 (调用了start方法,但是还没有被cpu调度到)
(3).运行状态 (线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。)
(4).阻塞/睡眠/等待状态(这时候的线程依旧是活的,等待某个条件将其唤醒,比如slepp时间到(这时候是进入就绪状态),调用了notify等)
(5).死亡状态(当run方法执行完毕进入死亡状态,并且不可再复活 ,如果你还要调用它的start方法,会抛出IllegalThreadStateException异常)

(1).进程有自己独立的地址空间;而线程共享进程的地址空间;
(2).一个程序至少有一个进程,一个进程至少有一个线程;
(3).线程是处理器调度的基本单位,但进程不是;
(4).二者均可并发执行

作用:提高查询速度,在一个或者一些字段需要频繁用作查询条件,并且表数据较多的时候,创建索引会明显提高查询速度,因为可由全表扫描改成索引扫描。
原理:「索引就像书的目录, 通过书的目录就准确的定位到了书籍具体的内容」索引是就是根据这种思想,用一种平衡树的数据结构来进行查找的

我们平时建表的时候都会为表加上主键, 在某些关系数据库中, 如果建表时不指定主键,数据库会拒绝建表的语句执行。 事实上, 一个加了主键的表,并不能被称之为「表」。一个没加主键的表,它的数据无序的放置在磁盘存储器上,一行一行的排列的很整齐, 跟我认知中的「表」很接近。如果给表上了主键,那么表在磁盘上的存储结构就由整齐排列的结构转变成了树状结构,也就是上面说的「平衡树」结构,换句话说,就是整个表就变成了一个索引。没错, 再说一遍, 整个表变成了一个索引,也就是所谓的「聚集索引」。 这就是为什么一个表只能有一个主键, 一个表只能有一个「聚集索引」,因为主键的作用就是把「表」的数据格式转换成「索引(平衡树)」的格式放置。

阿里-应届生面试求职基础题以及答案(1)_第1张图片
比如我们执行一个 select * from xx where id=232
创建了索引的话它会创建一张这个字段的索引表。首先根据索引定位到1256这个值所在的叶结点,然后再通过叶结点取到id等于232的数据行。 但是从上图能看出,树一共有三层, 从根节点至叶节点只需要经过三次查找就能得到结果。假如数据很大有1亿条,最坏情况下要匹配一亿次才能得到结果.并且索引表事先都是排好序的这就大大提高了效率。

索引副作用:
(1)索引是有大量数据的时候才建立的,没有大量数据反而会浪费时间,因为索引是使用二叉树建立.

(2)当一个系统查询比较频繁,而新建,修改等操作比较少时,可以创建索引,这样查询的速度会比以前快很多,同时也带来弊端,就是新建或修改等操作时,比没有索引或没有建立覆盖索引时的要慢。

(3)索引并不是越多越好,太多索引会占用很多的索引表空间,甚至比存储一条记录更多。
对于需要频繁新增记录的表,最好不要创建索引,没有索引的表,执行insert、append都很快,有了索引以后,会多一个维护索引的操作,一些大表可能导致insert 速度非常慢

Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入,通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。

所有散列函数都有如下一个基本特性:根据同一散列函数计算出的散列值如果不同,那么输入值肯定也不同。但是,根据同一散列函数计算出的散列值如果相同,输入值不一定相同。

两个不同的输入值,根据同一散列函数计算出的散列值相同的现象叫做碰撞(也叫哈希冲突)。

常见的hash函数:
直接定址法:直接以关键字k或者k加上某个常数(k+c)作为哈希地址。

数字分析法:提取关键字中取值比较均匀的数字作为哈希地址。

除留余数法:用关键字k除以某个不大于哈希表长度m的数p,将所得余数作为哈希表地址。

分段叠加法:按照哈希表地址位数将关键字分成位数相等的几部分,其中最后一部分可以比较短。然后将这几部分相加,舍弃最高进位后的结果就是该关键字的哈希地址。

平方取中法:如果关键字各个部分分布都不均匀的话,可以先求出它的平方值,然后按照需求取中间的几位作为哈希地址。

伪随机数法:采用一个伪随机数当作哈希函数。
上面介绍过碰撞。衡量一个哈希函数的好坏的重要指标就是发生碰撞的概率以及发生碰撞的解决方案。任何哈希函数基本都无法彻底避免碰撞,常见的解决碰撞的方法有以下几种:

开放定址法:
开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。
链地址法
将哈希表的每个单元作为链表的头结点,所有哈希地址为i的元素构成一个同义词链表。即发生冲突时就把该关键字链在以该单元为头结点的链表的尾部。
再哈希法
当哈希地址发生冲突用其他的函数计算另一个哈希函数地址,直到冲突不再产生为止。
建立公共溢出区
将哈希表分为基本表和溢出表两部分,发生冲突的元素都放入溢出表中。

第一种叫做悲观锁,第二种叫做乐观锁。什么叫悲观锁呢,悲观锁顾名思义,就是对数据的冲突采取一种悲观的态度,也就是说假设数据肯定会冲突,所以在数据开始读取的时候就把数据锁定住。而乐观锁就是认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让用户返回错误的信息,让用户决定如何去做。
二、使用场景
1、乐观锁:由于乐观锁的不上锁特性,所以在性能方面要比悲观锁好,比较适合用在DB的读大于写的业务场景。
2、悲观锁:对于每一次数据修改都要上锁,如果在DB读取需要比较大的情况下有线程在执行数据修改操作会导致读操作全部被挂载起来,等修改线程释放了锁才能读到数据,体验极差。所以比较适合用在DB写大于读的情况。
三、实现
1、乐观锁:目前比较常用的有两种方式,第一种是使用版本号或者时间戳。在表中加个version或updatetime字段,在每次更新操作时对此一下该字段,如果一致则更新数据,数据不等则放弃本次修改,根据实际业务需求做相应的处理。第二种是CAS方式,即Java中的compareAndSwap。CAS操作涉及到三个操作数,内存值(valueOffSet)、期望值(expect)、更新值(update)。当内存值与期望值一致时就会更新数据,反之不操作。
什么是CAS方法:CompareAndSwap
乐观锁的使用的机制就是CAS。
在CAS方法中,CAS有三个操作数,内存值V,旧的预期值E,要修改的新值U。当且仅当预期值E和内存值V相等时,将内存值V修改为U,否则什么都不做。
“ABA”问题
1、可以发现,CAS实现的过程是先取出内存中某时刻的数据,在下一时刻比较并替换,那么在这个时间差会导致数据的变化,此时就会导致出现“ABA”问题。
2、什么是”ABA”问题?
比如说现在有3个线程 a,b,c 。a先取出了数据 i为5 ,此时b进来将i修改了为10,而后c又进来又把i修改为了5,好了a回过头来看发现此时i还是为5,尽管线程a的CAS操作成功,但是不代表这个过程就是没有问题的。
ABA问题的解决办法就是使用版本号,在变量前面追加版本号,每次变量更新时把版本号加1,那么A-B-A就会变成1A-2B-3A。
那么实际中是如何解决的ABA问题呢:使用AtomicStampedReference类

解决案例:https://www.jianshu.com/p/8b227a8adbc1

2、悲观锁:一、数据库实现方式,使用数据库的读锁、写锁、行锁等实现进程的悬挂阻塞等当前操作完成后才能进行下一个操作。二、在Java里面可以使用synchronize实现悲观锁。

死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。
代码案例:因为面试官可能让你手写一个死锁案例

public class DeadLock implements Runnable {
	
	private static Object obj1 = new Object(),obj2 = new Object();
	private int flag;
	
	

	public static void main(String[] args) {
		DeadLock d1 = new DeadLock();
		DeadLock d2 = new DeadLock();
		Thread t1 = new Thread(d1);
		Thread t2 = new Thread(d2);
		d1.flag = 1;
		d2.flag = 2;
		t1.start();
		t2.start();
		
	}


	@Override
	public void run() {
		// TODO Auto-generated method stub
		if(flag == 1) {
			synchronized (obj1) {
				System.out.println("obj1进来了");
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}synchronized (obj2) {
					System.out.println("给obj2上锁");
				}
				
				System.out.println("obj1结束");
			}
		}
		if(flag == 2) {
			synchronized (obj2) {
				System.out.println("obj2进来了");
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				synchronized (obj1) {
					System.out.println("给obj1上锁");
				}
				
				System.out.println("obj2结束");
			}
		}
		
	}
}

//当然实际项目种可能不会一个syn中还嵌套一个syn

如何避免:避免在对象的同步方法中调用其它对象的同步方法,那么就可以避免死锁产生的可能性,如果非要调用。按某一顺序申请资源,释放资源则反序释放就可以避免比如改为这样:

if(flag == 2) {
			synchronized (obj1) {   //调换顺序
				System.out.println("obj2进来了");
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				synchronized (obj2) {   //调换顺序
					System.out.println("给obj1上锁");
				}
				
				System.out.println("obj2结束");
			}
		}

现在模拟含有syn的方法调用含有syn的方法

class A{
	
	public void A() {
		synchronized (A.class) {
		System.out.println("进入A了");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		B b = new B();
		b.B();
		System.out.println("Class A: A()");
		}
	}
}

class B{
	
	public void B() {
		synchronized (B.class) {
		System.out.println("进入B了");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		A a = new A();
		a.A();
		System.out.println("Class B: B()");
		}
	}
}

public void run() {
		if(flag == 1) {
			new A().A();
		}else {
			new B().B();
		}
}

因为class锁是锁的所有对象所以System.out.println(“Class A: A()”);这段代码永远不会打印,发生了死锁
如何检查了使用jdk自带的图形界面检测
教程:https://www.cnblogs.com/flyingeagle/articles/6853167.html
阿里-应届生面试求职基础题以及答案(1)_第2张图片

避免篇幅太长剩下的题将在后续博客中介绍

你可能感兴趣的:(阿里面试题)