【第二套】Java面试题

第二套:

一、JavaScript前端开发

1、下列的代码输出什么?

var y = 1;
if(function f(){}){
    y += typeof f;
}
console.log(y);

正确的答案应该是 1undefined。
JavaScript中if语句求值其实使用eval函数,eval(function f(){}) 返回 function f(){} 也就是 true。
下面我们可以把代码改造下,变成其等效代码。

var k = 1;
if (1) {
    eval(function foo(){});
    k += typeof foo;
 }
console.log(k);

上面的代码输出其实就是 1undefined。为什么那?我们查看下 eval() 说明文档即可获得答案

该方法只接受原始字符串作为参数,如果 string 参数不是原始字符串,那么该方法将不作任何改变地返回。

恰恰 function f(){} 语句的返回值是 undefined,所以一切都说通了。
注意上面代码和以下代码不同。

var k = 1;
if (1) {
    function foo(){};
    k += typeof foo;
 }
console.log(k); // output 1function 

2、写出一个caNum函数,可以将参数想成,使用方法如下:

Console.log(caNum(2)(3)(4)); //输出:24
Console.log(caNum(4)(3)(4)); //输出:48

最普通的实现版本:

var multiply = function () {
  var a = 1;
  for (let i = 0; i < arguments.length; i++) {
    a = a * arguments[i];
  }
  return a;
}
console.log('11:',multiply(1,2,3,4)); // 输出:24

如果想了解优化版本,链接:https://blog.csdn.net/qq_37268201/article/details/113262983

3、编写一个函数,使用JQuery如何判断变量obj是否为数组。

var arr= [1,1,1,1]
var a = '2323'
var b =  {name:'xiao',age:12}
var n = 1

1、instanceof
instanceof运算符用于检验构造函数的prototype属性是否出现在对象的原型链中的任何位置,返回一个布尔值。
注意的是,prototype属性是可以修改的,所以并不是最初判断为true就一定永远为真。

console.log('方法1',arr instanceof Array); //如果是数组 打印结果为 true

2、constructor
实例的构造函数属性constructor指向构造函数,那么通过constructor属性也可以判断是否为一个数组。
这种判断也会存在多个全局环境的问题,导致的问题与instanceof相同。

console.log('方法2',arr.constructor === Array);//true

3、Array.isArray() 最推荐方法
同样能准确判断,但有个问题,Array.isArray() 是在ES5中提出,也就是说在ES5之前可能会存在不支持此方法的情况。

console.log('方法3',Array.isArray(arr)); //true

function isArray(obj){
 return $.isArray(obj)
}//JQuery isArray 的实现其实就是方法5

4、typeof
使用该方法 判断数组时 打印结果为object(弊端在于如果数据类型为引用数据类型,他只能返回 Object)

此方法的返回结果只有以下几种:Number、String、undefined、Bollean、Object、Function

console.log('方法4',typeof n); //number
console.log('方法4',typeof(b)) //object

5 Object.prototype.toSrtring.call()
当使用该方法判断其他数据类型时要注意一点是,IE8及IE8以下,undefined和null均为Object,IE9及IE9以上为[object Undefined]和[object Null]

console.log(Object.prototype.toString.call(arr).indexOf('Array') !== -1); //true
console.log(Object.prototype.toString.call(arr) === '[object Array]');    //true

function isArray(obj){
 return Object.prototype.toString.call( obj ) === '[object Array]'; 
} //这里使用call来使 toString 中 this 指向 obj。进而完成判断

参考链接:https://blog.csdn.net/qq_43399210/article/details/111403146

4、以下代码运行时抛出异常,请将代码改正,确保可以执行。

var foo = function bar(){return 12;};
typeof bar();

输出是抛出异常,bar is not defined。 如果想让代码正常运行,需要这样修改代码:

var bar = function(){ return 12; };
typeof bar();

或者是

function bar(){ return 12; };
typeof bar();

二、Java后端开发:

1、Java 中如何将对象序列化,什么情况下需要序列化;

只有实现了Serializable或者Externalizable接口的类的对象才能被序列化为字节序列。(不是则会抛出异常)
一方面,发送方需要把这个Java对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出Java对象。

序列化:把Java对象转换为字节序列(二进制)的过程。
反序列化:把字节序列恢复为Java对象的过程。

注:序列化是将对象状态转换为可保持或可传输的格式的过程。反序列化将流转换为对象。这两个过程结合起来,可以轻松地存储和传输数据。JSON是用于接口数据交换的格式。核心作用就是对象状态的保存和重建

