Java面试常见知识点

代码
  • 堆排
    static void adjustHeap(int a[],int cur,int len){
        int temp = a[cur];
        for(int i=2*cur+1;i<len;i=2*i+1){
            if(i+1<len&&a[i+1]>a[i]){
                i++;
            }
            if(a[i]>a[cur]){
                a[cur] = a[i];
                cur = i;
            }else{
                break;
            }
        }
        a[cur] = temp;
    }

    static void heapSort(int a[]){
        int len = a.length;
        for(int i=len/2-1;i>=0;i--){
            adjustHeap(a,i,len);
        }
        for(int i=len-1;i>0;i--){
            swap(a[0],a[i]);
            adjustHeap(a,0,i);
        }
    }
  • 快排
public class QuickSort {
	public static void main(String[] args) {
		int[] arr = { 49, 38, 65, 97, 23, 22, 76, 1, 5, 8, 2, 0, -1, 22 };
		quickSort(arr, 0, arr.length - 1);
		System.out.println("排序后:");
		for (int i : arr) {
			System.out.println(i);
		}
	}

	private static void quickSort(int[] arr, int low, int high) {

		if (low < high) {
			// 找寻基准数据的正确索引
			int index = getIndex(arr, low, high);

			// 进行迭代对index之前和之后的数组进行相同的操作使整个数组变成有序
			quickSort(arr, low, index - 1);
			quickSort(arr, index + 1, high);
		}

	}

	private static int getIndex(int[] arr, int low, int high) {
		// 基准数据
		int tmp = arr[low];
		while (low < high) {
			// 当队尾的元素大于等于基准数据时,向前挪动high指针
			while (low < high && arr[high] >= tmp) {
				high--;
			}
			// 如果队尾元素小于tmp了,需要将其赋值给low
			arr[low] = arr[high];
			// 当队首元素小于等于tmp时,向前挪动low指针
			while (low < high && arr[low] <= tmp) {
				low++;
			}
			// 当队首元素大于tmp时,需要将其赋值给high
			arr[high] = arr[low];

		}
		// 跳出循环时low和high相等,此时的low或high就是tmp的正确索引位置
		// 由原理部分可以很清楚的知道low位置的值并不是tmp,所以需要将tmp赋值给arr[low]
		arr[low] = tmp;
		return low; // 返回tmp的正确位置
	}
}

  • 多线程顺序打印
public class forCharacter {
	/*
	 * Description:一个多线程的问题,用三个线程,顺序打印字母A-Z,输出结果是1A 2B 3C 1D 2E...打印完毕最后输出一个Ok
	 */
	private static char c = 'A';// 必要的时候声明为volatile类型的
	private static int i = 0;

