面试题目总结

垃圾回收器的基本原理?gc可以马上回收内存吗?有什么办法通知JVM进行垃圾回收?垃圾回收的优点?

对于GC而言,当程序员创建对象时,GC就开始监控这个对象的地址,大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是”可达的”,哪些对象是”不可达的”。当GC确定一些对象为”不可达”时,GC就有责任回收这些内存空间。

可以马上回收内存,使用System.gc()方法,只是通知GC运行,但是并不能保证GC一定运行

静态变量本身不会被回收,但是它所引用的对象是可以回收的

gc只回收heap里的对象,对象的回收和是否是static没有关系

优点:有效的防止内存泄漏。(内存泄露是指该内存空间使用完毕之后未回收,在不涉及复杂数据结构的一般情况下,Java 的内存泄露表现为一个内存对象的生命周期超出了程序需要它的时间长度,我们有时也将其称为“对象游离”

数组、List、ArrayList的区别是什么?

数组是有初始长度的,我们在声明数组的时候必须同时指明数组长度,长度过长会造成内存浪费,长度过短,会造成数据溢出而ArrayList是没有初始长度的,他的底层还是数组,但是在声明时不需要指定它的长度。而之所以后来又引进了List,是因为ArrayList只能插入一种数据类型的数据,除非E中使用Object类,但是这样的话使用时需要将其转换为对应的类型来处理,需要装箱拆箱,会造成很大的性能损耗。List是个接口,ArrayList实现了List接口

之所以大多数使用List list = new ArrayList()来定义而不是使用ArrayList alist = new ArrayList()的原因是因为方便以后修改,万一需求改变ArrayList需要改变成LinkedList时,只需要将List list = new ArrayList()改为List list = new LinkedList()即可。

List如何排序?

  • Collections

    • 方法一:集合中的对象必须实现Comparable接口的compare方法,用于比较两个对象大小。

    • 方法二:使用Collections.sort方法

1
2
3
4
5
6
7
8
// 使用
Collections.sort(medias, new Comparator<MediaPojo>() {
public int compare(MediaPojo o1, MediaPojo o2){
Integer i1 = order.indexOf(o1.getMediaName()); //order表示文件名列表,在方法体外引入
Integer i2 = order.indexOf(o2.getMediaName());
return i2.compareTo(i2);
}
});

mysql如何实现分页?

使用limit语法。

后端代码需要接收两个参数:偏移(offset),查询条数(rows)

前端传递给后端的参数可以是page_num(第几页),page_size(每一页显示多少条数据),后端接收到page_num和page_size之后可以通过(page_num - 1) * page_size 表达式换算offset,查询条数(rows)就是page_size

Object类有哪些方法

getClass`hashCodeequalsclonetoStringnotifynotifyAllwaitfinalize`

快速排序

是对冒泡排序的一种改进算法,它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列

一般取数组的第一个位置当作基准,分别从左往右和从右往左寻找大于和小于基准的值,找到之后交换二者的位置

为什么不能直接字符串相加而是要使用StringBuilder?

java8默认使用StringBuilder拼接字符串二者之间性能差异主要是看使用的频率,当上千的for循环时,拼接字符推荐使用StringBuilder,因为如果直接使用String进行字符串拼接。底部还是调用了StringBuilder,如果在for 循环内的话,则是不断的new一个新的StringBuilder对象,所以效率很低

字符串直接相加,是需要重新申请内存的,如"a"+"b"="ab",系统不会在字符串a的后面直接把b加上,而是再申请一块内存,里面放ab,如ab又加上其它字符串,则需要再申请内存,存放相加后的结果,不使用的内存将变成垃圾内存,等待GC回收。

字符流和字节流的区别?哪些类是字符流或字节流?

所以字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,所以它对多国语言支持性比较好!如果是音频文件、图片、歌曲,就用字节流好点,如果是关系到中文(文本)的,用字符流好点

InputStream是所有字节输入流的祖先,而OutputStream是所有字节输出流的祖先。

举例:FileInputStreamBufferInputStreamFilterInputStreamDataInputStream

Reader是所有读取字符串输入流的祖先,而writer是所有输出字符串的祖先。

举例:BufferedReaderFileReaderStringReader

内部类的作用?

内部类分为实例内部类、静态内部类和成员内部类

  • 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的 .class 文件,但是前面冠以外部类的类名和 $ 符号。
  • 内部类不能用普通的方式访问。内部类是外部类的一个成员,因此内部类可以自由地访问外部类的成员变量,无论是否为 private 的。
  • 内部类声明成静态的,就不能随便访问外部类的成员变量,仍然是只能访问外部类的静态成员变量。

静态内部类和非静态内部类的区别?

  • 在创建静态内部类的实例时,不需要创建外部类的实例,如new Outer.Inner()
  • 静态内部类中可以定义静态成员和实例成员
  • 静态内部类可以直接访问外部类的静态成员,如果要访问外部类的实例成员,则需要通过外部类的实例去访问。
1
2
3
4
5
6
7
8
9
10
11
public class Outer
{
int a=0; //实例变量
static int b=0; //静态变量
static class Inner
{
Outer o=new Outer;
int a2=o.a; //访问实例变量
int b2=b; //访问静态变量
}
}

局部内部类

局部内部类是指在一个方法中定义的内部类。

1
2
3
4
5
6
7
8
9
10
public class Test
{
public void method()
{
class Inner
{
//局部内部类
}
}
}
  • 局部内部类与局部变量一样,不能使用访问控制修饰符(public、private 和 protected)和 static 修饰符修饰。
  • 局部内部类只在当前方法中有效
1
2
3
4
5
6
7
8
9
10
11
public class Test
{
Inner i=new Inner(); //编译出错
Test.Inner ti=new Test.Inner(); //编译出错
Test.Inner ti2=new Test().new Inner(); //编译出错
public void method()
{
class Inner{}
Inner i=new Inner();
}
}

说出Servlet的生命周期

加载和实例化

Servlet容器负责加载和实例化Servlet,当容器启动时,或者检测到Servlet来响应第一个请求时,创建Servlet实例

初始化

Servlet实例化后,容器将调用Servlet的init()方法去初始化这个对象,(比如建立数据库、获取配置信息)对于每一个Servlet实例,init()只被调用一次

请求处理

Servlet容器调用Servlet对象的service()方法,在调用该方法之前必须成功调用init()方法,在service()方法中,Servlet实例通过ServletRequest对象得到客户端的相关信息和请求信息,在对请求进行处理后,调用ServletResponse对象的方法设置响应信息

服务终止

当容器检测到一个Servlet实例应该从服务器中被移除时,容器就会调用实例的destory()方法,以便让该实例可以释放它所使用的资源,destroy()方法调用之后,容器会释放这个Servlet实例,该实例随后会被垃圾收集器回收

在整个Servlet的生命周期过程中,创建Servlet实例、调用实例的init()和destroy()方法都只进行一次,当初始化完成后,Servlet容器会将该实例保存在内存中,通过调用它的service()方法,为接收到的请求服务。

Servlet和CGI的区别

在传统的CGI中,每个请求都要启动一个新的进程,但是在Servlet中,每个请求由一个轻量级的Java线程处理

Servlet优点

  • 方便 (提供了大量工具例程,如读取HTTP头,处理Cookie,跟踪会话)
  • 功能强大 (Servlet能够直接和Web服务器交互,各个程序之间共享数据)
  • 可移植性好 (Servlet用Java编写,几乎所有主流服务器都直接或通过插件支持Servlet)

Servlet和JSP的区别

  1. sp经编译后就变成了ServletJSP的本质就是Servlet,JVM只能识别java的类,不能识别JSP的代码,Web容器将JSP的代码编译成JVM能够识别的java类
  2. JSP更擅长表现于页面显示,servlet更擅长于逻辑控制
  3. JSP是Java和HTML组合扩展为.jsp文件,Servlet的应用逻辑在Java文件中
  4. MVC模式中,JSP为View层,Servlet为Controller层

JSP注释

在JSP文件的编写过程中共有三种注释方法

  • <!--html-->
  • <%--JSP--%>
  • <%//%>
  • <%/**/%>

冒泡排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) {
int[] aa = {1, 4, 5, 3, 6, 7};
for (int i = 0; i < aa.length - 1; i++) {
for (int j = 0; j < aa.length - i - 1; j++) {
// 注意 这里是j+1和j比
if (aa[j + 1] > aa[j]) {
int temp = aa[j];
aa[j] = aa[j + 1];
aa[j + 1] = temp;
}
}

}
for (int i1 : aa) {
System.out.println(i1);
}
}