序列化和反序列化总的来说可以归结为以下几点:

  • 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;(持久化对象)将对象转为字节流存储到硬盘上,当JVM停机的话,字节流还会在硬盘上默默等待,等待下一 次JVM的启动,把序列化的对象,通过反序列化为原来的对象,并且序列化的二进制序列能够减少存储空间

  • 在网络上传送对象的字节序列。(网络传输对象)

  • 通过序列化在进程间传递对象;

2、列举常用的Map集合,并简述HashMap 与 HashTable的区别;

Map映射:无序,键唯一,值不唯一
【第二套】Java面试题_第1张图片

HashMap: 哈希映射/字典,无序字典,键值对数据,key是唯一的,Key和Value都可以为null
Hashtable: 与HashMap类似,是HashMap的线程安全版,它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtale在写入时会比较慢,它继承自Dictionary类,不同的是它不允许记录的键或者值为null,同时效率较低。
TreeMap: 红黑树实现的key->value融合,可排序,红黑树是一种自平衡二叉查找树。
LinkedHashMap: 链表映射/字典,继承了hashmap的所有特性,同时又实现了双向链表的特性,保留了元素插入顺序。
ConcurrentHashMap: 线程安全,并且锁分离。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的hash table,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。

HashMap 与 HashTable的区别:

  • hashMap去掉了HashTable 的contains方法,但是加上containsValue()和containsKey()方法。
  • hashTable同步的,线程安全的;而HashMap是非同步的,效率上逼hashTable要高。
  • hashMap允许空键值,而hashTable不允许。

3、编写代码:Java遍历Map方式。

Map内容:{“name”:“张某某”,“age”:“30”,“sex”:“male”,“code”:“3010”}

1、在for循环中使用entrySet的遍历,也可以同时拿到key和value,用的最多

Map <String,String>map = new HashMap<String,String>();
map.put("熊大", "棕色");
map.put("熊二", "黄色");
for(Map.Entry<String, String> entry : map.entrySet()){
    String mapKey = entry.getKey();
    String mapValue = entry.getValue();
    System.out.println(mapKey+":"+mapValue);
}

2、在for循环中使用map.keySet()遍历key通过map.get(key)获取单个值,或使用map.values()遍历获取values
这种方式一般适用于只需要map中的key或者value时使用,在性能上比使用entrySet较好;

Map <String,String>map = new HashMap<String,String>();
map.put("熊大", "棕色");
map.put("熊二", "黄色");
//key
for(String key : map.keySet()){
    System.out.println(key);
}
//value
for(String value : map.values()){
    System.out.println(value);
}
System.out.println("通过map.keyset进行遍历key和value");

for (String key:map.keySet()){

    System.out.println("key= "+key+" and value= "+map.get(key));

}

3、通过Iterator遍历
在使用Iterator遍历时:使用foreach遍历map时,如果改变其大小,会报错,但如果只是删除元素,可以使用Iterator的remove方法删除元素

Iterator<Entry<String, String>> entries = map.entrySet().iterator();//用迭代器的时候注意:next()方法在同一循环中不能出现俩次!
//否则会抛出NoSuchElementException异常。
while(entries.hasNext()){
    Entry<String, String> entry = entries.next();
    String key = entry.getKey();
    String value = entry.getValue();
    System.out.println(key+":"+value);
}

4、通过键找值遍历(就是第二种遍历key的方式+get值的方式)
这种方式的效率比较低,因为本身从键取值是耗时的操作,不建议使用;

for(String key : map.keySet()){
    String value = map.get(key);
    System.out.println(key+":"+value);
}

一些结论:
如果只是获取key,或者value,推荐使用keySet或者values方式
如果同时需要key和value推荐使用entrySet
如果需要在遍历过程中删除元素推荐使用Iterator
如果需要在遍历过程中增加元素,可以新建一个临时map存放新增的元素,等遍历完毕,再把临时map放到原来的map中

map遍历参考链接:https://blog.csdn.net/qq_44750696/article/details/112254719

4、通过“反射”,创建com.weavercsd.po.UseerInfo 对象的实例。

通过反射创建对象有两种方式:
1、使用Class对象的newInstance方法创建该Class对象对应的类实例(该Class对象对应的类有默认的空构造器)

Class clazz = Class.forName("com.weavercsd.po.UseerInfo");
Person person = (Person) clazz.newInstance();

2、使用Constructor对象newInstance方法创建Class对象对应的类实例
该方法有两步:
1、通过Class对象获取指定的Constructor对象
2、调用Constructor对象的newInstance方法创建Class对象对应的类实例
该方法可以选定构造方法创建实例

Class clazz = Class.forName("com.weavercsd.po.UseerInfo");
Constructor declaredConstructor = clazz.getDeclaredConstructor(Integer.class, String.class, Integer.class, String.class);
Person person = (Person) declaredConstructor.newInstance(1, "张三", 30, "男");