	public static void main(String[] args) throws InterruptedException {
		Runnable r = new Runnable() {
			public void run() {
				synchronized (this) {
					try {
						int threadId = Integer.parseInt(Thread.currentThread().getName());
						System.out.println("当前线程id:" + threadId + " ");
						while (i < 26) {
							if (i % 3 == threadId - 1) {
								System.out.println("线程id:" + threadId + " " + (char) c++);
								i++;
								if (i == 26)
									System.out.println("哈哈,祝拿到offer!");
								notifyAll();// 唤醒其他线程
							} else {
								wait();// 阻塞其他线程
							}
						}
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		};

		Thread t1 = new Thread(r, "1");
		Thread t2 = new Thread(r, "2");
		Thread t3 = new Thread(r, "3");
		t1.start();
		t2.start();
		t3.start();
	}
}

  • 最长公共字串
public static int lcs(String str1, String str2) {
    int len1 = str1.length();
    int len2 = str2.length();
    int result = 0;     //记录最长公共子串长度
    int c[][] = new int[len1+1][len2+1];
    for (int i = 0; i <= len1; i++) {
        for( int j = 0; j <= len2; j++) {
            if(i == 0 || j == 0) {
                c[i][j] = 0;
            } else if (str1.charAt(i-1) == str2.charAt(j-1)) {
                c[i][j] = c[i-1][j-1] + 1;
                result = max(c[i][j], result);
            } else {
                c[i][j] = 0;
            }
        }
    }
    return result;
}
  • 最长公共子序列
public static int lcs(String str1, String str2) {
    int len1 = str1.length();
    int len2 = str2.length();
    int c[][] = new int[len1+1][len2+1];
    for (int i = 0; i <= len1; i++) {
        for( int j = 0; j <= len2; j++) {
            if(i == 0 || j == 0) {
                c[i][j] = 0;
            } else if (str1.charAt(i-1) == str2.charAt(j-1)) {
                c[i][j] = c[i-1][j-1] + 1;
            } else {
                c[i][j] = max(c[i - 1][j], c[i][j - 1]);
            }
        }
    }
    return c[len1][len2];
}
  • KMP算法
数据库
  • 据库连接池有没了解,解释下它的参数,最大连接数怎么配置比较合理,参考引用

  • 怎么判断数据库死锁,死锁的处理方式

  • 程序在执行的过程中,点击确定或保存按钮,程序没有响应,也没有出现报错。

  • 死锁的解决方法:1)查找死锁的进程:2)kill掉这个死锁的进程:
    
  • 核心就是数据库会把事务单元锁维持的锁和它所等待的锁都记录下来,Innodb提供了wait-for graph算法来主动进行死锁检测,每当加锁请求无法立即满足需要进入等待时,wait-for graph算法都会被触发。当数据库检测到两个事务不同方向地给同一个资源加锁(产生循序),它就认为发生了死锁,触发wait-for graph算法。比如,事务1给A加锁,事务2给B加锁,同时事务1给B加锁(等待),事务2给A加锁就发生了死锁。那么死锁解决办法就是终止一边事务的执行即可,这种效率一般来说是最高的,也是主流数据库采用的办法。

  • Innodb目前处理死锁的方法就是将持有最少行级排他锁的事务进行回滚。 这也是相对比较简单的死锁回滚方式。死锁发生以后,只有部分或者完全回滚其中一个事务,才能打破死锁。对于事务型的系统,这是无法避免的,所以应用程序在设计必须考虑如何处理死锁。大多数情况下只需要重新执行因死锁回滚的事务即可。

  • 索引的使用?失效的原因

1.单独引用复合索引里非第一位置的索引列,复合索引遵守“最左前缀”原则,即在查询条件中使用了复合索引的第一个字段,索引才会被使用。
2.对索引列运算,运算包括(+-*/、!、<>%、like’%_’(%放在前面)、or、in、exist等),导致索引失效。
	错误的例子:select * from test where id-1=9;
	正确的例子:select * from test where id=10;
	注意!!
	mysql sql 中如果使用了 not in , not exists , (<> 不等于 !=) 这些不走
	< 小于 > 大于 <= >= 这个根据实际查询数据来判断,如果全盘扫描速度比索引速度要快则不走索引 

3.对索引应用内部函数,这种情况下应该建立基于函数的索引。
	select * from template t where ROUND(t.logicdb_id) = 1
	此时应该建ROUND(t.logicdb_id)为索引。

4、类型错误,如字段类型为varchar,where条件用number。
	例:template_id字段是varchar类型。
	错误写法:select * from template t where t.template_id = 1
	正确写法:select * from template t where t.template_id =15.如果MySQL预计使用全表扫描要比使用索引快,则不使用索引
6.like的模糊查询以%开头,索引失效
7.索引列没有限制 not null,索引不存储空值,如果不限制索引列是not null,oracle会认为索引列有可能存在空值,所以不会按照索引计算
  • 分库分表
1.水平切分
2.垂直切分
  • 数据库索引设计原则
1.主键外检一定要建索引
2.对 where,on,group by,order by 中出现的列使用索引
3.尽量选择区分度高的列作为索引,区分度的公式是count(distinct col)/count(*)
4.对较小的数据列使用索引,这样会使索引文件更小,同时内存中也可以装载更多的索引键
5.索引列不能参与计算,保持列“干净”
6.为较长的字符串使用前缀索引
7.尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可
  • 数据库优化
1.SQL语句优化
1)应尽量避免在 where 子句中使用!=<>操作符,否则将引擎放弃使用索引而进行全表扫描。
2)应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
3)很多时候用 exists 代替 in 是一个好的选择
4)用Where子句替换HAVING 子句 因为HAVING 只会在检索出所有记录之后才对结果集进行过滤
2.索引优化
3.数据库结构优化
1)范式优化: 比如消除冗余(节省空间。。)
2)反范式优化:比如适当加冗余等(减少join)
3)拆分表
4)拆分其实分垂直拆分和水平拆分
  • drop,delete与truncate的区别
