章节目录
- 前言
- 一、Java基础
- Java的标识符规则与命名规范
- Java中命名规范(行业规范)
- JDK8的新特性
- Java中的关键字都有哪些?
- final关键字
- instanceof关键字
- 类和对象的关系
- 什么是线程安全的?什么是线程不安全的?
- String、StringBuffer、Stringbuilder之间的区别(可变性,线程安全,性能)
- Java中length与size的区别,Java中的length、length()和size方法的区别
- while(整数) 为什么不能是整形
- 类型的转换,int转char等等(未整理)
- 基础类型和引用类型传递有什么区别
- 集合和数组中存放数据类型的区别
- public数量(同一文件中和同一类中的区别)
- static静态,静态方法和静态属性
- 重写和重载的区别
- this代表本类,super代表父类
- 父类中的方法被子类继承了,子类中的方法访问限制不能低于父类的访问限制符
- 局部变量在什么时候创建
- 集合
- 二、集成问题
- 三、数据库
- 第三方框架
- 总结
前言
没什么好写的,就是自己遇到的面试题目,进行一个总结,如果有用,就参考,没有用的话就略过吧,以下题目都是自己进行一个学习总结的,不保证100%对!
一、Java基础
Java的标识符规则与命名规范
如果没有按照要求,则会在编译时报错
- 标识符由英文字母大小写,0~9,_或$组成。
- 数字不可开头。
- 不能使用关键字和保留字,但可以包含关键字和保留字
- Java中严格区分大小写,标识符长度无限制。
- 标识符不能含空格。
Java中命名规范(行业规范)
- 包名:多单词组成,所有字母都小写,xxyyzz.
- 类名|接口名:多单词组成,所有单词首字母大写:XxYyZz.
- 变量名|方法名:多单词组成时,第一个单词首字母小写,第二个单词开始首字母大写:xxxYyyZzz
- 常量名:所有字母大写,多单词用下划线连接:XXX_YYY_ZZZ
JDK8的新特性
- Lambda表达式
- Stream函数式操作流元素集合
- 接口新增:默认方法与静态方法
- 方法引用,与Lambda表达式联合使用
- 引入重复注解
- 类型注解
- 最新的Date/Time API
- 新增base64加解密API
- 数组并行(parallel)操作
- JVM的PermGen空间被移除:取代它的是Metaspace(JEP 122)
Lambda表达式
Java中的关键字都有哪些?
1、48个关键字:abstract、assert、boolean、break、byte、case、catch、char、class、continue、default、do、double、else、enum、extends、final、finally、float、for、if、implements、import、int、interface、instanceof、long、native、new、package、private、protected、public、return、short、static、strictfp、super、switch、synchronized、this、throw、throws、transient、try、void、volatile、while。
2个保留字(现在没用以后可能用到作为关键字):goto、const。
3个特殊直接量:true、false、null。
final关键字
对属性来说:
是将属性设置为常量
对方法来说:是将方法设置为最终类,不可被继承
instanceof关键字
instanceof 是java的保留关键字。他的作用就是测试左边的对象是不是右边类的实例,是的话就返回true,不是的话返回false。
类的实例包括本身的实例,以及所有直接或间接子类的实例
instanceof左边显式声明的类型与右边操作元必须是同种类或存在继承关系,也就是说需要位于同一个继承树,否则会编译错误
类和对象的关系
对象是类的解释
类是对象的代码实例
什么是线程安全的?什么是线程不安全的?
- 首先讲讲什么是线程
可以这样理解:
咱们去做核酸,如果有一百个人,但是只要一个医务人员,假设这个医务人员要给这一百个人做核酸要花费1小时,这就是单线程。
大家都觉得这一个小时太慢了,别的地方的医务人员过来支援了,假设过来了一个医务人员,哪现在就是有俩个医务人员,然后全部人就可以分成俩列,只需要半个小时就可以做完,这就叫双线程。
你可以理解为:处理一件事情,同一时间可以有多少问题在被解决,这就可以简单理解为线程。 - 线程安全与不安全
线程安全:假设售票系统有1000张票,A和B同时来买票,如果是线程是安全,那么售票系统正常是可以得到预期的结果998张。
指多个线程在执行同一段代码的时候采用加锁机制,使每次的执行结果和单线程执行的结果都是一样的,不存在执行程序时出现意外结果。
··
线程不安全:假设售票系统有1000张票,A和B同时来买票,如果是线程不安全,那么可能售票系统可能出现1000-1去同时执行的情况,最终结果是A和B都买完后剩下999张票,而不是998张。
是指不提供加锁机制保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
String、StringBuffer、Stringbuilder之间的区别(可变性,线程安全,性能)
可变性(长度是否可变)
String :字符串的内容一旦声明就不能改变,String内对象的的改变是依靠引用关系的变更来实现;
String就是通过创建一个新的String来创建新的字符串,将指针指向新的字符串,导致要冗余。
String 类中使用 final 关键字字符数组保存字符串:private final char value[]
;
··
StringBuffer和StringBuilder:二者都继承自 AbstractStringBuilder类,在 AbstractStringBuilder 中也是使用字符数组保存字符串 char[]value 但是没有用 final 关键字修饰,所以这两种对象都是可变的。
public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable,CharSequence{}
public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence{}
在线程中是否安全
-
String 中的对象是不可变的,也就可以理解为常量,因为不可变,不存在数据不一致问题,所以线程安全。
-
StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
-
StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
-
AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。
-
性能
- 每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。
- StringBuffer 每次都会对 StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。
- 相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
Java中length与size的区别,Java中的length、length()和size方法的区别
- Java中的length属性是针对 数组 " [ ] " 来说的,比如说你声明了一个数组,想知道这个数组的长度则用到了length这个属性。
- Java中的length()方法是针对 字符串String 来说的,如果想看这个字符串的长度则用到length()这个方法.
- java中的size()方法是针对 泛型集合 说的,如果想看这个泛型有多少个元素,就调用此方法来查看!
while(整数) 为什么不能是整形
- 在Java中,while中只能是boolean类型的
- Java中while()括号内的参数可以是表达式,因为表达式的结果是boolean值
- 例如:12==12,的最终结果也是boolean类型,所以也可以
类型的转换,int转char等等(未整理)
基础类型和引用类型传递有什么区别
8个基础类型:
- ①整数类型:byte、short、int、long
- ②浮点类型:float、double
- ③布尔类型:boolean
- ④字符类型:char
所占的字节数
bit
和byte
同称为:比特
二者的大小不同:1byte=8bit
但是大多数时候称:bit为比特,byte为字节
计算机的基本单位:bit(一个bit代表一个0或1)
- byte:1byte
- short:2byte
- int:4byte
- long:8byte
- float:4byte
- double:8byte
- boolean:1byte
- char:2byte
引用类型
- 大致包含:类、 接口类型、 数组类型、 枚举类型、 注解类型、 字符串型
俩者的值传递区别
-
在基础类型中,其值在传递的过程,jvm会重新开辟一块空间,将值进行传递给所要接受的形参中。
-
在引用类型中,其值在传递,有点类似C语言的中的指针一般,其传递的的都是地址的值,将地址传递到形参中。
集合和数组中存放数据类型的区别
- 数组:可以存储基本类型的数据,也可以存储引用类型的数据:
- 集合:只能存储引用类型的数据,想要存储基本类型的数据,就得用到基本类型的包装类(例如:int的包装类Interger)。
public数量(同一文件中和同一类中的区别)
- 同一个文件夹中可以有多个public类吗?
不可以,因为同一个文件中,只能有一个public类而且文件名必须和public的类名一致
- 同一个类中可以有多个public方法吗?
可以,因为同一个中可以有多个public方法,但是同一个内中只能有一个main方法
拓展:main不是Java中的关键字
static静态,静态方法和静态属性
- 如果静态方法想直接调用静态属性,就需要将属性也设置为静态的属性才可以。
- 在本类中的构造方法中
静态的属性可以直接用属性名访问
- 在本类中的方法中
静态的属性不能通过实例化的名直接访问,要通过其 “类名.属性名”来访问
重写和重载的区别
重写(Override):外壳不变,核心重写!
重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。
和父类不一样,表现为个体的差异,实现不一样的功能。
- 重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
- 重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。
例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,所以重写的方法只能抛出 IOException 异常或者 IOException 的子类异常。
- 在面向对象原则里,重写意味着可以重写任何现有方法
重写规则
- 参数列表与被重写方法的参数列表必须完全相同。
- 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值> 的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
- 父类的成员方法只能被它的子类重写。
- 声明为 final 的方法不能被重写。
- 声明为 static 的方法不能被重写,但是能够被再次声明。
- 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
- 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
- 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
- 构造方法不能被重写。
- 如果不能继承一个类,则不能重写该类的方法。
重载(overloading):名称不变,参数不同!
1.重载是在一个类里面,方法名字相同,而参数不同。
2.返回类型可以相同也可以不同。
3.每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
4.最常用的地方就是构造器的重载
重载规则
- 被重载的方法必须改变参数列表(参数个数或类型不一样);
- 被重载的方法可以改变返回类型;
- 被重载的方法可以改变访问修饰符;
- 被重载的方法可以声明新的或更广的检查异常;
- 方法能够在同一个类中或者在一个子类中被重载。
- 无法以返回值类型作为重载函数的区分标准。
二者的区别
区别点 | 重写方法 | 重载方法 |
---|---|---|
参数列表 | 一定不能修改 | 必须修改 |
返回类型 | 一定不能修改 | 可以修改 |
异常 | 可以减少或删除,一定不能抛出新的或者更广的异常 | 可以修改 |
访问 | 一定不能做更严格的限制(可以降低限制) | 可以修改 |
总结
方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。
- 方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。
- 方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。
- 方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
- 重写也重写重载的方法,二者可以嵌套使用。所以一个类和子类可以有2n个同名的方法。
this代表本类,super代表父类
- 在本类中调用本类,this表示本类:this.属性/this.方法名
- 在本类中调用父类,super表示父类:super.属性/super.方法名
父类中的方法被子类继承了,子类中的方法访问限制不能低于父类的访问限制符
- 父类:private 子类:public protect private
- 父类:protect 子类:public protect
- 父类:public 子类:public
局部变量在什么时候创建
方法中定义的局部变量,是在栈桢的局部变量表中的,局部变量表的创建是随着栈桢的创建而创建的;
根据 JVM虚拟机规范 定义,栈桢的创建是在方法执行时创建的,而栈桢的创建就是在该方法被调用时创建的,在方法执行完后该栈桢销毁,下一个栈桢创建;
集合
Collection(单列数据)
|--Collection接口:单列集合,用来存储一个一个的对象
|--List接口:存储有序的、可重复的数据。 -->“动态”数组
|--ArrayList:作为List接口的主要实现类,线程不安全的,效率高;底层采用Object[] elementData数组存储
|--LinkedList:对于频繁的插入删除操作,使用此类效率比ArrayList效率高底层采用双向链表存储
|--Vector:作为List的古老实现类,线程安全的,效率低;底层采用Object[]数组存储
|--Set接口:存储无序的、不可重复的数据 -->数学概念上的“集合”
|--HashSet:作为Set接口主要实现类;线程不安全;可以存null值
|----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加顺序遍历;对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
|--TreeSet:可以按照添加对象的指定属性,进行排序。
Collection常用的方法
- 添加
add(Object obj)
addAll(Collection coll)- 获取有效元素个数
int size()- 清空集合
void clear()- 是否为空集合
boolean isEmpty()- 是否包含某个元素
boolean contains(Object obj):是通过元素的equals方法来判断是否是同一个对象
boolean containsAll(Collection c):也是调用元素的equals方法来比较的。用两个两个集合的元素逐一比较- 删除
boolean remove(Object obj):通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素
boolean removeAll(Collection coll):取当前集合的差集- 取两个集合的交集
boolean retainAll(Collection c):把交集的结果存在当前的集合中,不影响c- 集合是否相等
boolean equals(Object obj)- 转换成对象数组
Object [] toArray()- 获取集合对象的哈希值
hashCode()- 遍历,返回迭代器对象,用于集合遍历
iterator()
Collection集合与数组之间的相互转化
// 集合 —>数组:toArray()
Object[] arr = coll.toArray();
for(int i = 0;i < arr.length;i++){
System.out.println(arr[i]);
}
// 拓展:数组 —>集合:调用Arrays类的静态方法asList(T … t)
List< String > list = Arrays.asList(new String[]{“AA”, “BB”, “CC”});
System.out.println(list);
List arr1 = Arrays.asList(new int[]{123, 456});
System.out.println(arr1.size());//1
List arr2 = Arrays.asList(new Integer[]{123, 456});
System.out.println(arr2.size());//2
Map(双列数据)
二、集成问题
登录的流程
- 前端发起登录请求,后端接收到登录请求。
- 后端生成一个验证码 & 生成一个Token,存入redis,Key为token,value为正确的验证码,并且设置有效期。
- 后端将验证码,使用工具将验证码的数值转化成一个图片,以Base64传给前端。
- 前端收到验证码和Token后,渲染验证码,并且将token存入本地缓存中。
- 前端进行账号密码的校验,然后将账号密码以rsa的公钥进行加密给后端,并且携带本地缓存中的token到请求头中。
- 后端收到账号密码,验证码后利用rsa私钥进行解密,解密后将密码进行MD5加密,与数据库的账号密码比较。
- 将redis中的token信息进行销毁,重新存入,以token为key,value为用户的基础信息到redis中。
- 返回给前端,登录成功。
Token的生成方式
- 当前用户的验证码+用户的IP地址+当前的时间戳+当前用户的登录平台。
三、数据库
数据常见的索引类型
怎么对数据库百万级数据进行优化?
- 对查询进⾏优化,要尽量避免全表扫描,⾸先应考虑在 where 及 order by 涉及的列建⽴索引,以此加快查询速度。
- 应尽量避免在 where ⼦句中对字段进⾏ null 值判断,否则将导致引擎放弃使⽤索引⽽进⾏全表扫描,如:select id from t where num isnull
最好不要给数据库留NULL,尽可能的使⽤ NOT NULL填充数据库.
备注、描述、评论之类的可以设置为 NULL,其他的,最好不要使⽤NULL。
不要以为 NULL 不需要空间,⽐如:char(100) 型,在字段建⽴时,空间就固定了,不管是否插⼊值(NULL也包含在内),都是占⽤ 100个字符的空间的,如果是varchar这样的变长字段, null 不占⽤空间。
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
select id from t where num =0 - 应尽量避免在 where ⼦句中使⽤ != 或 <> 操作符,否则引擎将放弃使⽤索引⽽进⾏全表扫描。
- 应尽量避免在 where ⼦句中使⽤ or 来连接条件,如果⼀个字段有索引,⼀个字段没有索引,将导致引擎放弃使⽤索引⽽进⾏全表扫描,如:
select id from t where num=10 or Name =‘admin’
可以这样查询:
select id from t where num = 10
unionall
select id from t where Name =‘admin’ - in 和 not in 也要慎⽤,否则会导致全表扫描,如:
select id from t where num in(1,2,3)
对于连续的数值,能⽤ between 就不要⽤ in 了:
select id from t where num between1and3
很多时候⽤ exists 代替 in是⼀个好的选择:
select num from a where num in(select num from b)
⽤下⾯的语句替换:
select num from a whereexists(select1from b where num=a.num) - 下⾯的查询也将导致全表扫描:
select id from t where name like ‘%abc%’
若要提⾼效率,可以考虑全⽂检索。 - 如果在 where ⼦句中使⽤参数,也会导致全表扫描。因为SQL只有在运⾏时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运⾏时;它必须在编译时进⾏选择。然⽽,如果在编译时建⽴访问计划,变量的值还是未知的,因⽽⽆法作为索引选择的输⼊项。如下⾯语句将进⾏全表扫描:
select id from t where num = @num
可以改为强制查询使⽤索引:
select id from t with(index(索引名)) where num = @num - 应尽量避免在 where ⼦句中对字段进⾏表达式操作(也就是计算操作),这将导致引擎放弃使⽤索引⽽进⾏全表扫描。如:
select id from t where num/2 = 100
应改为:
select id from t where num = 100*2 - 应尽量避免在where⼦句中对字段进⾏函数操作,这将导致引擎放弃使⽤索引⽽进⾏全表扫描。如:
select id from t wheresubstring(name,1,3) = ’abc’ –name以abc开头的id
select id from t wheredatediff(day,createdate,’2005-11-30′) = 0 -–‘2005-11-30’ --⽣成的id
应改为:
select id from t where name like’abc%’
select id from t where createdate >= '2005-11-30’and createdate < ‘2005-12-1’ - 不要在 where ⼦句中的“=”左边进⾏函数、算术运算或其他表达式运算,否则系统将可能⽆法正确使⽤索引。
- 在使⽤索引字段作为条件时,如果该索引是复合索引,那么必须使⽤到该索引中的第⼀个字段作为条件时才能保证系统使⽤该索引,否则该索引将不会被使
⽤,并且应尽可能的让字段顺序与索引顺序相⼀致。 - 不要写⼀些没有意义的查询,如需要⽣成⼀个空表结构:
select col1,col2 into #t from t where1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
create table #t(…) - Update 语句,如果只更改1、2个字段,不要Update全部字段,否则频繁调⽤会引起明显的性能消耗,同时带来⼤量⽇志。
- 对于多张⼤数据量(这⾥⼏百条就算⼤了)的表JOIN,要先分页再JOIN,否则逻辑读会很⾼,性能很差。
- select count(*) from table;这样不带任何条件的count会引起全表扫描,并且没有任何业务意义,是⼀定要杜绝的。
- 索引并不是越多越好,索引固然可以提⾼相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况⽽定。⼀个表的索引数最好不要超过6个,若太多则应考虑⼀些不常使⽤到的列上建的索引是否有必要。
- 应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,⼀旦该列值改变将导致整个表记录的顺序的调整,会耗费相当⼤的资源。若应⽤系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。
第三方框架
Redis
Redis有哪几种数据类型
- string(字符串)
- Hash(哈希)
- list(列表)
- set(集合)
- sorted set:有序集合
总结
不积跬步,无以至千里;不积小流,无以成江海。