使用Class对象的newInstance方法:

@Data
public class Person {

    private Integer id;
    private String name;
    private Integer age;
    private Integer sex;
}
public class TestReflectCreateObj {
    public static void main(String[] args) {
        Class clazz = null;
        try{
            clazz = Class.forName("com.kevin.base.reflect.Person");
            Person person = (Person) clazz.newInstance();
            System.out.println(person);
        }catch (ClassNotFoundException e){
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

 执行结果:
 Person(id=null, name=null, age=null, sex=null)

使用Constructor对象newInstance方法:

@Data
@AllArgsConstructor
public class Person {

    private Integer id;
    private String name;
    private Integer age;
    private String sex;
}

public class TestReflectCreateObj {
    public static void main(String[] args) {
        Class clazz = null;
        try{
            //1.获取Person类的Class对象
            clazz = Class.forName("com.kevin.base.reflect.Person");
            //2.获取构造方法并创建对象
            Constructor declaredConstructor = clazz.getDeclaredConstructor(Integer.class, String.class, Integer.class, String.class);
            //3.根据构造方法创建对象并设置属性
            Person person = (Person) declaredConstructor.newInstance(1, "张三", 30, "男");
            System.out.println(person);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

执行结果:
Person(id=1, name=张三, age=30, sex=)

5、XML 和 JSON 有什么区别,分别有哪些解析方式;

区别:
XML主要是由element、attribute和element content组成。JSON主要是由object、array、string、number、boolean(true/false)和null组成。
XML要表示一个object(指name-value pair的集合),最初可能会使用element作为object,每个key-value pair 用 attribute 表示。
XML需要选择怎么处理element content的换行,而JSON string则不须作这个选择。
XML只有文字,没有预设的数字格式,而JSON则有明确的number格式,这样在locale上也安全。
XML映射数组没大问题,就是数组元素tag比较重复冗余。JSON 比较易读。
JSON的true/false/null也能容易统一至一般编程语言的对应语义。
XML文档可以附上DTD、Schema,还有一堆的诸如XPath之类规范,使用自定义XML元素或属性,能很方便地给数据附加各种约束条件和关联额外信息,从数据表达能力上看,XML强于Json,但是很多场景并不需要这么复杂的重量级的东西,轻便灵活的Json就显得很受欢迎了。

XML:JAVA中可使用DOM、SAX、DOM4J 或JDOM等解析
DOM形成了树结构,直观好理解,代码更易编写。解析过程中树结构保留在内存中,方便修改。当xml文件较大时,对内存耗费比较大,容易影响解析性能并造成内存溢出。
SAX采用事件驱动模式,对内存耗费比较小。适用于只需要处理xmI中数据时。但不易编码,很难同时访问同一个xml中的多处不同数据。
DOM4J 是一个非常非常优秀的Java XML API,将整个文档读入,然后解析,具有性能优异、功能强大和极端易用使用的特点,同时它也是一个开放源代码的软件。
JDOM是基于树的处理XML的Java API,把树加载在内存中,没有向下兼容的限制,因此比DOM简单速度快,缺陷少具有SAX的JAVA规则
,但不能处理大于内存的文档,JDOM表示XML文档逻辑模型。不能保证每个字节真正变换。针对实例文档不提供DTD与模式的任何实际模型。不支持与DOM中相应遍历包。
JSON:JAVA 使用FastJSON、Gson或Jackson等解析
Fastjson是一个Java语言编写的高性能的JSON处理器,由阿里巴巴公司开发。无依赖,不需要例外额外的jar,能够直接跑在JDK上。FastJson采用独创的算法,将parse的速度提升到极致,超过所有json库。
Gson是目前功能最全的Json解析神器,Gson当初是为因应Google公司内部需求而由Google自行研发而来,但自从在2008年五月公开发布第一版后已被许多公司或用户应用。Gson的应用主要为toJson与fromJson两个转换函数,无依赖,不需要例外额外的jar,能够直接跑在JDK上。
Jackson所依赖的jar包较少,简单易用并且性能也要相对高些。而且Jackson社区相对比较活跃,更新速度也比较快。Jackson对于复杂类型的json转换bean会出现问题,一些集合Map,List的转换出现问题。Jackson对于复杂类型的bean转换Json,转换的json格式不是标准的Json格式。json-lib最开始的也是应用最广泛的json解析工具,json-lib 不好的地方确实是依赖于很多第三方包

xml
可扩展标记语言,是互联网数据传输的重要工具,可以跨越互联网任何平台,不受编程语言和操作系统的限制.

特点:

  • 1.xml与操作系统,变成语言的开发平台无关
  • 2.实现不同系统之间的数据交互

作用:

  1. 配置应用程序
  2. 数据交互
  3. Ajax基石

规则:

  1. 必须有xml声明语句
  2. 必须有且仅有一个根元素
  3. 标签大小写敏感
  4. 属性值用双引号
  5. 标签成对出现
  6. 空标签关闭
  7. 元素正确嵌套

JSON
是一种数据交换格式.json全称是JavaScript Object Notation, 意思是对象表示法,它是一种基于文本,独立于语言的轻量级数据交换格式

特点:

  1. JSON是纯文本。
  2. JSON具有良好的自我描述性,便于阅读。
  3. JSON具有层级结构(值中存在值)。
  4. JSON可通过JavaScript进行解析。
  5. JSON数据可使用AJAX进行传输。

JSON和XML的区别:
6. 数据体积方面, JSON相对于XML来说, 数据的体积小,传递快
7. 数据交互方面,JSON与JavaScript交互更加方便,更容易解析处理,更好地数据交互
8. 数据描述方面,JSON对数据的描述性比xml较差
9. 传递速度方面,JSON的速度要远远快于xml

JSON和xml的优缺点:

  1. JSON轻便,解析简单,对客户端更加友好
  2. xml更加笨重,解析为DOM数并遍历节点来获取数据,优点是结构清晰,扩展性好

6、简述Restful架构,列举最常用的Rest支持的HTTP请求方法。

REST是所有Web应用都应该遵守的架构设计指导原则。就是用URI定位资源,用HTTP(GET,POST,DELETE,PUT)描述操作。
REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征)性状态转移。
RESTful是一个针对URI的架构,使用RESTful风格的URI可以使URI更具可读性。代码更加整洁且富有逻辑性。
首先要明白,RESTful架构是面向资源的。每一个URI可以确定一个资源。而用户或程序员使用URI其实就是在操作资源。
因此至始至终都应该围绕资源来设计URI。(可以在URI里带上资源的主键,在URI里边带上版本号,使用/来区分资源的层级关系,使用?用来过滤资源)

REST最大的几个特点为:资源(需要查询的数据库信息等等)、统一接口(get,post,delete,put)、URI(URI既可以看成是资源的地址,也可以看成是资源的名称,每个URI对应一个特定的资源)、无状态(所有的资源,都可以通过URI定位,而且这个定位与其他资源无关,也不会因为其他资源的变化而改变)。
EX:有状态和无状态的区别,举个简单的例子说明一下。如查询员工的工资,如果查询工资是需要登录系统,进入查询工资的页面,执行相关操作后,获取工资的多少,则这种情况是有状态的,因为查询工资的每一步操作都依赖于前一步操作,只要前置操作不成功,后续操作就无法执行;如果输入一个url即可得到指定员工的工资,则这种情况是无状态的,因为获取工资不依赖于其他资源或状态,且这种情况下,员工工资是一个资源,由一个url与之对应,可以通过HTTP中的GET方法得到资源,这是典型的RESTful风格。REST遵循无状态通信原则。这并不代表客户端应用不能有状态。而是指服务端不应保留客户端的状态。

RESTful架构:
  (1)每一个URI代表一种资源;
  (2)客户端和服务器之间,传递这种资源的某种表现层;
  (3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。

2、客户端的HTTP的请求方式一般分为四种:GET、POST、PUT、DELETE、PATCH、HEAD 和 OPTIONS。
GET就是获取资源, POST就是创建资源(也可用于更新资源),PUT就是更新资源,DELETE就是删除资源
GET:获取资源, 当客户端使用 GET 方法请求服务器时,服务器将返回与请求 URI 相关联的资源的表示形式。GET 方法不应该改变服务器上的资源状态。GET只是访问和查看资源。GET操作是安全的。

GET /users HTTP/1.1
Host: api.example.com

上述示例中,客户端使用 GET 方法获取了所有用户的信息,并将响应数据以 JSON 或 XML 格式返回。
POST:创建资源(也可用于更新资源), 当客户端使用 POST 方法向服务器发送请求时,服务器会创建一个新的资源,并返回该资源的 URI。操作不是安全的,每次请求都会创建资源,当我们多次发出POST请求后,其结果是创建出了多个资源。
还有一点需要注意的就是,创建操作可以使用POST,也可以使用PUT,区别在于POST 是作用在一个集合资源之上的(/uri),而PUT操作是作用在一个具体资源之上的(/uri/xxx),再通俗点说,如果URL可以在客户端确定,那么就使用PUT,如果是在服务端确定,那么就使用POST,比如说很多资源使用数据库自增主键作为标识信息,而创建的资源的标识信息到底是什么只能由服务端提供,这个时候就必须使用

POSTPOST /users HTTP/1.1
Host: api.example.com
Content-Type: application/json

{
  "name": "John Doe",
  "age": 30,
  "email": "[email protected]"
}

上述示例中,客户端使用 POST 方法创建一个新用户,并将用户信息以 JSON 格式发送到服务器。
PUT:更新资源。 当客户端使用 PUT 方法向服务器发送请求时,服务器将更新与请求 URI 相关联的资源的表示形式。用来修改数据的内容,但是资源不会增加

PUT /users/1 HTTP/1.1
Host: api.example.com
Content-Type: application/json

{
  "name": "John Doe",
  "age": 31,
  "email": "[email protected]"
}

上述示例中,客户端使用 PUT 方法更新 ID 为 1 的用户信息,并将更新后的用户信息以 JSON 格式发送到服务器。
DELETE:删除资源。 当客户端使用 DELETE 方法向服务器发送请求时,服务器将删除与请求 URI 相关联的资源。

DELETE /users/1 HTTP/1.1
Host: api.example.com

上述示例中,客户端使用 DELETE 方法删除 ID 为 1 的用户信息。
PATCH:用于更新部分资源。 当客户端使用 PATCH 方法向服务器发送请求时,服务器将更新与请求 URI 相关联的资源的部分属性。

PATCH /users/1 HTTP/1.1
Host: api.example.com
Content-Type: application/json

{
  "age": 31
}

上述示例中,客户端使用 PATCH 方法更新 ID 为 1 的用户的年龄信息,并将更新后的用户信息以 JSON 格式发送到服务器。

HEAD :用于获取资源的元数据,但不返回实际的响应主体。当客户端使用 HEAD 方法向服务器发送请求时,服务器将返回与请求 URI 相关联的资源的元数据,例如资源的大小和修改时间等。

HEAD /users HTTP/1.1
Host: api.example.com

上述示例中,客户端使用 HEAD 方法获取所有用户的元数据,并不返回实际的响应主体。
OPTIONS :用于获取服务器支持的 HTTP 方法和资源的元数据。当客户端使用 OPTIONS 方法向服务器发送请求时,服务器将返回与请求 URI 相关联的资源的支持的 HTTP 方法和元数据。

OPTIONS /users HTTP/1.1
Host: api.example.com

上述示例中,客户端使用 OPTIONS 方法获取所有用户支持的 HTTP 方法和元数据。

我们使用了 SpringBoot 提供的注解来定义 RESTful Web 服务的各种操作,例如 @RestController、@GetMapping、@PostMapping、@PutMapping、@DeleteMapping 和 @PathVariable 等注解。这些注解可以帮助我们更方便地定义 RESTful Web 服务的各种操作。

GET和POST的区别:

  1. GET请求的数据会附在URL之后(就是把数据放置在HTTP协议头中),以?分割URL和传输数据,参数之间以&相连,如:getCitycode?lat=100.22&lon=35.33;POST把提交的数据则放置在是HTTP包的包体中。
  2. 在浏览器上,GET方式提交的数据是有限制的,例如有时候请求的URL太长,会返回错误;但如果是客户端GET请求,是没有数据的限制的。POST没有限制,可传较大量的数据。
  3. POST的安全性要比GET的安全性高。这里所说的安全性和上面GET提到的“安全”不是同个概念。上面“安全”的含义仅仅是不作数据修改,而这里安全的含义是真正的Security的含义,比如:通过GET提交数据,用户名和密码将明文出现在URL上,查看浏览器的历史纪录,就可以查看到GET请求的参数,比如登录的帐号密码、搜索关键字、个人信息等。

POST和PUT在创建资源的区别在于, 所创建的资源的名称(URI)是否由客户端决定。 例如为我的博文增加一个java的分类,生成的路径就是分类名/categories/java,那么就可以采用PUT方法。
PUT和POST方法语义中都有修改资源状态的意思,因此都不是安全的。但是PUT方法是幂等的,POST方法不是幂等的,这么设计的理由是:
HTTP协议规定,POST方法修改资源状态时,URL指示的是该资源的父级资源,待修改资源的ID信息在请求体中携带。而PUT方法修改资源状态时,URL直接指示待修改资源。因此,同样是创建资源,重复提交POST请求可能产生两个不同的资源,而重复提交PUT请求只会对其URL中指定的资源起作用,也就是只会创建一个资源。

7、描述Java对Excel读写的常用方式(可用文字说明或代码示例解答)。

EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。
EasyExcel采用一行一行的解析模式,并将一行的解析结果以观察者的模式通知处理。

1、java读取Excel表中的内容:

  • 准备实体类,converter转换类
  • 准备导入监听器,主要用来监听读取Excel文件数据,用来一行行的读取数据,需要继承EasyExcel方法AnalysisEventListener,重写有三个方法(读取数据操作,读取表头操作,读取数据结束后操作)
  • 使用 EasyExcel.read方法,指定读取的文件路径,使用哪个实体类读取,以及使用的监听器对象,读取那个sheet等等。
public class ExceListener extends AnalysisEventListener<UserData> {
    /**
     * 进行读的操作具体执行方法,一行一行的读取数据
     * 从第二行开始读取,不读取表头
     *
     * @param userData
     * @param analysisContext
     */
    @Override
    public void invoke(UserData userData, AnalysisContext analysisContext) {
        System.out.println(userData);
    }

    /**
     * 读取表头信息
     *
     * @param headMap
     * @param context
     */
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        super.invokeHeadMap(headMap, context);
        System.out.println("表头信息:" + headMap);
    }

    /**
     * 读取完数据的操作
     *
     * @param analysisContext
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
    }
}
private static void read() {
    // 指定读的路径
    String fileName = "D:\1627870562085.xlsx";
    //  调用读的方法
    EasyExcel.read(fileName, UserData.class, new ExceListener())
        .sheet().doRead();
}

2、通过java将数据生成为Excel表:

  • 导入相应的包,创建实体类
  • 往外导出数据时,需要到调用EasyExcel的write方法写入文件或者写入有个输出流中都是可以的,指定使用哪个实体类写入文件,sheet指定Excel文件页面sheet值,dowrite(写入的数据集合)
private void writer() {
        List<UserData> userData = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            UserData data = new UserData();
            data.setUid(i);
            data.setName("zdk" + i);
            userData.add(data);
        }
        // 指定写的路径
        String fileName = "D:\\" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,
        // 名字为模板 然后文件流会自动关闭
        EasyExcel.write(fileName, UserData.class)
                .sheet("模板")
                .doWrite(userData);
 }

8、编码实现创建多线程。(创建多线程的4种方式)

继承Thread类、实现Runnable接口、实现Callable接口、线程池的方式;

第一种:继承Thread类:

  1. 创建一个继承于Thread类的子类
  2. 重写Thread类的run() 将此线程执行的的操作声明在run()中
  3. 创建Thread类的子类的对象
  4. 通过此对象调用start()
/**
 * 多线程的创建,方式一:继承于Threadlei
 * 1.创建一个继承于Thread类的子类
 * 2.重写Thread类的run()  将此线程执行的的操作声明在run()中
 * 3.创建Thread类的子类的对象
 * 4.通过此对象调用start()
 *
 * 例子:遍历100以内所有的偶数
 */
public class ThreadTest {
    public static void main(String[] args) {
        //3.创建Thread类的子类的对象
        MyThread myThread = new MyThread();

        //4.通过此对象调用start()  ①启动当前线程 ②调用当前线程的run()
        myThread.start();

        //不能通过myThread.run()的方式启动线程

        //再启动一个线程
        MyThread myThread1 = new MyThread();
        myThread1.start();

        //如下操作仍然是在main()线程中执行的
        for (int i = 0;i < 100 ;i++){
            if (i % 2 != 0){
                System.out.println(Thread.currentThread().getName() + ":"+"hello");
            }
        }
    }
}

//  1.创建一个继承于Thread类的子类
class MyThread extends Thread {
    //2.重写Thread类的run()
    @Override
    public void run() {
        for (int i = 0;i < 100 ;i++){
            if (i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

第二种:实现Runnable接口

  1. 创建一个实现了Runnable接口的类
  2. 实现类去实现Runnable中的抽象方法:run()
  3. 创建实现类的对象
  4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  5. 通过Thread类的对象调用start()
/**
 * 创建多线程的方式二:实现Runnable接口
 * 1.创建一个实现了Runnable接口的类
 * 2.实现类去实现Runnable中的抽象方法:run()
 * 3.创建实现类的对象
 * 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
 * 5.通过Thread类的对象调用start()
 *
 * 比较创建线程的两种方式:
 * 开发中优先选择实现Runnable接口的方式
 * 原因:1.实现的方式没有类的单继承性的局限性
 *      2.实现的方式更适合来处理多个线程有共享数据的情况
 *
 * 联系:都实现了Runnable接口
 * 相同点:两种方式都需要重写run()方法,将线程要执行的逻辑声明在run()中
 *
 */
public class ThreadTest1 {
    public static void main(String[] args) {
        // * 3.创建实现类的对象
        MyThread myThread = new MyThread();
        // * 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
        Thread thread1 = new Thread(myThread);
        // * 5.通过Thread类的对象调用start() ①启动线程 ②调用当前线程的run()
        thread1.start();

        Thread thread2 = new Thread(myThread);
        thread2.start();
    }
}

// * 1.创建一个实现了Runnable接口的类
class MyThread implements Runnable{
    // * 2.实现类去实现Runnable中的抽象方法:run()
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

第三种:实现Callable接口

  1. 创建一个实现Callable的实现类
  2. 实现call方法,将此线程需要执行的操作声明在call()中
  3. 创建Callable接口实现类的对象
  4. 将Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
  5. 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()方法
  6. 获取Callable中call方法中的返回值
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 创建线程的方式三:实现Callable接口。   JDK5.0新增
 *
 * 如何理解实现Callable接口的方式创建多线程的方式比是实现Runnable接口创建多线程方式强大
 * 1.call()可以有返回值
 * 2.call()可以抛出异常
 */
public class ThreadNew {
    public static void main(String[] args) {
        //3.创建Callable接口实现类的对象
        NumThread numThread = new NumThread();
        //4.将Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(numThread);


        //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()方法
        Thread thread1 = new Thread(futureTask);
        thread1.start();

        try {
            //6.获取Callable中call方法中的返回值
            //get()返回值即为FutureTask构造器参数Callable实现重写的call()返回值。
            Object sum = futureTask.get();
            System.out.println(" 总和为:" + sum);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

//1.创建一个实现Callable的实现类
class NumThread implements Callable{
    //2.实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            if(i % 2 == 0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}

第四种:线程池的方式

  1. 提供指定线程数量的线程池
  2. 执行指定线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 创建线程的方式四:使用线程池
 *
 * 好处:1.提高响应速度(减少创建新线程的时间)
 *      2.降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
 *      3.便于线程管理
 *
 *      corePoolSize:核心池的大小
 *      maximumPoolSize:最大线程数
 *      keepAliveTime:线程没有任务时最多保持多长时间后会终止
 */
public class ThreadPool {
    public static void main(String[] args) {
        //1.提供指定线程数量的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        ThreadPoolExecutor service1 = (ThreadPoolExecutor) executorService;
        //设置线程池的属性
        service1.setCorePoolSize(10);

        //2.执行指定线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象
        executorService.execute(new NumberThread());//适合使用于Runnable
        //executorService.submit();//适合使用与Callable

        executorService.execute(new NumberThread1());

        //关闭线程池
        executorService.shutdown();
    }
}

class NumberThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <= 100 ; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" +i);
            }

        }
    }
}

class NumberThread1 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <= 100 ; i++) {
            if(i % 2 != 0){
                System.out.println(Thread.currentThread().getName() + ":" +i);
            }

        }
    }
}

【第二套】Java面试题_第2张图片

9、list存的值是有序可重复,set存的值无序不可重复,所以set根据什么判断不可重复?

  1. TreeSet,底层是一个TreeMap,子类是利用Comparable接口来实现重复元素的判断,但是Set集合的整体特征就是不允许保存重复元素。
  2. HashSet,底层是一个HashMap,值为HashMap的键,判断元素重复是利用Object类中的方法实现的:
    • 对象编码:public int hashCode();
    • 对象比较:public boolean equals(Object obj);

在进行重复元素判断的时候首先利用hashCode()进行编码匹配,如果该编码不存在表示数据不存在,证明没有重复,如果该编码存在了,则使用equals()方法,进一步进行对象的比较处理,如果发现重复了,则此数据是不能保存的。

在java程序中真正的重复元素的判断处理利用的就是hashCode()与equals()两个方法共同作用完成的。
只有在排序要求的情况下(TreeSet)才会利用Comparable接口实现。

三、数据库知识

1、人力资源表:HrmResource中存放的用户基本信息数据,分别用SqlServer,Oracle,MySQL 三种不同数据库查询第11条到20条的数据(备注:表中含唯一主键ID,但不连续)

-- 1.mysql : select * from tableName where 条件 limit 当前页码*页面容量-1 , 页面容量
select * from HrmResource limit 11,10; -- MySQL   limit(起始行,每行多少页)


--2.oracle 查询20-40条记录   注:第一种方法比第二种方法效率高
第一种方法:select * from (select A.* ,rownum rn from (select * from table_name) A where rn<=40) where rn>=20;

第一种方法:select * from (select A.* ,rownum rn from (select * from table_name) A ) where rn between 20 and 40;


-- 3. sql server分页查询:  select top n <字段1>,...,<字段n> from <表名>
假设现在有这样的一张表:
 CREATE TABLE test
 (
  id int primary key not null identity,
  names varchar(20)
 )
 然后向里面插入大约1000条数据,进行分页测试
 假设页数是10,现在要拿出第5页的内容
 
 方法一:原理:拿出数据库的第5页,就是40-50条记录。首先拿出数据库中的前40条记录的id值,然后再拿出剩余部分的前10条元素
 select top 10 * from test where id not in
 (
  --40是这么计算出来的:10*(5-1)
  select top 40 id from test order by id
 )
 order by id

方法二:原理:先查询前40条记录,然后获得其最id值,如果id值为null的,那么就返回0
 然后查询id值大于前40条记录的最大id值的记录。这个查询有一个条件,就是id必须是int类型的。
select top 10 * from test where id >
 (
  select isnull(max(id),0) from 
   ( select top 40 id from test order by id ) A
 )
 order by id
 
 方法三:原理:先把表中的所有数据都按照一个rowNumber进行排序,然后查询rownuber大于40的前十条记录 
 这种方法和oracle中的一种分页方式类似,不过只支持2005版本以上的
 row_number()函数,为结果集的分区中的每一行分配一个连续的整数。
 OVER()是一个开窗函数,对集合进行聚合计算
 select top 10 * from 
 (
  select row_number() over(order by id) as rownumber,* from test
 ) A
 where rownumber > 40
 
 方法四:存储过程
1. 创建存储过程
alter procedure pageDemo
@pageSize int,
@page int
AS
declare @temp int
set @temp=@pageSize*(@page - 1)
begin
 select top (select @pageSize) * from test where id not in (select top (select @temp) id from test) order by id
end

2. 执行存储过程
exec 10,5

2、考勤签到表的数据结构如下:表名:hrmschedulesign 请用SQL查询:昨天所有用户的签到和签退时间;

ID userid signdate signtime clientaddress
1 A01 2019-06-17 08:32:40 192.168.3.232
2 A01 2019-06-17 10:30:22 192.168.3.232
3 A01 2019-06-17 18:15:30 192.168.3.232
4 A02

(备注:在考勤表中每个用户会有1-N条打卡记录,以每天的最早时间为签到数据,最晚时间为签退数据,跨天问题不需考虑)

select userid,min(signtime) startTime,max(signtime) endTime from hrmschedulesign 
where (sysdate - to_date(signdate,'YYYY-MM-DD'))=1 group by userid;

通过where (sysdate - to_date(signdate,'YYYY-MM-DD'))=1 筛选出昨天的所有数据,
select sysdate from dual,可以得到目前系统的时间;
由于signdate不是标准的YYYY/MM/DD的形式,所以肯定不是date类型的,
因为我们在和sysdate比较的时候需要通过to_date()函数把字符串变成date类型的数据,
之后就可以比较了,得出的就是天数,对于signtime来说,可以使用min()max()进行比较,
最后在通过userid进行分组

3、简述常见数据库索引类型和作用。

1.普通索引(NORMAL)
没有任何限制条件
2.唯一索引(UNIQUE)
要求关键字值不能重复,同时增加唯一约束。
3.主键索引:
要求关键字不能重复,也不能为NULL。同时增加主键约束。
4.全文索引(FULLTEXT)
关键字的来源不是所有字段的数据,而是从字段中提取的特别关键词。mysql支持不是太好,一般用第3方的搜索引擎来解决 elasticsearch工具完成搜索;FULLTEXT 用于搜索很长一篇文章的时候,效果最好。用在比较短的文本,如果就一两行字的,普通的 INDEX 也可以

索引优点:

  • 查询快,大大加快数据的检索速度;
  • 创建唯一性索引,保证数据库表中每一行数据的唯一性;
  • 加速表和表之间的连接;
  • 在使用分组和排序子句进行数据检索时,可以显著检索查询中分组和排序的时间。

索引缺点:

  • 索引需要占物理空间,增删改的频率大于查询的频率时,不建议使用索引;在不必要的字段上加索引的话性能是很差劲的。
  • 当对表中的数据进行增加,删除和修改的时候,索引也要动态的维护,降低了数据的维护速度。

1)B+索引和Hash索引的区别:
B+索引的数据结构是红黑树,查询的时间复杂度是Ologn级别的 Hash索引的存储数据结构是哈希表,查询时间复杂度是O(1)级别的,
我们之所以用B+索引是因为Hash无序,不支持范围查找和排序功能
2)主键索引和普通索引的区别:
平时我们用索引的时候 进来用主键查询:因为主键索引的data存的是那一行的数据(innodb引擎),而普通索引(辅助索引)的data存的是主键,需要通过主键回表再次查询

4、写出为workplan表title字段创建普通索引的SQL。

创建索引
create index index_name on table_name(column_name)

EX:
create index index_id on workplan(title asc)

修改表结构(添加索引)
alter table table_name add index index_name(column_name)

EX:
alter table workplan add index index_id(title asc)

创建表时,直接指定索引
create table table_name(
  id int not null,
  username varchar(64) not null,
  index [index_name] (username)  
);

删除索引
drop index [index_name] on table_name

你可能感兴趣的:(面试,java,开发语言)