drop直接删掉表 truncate删除表中数据,再插入时自增长id又从1开始 delete删除表中数据,可以加where字句。
  • 数据表类型有哪些
MyISAM:成熟、稳定、易于管理,快速读取。一些功能不支持(事务等),表级锁。
InnoDB:支持事务、外键等特性、数据行锁定。空间占用大,不支持全文索引等。
  • 用户量大的时候应该怎么处理?
一、SQL查询语句优化

    1、使用索引

    建立索引可以使查询速度得到提升,我们首先应该考虑在where及order by,group by涉及的列上建立索引。

   2、借助explain(查询优化神器)选择更好的索引和优化查询语句

    SQL 的 Explain 通过图形化或基于文本的方式详细说明了 SQL 语句的每个部分是如何执行以及何时执行的,以及执行效果。通过

对选择更好的索引列,或者对耗时久的SQL语句进行优化达到对查询速度的优化。

   3、任何地方都不要使用SELECT * FROM语句。

   4、不要在索引列做运算或者使用函数

   5、查询尽可能使用limit来减少返回的行数

   6、使用查询缓存,并将尽量多的内存分配给MYSQL做缓存

二、主从复制,读写分离,负载均衡

   目前大多数的主流关系型数据库都提供了主从复制的功能,通过配置两台(或多台)数据库的主从关系,可以将一台数据库服务器的数据更新同步到另一台服务器上。网站可以利用数据库这一功能,实现数据库的读写分离,从而改善数据库的负载压力。一个系统的读操作远远多于写操作,因此写操作发向master,读操作发向slaves进行操作(简单的轮询算法来决定使用哪个slave)。

   利用数据库的读写分离,Web服务器在写数据的时候,访问主数据库(master),主数据库通过主从复制将数据更新同步到从数据库(slave),这样当Web服务器读数据的时候,就可以通过从数据库获得数据。这一方案使得在大量读操作的Web应用可以轻松地读取数据,而主数据库也只会承受少量的写入操作,还可以实现数据热备份,可谓是一举两得。

三、数据库分表、分区、分库

   1、分表

   通过分表可以提高表的访问效率。有两种拆分方法:

   垂直拆分

   在主键和一些列放在一个表中,然后把主键和另外的列放在另一个表中。如果一个表中某些列常用,而另外一些不常用,则可以采用垂直拆分。

   水平拆分

   根据一列或者多列数据的值把数据行放到两个独立的表中。

   2、分区

   分区就是把一张表的数据分成多个区块,这些区块可以在一个磁盘上,也可以在不同的磁盘上,分区后,表面上还是一张表,但是数据散列在多个位置,这样一来,多块硬盘同时处理不同的请求,从而提高磁盘I/O读写性能。实现比较简单,包括水平分区和垂直分区。

   3、分库
   分库是根据业务不同把相关的表切分到不同的数据库中,比如web、bbs、blog等库。
  • 关系型和非关系型数据库区别
