1.ArrayList集合中的元素怎么进行排序?(元素为自定义类型)
因为Collections.sort(List<T> list)中的list参数必须要有明确的排序规则,所以要对存放了自定义对象的list集合进行排序,需要该对象实现Comparable接口,并实现接口唯一的方法compareTo(T o),按照我们自定义精酿3的规则进行排序。例如自定义Person类,按照年龄升序排序:
public class Person implements Comparable<Person>{
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
@Override
public int compareTo(Person o) {
return this.age - o.age;//this-参数 升序
//return o.age - this.age 降序
}
}
public static void main(String[] args){
ArrayList<Person> list2 = new ArrayList<>();
list2.add(new Person("张三",32));
list2.add(new Person("李四",12));
list2.add(new Person("王五",23));
Collections.sort(list2);
for(Person p:list2)
System.out.println(p);
//输出结果
/*Person [name=李四, age=12]
Person [name=王五, age=23]
Person [name=张三, age=32]*/
}
第二种方法
第二种方法中的第二个参数是需要实现Comparator接口,需要实现接口中的compare(T o1,T o2)方法,一般对自定义对象排序的时候可采取这种方法。
Comparator和Comparable接口的区别在于,Comparator接口相当于第三方的裁判,对传入的两个参数进行比较;Comparable接口是用自身(this)与参数进行比较。
例子如下,先创建一个Student类
public class Student {
private String name;
private int score;
public Student() {
}
public Student(String name,int score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public String toString() {
return "Student [name=" + name + ", score=" + score + "]";
}
创建集合,在集合中存放Student对象,采用匿名内部类的方法实现Comparator
接口,按照分数降序排序
public static void main(String[] args){
ArrayList<Student> list3 = new ArrayList<>();
list3.add(new Student("张三",75));
list3.add(new Student("李四",91));
list3.add(new Student("王五",89));
Collections.sort(list3, new Comparator<Student>(){
@Override
public int compare(Student o1, Student o2) {
//return o1.getScore() - o2.getScore();//升序
return o2.getScore() - o1.getScore(); //降序
}
});
}
//输出结果
/*
Student [name=李四, score=91]
Student [name=王五, score=89]
Student [name=张三, score=75]
*/
2.对Arraylist元素进行去重
第一种方法遍历后赋值给另一个元素
List<String> list = new ArrayList<String>();
list.add("aaa");
list.add("bbb");
list.add("aaa");
list.add("aba");
list.add("aaa");
//遍历后判断赋给另一个list集合
List<String> newList = new ArrayList<String>();
for (String cd : list) {
if (!newList.contains(cd)) {
newList.add(cd);
}
}
System.out.println("去重后的集合: " + newList);
第二种方法
利用set集合去重
private static void removeDuplicate(List<String> list) {
HashSet<String> set = new HashSet<String>(list.size());
List<String> result = new ArrayList<String>(list.size());
for (String str : list) {
if (set.add(str)) {
result.add(str);
}
}
list.clear();
list.addAll(result);
}
3.B树和B+树区别
1.B+树内节点不存储数据,所有数据都存在叶节点,查询时时间复杂度为log以2为底n的对数,B树每个节点都有都有key和data,时间复杂度为O(n),最好的情况为O(1)。
2.B+树页节点是两两相连的,可以使用范围查找,B树不行。(因为根据空间局部性原理),如果存储器的某个位置被访问,那么将它附近的位置也会被访问。
3.因为B+树内节点不携带数据,所以每次IO操作,B+树获得数据量大于B树。因为每次磁盘IO大小是固定的,单个元素越小,量越大。
4.Stringbuffer,string,stringbuilder的区别
1.不可变性角度。string类被final关键字修饰,属性被final和private修饰,且没有向外暴露修改这个字符串的方法。
stringbuilder、stringbuffer是可变的,都继承自abstractStringbuilder类,这个类没有被final和private修饰,而且提供修改字符串的方法。(append insert index of)
2.线程安全性,string对象不可变,也可以理解为线程安全,stringbuffer对方法加了同步锁,所以线程安全的,stringbuider没有对方法进行同步锁,所以非线程安全。
3.性能方面:string类型改变的时候都会生成一个新的string对象,然后指向新的string对象。stringbuffer和stringbuilder对对象本身操作,但stringbuilder性能更好,但要冒对多线程不安全的风险。
总结:1.操作量少的适用string2.单线程且有大量操作用stringbuilder3.多线程且有大量操作用stringbuffer
5.类加载器加载过程
系统加载 Class 类型的文件主要三步:加载->连接->初始化。连接过程又可分为三步:验证->准备->解析。
这就得说一下双亲委派,当一个类加载器接收到一个类加载任务时,不会立即加载,而是将加载任务交由父类加载器去完成,若父类加载过则直接返回,否则一直往上抛,直到bootstrap class loader,若bootstrap classloader未加载到,则会让一直往下让子类尝试加载。
6.springboot的常用注解
7.jvm优化方法
1.通过-Xmx -Xms调整最大堆内存和最小堆内存,开发过程中,通常将两个参数值设置为相等,目的是为了垃圾回收机制清理完堆区后,不需要重新分割堆区大小而浪费资源。
2.调整新生代:老年代的比值为 1:4
3.调整Survivor区和Eden区的值 survivor:eden=1:1:8,即一个survivor占年轻代的十分之一
8.jvm内存区域分析
1.线程私有:程序计数器、虚拟机栈、本地方法栈
2.线程共享:堆、方法区
9.运行时常量池
运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池表(用于存放编译期生成的各种字面量和符号引用);
JDK1.8 hotspot 移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)
10.缓存穿透、缓存雪崩的解决方法
缓存穿透解决方案:1.在redis中缓存无效的key,并设置过期时间 2.设置布隆过滤器
缓存雪崩解决方案:1.采用redis集群,避免单机出现问题整个缓存服务都没办法使用
2.限流,避免同时处理大量请求
3.随即设置缓存失效时间
11.mysql高性能优化建议
建表时把经常一起使用的列放到一个表中(避免更多的关联操作);尽可能把所有列定义为not null,索引null列需要额外的空间来保存,所以要占用更多的空间;进行比较和计算时要对null值做处理;同财务相关的金额数据必须用decimal。索引方面,限制每张表的索引数量,限制单表索引不超过五个,将经常出现在where条件中的列建立索引,索引列的基数不能太小,能建联合索引的尽量建联合索引。
写查询语句时,能用覆盖索引尽量用覆盖索引,可避免回表查询,可以把随机io变为顺序io加快查询效率。尽量避免子查询,可以把子查询优化为join操作,
建立索引目的,通过索引查找数据,减少随机IO,增加查询性能。
12.springboot全局配置文件加载顺序
优先加载当前项目根目录下/config包中的文件,接着是当前项目根目录下的配置文件,然后是当前项目resource下的/config中的文件,最后是resource下的配置文件。在同一目录下,若同时存在application.yml和application.properties两种不同的配置文件,默认先读取application.properties中的,如果统一配置在多个配置文件中都配置了,默认会使用第一个读取到的值,后面读取不会覆盖前面读取到的内容,而会进行互补。
13.springboot中读取配置文件的注解有哪些 ?
@value和@configurationProperties注解方式 @PropertitySource+@Value注解读取方式
14. 当服务端从单体应用升级为分布式之后,cookie+session这种机制怎么拓展?
session共享:就是将服务端的session信息保存到一个第三方中,比如Redis。
15.session和cookie的区别?
session属于服务端技术,cookie属于客户端技术;HTTP协议是无状态的协议,服务端需要记录用户的状态时,就需要用特定机制来识别具体的用户,这个机制就是session。session在服务端,有一个唯一的标识,session可以保存在内存、数据库、文件都有。集群时也要考虑session的转移。
当服务器tomcat第一次接收到客户端的请求时,会开辟一块独立的session空间,建立一个session对象,同时生成一个sessionid,通过响应头的方式保存到客户端浏览器的cookie当中,以后客户端的每次请求,都会在请求头部带上这个sessionid,这样就可以对应上服务端的一些会话的相关信息,比如用户登录状态。
总结:session是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中;Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现session的一种方式。
16.synchronized和Lock锁的区别?
synchronized可以给类方法代码块加锁;而lock只能给代码块加锁。synchronized不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而lock锁需要自己加锁和释放锁,如果使用不当没有unlock()区释放锁会造成死锁。通过lock可以知道有没有成功获取锁,而synchronized却无法办到。