记7.9面试题

1.使用new和newInstance()创建类的区别?

(1)类的加载方式不同

   Java中工厂模式经常使用newInstance()方法来创建对象,例如:

class c = Class.forName(“Example”);
//从xml 配置文件中获得字符串
//class c = Class.forName(“xml名字”);
factory = (ExampleInterface)c.newInstance();

  在执行Class.forName(“a.class.Name”)时,JVM会在classapth中去找对应的类并加载,这时JVM会执行该类的静态代码段。在使用newInstance()方法的时候,必须保证这个类已经加载并且已经连接了,而完成这两个步骤(加载、连接)可以通过Class的静态方法forName()来完成,这个静态方法调用了启动类加载器
  使用关键字new创建一个类的时候,这个类可以没有被加载,一般也不需要该类在classpath中设定,但可能需要通过classlaoder来加载。

(2)所调用的构造方法不尽相同

  new关键字能调用任何构造方法
  newInstance()只能调用无参构造方法

class ceshi{
    int a=0;
    public ceshi(){
        System.out.println(a);
    }
}

      Class aaa=Class.forName("ceshi");//没有调用构造方法
      aaa.newInstance();//调用
      new ceshi();//调用

//如果想要调用构造方法,需要调用newInstance
//forname()会导致类被初始化,newInstance()才会实例化; 而new:初始化+实例化
//所以newInstance()实际上是把new这个方式分解为两步,即首先调用Class加载方法加载某个类,然后实例化

(3)执行效率不同

  new关键字是强类型的,效率相对较高
  newInstance()是弱类型的,效率相对较低

既然使用newInstance()构造对象的地方通过new关键字也可以创建对象,为什么又会使用newInstance()来创建对象呢?

  假设定义了一个接口Door,开始的时候是用木门的,定义为一个类WoodenDoor,在程序里就要这样写 Door door = new WoodenDoor() 。假设后来换为自动门了,定义一个类AutoDoor,这时程序就要改写为 Door door = new AutoDoor() 。虽然只是改个标识符,如果这样的语句特别多,改动还是挺大的。于是出现了工厂模式,所有Door的实例都由DoorFactory提供,这时换一种门的时候,只需要把工厂的生产模式改一下,还是要改一点代码。
  而如果使用newInstance(),则可以在不改变代码的情况下,换为另外一种Door。具体方法是把Door的具体实现类的类名放到配置文件中,通过newInstance()生成实例。这样,改变另外一种Door的时候,只改配置文件就可以了。示例代码如下:
  String className = 从配置文件读取Door的具体实现类的类名;
  Door door = (Door) Class.forName(className).newInstance();
  再配合依赖注入的方法,就提高了软件的可伸缩性、可扩展性。

参考Java中new和newInstance的区别

  new与newInstance()的区别

2.类加载器

  •  Java源代码.java文件通过编译成字节码.class文件后,需要被加载到虚拟机的内存空间中使用,这个过程就是类加载。类加载依靠的是Java类加载器。
  • Java类加载器是Java运行时环境的一部分,负责动态加载Java类到虚拟机内存空间中。类加载通常是按需加载的,即第一次使用该类时才加载。

JVM的3个默认类加载器?

  启动类加载器(Bootstrap ClassLoader):由原生代码C语言编写,不继承java.lang.ClassLoader。负责加载核心Java库,加载<JAVA_HOME>/jre/lib目录下的jar包和类。

  扩展类加载器(Extension ClassLoader):用来在指明的目录中加载Java的扩展类,主要负责加载<JRE_HOME>/jre/lib目录下的jar包和类。

  系统类加载器(Application ClassLoader):根据Java应用程序的类路径来加载Java类,一般来说,Java应用程序的类都是由它来完成加载的。负责加载当前应用classpath下的所有jar包和类

 如何保证一个类被加载一次?

  每一个类都有一个对应的类加载器,系统中的类加载器在协同工作的时候会默认使用双亲委派模型。它的工作过程是:如果一个类加载器收到了类加载的请求,系统会首先判断当前类是否被加载过,已经加载的类会直接返回,否则才会尝试加载。加载的时候,他首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,因此所有的加载请求最终都应传送到最顶层的启动类加载器中。只有当父加载器无法处理时,子加载器才会尝试自己去加载。

  每个类加载器只会加载自己负责的部分,这样每个类只会被加载一次。

参考面试题:类加载器

3.数据库:InnoDB存储引擎与MyISAM存储引擎区别?

 (1)InnoDB存储引擎支持事务,而MyISAM不支持事务。

(2)InnoDB是行级锁,MyISAM是表级锁。

  MySQL表级锁有两种模式:表共享读锁和表独占写锁。就是说对MyIASM表进行读操作时,它不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写操作;而对MyISAM表的写操作,会阻塞其他用户对同一表的读和写操作。

  InnoDB行锁是通过给索引项加锁实现的,即只有通过索引条件检索数据,InnoDB才使用行级锁,否则将使用表级锁行级锁在每次获取锁和释放锁的操作需要比表级锁消耗更多的资源。在InnoDB两个事务发生死锁的时候,会计算出每个事务影响的行数,然后回滚行数少的那个事务。当锁定的场景中不涉及Innodb的时候,InnoDB是检测不到的。只能依靠锁定超时来解决。