关系型数据库最典型的数据结构是表,由二维表及其之间的联系所组成的一个数据组织
优点:
1、易于维护:都是使用表结构,格式一致;
2、使用方便:SQL语言通用,可用于复杂查询;
3、复杂操作:支持SQL,可用于一个表以及多个表之间非常复杂的查询。
缺点:
1、读写性能比较差,尤其是海量数据的高效率读写;
2、固定的表结构,灵活度稍欠;
3、高并发读写需求,传统关系型数据库来说,硬盘I/O是一个很大的瓶颈。

非关系型数据库严格上不是一种数据库,应该是一种数据结构化存储方法的集合,可以是文档或者键值对等。
优点:
1、格式灵活:存储数据的格式可以是key,value形式、文档形式、图片形式等等,文档形式、图片形式等等,使用灵活,应用场景广泛,而关系型数据库则只支持基础类型。
2、速度快:nosql可以使用硬盘或者随机存储器作为载体,而关系型数据库只能使用硬盘;
3、高扩展性;
4、成本低:nosql数据库部署简单,基本都是开源软件。

缺点:
1、不提供sql支持,学习和使用成本较高;
2、无事务处理;
3、数据结构相对复杂,复杂查询方面稍欠。
  • 增加索引sql

CREATE INDEX index_name ON table_name(column_name,column_name) include(score)

普通索引

CREATE UNIQUE INDEX index_name ON table_name (column_name) ;

非空索引

CREATE PRIMARY KEY INDEX index_name ON table_name (column_name) ;
线程并发
  • 线程的状态
