Java面试题(二)

什么是堆排序

堆排序介绍

堆排序是指利用堆积树这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。

堆是一个近完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或大于)它的父节点。

完全二叉树

除了最后一层之外的其他每一层都被完全填充,并且所有结点都保持向左对齐。
在这里插入图片描述

满二叉树

除了叶子结点之外的每一个结点都有两个孩子,每一层(当然包含最后一层)都被完全填充。

在这里插入图片描述

完满二叉树

除了叶子结点之外的每一个结点都有两个孩子结点

在这里插入图片描述

总得来说,堆排序是将数据看成是完全二叉树、根据完全二叉树的特性来进行排序的一种算法,最大堆要求节点元素都要不小于其孩子,最小堆要求节点元素都不大于其左右孩子,那么处于最大堆的根节点的元素就是这个堆中的最大值。

完全二叉树有个特性:

左边子节点位置 = 当前父节点的两倍 + 1,右边子节点位置 = 当前父节点的两倍 + 2.

堆排序代码实现

1 /**
 2      * 建堆
 3      *
 4      * @param arrays          看作是完全二叉树
 5      * @param currentRootNode 当前父节点位置
 6      * @param size            节点总数
 7      */
 8     public static void heapify(int[] arrays, int currentRootNode, int size) {
 9 
10         if (currentRootNode < size) {
11             //左子树和右字数的位置
12             int left = 2 * currentRootNode + 1;
13             int right = 2 * currentRootNode + 2;
14 
15             //把当前父节点位置看成是最大的
16             int max = currentRootNode;
17 
18             if (left < size) {
19                 //如果比当前根元素要大,记录它的位置
20                 if (arrays[max] < arrays[left]) {
21                     max = left;
22                 }
23             }
24             if (right < size) {
25                 //如果比当前根元素要大,记录它的位置
26                 if (arrays[max] < arrays[right]) {
27                     max = right;
28                 }
29             }
30             //如果最大的不是根元素位置,那么就交换
31             if (max != currentRootNode) {
32                 int temp = arrays[max];
33                 arrays[max] = arrays[currentRootNode];
34                 arrays[currentRootNode] = temp;
35 
36                 //继续比较,直到完成一次建堆
37                 heapify(arrays, max, size);
38             }
39         }
40     }

要注意的是:

在上面体验堆排序时,我们是左子树和右子树都是已经有父子这么一个条件的,显然,一个普通的数组并不能有这种条件(父>子),因此,我们往往是从数组最后一个元素来进行建堆。

 1     /**
 2      * 完成一次建堆,最大值在堆的顶部(根节点)
 3      */
 4     public static void maxHeapify(int[] arrays, int size) {
 5 
 6         // 从数组的尾部开始,直到第一个元素(角标为0)
 7         for (int i = size - 1; i >= 0; i--) {
 8             heapify(arrays, i, size);
 9         }
10 
11     }

接下来不断建堆,然后让数组最后一位与当前堆顶(数组第一位)进行交换即可排序。

 1     for (int i = 0; i < arrays.length; i++) {
 2 
 3         //每次建堆就可以排除一个元素了
 4         maxHeapify(arrays, arrays.length - i);
 5 
 6         //交换
 7         int temp = arrays[0];
 8         arrays[0] = arrays[(arrays.length - 1) - i];
 9         arrays[(arrays.length - 1) - i] = temp;
10 
11     }

用set集合的时候,重写过hashcode()和equal()方法吗?有什么作用?

set集合特点

1.没有重复元素
2.set集合是无序的
3.允许包含值为null的元素,但最多只能有一个null元素

HashSet集合

HashSet集合采用用哈希表结构存储数据,保证元素唯一性的方式依赖于hashCode()和equals()方法。

1.HashSet集合排重时,需要判断两个对象是否相同,对象相同的判断可以通过hashCode值判断,所以需要重写hashCode()方法。

2.HashSet不能为一样的,放入一个值首先判断hashCode(类似下标)是否已经存在,然后用equals()判断是否有一样的。

3.如果只重写其中一个方法的时候,向HashSet集合中添加多个对象时,所有属性都相同时,并没有完成想要的排重效果。HashSet不能为一样的,放入一个值首先判断hashCode(内存中的位置)是否已经存在,然后用equals判断是否有一样的值。

情况一:当我们往HashSet集合中添加 8大基本类型和String类型的时候,不需要重写hashCode()和equals()方法。因为任何对象都是Object类的子类,所以任何对象都拥有这个方法。
情况二:当我们往HashSet集合添加自定义对象的时候,就需要重写hashCode()和equals()方法。建立自己的比较方式,才能保证HashSet集合中的对象唯一。

案列:

public class Student {
 private String name;

 public Student(String name) {
     super();
     this.name = name;
 }
 
 public Student() {
    super();
 }

 public String getName() {
    return name;
 }

 public void setName(String name) {
    this.name = name;
 }

 @Override
 public String toString() {
    return "Student [name=" + name + "]";
 }

 @Override
  //重写equals
 public boolean equals(Object obj) {
      //先判断传入的参数对象是否是Student对象,若不是直接返回false
      if(obj instanceof Student) {
            //若是,强转成Student对象,并比较属性的值
            Student s = (Student) obj;
            if(this.name.equals(s.name)) {
                //若属性的值相同,则返回true
                return true;    
            }
      }       
      return false;
 }

@Override
public int hashCode(){
   /*hashCode方法返回值是int类型,所以重写时需要找到int类型的数据返回,还要保证此方法的返回值与对象的所有属性都相关,所以返回姓名属性的字符串的长度*/
    return this.name.length();
 }
}

多数据源配置情况下的事务管理