(3)InnoDB支持外键,MyISAM不支持外键

(4)InnoDB不保存数据库表中表的具体行数,而MyISAM会保存

  也就是说,执行 select count(*) from table 时,InnoDB要扫描一遍整个表来计算有多少行,而InnoDB只需要读出保存好的行数即可。

  【注】:count(*)语句包含where条件时,两种表的操作是一样的。也就是 上述介绍到的InnoDB使用表锁的一种情况。

(5)对于select ,update ,insert ,delete 操作:

  如果执行大量的SELECT,MyISAM是更好的选择(因为MyISAM不支持事务,使得MySQL可以提供高速存储和检索,以及全文搜索能力)

  如果执行大量的INSERT或UPDATE,出于性能方面的考虑,应该使用InnoDB表(因为InnoDB支持事务,在一些列增删改中只要哪个出错还可以回滚还原,而MyISAM就不可以了)

如何选择?            

  MyISAM适合:

  • (1)做很多count 的计算;
  • (2)插入不频繁,查询非常频繁,如果执行大量的SELECT,MyISAM是更好的选择;
  • (3)没有事务。

  InnoDB适合:

  • (1)可靠性要求比较高,或者要求事务;
  • (2)表更新和查询都相当的频繁,并且行锁定的机会比较大的情况。
  • 另外:
  • DELETE FROM table时,InnoDB不会重新建立表,而是一行一行的 删除;
  • LOAD TABLE FROM MASTER操作对InnoDB是不起作用的,解决方法是首先把InnoDB表改成MyISAM表,导入数据后再改成InnoDB表,但是对于使用的额外的InnoDB特性(例如外键)的表不适用。

参考MySQL存储引擎MyISAM与InnoDB区别总结整理

  mysql存储引擎中InnoDB与Myisam的区别及应用场景

  MySQL存储引擎MyISAM与InnoDB区别总结整理

4.项目中你提到用fastDFS搭建图片服务器,说一下用fastDFS搭建图片服务器的过程?

参考用FastDFS一步步搭建文件管理系统

  FastDFS分布式文件系统&Nginx负载均衡最小环境安装配置[超级详解]

  首先安装并配置fastDFS,这里配置的话主要配置跟踪器tracker(配置提供服务的端口、数据目录和http服务端口),和存储器storage(配置提供服务的端口、数据目录、连接的tracker和访问端口)。配置好fastDFS后,只是解决了图片保存的问题,要想通过http访问图片还需要安装nginx

Nginx只需要安装到StorageServer所在的服务器即可,用于访问文件。

FastDFS 架构包括 Tracker server 和 Storage server。客户端请求 Tracker server 进行文件上传、下载,通过 Tracker server 调度最终由 Storage server 完成文件上传和下载。

  • Tracker server(跟踪服务器)作用是:负载均衡和调度,主要负责调度storage节点与client通信,在访问上起负载均衡的作用和记录storage节点的运行状态,是连接client和storage节点的枢纽。
    • 跟踪服务器主要做调度工作,起到均衡的作用;负责管理所有的 storage server和 group,每个 storage 在启动后会连接 Tracker,告知自己所属 group 等信息,并保持周期性心跳。
  • Storage server(存储服务器)作用是:文件存储,客户端上传的文件最终存储在 Storage 服务器上,Storageserver 没有实现自己的文件系统而是利用操作系统的文件系统来管理文件。
    • Storage 分为多个组,每个组之间保存的文件是不同的。每个组内部可以有多个成员,组成员内部保存的内容是一样的,组成员的地位是一致的,没有主从的概念。
    • Group:文件组,也可以称为卷。同组内服务器上的文件是完全相同的,做集群时往往一个组会有多台服务器,上传一个文件到同组内的一台机器上后,FastDFS会将该文件即时同步到同组内的其它所有机器上,起到备份的作用。 

FastDFS的上传过程:

 首先客户端 client 发起对 FastDFS 的文件传输动作,是通过连接到某一台 Tracker Server 的指定端口来实现的,Tracker Server 根据目前已掌握的信息,来决定选择哪一台 Storage Server ,然后将这个Storage Server 的地址等信息返回给 client,然后 client 再通过这些信息连接到这台 Storage Server,将要上传的文件传送到给 Storage Server上。

  • 1、client询问tracker上传到的storage,不需要附加参数; 
  • 2、tracker返回一台可用的storage; 
  • 3、client直接和storage通讯完成文件上传。

FastDFS的下载过程:

  在downloadfile时客户端可以选择任意tracker server。tracker发送download请求给某个tracker,必须带上文件名信息,tracke从文件名中解析出文件的group、大小、创建时间等信息,然后为该请求选择一个storage用来服务读请求。

  • 1、client询问tracker下载文件的storage,参数为文件标识(组名和文件名); 
  • 2、tracker返回一台可用的storage; 
  • 3、client直接和storage通讯完成文件下载。