group by 和 order by的区别

  • order by是行的排序,默认为升序
  • group by分组,必须由聚合函数来配合,比如sum()count()avg()

Web服务器在客户端交互时Servlet的工作过程

  1. 在客户端对web服务器发出请求
  2. web服务器接收到请求后将其发送给Servlet
  3. Servlet容器为此产生一个实例对象并调用ServletAPI中相应的方法来对客户端HTTP请求进行处理,然后将处理的结果返回给WEB服务器
  4. web服务器将从Servlet实例对象中接收到响应结构发送回客户端

JVM底层了解

你自学期间遇到过什么令你印象深刻的问题?你是如何解决的?

java中的基本类型有哪些?

long 8字节 int 4字节 short 2字节 byte 1字节(整数类型)

boolean 4字节 (布尔类型)

char 2字节 (字符类型)

double 8字节 float 4字节 (浮点类型)

String是对象是引用类型,不属于基本类型

native、strictfp、transient、volatile、assert、const、goto关键字的用处

native

使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用

strictfp

如果你想让你的浮点运算更加精确,而且不会因为不同的硬件平台所执行的结果不一致的话,可以用关键字strictfp

transient

当序列化某个对象时,如果该对象的某个变量是transient,那么这个变量不会被序列化进去

volatile

volatile是Java提供的一种轻量级的同步机制,比synchronized轻量

