2020年3月份一些中小型互联网或计算机软件公司的社招题目,我会尽最大努力确保答案的正确性,但是我也才疏学浅,如果有错误或者遗漏的地方,欢迎指出。持续更新中……。
类型 | 字节数 | 位数 | 取值范围 |
---|---|---|---|
byte | 1 | 8 | -27 ~ 27-1 |
short | 2 | 16 | -215 ~ 215-1 |
char | 2 | 16 | |
int | 4 | 32 | -231 ~ 231-1 |
long | 8 | 64 | -263 ~ 263-1 |
float | 4 | 32 | 3.402823e+38 ~ 1.401298e-45 |
double | 8 | 64 | 1.797693e+308 ~ 4.9000000e-324 |
boolean | 1(前7位是0) | 1 | 0或者1 |
float
表示的 范围更大
。float
和 int
都是4
个字节,而 float
还要表示小数,为什么 float
表示的数字范围大?
int
底层使用的是补码的方式表示一个数:1 位符号位 + 31 位二进制数float
底层使用的是IEEE 754
浮点单精度数字格式,简单来说就是用指数的形式去表示一个数:1 位符号位 + 8 位指数 + 23位尾数Integer
是int
的包装类,int
则是java
的一种基本数据类型Integer
变量必须实例化后才能使用,而int
变量不需要Integer
实际是对象的引用,当new
一个Integer
时,实际上是生成一个指针指向此对象;而int
则是直接存储数据值Integer
的默认值是null
,int
的默认值是0
final
方法abstract
方法==
号在比较基本数据类型时比较的是值,而在比较两个对象时比较的是两个对象的地址值equals()
方法存在于Object
类中,其底层依赖的是==
号,如果类没有重写Object
中的equals()
方法的类中,则调用equals()
方法其实和使用==
号的效果一样相同点:
不同点:
public abstract
修饰),常量(public static final
修饰),在JDK8中,接口还可以定义static
静态方法和default
方法;而抽象类可以有普通类的所有内容和抽象方法。String
的底层使用的是char
数组。这个char
数组和String
这个类都是final
修饰的,所以不可变。
String
是只读字符串,它并不是基本数据类型,而是一个对象。从底层源码来看是一个final
类型的字符数组,所引用的字符串不能被改变,一经定义,无法再增删改。每次对String
的操作都会生成新的String
对象。
String
的每次+
操作 : 隐式在堆上new
了一个跟原字符串相同的StringBuilder
对象,再调用append
方法 拼接
+
后面的字符,最后再调用toString
方法,返回String
对象
StringBuffer
和StringBuilder
他们两都继承了AbstractStringBuilder
抽象类,从AbstractStringBuilder
抽象类中我们可以看到他们的底层都是可变的字符数组,所以在进行频繁的字符串操作时,建议使用StringBuffer
和StringBuilder
来进行操作。
另外StringBuffer
对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder
并没有对方法进行加同步锁,所以是非线程安全的
使用的是 SimpleDateFormat
。
//使用SimpleDateFormat类,在构造方法中指定日期的格式:y年,M月,d日,H小时,m分钟,s秒
SimpleDateFormat sdf = new SimpleDateFormat("yyy y-MM-dd HH:mm:ss");
//调用SimpleDateFormat实例方法format(Date)可以把日期转换为字符串
String text = sdf.format(date);
text = "2068年5月12日 8:28:58";
//先创建SimpleDateFormat对象, 指定的格式串要与字符串匹配
SimpleDateFormat anotherSdf = new SimpleDateFormat("yyyy年M月dd日 H:mm:ss");
//调用SimpleDateFormat的实例方法parse(String)可以把字符串转换为日期对象, 如果格式串与字符串不匹配就会产生异常
date2 = anotherSdf.parse(text);
节点流(介质流):
父类 | InputStream(输入流、字节流) | OutputStream(输出流、字节流) | Reader(输入流、字符流) | Writer(输出流、字符流) |
---|---|---|---|---|
文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
字符串 | StringReader | StringWriter | ||
管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
处理流(装饰流):
父类 | InputStream(输入流、字节流) | OutputStream(输出流、字节流) | Reader(输入流、字符流) | Writer(输出流、字符流) |
---|---|---|---|---|
转换流 | InputStreamReader | OutputStreamWriter | ||
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
数据流 | DataInputStream | DataOutputStream | ||
对象流 | ObjectInputStream | ObjectOutputStream | ||
打印流 | PrintStream | PrintWriter |
File 对象可以表示文件或目录:
//第一个参数为父路径字符串,第二个参数为子路径名字
File f = new File("D:", "test");
f.mkdir(); //创建文件夹, 如果已存在重名的文件(夹),创建失败
f.createNewFile(); //创建文件
f.delete(); //删除文件
开闭原则:
开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类。
里氏替换原则:
所有引用基类的地方必须能透明地使用其子类的对象,也就是说子类可以扩展父类的功能,但不能改变父类原有的功能
里氏替换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏替换原则是对实现抽象化的具体步骤的规范。
依赖倒转原则:
高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。简单的说就是尽量面向接口编程.
接口隔离原则:
客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。接口最小化,过于臃肿的接口依据功能,可以将其拆分为多个接口。
迪米特法则,又称最少知道原则:
一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。简单的理解就是高内聚,低耦合,一个类尽量减少对其他对象的依赖。
单一职责原则:
单一职责原则通俗来说就是一个类只负责一项职责。如果一个类有多个职责,这些职责就耦合在了一起。当一个职责发生变化时,可能会影响其它的职责。另外,多个职责耦合在一起会影响复用性。
jdk
动态代理和cglib
动态代理。存储特点:有序,可重复。存储顺序与添加顺序一致,可以存储重复的数据
ArrayList
Vector
ArrayList
不是线程安全的LinkedList
存储特点:无序,不可重复。存储顺序与添加顺序可能不一样;不能存储重复(通过equals()
方法来判断)的数据
HashSet
HashMap
HashSet
添加元素就是把元素作为键添加到底层的HashMap
中TreeSet
TreeSet
实现了SortedSet
接口,可以根据元素自然排序,要求集合中的元素必须是可比较的(Comparator
与Comparable
)TreeSet
底层是TreeMap
TreeSet
添加元素就是把元素作为键添加到底层的TreeMap
中Map
是按<键,值>
对的形式存储数据,Map
的key
不允许重复
HashMap
null
,线程不安全8
时,链表转换为红黑树;插入时,新增的结点插入到链表的尾部16
0.75
,当键值对的数量 > 容量*加载因子时,按2倍大小扩容HashMap
会把初始化容量自动调整为2
的幂次方HashMap
是先插入数据再进行扩容的,但是如果是刚刚初始化容器的时候是先扩容再插入数据。CurrentHashMap
CAS+Synchronized
来保证线程安全详见:
深入浅出ConcurrentHashMap1.8
HashMap和ConcurrentHashMap的知识总结
HashTable
key
还是value
都不能为null
,线程安全,实现线程安全的方式是在修改数据时锁住整个HashTable
,效率低,ConcurrentHashMap做了相关优化11
,扩容:2倍+1
两种方式遍历:使用EntrySet
遍历;使用KeySet
遍历
比如说读取系统参数,在服务之间传递一些键值对时使用。
首先根据键的哈希码,经过hash
函数计算hash
值,然后根据hash
值计算数组下标,当下标相同时,就会出现hash
碰撞。
HashMap
采用链表法,将数组下标相同的键值对用链表存起来,解决了hash
碰撞。
HashMap
相较来说写入慢,读取快,上面介绍过了,就不赘述了;LinkedHashMap
它内部有一个链表,保持Key
插入的顺序,写入快,读取慢。迭代的时候,也是按照插入顺序迭代;TreeMap
可以根据Key
的自然顺序(如整数从小到大)或者自己定义的比较器,实现 Key
的有序排列。不能使用for
或者foreach
循环加list.remove()
进行删除,因为使用remove
方法删除后,集合的长度会变,导致一些元素被漏掉。
使用外部迭代器删除集合中的所有奇数:
List<Integer> list = new ArrayList<>();
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer i = iterator.next();
if (i % 2 == 1) {
iterator.remove();
}
}
使用内部迭代器(流),删除集合中的所有奇数:
List<Integer> list = new ArrayList<>();
list = list.stream().
filter(x -> x % 2 == 0).
collect(Collectors.toList());
Thread
类,重写run
方法(其实Thread类本身也实现了Runnable接口)Runnable
接口,重写run
方法Callable
接口,重写call
方法(有返回值)Callable
接口的任务线程能返回执行结果;而实现Runnable
接口的任务线程不能返回结果;Callable
接口的call()
方法允许抛出异常;而Runnable
接口的run()
方法的异常只能在内部消化,不能继续上抛;
Callable
接口支持返回执行结果,此时需要调用FutureTask.get()
方法实现,此方法会阻塞主线程直到获取结果;当不调用此方法时,主线程不会阻塞!
start()
方法来启动线程,而启动后的线程运行的是run()
方法中的代码。run()
方法当作普通方法的方式调用时,并不会开启新的线程。最常用的三个参数:
corePoolSize
:核心线程数queueCapacity
:任务队列容量(阻塞队列)maxPoolSize
:最大线程数三个参数的作用:
五种线程池:
ExecutorService threadPool = null;
threadPool = Executors.newCachedThreadPool();//有缓冲的线程池,线程数 JVM 控制
threadPool = Executors.newFixedThreadPool(3);//固定大小的线程池
threadPool = Executors.newScheduledThreadPool(2);//一个能实现定时、周期性任务的线程池
threadPool = Executors.newSingleThreadExecutor();//单线程的线程池,只有一个线程在工作
threadPool = new ThreadPoolExecutor();//默认线程池,可控制参数比较多
四种拒绝策略:
RejectedExecutionHandler rejected = null;
rejected = new ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务抛出异常
rejected = new ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务不异常
rejected = new ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列
rejected = new ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务
三种阻塞队列:
BlockingQueue<Runnable> workQueue = null;
workQueue = new ArrayBlockingQueue<>(5);//基于数组的先进先出队列,有界
workQueue = new LinkedBlockingQueue<>();//基于链表的先进先出队列,无界
workQueue = new SynchronousQueue<>();//无缓冲的等待队列,无界
利用Java自带工具JConsole
Java线程死锁查看分析方法
当一个变量定义为volatile
后,它将具备两种特性:1. 可见性,2. 禁止指令重排序。
可见性:编译器为了加快程序运行速度,对一些变量的写操作会现在寄存器或CPU缓存上进行,最后写入内存。而在这个过程中,变量的新值对其它线程是不可见的。当对volatile
标记的变量进行修改时,先当前处理器缓存行的数据写回到系统内存,然后这个写回内存的操作会使其他CPU里缓存了该内存地址的数据无效。
处理器使用嗅探技术保证它的内部缓存、系统内存和其他处理器的缓存的数据在总线上保持一致。如果一个正在共享的状态的地址被嗅探到其他处理器打算写内存地址,那么正在嗅探的处理器将使它的缓存行无效,在下次访问相同内存地址时,强制执行缓存行填充。
NEW
:新建状态,创建了一个线程对象RUNNABLE
:可以运行状态,线程调用了start()
方法。BLOCKED
:阻塞状态,遇到了阻塞事件,或者等待锁对象.WAITING
:等待状态。wait()
,join()
TIMED_WAITING
:等待状态,sleep()
TERMINATED
:终止状态更多多线程常见面试题及答案
where
及 order by
涉及的列上建立索引select * from t
,用具体的字段列表代替“*
”,不要返回用不到的任何字段。where
子句中使用!=
或<>
操作符,否则将引擎放弃使用索引而进行全表扫描,where
子句中对字段进行 null
值判断,否则将导致引擎放弃使用索引而进行全表扫描。可以在null
上设置默认值,确保表中没有null
值。in
和 not in
也要慎用,否则会导致全表扫描。对于连续的数值,能用 between
就不要用 in
了。%
"开头,会导致全表扫描更多sql优化的几种方法
索引(Index)是帮助MySQL高效获取数据的数据结构,可以理解为一本字典的目录,提高程序的检索 和查询效率
主键索引:数据列不允许重复,不允许为NULL,一个表只能有一个主键。
唯一索引:数据列不允许重复,允许为NULL值,一个表允许多个列创建唯一索引。
#创建唯一索引
ALTER TABLE table_name ADD UNIQUE (column);
#创建唯一组合索引
ALTER TABLE table_name ADD UNIQUE (column1,column2);
普通索引:基本的索引类型,没有唯一性的限制,允许为NULL值。
#创建普通索引
ALTER TABLE table_name ADD INDEX index_name (column);
#创建组合索引
ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3);
全文索引:是目前搜索引擎使用的一种关键技术。
#创建全文索引
ALTER TABLE table_name ADD FULLTEXT (column);
通过执行计划,可以判断本次查询中对于数据行定位是否是从索引获取的
EXPLAIN 查询语句
对于查询的评级由好到坏:const > primary(主键) > ref > range > index > all
show processlist;
,查看当前 mysql 使用频繁的 sql 语句。事务隔离级别 | 说明 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
读未提交(read-uncommitted) | 读取数据的事务对象,可以读取到另一个事务对象尚未提交的数据 | 是 | 是 | 是 |
不可重复读(read-committed) | 读取数据的事务对象,只能读取另一个事务对象提交过后的数据 | 否 | 是 | 是 |
可重复读(repeatable-read) (默认) |
读取数据的事务对象,在另一个事务对象提交前后读取的内容要保持一致 | 否 | 否 | 是 |
串行化(serializable) | 串行读取 | 否 | 否 | 否 |
脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。
幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。事务1在两次查询的过程中,事务2对该表进行了插入、删除操作,从而事务1第二次查询的结果发生了变化。
总结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
MyIsam:
使用三个文件表示每个表:
对表文件中数据行查询提速,有很大帮助。
灵活的AUTO_INCREMENT
字段处理
可被转换为压缩、只读表来节省空间
但是MyIsam不支持事务,因此在进行数据行添加,修改,删除时,无法保证数据安全性
INNODB:MySQL数据库的默认引擎;
InnoDB
表在数据库目录中以.frm
格式文件表示InnoDB
表空间tablespace
被用于存储表的内容ACID
兼容MySQL
服务器崩溃后提供自动恢复MVCC
)和行级锁定MEMORY:其数据存储在内存中,且行的长度固定,这两个特点使得MEMORY存储引擎非常快;
.frm
格式文件表示;MyISAM
表最适合于大量的数据读而少量数据更新的混合操作。MyISAM表的另一种适用情形是使用压缩的中读表。InnoDB
。其行级锁机制和多版本的支持为数据读取和更新的混合提供了良好的并发机制。MEMORY
存储引擎存储非永久需要的数据,或者是能够从基于磁盘的表中重新生成的数据。innodb引擎的4大特性
行级锁:
表级锁:
由于Innodb引擎支持的均为行锁,所以意向锁其实不会阻塞除全表扫描之外的任何请求
乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。
实现方式:
+1
操作,否则就执行失败+1
操作。使用场景:
乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry
,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
使用Power Designer
来创建表
第一范式:每一个字段是原子性不能再分;
第二范式:在第一范式基础之上,要求数据库中所有非主键字段完全依赖主键,不能产生部分依赖;
第三范式:在第二范式基础之上,要求非主键字段不能产生传递依赖于主键字段
SQL注入产生的原因,就是用户提交的数据,被数据库系统编译而产生了开发者预期之外的动作。也就是,SQL注入是用户输入的数据,在拼接SQL语句的过程中,超越了数据本身,成为了SQL语句查询逻辑的一部分,然后这样被拼接出来的SQL语句被数据库执行,产生了开发者预期之外的动作。
解决办法:
PreparedStatement
),在SQL语句中放置占位符’?
’,在执行前,数据库会先对含有占位符的SQL语句进行校验和预编译;执行时才会把参数传递进去,防止参数参与编译变成sql语句的一部分。'
),分号(;
) 和 注释符号(--
)的语句给替换掉来防止SQL注入单线程,利用redis队列技术并将访问变为串行访问,消除了传统数据库串行控制的开销
支持数据持久化,支持AOF和RDB两种持久化方式。
分布式 读写分离模式
支持丰富数据类型
支持事务,操作都是原子性,所谓原子性就是对数据的更改要么全部执行,要不全部不执行。
String类型:
Hash类型:
List类型:
Set类型:
Zset类型:
定期删除+惰性删除。
定期删除:redis
默认是每隔 100ms
就会扫描一定数量的数据库的expires
字典中的key
,检查其是否过期,如果过期就删除。定期删除可能会导致很多过期 key
到了时间并没有被删除掉,所以就使用惰性删除。
expires
字典会保存所有设置了过期时间的key
的过期时间数据,其中,key
是指向键空间中的某个键的指针,value
是该键的毫秒精度的UNIX
时间戳表示的过期时间。键空间是指该Redis
集群中保存的所有键。
惰性删除:获取 key
的时候,如果此时 key
已经过期,就删除,不会返回任何东西。
Redis Database(RDB),就是在指定的时间间隔内将内存中的数据集快照写入磁盘,数据恢复时将快照文件直接再读到内存。
RDB 保存了在某个时间点的数据集(全部数据)。存储在一个二进制文件中,只有一个文件。默认是 dump.rdb
。 RDB 技术非常适合做备份,可以保存最近一个小时,一天,一个月的全部数据。保存数据是在单独的进程中写文件,不影响 Redis 的正常使用。 RDB
恢复数据时比其他 AOF
速度快。
Append-only File(AOF), Redis
每次接收到一条改变数据的命令时,它将把该命令写到一个 AOF
文件中(只记录写操作,读操作不记录),当 Redis
重启时,它通过执行 AOF
文件中所有的命令来恢复数据。
CAP原则又称CAP定理,指在一个分布式系统不可能同时满足 Consistency
(一致性)、 Availability
(可用性)、Partition tolerance
(分区容错性)。由于分区容错性是分布式系统中必须要保证的,因此我们只能在A和C之间进行权衡
Redis集群是通过牺牲一致性来保证后两者(AP
)。无法保证强一致性(主从服务器始终保持数据一致),只能保证最终一致性(最终从服务器与主服务器数据一致)
当启动一个 从结点 的时候,它会发送一个 PSYNC
命令给 主结点。
如果这是 从结点 初次连接到 主结点,那么会触发一次全量复制:此时 主结点 会启动一个后台线程,开始生成一份 RDB
快照文件,同时还会将从客户端新收到的所有写命令缓存在内存中。RDB 文件生成完毕后, 主结点 会将这个 RDB
发送给 从结点,从结点 会先写入本地磁盘,然后再从本地磁盘加载到内存中,接着 主结点 会将内存中缓存的写命令发送到 从结点,从结点 也会同步这些数据。
从结点 如果跟 主结点 有网络故障,断开了连接,会自动重连,连接之后 主结点 仅会复制给 从结点 部分缺少的数据。
Redis并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会丢失写操作。
更多:面了BAT,我总结了他们会问的Redis基础知识
Redis面试题(2020最新版)
Redis持久化与集群 (Redis高频面试点: AOF,RDB,CAP,主从复制,哨兵,集群)
管道就是用“|
”连接两个命令,将前面一个命令的输出作为后面命令的输入,用于把管道左边的输出作为右边的输入。
Linux top命令详解
Linux 常用命令-- top
IoC 就是控制反转,是指创建对象的控制权的转移,以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到 Spring 容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。DI 依赖注入,和控制反转是同一个概念的不同角度的描述,即 应用程序在运行时依赖 IoC 容器来动态注入对象需要的外部资源。
AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为 “切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理。AOP 实现的关键在于 代理模式.
切面(Aspect):被抽取的公共模块(类),可能会横切多个对象。例如银行系统,先检测是否安全,再执行核心(业务)层,再记录日志,再清空缓存记录,除了核心层都是切面
连接点(Join point):指方法(可以被切面织入的具体方法),通常业务接口中的方法均为连接点。例如上面的银行系统中,核心类(目标业务方法)就是连接点
通知(Advice):Advice也叫增强。切面为类,而切面里的方法叫通知。有前置通知,后置通知,环绕通知等等。
切入点(Pointcut):切入点就是一个或多个连接点的集合。通过切入点表达式,来获取满足条件的连接点的集合。
目标对象(Target Object): 目标对象指被增强了的对象(包含主业务逻辑的类的对象),即被一个或者多个切面所通知的对象。这个对象永远是一个被代理(proxied) 对象。
织入(Weaving):指把增强应用到目标对象来创建新的代理对象的过程(将通知与连接点连接起来的过程)。
Aop一般用来处理权限控制,日志记录,加载事务等非主要业务逻辑的事情。底层是采用cglib和jdk的动态代理实现的。
Sring 的AOP和IoC都是为了解决系统代码耦合度过高的问题。使代码重用度高、易于维护。
Spring AOP采用的是动态织入(运行时织入),而Aspectj是静态织入(编译时期就织入),通过修改代码来实现。
AspectJ由于是在编译时期就织入,故性能更优。
Spring AOP的通知是基于该对象是SpringBean对象才可以,而AspectJ可以在任何Java对象上应用通知。
Spring AOP仅支持方法切入点,而AspectJ支持所有切入点
事务就是对一系列的数据库操作(比如插入多条数据)进行统一的提交或回滚操作,如果插入成功,那么一起成功,如果中间有一条出现异常,那么回滚之前的所有操作。这样可以防止出现脏数据,防止数据库数据出现问题。开发中为了避免这种情况一般都会进行事务管理。
Spring
提供了声明式事务和编程式事务。
AOP
实现的,自己不用额外的写代码,只要在Spring
配置文件中声明即可;通常用在数据库的操作里面;Spring
中也有自己的事务管理机制,一般是使用TransactionMananger
进行管理,可以通过Spring
的注入来使用此功能。
Spring面试总结(全)
ModelAndView
中,并将其返回给处理器适配器。ModelAndView
中的视图名称封装为视图对象。@RequestParam
:将请求参数绑定到控制器的方法参数上,同时可以给参数一些限定,比如说指定参数的默认值,是否允许参数为空等等。@PathVariable
:是将Rest
风格的请求中{}
中的值取出来@ResponseBody
:作用在方法上,表示该方法的返回结果直接写入响应体中,如果不使用@ResponseBody
,则返回值会解析为跳转路径。@RequestBody
:作用在形参列表上,用于将前台发送过来固定格式的数据【xml
格式或者 json
等】封装为对应的 JavaBean
对象,封装时使用到的一个对象是系统默认配置的 HttpMessageConverter
进行解析,然后封装到形参上。javax.servlet.Filter
接口)HandlerInterceptor
接口)#{}
是预编译处理,${}
是字符串替换
#{}
:Mybatis
在处理#{}
时,会将sql
中的#{}
替换为?
号,对sql语句预编译后,再调用PreparedStatement
的set
方法来赋值占位符,告诉 mybatis
使用实际的参数值代替。可以有效的防止SQL注入,提高系统安全性
${}
:字符串替换, 告诉 mybatis
使用$
包含的“字符串
”替换所在位置。使用的是 Statement
对象,有SQL
注入的风险。
<select id="selectLikeTwo" resultType="com.bjpowernode.domain.Student">
select * from student where name like "%" #{name} "%"
select>
MyBatis学习记录(3)之动态SQL
MyBatis提供查询缓存,用于减轻数据库压力,提高数据库性能,MyBatis
提供了一级缓存和二级缓存
一级缓存:SqlSession
级别的缓存
在操作数据库时,需要构造SqlSession
对象,在对象中有一个数据结构(HashMap
)用于存储缓存数据
不同的SqlSession
之间的缓存区域是互相不影响的。
二级缓存:mapper
级别的缓存
多个SqlSession
去操作同一个mapper
的sql语句,多个SqlSession
可以共用二级缓存,所得到的数据会存在二级缓存区域,二级缓存是跨SqlSession
的
二级缓存默认是没有开启的。需要在setting全局参数中配置开启二级缓存
二级缓存的具体流程:
当一个SqlSession
执行了一次select
后,在关闭此session
的时候,会将查询结果缓存到二级缓存
当另一个SqlSession
执行select
时,首先会在他自己的一级缓存中找,如果没找到,就回去二级缓存中找,找到了就返回,就不用去数据库了,从而减少了数据库压力提高了性能
如果SqlSession
执行了DML操作(insert、update、delete),并commit
了,那么mybatis
就会清空当前mapper
缓存中的所有缓存数据,这样可以保证缓存中的存的数据永远和数据库中一致,避免出现脏读
在 Spring Boot 里面,可以使用以下几种方式来加载配置,他们的加载优先级从高到低为:
不同目录下的配置文件(properties文件与yaml文件)优先级从高到低顺序:(高优先级配置会覆盖低优先级配置,多个配置文件互补)
file:./config/
- 优先级最高(项目根路径下的config)file:./
- 优先级第二 -(项目根路径下)classpath:/config/
- 优先级第三(项目resources/config下)classpath:/
- 优先级第四(项目resources根目录)启动类上面的注解是@SpringBootApplication
,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解:
@SpringBootConfiguration
:组合了 @Configuration 注解,实现配置文件的功能。
@EnableAutoConfiguration
:打开自动配置的功能
@ComponentScan
:Spring组件扫描。
注解 @EnableAutoConfiguration
,@Configuration
,@ConditionalOnClass
就是自动配置的核心。
@EnableAutoConfiguration
给容器导入META-INF/spring.factories
里定义的自动配置类,并根据一定的条件筛选有效的自动配置类,然后每一个自动配置类结合对应的 xxxProperties.java 读取配置文件进行自动配置功能
Spring Boot面试杀手锏——自动配置原理
Starters
可以理解为启动器,它包含了一系列可以集成到应用里面的依赖包,你可以一站式集成Spring
及其他技术,而不需要到处找示例代码和依赖包。例如,你想使用Spring JPA
访问数据库,只要加入springboot-starter-data-jpa
启动器依赖就能使用了。Starters包含了许多项目中需要用到的依赖,它们能快速持续的运行,都是一系列得到支持的管理传递性依赖。
Starter
简单来讲就是引入了一些相关依赖和一些初始化的配置。然后通过自动配置,完成这些bean
的自动配置。
Spring Boot面试题(2020最新版)
Publisher
:消息的生产者,也是一个向交换器发布消息的客户端应用程序。Broker
:表示消息队列服务器实体(就是个服务器)。Virtual Host
:虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是 /
。Exchange
:交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。Binding
:绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。Queue
:消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。Connection
:网络连接,比如一个TCP连接。Channel
:信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内地虚拟连接,AMQP 命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接。Consumer
:消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。数据丢失可能有下面几种情况:生产者丢失消息、消息列表丢失消息、消费者丢失消息;
生产者丢失消息:从生产者弄丢数据这个角度来看,RabbitMQ
提供transaction
和confirm
模式来确保生产者不丢消息;
transaction
机制:同步,发送消息前,开启事务(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事务就会回滚(channel.txRollback()),如果发送成功则提交事务(channel.txCommit())。然而,这种方式有个缺点:吞吐量下降;
confirm
模式(推荐):异步,一旦channel
进入confirm
模式,所有在该信道上发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后;rabbitMQ
就会发送一个ACK
给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了;如果rabbitMQ
没能处理该消息,则会发送一个Nack
消息给你,你可以进行重试操作。
消息队列丢数据:消息持久化。
处理消息队列丢数据的情况,一般是开启持久化磁盘的配置。
这个持久化配置可以和confirm
机制配合使用,你可以在消息持久化磁盘后,再给生产者发送一个Ack信号。这样,如果消息持久化磁盘之前,rabbitMQ阵亡了,那么生产者收不到Ack信号,生产者会自动重发。
在代码中成功处理完消息后,使用手动回复确认消息,避免消费者丢失消息。
保证消息的唯一性,就算是多次传输,不要让消息的多次消费带来影响;比如:在写入消息队列的数据做唯一标示,消费消息时,根据唯一标识判断是否消费过;
假设你有个系统,消费一条消息就往数据库里插入一条数据,要是你一个消息重复两次,你不就插入了两条,这数据不就错了?但是你要是消费到第二次的时候,自己判断一下是否已经消费过了,若是就直接扔了,这样不就保留了一条数据,从而保证了数据的正确性。
更多:
面试官问我:什么是消息队列?什么场景需要他?用了会出现什么问题?
消息中间件MQ与RabbitMQ面试题(2020最新版)
【HTTP协议】—HTTP协议详解
http三次握手四次挥手详解
Session
与Cookie
功能效果相同,都是记录一系列状态。
Session
与Cookie
的区别在于:
Session
是记录在服务端的内存中,而Cookie
是记录在客户端的浏览器内存,硬盘中。Cookie
中数据名和数据,只能String
,而Session
中数据名必须是String
,但是数据可以是Object
类型HTTP协议是非连接性的,取完当前浏览器的内容,然后关闭浏览器后,链接就断开了,而没有任何机制去记录取出后的信息。而当需要访问同一个网站的另外一个页面时(就好比如在第一个页面选择购买的商品后,跳转到第二个页面去进行付款)这个时候取出来的信息,就读不出来了。所以必须要有一种机制让页面知道原理页面的session内容。
Tomcat
在创建一个Session
对象时,自动为这个session
对象提供一个唯一编号。开发人员将这个唯一编号称为【sessionId
】。Tomcat
将sessionid
保存到一个Cookie
对象,在响应时将cookie
写入到响应头交给当前客户的浏览器上。浏览器收到sessionId
之后将其保存在浏览器内存中。
等到用户再次通过浏览器发起请求时,其拥有sessionId
作为Cookie
写入到请求头发送回服务端。此时Tomcat
接收到命令之后,就可以根据用户请求协议包中请求头是否拥有sessionId
,来判断用户在服务端是否存在Session
使用URL重写,就是通过发送请求时加入sessionId的参数
Windows下,在文件/bin/catalina.bat
,Linux下,在文件/bin/catalina.sh
的前面,增加如下设置:
# 配置JAVA_OPTS,后面跟需要配置的参数,变量名必须是JAVA_OPS,例如:
JAVA_OPTS='-Xms256m -Xmx512m'
# 表示初始化内存为256MB,可以使用的最大内存为512MB。
环境变量中进行配置,例如(变量名必须是JAVA_OPS
):
变量名:JAVA_OPTS
变量值:-Xms512m -Xmx512m
通过JavaScript
屏蔽提交按钮(不推荐):
通过js代码,当用户点击提交按钮后,屏蔽提交按钮使用户无法点击提交按钮或点击无效,从而实现防止表单重复提交。但是如果用户通过刷新页面方式,或使用postman
等工具绕过前段页面仍能重复提交表单。因此不推荐此方法。
给数据库增加唯一键约束(会增加数据库的负载):
在数据库建表的时候在ID字段添加主键约束,用户名、邮箱、电话等字段加唯一性约束。确保数据库只可以添加一条数据。
利用Session
防止表单重复提交(推荐):
在服务器端响应表单页面之前,先生成一个唯一的标识符,将它存入session
,同时将它写入表单的隐藏字段中,然后将表单页面发给浏览器,用户录入信息后点击提交,在服务器端,获取表单中隐藏字段的值,与session
中的唯一标识符比较,相等说明是首次提交,就处理本次请求,然后将session
中的唯一标识符移除;不相等说明是重复提交,就不再处理。
跨域问题的产生原因和相应的解决方式
标签指明父项目gav坐标。//
)
)${}
来引用(
)
)
)
)Maven配置文件POM属性最全详解
如果对方是大公司,你是小公司,你可以说,在小公司待的时间长了,想到大公司见识一下。
如果对方是小公司,你是大公司,你可以说,在大公司待的时间长了,职业天花板就在眼前,想在小公司试一下,有没有发挥自己才华的空间。
如果对方是外包公司,你可以说,自己还年轻,想进外包公司锻炼一下,多积累项目经验,提升自己的技术能力
如果对方不是外包公司,你就可以说上家公司是外包公司,每天都把自己外派到别的公司,感觉派来派去,没有归属感
如果对方是创业公司,你可以说自己想要更有挑战性的工作。
如果对方是国企,你可以说自己想有一份稳定一些的工作。
如果对方是外企,你可以说自己想要学习和感受一下国外的氛围。