负载均衡:将所有请求平均分配给所有服务器。

  • Nginx对网络稳定性的依赖非常小,理论上能ping通就能进行负载均衡功能。
推荐:关于FastDFS蛋疼的集群和负载均衡(十二)之浅谈负载均衡

你搭建的fastDFS是单节点还是多节点?

 单节点

多结点如何保证同步的?

 

5.一道算法题:给你一个数组[6,5,18,19,23,20],找出数组里满足这样条件的数字:左边数字都比它小,右边数字都比它大。

 思路:从左往右找升序的数字:[18,19,23](不包括第一个数字),从右往左找降序的数字:[19,18,5](不包括最后一个数字),两个交集即是要找的数字:18,19

/**定义一个相同大小的数组flags(初始全零0),
    对原始数组,从左向右找升序的数字,并将数字对应flags位置的数字+1,此时升序的数字标记为了1;
    然后从右往左找降序的数字,并将数字对应flags位置的数字+1,此时升序的数字标记为了2;
    这样flags为2的对应的数字就是我们要找的*/
public static void find(int[] nums) {
        int n = nums.length;
        int[] flags = new int[n];
        for(int i = 1; i < n - 1; i++){
            if(nums[i] > nums[i - 1])
                flags[i]++;
        }
        for(int i = n - 2; i > 0; i--){
            if(nums[i] < nums[i + 1])
                flags[i]++;
        }
        
        for(int i = 0; i < n; i++){
            if(flags[i] == 2)
                System.out.println(nums[i]);
        }
    }

类似的:(leetcode739 每个元素后面比它大的第一个数)

  给定一个整数数组T(每个值代表当天的温度),找到每个元素后面比它大的第一个元素,并输出两个元素距离。

例:T = [73, 74, 75, 71, 69, 72, 76, 73], 输出:[1, 1, 4, 2, 1, 1, 0, 0]

逆序遍历,把每天温度记录下来,将其索引存在一个单调递减的栈stack中(温度单调递减),如果当前温度比栈顶元素高(不满足单调递减),就将栈顶元素出栈,直到栈为空或者满足要求。

示例:[73, 74, 75, 71, 69, 72, 76, 73]

第1次遍历:i=7,T[i]=73,stack=[]

      最后一天,后面没有比今天温度更高的,res[7]=0,stack=[7]

第2次遍历:i=6,T[i]=76,stack=[7]

     栈顶对应温度T[7]=73<76,出栈,此时栈为空。加入6,res[6]=0,stack=[6]

第3次遍历:i=5,T[i]=72,stack=[6]

     栈顶对应温度T[6]=76>72,满足要求,计算结果后入栈。res[5]=6-5=1,stack=[6,5]

第4次遍历:i=4,T[i]=69,stack=[6,5]

     栈顶对应温度T[5]=72>69,满足要求,计算结果后入栈。res[4]=5-4=1,stack=[6,5,4]

第5次遍历:i=3,T[i]=71,stack=[6,5,4]

     栈顶对应温度T[4]=69>71,出栈,stack=[6,5]

     栈顶对应温度T[5]=72>71,满足要求,计算结果后入栈。res[3]=5-3=2,stack=[6,5,3]

第6次遍历:i=2,T[i]=75,stack=[6,5,3]

     栈顶对应温度T[3]=71<75,出栈,stack=[6,5]

     栈顶对应温度T[5]=72<71,出栈,stack=[6]

     栈顶对应温度T[6]=76>75,满足要求,计算结果后入栈。res[2]=6-2=4,stack=[6,2]

第7次遍历:i=1,T[i]=74,stack=[6,2]

     栈顶对应温度T[2]=75>74,满足要求,计算结果后入栈。res[1]=2-1=1,stack=[6,2,1]

第8次遍历:i=0,T[i]=73,stack=[6,2,1]

     栈顶对应温度T[1]=74>73,满足要求,计算结果后入栈。res[0]=1-0=1,stack=[6,2,1,0]

    public static int[] dailyTemperatures(int[] T) {

        int n = T.length;
        //存放结果
        int[] res = new int[n];
        //定义栈
        Stack<Integer> stack = new Stack<Integer>();
        
        //思路:逆序遍历,如果当前元素比栈顶元素小,就将当前元素索引入栈;否则出栈,直到栈为空或者当前元素比栈顶元素小。每次栈操作完后计算res=栈顶元素索引-i。最后将当前元素索引i入栈
        for(int i = n - 1; i >= 0; i--){
            //当前元素不比后面的元素小的话(大于或等于),出栈
            while(!stack.isEmpty() && T[i] >= T[stack.peek()]){
                      stack.pop();  
            }
            // 栈为空 即后面没有比当前天温度高的
            // 不为空 栈顶元素对应的下标减去当前下标即为经过几天后温度比当前天温度高
            res[i] = stack.isEmpty() ? 0 : stack.peek()-i;
            
            //当前元素入栈
            stack.push(i);
        }
        return res;
    }
原文地址:https://www.cnblogs.com/toria/p/11167159.html