NEW:新建,线程被构建,但是还没有start()
RUNNABLE:运行,java中将就绪和运行统称为运行中
BLOCKED:阻塞,线程阻塞于锁
WAITING:等待,表示线程进入等待状态,需要其他线程的特定动作(通知或中断)
TIMED_WAITING:带超时的等待,可以在指定的时间内自动返还
TERMINATED:终止,表示线程已经执行完毕
1. 继承Thread类
2.实现Runnable接口
3.实现Callable接口
4.线程池:提供了一个线程队列,队列中保存着所有等待状态的线程。避免了创建与销毁额外开销,提高了响应的速度。
  • 尝试写一下怎么保证多个线程的有序执行(join或单线程池)

    public static void main(String[] args) throws InterruptedException {
           Thread1 thread1=new Thread1();
//               thread1.start();
//               thread1.join();
          Thread2 thread2=new Thread2();
//               thread2.start();
//               thread2.join();
          Thread3 thread3=new Thread3();
//           thread3.start();
        ExecutorService executorService=Executors.newSingleThreadExecutor();
        executorService.execute(thread1);
        executorService.execute(thread2);
        executorService.execute(thread3);
        executorService.shutdown();

  • Thread类和Runnable接口的区别

  • 线程池的几种类型(FixedThreadPool、CachedThreadPool、SingleThreadExecutor、ScheduledThreadPoolExecutor、SingleThreadScheduledExecutor、WorkStealingPool)以及应用场景

  • 线程数怎么设定?过大会怎么样?

Thread类也实现了Runnable接口,应尽量使用实现Runnable的方式,因为实现Runnable接口的方式比继承Thread类方法多以下优势:
① 适合多个相同程序代码的线程去处理同一资源;
② 避免了Java单继承带来的局限性;
③ 增强了程序的健壮性,代码能被多个线程共享,代码与数据是独立的。

cpu密集型:
就是指线程大部分时间都在用cpu,一般来说,普通的操作都需要用到cpu,比如计算,读取,循环,赋值,查询,排序等等。在最理想的情况下,大牛们建议将线程数设置为count(cpu)±1
io密集型
io操作一般不需要cpu的参与,线程在io时,线程会被阻塞(线程的六个状态之一就有Blocked)如果一个线程完成某项工作一共需要100ms,其中io需要80ms,cpu需要20ms(忽略其他时间).那么线程数应该设立为5.
有锁的情况
多线程为了安全,往往会加锁。对于关键代码(被频繁调用的代码),往往可以成为线程个数的依据之一。对于全局锁(比如static上锁),无论多少个线程,代码都是串行执行的。这样线程越多反而越不好。对于锁粒度的越小,对于线程并发来说越有利。比如ConcurrentHashMap来说,分了16个segment,也就是加了16把锁。在理性的情况下,锁粒度可以降低16倍,那么自然可以允许16个并发。最坏的情况是16个线程去争用一个segment。这个线程的个数需要根据实际情况去调优。

  • 多线程技术的优势?
资源利用率更好
程序设计在某些情况下更简单
程序响应更快
  • 多线程有什么缺点?
上下文切换
线程安全
线程同步
线程死锁
  • 怎么实现线程安全?
1.不可变
2.乐观锁
3.悲观锁
4.线程私有:栈封闭(如局部变量),ThreadLocal
  • HTTP 和 HTTPS 有什么区别?具体怎么做的?

  • 幂等性是什么?会导致什么问题?如何解决?
    幂等性:就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了了副作用。 举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额发现多扣了钱,流水记录也变成了两条。
    解决方案:
    1)乐观锁:通过版本号去控制
    2)悲观锁:select * from xx for update
    3)唯一索引,防止新增脏数据。
    4).分布式锁——还是拿插入数据的例子,如果是分布是系统,构建全局唯一索引比较困难,例如唯一性的字段没法确定,这时候可以引入分布式锁,通过第三方的系统(redis或zookeeper),在业务系统插入数据或者更新数据,获取分布式锁,然后做操作,之后释放锁,这样其实是把多线程并发的锁的思路,引入多多个系统,也就是分布式系统中得解决思路。要点:某个长流程处理过程要求不能并发执行,可以在流程执行之前根据某个标志(用户ID+后缀等)获取分布式锁,其他流程执行时获取锁就会失败,也就是同一时间该流程只能有一个能执行成功,执行完成后,释放分布式锁(分布式锁要第三方系统提供);
    5)token令牌的问题
    支付前拿到一个token,然后支付的时候带上这个token,验证真实性然后如果redis上有这个token说明还没支付,否则就已经支付完成

  • 对象的创建方式(new、反射、clone、反序列化)

  • JDBC为什么要破坏双亲委派模型
    因为类加载器受到加载范围的限制,在某些情况下父类加载器无法加载到需要的文件,这时候就需要委托子类加载器去加载class文件。
    JDBC的Driver接口定义在JDK中,其实现由各个数据库的服务商来提供,比如MySQL驱动包。DriverManager 类中要加载各个实现了Driver接口的类,然后进行管理,但是DriverManager位于 $JAVA_HOME中jre/lib/rt.jar 包,由BootStrap类加载器加载,而其Driver接口的实现类是位于服务商提供的 Jar 包,根据类加载机制,当被装载的类引用了另外一个类的时候,虚拟机就会使用装载第一个类的类装载器装载被引用的类。 也就是说BootStrap类加载器还要去加载jar包中的Driver接口的实现类。我们知道,BootStrap类加载器默认只负责加载 $JAVA_HOME中jre/lib/rt.jar 里所有的class,所以需要由子类加载器去加载Driver实现,这就破坏了双亲委派模型。

  • Tomcat为什么要破坏双亲委派模型
    Tomcat如何破坏双亲委派模型的呢?
    每个Tomcat的webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器。
    事实上,tomcat之所以造了一堆自己的classloader,大致是出于下面三类目的:

  • 对于各个 webapp中的 class和 lib,需要相互隔离,不能出现一个应用中加载的类库会影响另一个应用的情况,而对于许多应用,需要有共享的lib以便不浪费资源。

  • 与 jvm一样的安全性问题。使用单独的 classloader去装载 tomcat自身的类库,以免其他恶意或无意的破坏;

  • 热部署。相信大家一定为 tomcat修改文件不用重启就自动重新装载类库而惊叹吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值