assert

断言(避免使用)

const 和 goto

预留关键字,现版本还没有相关用处

List、Set、Map的区别

List

可重复,有序,可以通过索引快速查找

  • ArrayList (改查优)

    • 因为ArrayList是基于数组实现的,而数组内存在一块连续的内存空间,所以当需要在指定位置添加(删除)元素时,必然导致在该位置后的所有元素需要重新排列,删除的位置越靠前,数组重组开销越大。适合添加(删除)末尾的数据
    • 数组是有容量大小的,当超出数组的容量时需要扩容操作
  • LinkedList(增删优)

    • 关于增加元素到列表任意位置,LinkedList会比较方便,只需要改变指针指向,但是访问数据的平均效率低,需要对链表进行遍历
  • Vector(已不用)

    • 方法都是同步的,是线程安全的,所以性能很低。扩容时容量翻倍,而ArrayList则只增加一半,节约内存空间。

总结

  1. ArrayList是实现了基于动态数组的数据结构,LinkedList是基于循环双向链表数据结构。
  2. 对于随机访问的get和set方法,ArrayList要优于LinkedList,因为LinkedList要移动指针。
  3. 对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。

Set

无序,或者根据提供的Comparator方法进行排序,不可重复。

  • HashSet
  • TreeSet

Map

是键值对,key唯一,value不唯一。

  • HashMap
  • HashTable

如何防止表单重复提交

  • 提交表单后使用js使提交按钮disable
  • 在提交表单后执行页面重定向,转到提交成功页面

MySQL

内连接

语法:左表 inner join 右表 on 左表.字段 = 右表.字段

外连接

语法:left/right join 右表 on 左表.字段 = 右表.字段

以left为例子,左查询时,以左表为基础,将右表中匹配上条件的数据都一一例出来,不能匹配的以左表为标准,字段置空NULL

联合查询

语法:Select 语句1 Union [Union 选项] Select 语句2

  • All 保留所有
  • Distinct (默认) 去重

这里值得注意的是,查询多张表格时,多张表的数据属性需要完全一致,否则报错

sql的预编译

 sql 预编译指的是数据库驱动在发送 sql 语句和参数给 DBMS 之前对 sql 语句进行编译,这样 DBMS 执行 sql 时,就不需要重新编译  

sql预编译的好处

预编译阶段可以优化 sql 的执行,预编译之后的 sql 多数情况下可以直接执行,DBMS 不需要再次编译,越复杂的sql,编译的复杂度将越大,预编译阶段可以合并多次操作为一个操作。预编译语句对象可以重复利用。把一个 sql 预编译后产生的对象缓存下来,下次对于同一个sql,可以直接使用这个缓存的对象。mybatis 默认情况下,将对所有的 sql 进行预编译。

Mysql数据库引擎

  • Innodb (5.X版本之后的默认数据库引擎)

Innodb引擎提供了对数据库ACID事务的支持,并且实现了SQL标准的四种隔离级别,提供了行级锁和外键约束,但是该引擎不支持FULLTEXT类型的索引,而且它没有保存表的行数(SELECT COUNT(*) FROM TABLE时需要扫描全表),MySQL运行时Innodb会在内存中建立缓冲池,用于缓冲数据和索引

  • MyIASM

但是它没有提供对数据库事务的支持,也不支持行级锁和外键,可是它存储了表的行数(SELECT COUNT(*) FROM TABLE时只需要直接读取已经保存好的值)