可以是分布式事务管理也可以是简单的DataSourceTranctionManager.
但是建议不做分布式事务管理,尽量保证一个事务不只操作一个数据库。
保持服务功能的单一性,如果一个服务会操作到两个数据库中的数据,那设计到的所有表最好放到同一个数据库中。因为分布式事务管理,消耗资源严重,性能下降。

现在我们有两个服务a和b,分别操作一个数据库,如果a中又调用b服务,这时候实际上a服务是操作了多个数据源,如果要保证事务一致性,就需要判断b服务的返回结果。

如果b出错了会抛异常,那我们就try catch b 服务,并且重新在a中把这个异常抛出。如果b服务中始终返回结果,并且结果的状态是错误的,我们就不能try catch了,而是判断返回救过的状态,如果是错误的 就在a中抛出异常。

如果是分布式事务管理,意味着项目里有服务 需要操作多个数据库,并且要做到事务一致性。
spring的org.springframework.transaction.jta.JtaTransactionManager,提供了分布式事务支持。如果使用WAS的JTA支持,把它的属性改为WebSphere对应的TransactionManager。
在tomcat下,是没有分布式事务的,不过可以借助于第三方软件jotm(Java Open Transaction Manager )和AtomikosTransactionsEssentials实现,在spring中分布式事务是通过jta(jotm,atomikos)来进行实现。
1、http://jotm.objectweb.org/
2、http://www.atomikos.com/Main/TransactionsEssentials

前后端分离有什么好处?那前端是如何测试的?

前后端分离的优势

1.可以实现真正的前后端解耦,前段服务器使用nginx,前段/WEB服务器放的是css,js,图片等一系列静态资源(甚至还可以css,js,图片等资源放到特定的文件服务器,例如阿里的oss,并使用cdn加速),前段服务器负责控制页面引用、跳转、路由,前段页面异步调用后端的接口,后端/应用服务器使用tomcat(把tomcat想象成一个数据提供者),加快整体响应速度。(这里需要使用一些前端工程化的框架比如nodejs,react,router,react,redux,webpack)
  2、发现bug,可以快速定位是谁的问题,不会出现互相踢皮球的现象。页面逻辑,跳转错误,浏览器兼容性问题,脚本错误,页面样式等问题,全部由前端工程师来负责。接口数据出错,数据没有提交成功,应答超时等问题,全部由后端工程师来解决。双方互不干扰,前端与后端是相亲相爱的一家人。
  3、在大并发情况下,我可以同时水平扩展前后端服务器,比如淘宝的一个首页就需要2000+台前端服务器做集群来抗住日均多少亿+的日均pv。(去参加阿里的技术峰会,听他们说他们的web容器都是自己写的,就算他单实例抗10万http并发,2000台是2亿http并发,并且他们还可以根据预知洪峰来无限拓展,很恐怖,就一个首页。。。)
  4、减少后端服务器的并发/负载压力。除了接口以外的其他所有http请求全部转移到前端nginx上,接口的请求调用tomcat,参考nginx反向代理tomcat。且除了第一次页面请求外,浏览器会大量调用本地缓存。
  5、即使后端服务暂时超时或者宕机了,前端页面也会正常访问,只不过数据刷不出来而已。
  6、也许你也需要有微信相关的轻应用,那样你的接口完全可以共用,如果也有app相关的服务,那么只要通过一些代码重构,也可以大量复用接口,提升效率。(多端应用)
  7、页面显示的东西再多也不怕,因为是异步加载。
  8、nginx支持页面热部署,不用重启服务器,前端升级更无缝。
  9、增加代码的维护性&易读性(前后端耦在一起的代码读起来相当费劲)。
  10、提升开发效率,因为可以前后端并行开发,而不是像以前的强依赖。
  11、在nginx中部署证书,外网使用https访问,并且只开放443和80端口,其他端口一律关闭(防止黑客端口扫描),内网使用http,性能和安全都有保障。
  12、前端大量的组件代码得以复用,组件化,提升开发效率,抽出来!

注意事项:
  1、在开需求会议的时候,前后端工程师必须全部参加,并且需要制定好接口文档,后端工程师要写好测试用例(2个维度),不要让前端工程师充当你的专职测试,推荐使用chrome的插件postman或soapui或jmeter,service层的测试用例拿junit写。ps:前端也可以玩单元测试吗?
  2、上述的接口并不是java里的interface,说白了调用接口就是调用你controler里的方法。
  3、加重了前端团队的工作量,减轻了后端团队的工作量,提高了性能和可扩展性。
  4、我们需要一些前端的框架来解决类似于页面嵌套,分页,页面跳转控制等功能。(上面提到的那些前端框架)。
  5、如果你的项目很小,或者是一个单纯的内网项目,那你大可放心,不用任何架构而言,但是如果你的项目是外网项目,呵呵哒。
  6、 以前还有人在使用类似于velocity/freemarker等模板框架来生成静态页面,仁者见仁智者见智。
  7、这篇文章主要的目的是说jsp在大型外网java web项目中被淘汰掉,可没说jsp可以完全不学,对于一些学生朋友来说,jsp/servlet等相关的java web基础还是要掌握牢的,不然你以为springmvc这种框架是基于什么来写的?
  8、如果页面上有一些权限等等相关的校验,那么这些相关的数据也可以通过ajax从接口里拿。
  9、对于既可以前端做也可以后端做的逻辑,我建议是放到前端,为什么?因为你的逻辑需要计算资源进行计算,如果放到后端去run逻辑,则会消耗带宽&内存&cpu等等计算资源,你要记住一点就是服务端的计算资源是有限的,而如果放到前端,使用的是客户端的计算资源,这样你的服务端负载就会下降(高并发场景)。类似于数据校验这种,前后端都需要做!
  10、前端需要有机制应对后端请求超时以及后端服务宕机的情况,友好的展示给用户。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值