如果表的读操作远远多于写操作且不需要数据库事务的支持,那么MyIASM也是很好的选择

ACID事务

  • 原子性

要么所有事务都提交成功,要么全部失败回滚

  • 一致性

数据库总是从一个一致性的状态转换到另一个一致性的状态

  • 隔离性

一个事务所做的修改在最终提交以前,对其他事务是不可见的

  • 持久性

一旦事务提交,则其所做的修改不会永久保存到数据库

脏读、不可重复读、幻读

  • 脏读

读到了别的事务回滚前的脏数据,当前事务读到的数据是别的事务想要修改成为的但是没有修改成功的数据

  • 不可重复读

当前事务先进行了一次数据读取,然后再次读取到的数据是别的事务修改成功的数据,导致两次读取到的数据不匹配

  • 幻读

事务A首先根据条件索引得到N条数据,然后事务B改变了这N条数据之外的M条或者增添了M条符合事务A搜索条件的数据,导致事务A再次搜索发现有N+M条数据

其中不可重复读和幻读有点类似,但是前者针对的是update或delete,后者针对的insert

SQL标准的四种隔离级别

  • read-uncommitted 读未提交
  • read-committed 读提交

只有在事务提交后,其更新结果才会被其他事务看见,可以解决脏读问题

  • repeatable-read 可重复读 (mysql默认隔离级别

在一个事务中,对于同一份数据的读取结果总是相同的,无论是否有其他事务对这份数据进行操作,以及这个事务是否提交。可以解决脏读、不可重复读

  • serializable 可串行化

事务串行化执行,会在读的每一行数据上都加上锁,所以可能导致大量的超时和锁征用问题,隔离级别最高,牺牲了系统的并发性。

Linux Shell命令

1
2
3
4
5
6
7
8
9
10
11
# 查找所有的java进行,然后打印出来
pidlist=`ps -ef|grep java |grep -v "grep" |awk '{print $2}'`
if [ "$pidlist" = "" ]
then
echo "no java process alive"
else
for pid in ${pidlist}
{
echo "java process is: $pid:"
}
fi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/bin/bash

## Linux 随机生成40000000 - 50000000的数字
function mimvp_randnum_file() {
min=$1
max=$2
mid=$(($max-$min+1))
# 用到了 cksum 命令,其读取文件内容,生成唯一的整型数据,只有文件内容没变,生成结果就不会变化
num=$(head -n 20 /dev/urandom | cksum | cut -f1 -d ' ')
# num=$(head -n 20 /dev/urandom | cksum | cut -d ' ' -f1) # ok
# num=$(head -n 20 /dev/urandom | cksum | awk '{print $1}') # ok
# num=$(head -n 20 /dev/urandom | cksum | awk -F " " '{print $1}') # ok
randnum=$(($num%$mid+$min))
echo $randnum
}

function print_randnum_file() {
for i in {1..10};
do
randnum=$(mimvp_randnum_file 40000000 50000000)
echo -e "$i \t $randnum"
done
}

print_randnum_file

kill -9 和 kill -15(默认)的区别?

kill - 9 表示强制杀死该进程,表示无条件终止
kill - 15 告诉进程,你需要被关闭,请自行停止运行并退出

什么叫面向对象(oop)?

对象就是事物存在的实体,比如人类就是一个对象,然而对象是有属性和方法的,那么身高,体重,年龄,姓名为属性,思考吃饭这些可以定义为一个个方法

面向对象和面向过程的区别在哪?

举个我在网上看到的简单例子,当你想吃红烧肉的时候,你可以有2种选择,一种是自己动手买材料烧肉,另一种是去饭店吃。那么第一种就是面向过程,解决问题需要一步步分析一步步去实现,而且换个东西你又要重新去编写代码,第二种为面向对象,优点为降低耦合,提高维护性

面向对象可以讲方法都封装起来,我们只需要调用即可,其内部的实现可以不用去管,其底层还是面向过程

面向对象和面向过程的优缺点?

oop由于有封装,多态,继承的特性,可以设计出低耦合的系统,使其更加灵活易于维护,性能比较差,因为类的调用需要实例化

面向过程性能比oop好

面向对象七大设计原则

将七个原则分为2类

设计目标:开闭原则、里氏代换原则、迪米特原则

设计方法:单一职责原则、接口分隔原则、依赖倒置原则、组合/聚合复用原则

开闭原则

对扩展开放,对修改关闭

根据开闭原则,在设计一个软件系统模块(类,方法)的时候,应该可以在不修改原有的模块(修改关闭)的基础上,能扩展其功能(扩展开放)

举例商品打折,不需要在商品的接口类里添加打折方法,而是写一个类继承商品类,覆盖其getPrice()方法

里氏替换原则

子类可以扩展父类的功能,但不能改变父类原有的功能

  • 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类方法的输入参数更宽松。
  • 当子类的方法实现父类的方法时(重载/重写或实现抽象方法)的后置条件(即方法的输出/返回值)要比父类更严格或相等。

迪米特原则(最少知道原则)

降低类之间耦合 缺点是系统中存在大量中介类,增加系统复杂度

多使用private、protected访问权限,少使用public修饰

单一职责原则

永远不要让一个类存在多个改变的理由

只能让一个类/接口/方法有且仅有一个职责

优点是类的可读性增加,复杂性降低

比如手机的播放音乐和照相职责就可以分成2个类而不是都放在手机这个类里,每一个类最好只有一个职责,可以有多个关于这个职责的方法(调低音量,切换歌曲),但只有一个职责(播放音乐)

接口分隔原则

不能强迫用户去依赖那些他们不使用的接口。

接口的设计原则:接口的设计应该遵循最小接口原则,不要把用户不使用的方法塞进同一个接口里。如果一个接口的方法没有被使用到,则说明该接口过胖,应该将其分割成几个功能专一的接口。

接口的依赖(继承)原则:如果一个接口a继承另一个接口b,则接口a相当于继承了接口b的方法,那么继承了接口b后的接口a也应该遵循上述原则:不应该包含用户不使用的方法。 反之,则说明接口a被b给污染了,应该重新设计它们的关系。

注意

  • 一个类对一个类的依赖应该建立在最小的接口上

  • 建立单一接口,不要建立庞大臃肿的接口

  • 尽量细化接口,接口中的方法尽量少

依赖倒置原则

A. 高层模块不应该依赖于低层模块,二者都应该依赖于抽象

B. 抽象不应该依赖于细节,细节应该依赖于抽象

C.针对接口编程,不要针对实现编程。

在高层模块和低层模块之间引入一个抽象接口层,高层模块不直接依赖低层模块,而是依赖抽象接口层。抽象接口也不依赖低层模块的实现细节,而是低层模块依赖(继承或实现)抽象接口,类与类之间都通过抽象接口层来建立关系

组合/聚合复用原则

在面向对象设计中,有两种基本的办法可以实现复用:第一种是通过组合/聚合,第二种就是通过继承

看一个例子:

如果我们把“人”当成一个类,然后把“雇员”,“经理”,“学生”当成是“人”的派生类。这个的错误在于把 “角色” 的等级结构和 “人” 的等级结构混淆了。“经理”,“雇员”,“学生”是一个人的角色,一个人可以同时拥有上述角色。如果按继承来设计,那么如果一个人是雇员的话,就不可能是学生,这显然不合理。

封装、继承、多态的优点有哪些?

封装

即隐藏了对象的属性和实现细节,返回一个公共的访问方法,让调用者可以在不了解内部如何实现的前提下,只通过传入指定的参数即可使用,提高了代码的安全性和复用性

继承

优点

  • 代码共享,子类拥有父类的方法和属性,提高了代码的重用性
  • 子类可以覆盖父类的方法,提高了代码的可扩展性

缺点

  • 继承使侵入性的,子类只要继承就必须拥有父类的属性和方法
  • 增强了耦合性,当父类的方法被修改时要注意子类是否也需要修改

所以复合(不扩展现有的类,而是在新的类中增加一个私有域,引用现有类的一个实例)优于继承,如果子类只需要实现超类的部分行为则考虑使用复合

多态

分为方法重载和对象多态

方法重载的意思是一个类中可以有多个方法名相同但是方法签名(比如参数的顺序,类型,个数)不同方法。

对象多态打个比方猫和狗,他们都属于动物Animal,但是猫吃鱼狗吃骨头,如果使用多态,可以消除耦合性

1
2
3
4
Animal cat = new Cat();
Animal dog = new Dog();
cat.eat() //吃鱼
dog.eat() //吃骨头

代码块的执行顺序

  • JVM在加载类的时候就会执行静态代码块,优先于主方法的执行,如果主方法所在的这个类中有静态代码块则优先执行它,注意的是静态代码块只执行一次
  • public中静态代码块 –>构造块({}中的代码)–>构造器方法
  • 有父类优先执行父类的中的构造块

静态

  • 静态变量会在该类的任何对象创建之前完成初始化
  • 静态变量会在该类的任何静态方法执行之前完成初始化
  • 静态代码块是一段在加载类时就执行的程序代码,只初始化一次
  • 静态代码块不是必须产生对象,它所在的类只要被加载了就会执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 父类
public abstract class father {

public father() {
System.out.println("父类的构造器方法");
}

static {
System.out.println("父类的静态代码块");
}

{
System.out.println("父类的构造块方法");
}
}

// 子类
public class son extends father {

public son() {
System.out.println("父类的构造器方法");
}

static {
System.out.println("子类的静态代码块");
}

{
System.out.println("子类的构造块");
}
// 因为类son被加载了,所以即使没有new一个对象,也会出现静态代码块中的方法
public static void main(String[] args) {

}
}
//结果
/*
父类的静态代码块
子类的静态代码块
*/

final

由final修饰的变量一定需要在定义的时候就将它初始化。

public static final中定义的变量可以在静态代码块中初始化。非static定义的final变量可以在构造代码块或者构造器代码块中初始化

  • final变量代表你不能改变它的值
  • final的method代表你不能覆盖掉该method
  • final的类代表你不能继承该类

基本类型的转换

  • String转int(Integer.parseInt()
  • String转boolean(Boolean("true").booleanValue())
  • int转String
    • toString()方法
    • “”+类型数据

Calendar日历类

  • 需要注意的是日历类的月份是0-11范围的,如果月份输入12,则为后一年的1月

try-catch-finally

如果try-catch有return指令,finally还是会执行,流程会先跳到finally再回到return指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static String getName() {
try {
System.out.println("try执行");
return "return 内容";
}catch (Exception e){
e.printStackTrace();
}finally {
System.out.println("finally执行");
}
return null;
}
// 结果
/*
try执行
finally执行
return 内容
*/

有多个catch块时,异常需要从小到大排序,否则你第一个异常就捕获到了,剩下的异常处理就不能够执行了,不能把大篮子放在小篮子上面

IO

序列化

将序列化对象写入文件中

1
2
3
4
5
6
7
8
9
10
11
12
// 注意的是这里序列化的对象都必须实现Serializable接口
Demo d = new Demo();
aa a = new aa();
try (FileOutputStream fs = new FileOutputStream("OPP.txt");
ObjectOutputStream os = new ObjectOutputStream(fs)) {

os.writeObject(d);
os.writeObject(a);

} catch (IOException e) {
e.printStackTrace();
}

序列化保存的是对象的各种状态(也就是实例变量而不是方法)

序列化发生的事情

将在堆上的对象(像是类的名称)和非transient修饰的实例变量(注意 静态变量不会被序列化,因为所有对象都是共享一份静态变量),不包括对象方法。将它们保存在自定义的文件中

解序列化发生的事情

  • 将对象从stream中读取出来
  • JVM通过存储的信息找出对象的class类型
  • JVM尝试寻找和加载对象的类,如果找不到或无法加载该类JVM抛出异常,结束
  • 如果成功寻找并加载了对象,新的对象会被配置在堆上,但是构造函数不会执行,为的是使对象回到存储时的状态而不是全新的
  • 如果对象在继承树上有不可序列化的父类,那么不可序列化的父类会执行它的构造函数,也就是说会恢复初始状态,父类实现了Serializable,那么子类就会自动实现该接口
  • 对象的实例变量会被还原成序列化时的状态值,transient修饰的对象变量会被赋值为null,基本数据类型则默认为0,0.0,false等

写入文件

  • 创建文件File f = new File("XXX.txt")
  • 写入文件FileWriter fw = new FileWriter(f)
  • 使用缓存,减少每次写入对磁盘的操作BufferedWriter bw = new BufferedWriter(fw),强制缓冲区立即写入为bw.flush()

结合起来写就是
BufferedWriter bw = new BufferedWriter(new FileWriter(new File("XXX.txt")));

读取文件

1
2
3
4
5
6
7
8
9
//循环读取文件
File f = new File("OOP.txt");
BufferedReader br = new BufferedReader(new FileReader(new File("OPP.txt")));
String read = null;
// 注意read = br.readLine()得写进while后,因为这个操作需要循环进行,而不是定义在外面,如果定义在外面,得到TRUE,则进行while(TRUE)一直会循环进行
while ((read = br.readLine()) != null){
System.out.println(read);
}
br.close();

读取文件时,如果想要按照某一个规则循环显示,则使用String类的split(分割字符)方法,将数据分割成一条一条,其中分割字符不会被显示,它返回一个String数组

nio类

提高了效能,可以充分利用执行程序的机器上的原始容量。可以直接控制buffer,另一项能力是non-blocking(非阻塞)的输入输出

Shiro框架

Shiro是一个java安全框架,作用是认证、授权、加密、会话管理、缓存等

  • 从功能角度来看

    • Authentication:[ɔ:ˌθentɪ’keɪʃn]身份认证/登录,验证用户是不是拥有相应的身份;
    • Authorization:[ˌɔ:θərəˈzeɪʃn] 授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
    • Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是Web环境的;
    • Cryptography:[krɪpˈɑ:grəfi]加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
    • Web Support:Web支持,可以非常容易的集成到Web环境;
    • Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
    • Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
    • Testing:提供测试支持;
    • Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
    • Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
  • 从架构看

    • Subject : 访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体;

      Subject一词是一个安全术语,其基本意思是“当前的操作用户”。它是一个抽象的概念,可以是人,也可以是第三方进程或其他类似事物,如爬虫,机器人等。
      在程序任意位置:Subject currentUser = SecurityUtils.getSubject(); 获取shiro
      一旦获得Subject,你就可以立即获得你希望用Shiro为当前用户做的90%的事情,如登录、登出、访问会话、执行授权检查等

    • SecurityManager

      安全管理器,它是shiro功能实现的核心,负责与后边介绍的其他组件(认证器/授权器/缓存控制器)进行交互,实现subject委托的各种功能。有点类似于spirngmvc中的DispatcherServlet前端控制器。

    • Realms

      Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。;可以把Realm看成DataSource,即安全数据源。执行认证(登录)和授权(访问控制)时,Shiro会从应用配置的Realm中查找相关的比对数据。以确认用户是否合法,操作是否合理

MyBatis Generator 返回主键如何配置?

<generatedKey column="id" sqlStatement="MySql" identity="true"/>返回的主键自增

MyBatis传字符#和$的区别

MyBatis中#{}方式可以有效的防止sql注入,${}一般用于传入数据库对象,例如传入表名,${ } 变量的替换阶段是在动态 SQL 解析阶段,而 #{ }变量的替换是在 DBMS(如mysql) 中

MyBatis是什么框架?它的优缺点是什么?

它是持久层框架,属于ORM映射(对象关系映射),优点是可以灵活的编写sql语句,可以用特有的逻辑标签动态编写sql,在保证名称相同的前提下,查询的结果集和java对象自动映射,sql统一写在xml中方便统一管理和优化,解除sql与代码的耦合,通过提供DAO层,将业务逻辑和数据访问逻辑分离开来缺点是数据库移植比较麻烦。,SQL语句依赖性很高属于半自动

Spring和SpringMVC和SpringBoot和SpringCloud的区别?

Spring框架是一个大家族,像SpringBoot、SpringMVC都是它的衍生产品,其基础都是ioc和aop,ioc 提供了依赖注入的容器, aop解决了面向横切面的编程。

SpringMVC是一个基于Servlet的一个MVC框架主要解决WEB开发的问题,因为其配置非常繁琐每次开发都写很多样板代码,所以推出了Springboot,简化了配置的流程涵盖面包括前端视图开发、文件配置、后台接口逻辑开发等

Springboot内置3种web容器(Tomcat、Jetty和Undertow),集成了大量第三方库的配置(如redis,mongodb),提供starter简化Manen配置,降低了项目搭建的复杂度,使开发者能够更加专注于业务逻辑,它更注重于后台接口,不开发前端视图

SpringCloud关注于全局的微服务整合和管理,将多个SpringBoot单体微服务进行整合以及管理,分布式

Spring 是一个“引擎”;Spring MVC 是基于Spring的一个 MVC 框架;Spring Boot 是基于Spring的一套快速开发整合包。

Spring bean实例化有什么方法?

  • 使用类构造器
  • 使用静态工厂方法 (直接可以通过静态方法来实例化一个对象)
1
2
3
4
5
6
public class HelloWorldFactory {
public static HelloWorld getInstance(){
return new HelloWorld();
}
}
HelloWorldFactory.getInstance()
  • 使用实例工厂方法
1
2
3
4
5
6
7
public class HelloWorldFactory {
public HelloWorld createHelloWorld(){
return new HelloWorld();
}
}
HelloWorldFactory helloFactory = new HelloWorldFactory();
helloFactory.createHelloWorld();

Springmvc是什么时候创建bean?是添加Autowire注解时创建bean?

默认情况下Spring容器启动的时候就创建了bean,如果开启了懒加载,那么该bean在调用getBean方法时创建对象。在Springmvc中需要在配置文件中声明

SpringBoot怎么配置的?

maven如何排查jar包冲突?

  • Maven默认处理策略为最短路径优先和最先声明优先
  • 我们可以借助Maven Helper插件中的Dependency Analyzer分析冲突的jar包,然后在对应标红版本的jar包上面点击execlude,就可以将该jar包排除出去。

SpringIOC的问题

传统设计中我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建。

谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

为何是反转,哪些方面反转了?

有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;

为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;

哪些方面反转了?依赖对象的获取被反转了。

DI注入

依赖注入,由容器动态的将某个依赖关系注入到组件之中  

谁依赖于谁?当然是应用程序依赖于IoC容器;

为什么需要依赖?应用程序需要IoC容器来提供对象需要的外部资源;

谁注入谁?很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;

注入了什么?就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

synchronized(this)跟synchronized(类的名.class)有什么不同之处

synchronized(this)是对象锁,同一时间只有一个对象可以执行代码块

而synchronized(类的名.class)是类锁,所以每次只能有一个类对象获取锁

防止多个线程同时执行同一个对象的同步代码段,synchronized锁住的是括号里的对象,而不是代码

了解的锁机制有哪些

自旋锁、可重入锁、读写锁、乐观锁、悲观锁

自旋锁和互斥锁的区别和相同

相同

都是为了解决对某项资源的互斥使用,即任何时刻都只能有一个线程获取锁

不同

自旋锁不会引起调用者线程睡眠,而互斥锁则将调用者线程进入睡眠状态,当一个线程在获取锁的时候,如果锁被其他线程获取,那么该线程就一直循环看该自旋锁的保持者是否已经释放了锁,直到获取到锁才会退出循环。因为自旋锁不会引起调用者睡眠,所以效率远高于互斥锁

但是不足之处也比较明显,1.自旋锁一直占用着cpu,2.可能会造成死锁(递归调用,调用其他函数如 copy_to_user()、copy_from_user())

可重入锁和不可重入锁

可重入锁(例如synchronized和ReentrantLock),指的是在同一线程内,外层函数获得锁之后,内层递归函数仍然可以获取到该锁防止在同一线程中多次获取锁而导致死锁发生例如子类需要调用父类的方法,如果使用自旋锁,那么将进入死锁,如果加上synchronized关键字使它成为可重入自旋锁,那么将不会进入死锁。

不可重入锁就是,在A方法的锁还没有被释放时,调用B方法时,B方法也获得不了该锁,必须等A方法释放掉这个锁。

行级锁、表级锁、页级锁

行级锁

开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。

  • 共享锁(又称读锁)

所有的事务只能对其进行读操作不能写操作,加上共享锁后在事务结束之前其他事务只能再加共享锁,除此之外其他任何类型的锁都不能再加了

  • 排他锁

若某个事物对某一行加上了排他锁,只能这个事务对其进行读写,在此事务结束之前,其他事务不能对其进行加任何锁,其他进程可以读取,不能进行写操作,需等待其释放。

表级锁

开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。

页面锁

开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。

行级锁如何避免死锁

  • 死锁造成的条件(4个当中破坏一个即可避免死锁)

(1) 互斥条件:一个资源每次只能被一个进程使用。

(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

mysql如何处理百万级别的表的查询优化?

  1. 如果当读操作的次数远远大于写操作那么可以考虑使用MyIASM引擎结构,因为它记录了表行数,不用再全表扫描
  2. 减少筛选的条件,避免SELECT *
  3. 正确的使用索引
  4. 当只要一行数据时使用LIMIT 1
  5. 应尽量避免在 where 子句中对字段进行表达式操作
  6. 应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
  7. 避免查询时候使用or,可以用union all代替

如何生成全网唯一订单号?

  • 方案一

如果没有并发情况,订单号只在一个线程内产生,那么就可以简单的用时间戳和自赠数来进行编号,如果有并发且订单号由同一个进程的不同线程产生,那么可以将线程id也添加进订单号,保证其唯一。如果订单号是同一台主机多个进程的产生的,那么就添加进程id进去,如果是不同主机产生的订单号,那么MAC地址、IP地址或CPU序列号等能够区分主机的号码添加到序列号中就可以保证订单号唯一

  • 方案二

时间戳+用户ID+几个随机数

如何学校教务系统如何设计数据库表格?

如何设计ERP系统?

反射技术

赏个🍗吧
0%