自己总结面试题-持续更新。。。

基础篇

Java基础

面试题

重写Override和重载Overload

重写是应用在有继承的关系中的,重写父类的方法,方法体不一样。

重载是可以有不同的参数,参数类型,返回值,修饰符可以发生在子父类中,或者在本类中。

String,StringBuffer和StringBuilder的区别及使用场景

String,StringBuffer和StringBuilder

String 不能更改长度,StringBuffer和StringBuilder可以改变长度,StringBuilder单线程,StringBuffer多线程场景

1.使用String类的场景:在字符串不经常变化的场景中可以使用String类,例如常量的声明、少量的变量运算。

2.使用StringBuffer类的场景:在频繁进行字符串运算(如拼接、替换、删除等),并且运行在多线程环境中,则可以考虑使用StringBuffer,例如XML解析、HTTP参数解析和封装。

3.使用StringBuilder类的场景:在频繁进行字符串运算(如拼接、替换、和删除等),并且运行在单线程的环境中,则可以考虑使用StringBuilder,如SQL语句的拼装、JSON封装等。

类和接口有什么区别:

1、接口类似于类,但接口的成员都没有执行方式,它只是方法、属性、事件和索引的组合而已,并且也只能包含这四种成员;类除了这四种成员之外还可以有别的成员(如字段)。
2、不能实例化一个接口,接口只包括成员的签名;而类可以实例化(abstract类除外)。
3、接口没有构造函数,类有构造函数。
4、接口不能进行运算符的重载,类可以进行运算符重载。
5、接口的成员没有任何修饰符,其成员总是公共的,而类的成员则可以有修饰符(如:虚拟或者静态)。
6、派生于接口的类必须实现接口中所有成员的执行方式,而从类派生则不然。

反射获取对象的三种方式:

Class.froName

.class

类对象的getClass()

用一个iterable 实现一个随机序列生产器

public class RandomStringGenerator<T> implements Iterable<T> {
    private List<T> list;
    public RandomStringGenerator(List<T> list){
        this.list = list;
    }
    @Override
    public Iterator<T> iterator() {
        return new Iterator<T>() {
            @Override
            public boolean hasNext() {
                return true;
            }
            @Override
            public T next() {
                return list.get((int) (list.size() * Math.random()));
            }
        };
    }
    public static void main(String[] args) {
        var list = Arrays.asList("list","tree","table");
        var gen = new RandomStringGenerator<String>(list);
        for (var s :gen)
            System.out.println(s);
//        var it = gen.iterator();
//        for (int i = 0; i < 100; i++) {
//            System.out.println(it.next());
//        }
    }
}

Treeset不支持null

Map的本质时映射,kv

hashmap 哈希表实现的map

TreeSet 树实现的集合,set不支持null的容器

什么是流?

:: 运算符的作用?

Java8 Stream价值是什么?

创建Stream有几种方法?

coding: 利用parallel并发执行任务?

== 和 equals

== 在引用数据类型中是地址比较,

在基本数据类型中是值比较,

equals只能比较引用类型,是值比较,在基本数据类型的包装类中比较的是地址(重写equals方法)

Buffer的原理和使用场景+面试题解读

流:随着时间到来的数据。

缓冲区:缓冲数据,请求太多…

缓冲的本质是排队,流的本质是数据。

没有缓冲:只能拒绝服务,性能低

有缓冲:排队处理,批量处理(批量处理请求避免拒绝服务,批量写入磁盘快过多次写入,批量执行sql快过多次执行)

Device->Kernel Space->User Space(buffer) ->Thread

image-20210429094327231

IO的成本很高3次拷贝

缓冲区是不是流?

  • 不是,流是数据,缓冲区是缓冲数据。

缓冲区操作的几种含义:filp/rewind/clear?(Position,Limit,Capacity)

  • 当想缓冲区放入数据时,每放一个P指针会指向下一个空间,当要读取数据时,要进行翻转(flip)操作,重读或重写用rewind操作,清空缓冲区用clear操作

  • 翻转filp操作image-20210429100051811

  • clear操作image-20210429100216582

  • rewind操作image-20210429100127873

缓冲区设置多大?

  • 根据不同的系统对应设置的缓冲区的大小也不应该一样,buffer大小一般不需要自己设置,默认8192 ,
  • 根据不同的业务设置缓冲区,再进行批量处理。

NIO的Channel比缓冲区快么?

  • 如果都用了buffer手段,nio不是更快,只不过是提供了更标准化的操作。只需要记住缓冲区的操作。

缓冲过程中,中文乱码如何处理?

并发分析数据更快么?

计算一个大文件的词频?

MySQL

索引

就是帮助mysql高效获取数据的排好序的数据结构

底层实现 B+树 双向指针image-20210523110604786

红黑树:平衡二叉树

哈希索引

算出key的hash值,存地址。

不适合范围查询

B树image-20210523110451169

数据库表存储引擎:
修饰数据库表的

  • 表数据文件本身就是按照B+树组织的一个索引结构文件
  • 聚集索引-叶节点包含了完整的数据记录
  • 为什么建议Innodb必须建主键,并且推荐使用整形的自增主键?
    • 如果不建立主键,数据库会根据你的表里的数据自己寻找一个不重复的列作为主键,如果表里的数据没有可选的主键,数据库会自己维护一个rowid来作为主键,非常耗费资源,影响性能。
  • 为什么非主键索引结构叶子节点存储的是主键值?(一致性和节省储存空间)

联合索引的底层存储结构什么样?

image-20210524084605757

索引最左前缀原理

为什么使用主键自增而不是uuid?
uuid是字符串,主键自增
插入可以更快减少比较和移动.

  • 每一个索引都是B+树

当查的字段类型不匹配时会转型,int的字符串会转为0

索引失效:对字段进行操作

Buffer Pool

空间 128M 有三个链表

  • free链表 存空位置的
    image-20210524210359626

  • flush链表 存脏页
    image-20210525080412131

  • LRU链表 热数据:冷数据 = 5:3
    image-20210525083055042

update
更新buffer pool里面的页数据
生成redoLog,commit之后持久化redoLog,顺序IO
即使数据库挂了,也可以通过redoLog还原数据redolog文件一开始就开辟48M的空间

两个redolog文件都有checkpoint到达这个点就要持久化 ,redolog文件先存在logbuffer, redolog是innodb层面的

随机IO费时

binlog 是mysql层面的用于给其他从数据库数据

undolog还原用记录之前

doublewritebuffer innodb持久化时16->4需要判断能不能写成功

面试题

索引的原理

把无序的数据建立有序的查询

  • 把创建了索引的列的内容进行排序
  • 对排序结果生成倒排表
  • 在倒排表内容上拼上数据地址链
  • 在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体数据
聚簇索引和非聚簇索引

聚簇索引:数据和索引在一起,而且有排序

非聚簇索引:叶子节点存储的不是数据,是地址

innoDB中一定有主键,主键一定是聚簇索引,不手动设置会使用唯一索引,没有唯一索引会自己生成一个rowid

MyISM使用的是非聚簇索引,没有聚簇索引,存储的内容不一样

索引的数据结构

B+树:一般时候

Hash:单条记录查询多

索引设计的原则

查询更快,占用空间更小

  • 需要在where中出现,或者连接某一个列
  • 基数比较小表,索引效果差,没必要建立索引
  • 使用短索引
  • 不要过度索引
  • 定义有外键,一定要建立索引
musql锁的类型有哪些

属性分类:共享锁,排他锁

粒度分类:行级锁,表级锁,页级所,记录锁,间隙锁,临键锁

状态分类:意向共享锁,意向排他锁

  • 共享锁

    共享锁就是读锁,S锁,当一个事务为数据加上读锁之后,其他事务只能对该数据加读锁,而不能对数据加写锁,直到所有的读锁释放之后其他事务才能对其进行加写锁。共享锁的特性主要是为了支持并发的读取数据,读取数据的时候不支持修改,避免出现重读的问题。
    
  • 排他锁

    排他锁又称为写锁,X锁,当一个事务为数据加上写锁时,其他请求将不能再为数据加任何锁,直到该锁释放之后,其他事务才能对数据进行加锁。排他锁的目的是在数据修改时,不允许其他人同时修改,也不能读。避免脏读,脏数据
    
  • 表锁

    整个表,粒度大,加锁简单,但是容易冲突   myISM
    
  • 行锁

    可以多行,粒度小,不容易冲突,并发比表锁高  innodb
    
  • 记录锁

    记录锁是行锁的一种,只查一条记录,命中条件字段是唯一索引。
    
  • 页锁

    介于表锁和行锁之间,
    
  • 间隙锁

    左开右闭
    
  • 临建锁

    记录锁和间隙锁的组合
    
  • 意向共享锁

    当一个事务试图对整个表进行加共享锁之前,首先需要获得这个表的意向共享锁
    
  • 意向排他锁

    当一个事务试图对整个表进行加排他锁之前,首先需要获得这个表的意向排他锁
    
执行计划

explain

执行效率:

ALL<inde<range<ref<eq_ref<counst<system.最好避免ALL和index

事务的基本特性 ACID

原子性 一起成功,一起失败

一致性 一致性关注数据的可见性,中间状态的数据对外部不可见,只有最初状态和最终状态的数据对外可见

隔离性 一个事务在最终提交前,对其他事务是不可见的

持久性 事务提交,所做的修改就会永久保存到数据库中。

ACID靠什么保证的

A原子性由undolog日志保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行的sql。

C一致性由其他三大特性保证、程序代码也要保证业务上的一致性

I隔离性由MVCC来保证

D持久性 由内存+redolog来保证,mysql修改数据同时在内存和redolog记录这次操作,宕机也可以从redolog恢复

InnoDB redolog写盘,InnoDB事务进入prepare状态。如果prepare成功,binlog写盘,再继续将事务日志持久化到binlog,如果持久化成功,那么InnoDB事务则进入commit状态(就是在redolog里面写一个commit记录)
隔离性有四个隔离级别

SQL 标准定义了四种隔离级别,MySQL 全都支持。这四种隔离级别分别是:

  1. 读未提交(READ UNCOMMITTED)

  2. 读提交 (READ COMMITTED Oracle mvcc)

  3. 可重复读 (REPEATABLE READ Mysql mvcc)

  4. 串行化 (SERIALIZABLE)

image-20210525164511145

MySQL 的 InnoDB 引擎才支持事务,其中可重复读是默认的隔离级别。

读未提交和串行化基本上是不需要考虑的隔离级别,前者不加锁限制,后者相当于单线程执行,效率太差。

读提交解决了脏读问题,行锁解决了并发更新的问题。并且 MySQL 在可重复读级别解决了幻读问题,是通过行锁和间隙锁的组合 Next-Key 锁实现的。

MVCC

多版本并发控制:它的实现原理主要是版本链,undo日志 ,Read View 来实现的

mvcc只在 rc rr

从以上的描述中我们可以看出来,所谓的MVCC指的就是在使用READ COMMITTD、REPEATABLE READ这两种隔离级别的事务在执行普通的SEELCT操作时访问记录的版本链的过程,这样子可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。

版本链

除了数据 以外分别是db_trx_id、db_roll_pointer、db_row_id。

对该记录每次更新后,都会将旧值放到一条undo日志中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被roll_pointer属性连接成一个链表,我们把这个链表称之为版本链,版本链的头节点就是当前记录最新的值。另外,每个版本中还包含生成该版本时对应的事务id,这个信息很重要,在根据ReadView判断版本可见性的时候会用到。

慢查询

看看查询条件有没有命中索引,是不是加载了不需要的数据列,分析语句执行计划,如果只是因为数据量太大,就可以考虑横向或者纵向分表

分库分表

shardingSphere和shardingProxy

mysql主从同步

MySQL 主从复制模式:

异步模式(mysql async-mode)

全同步模式

半同步

MyISAM 和 InnoDB的区别

MyISAM:

  • 不支持事务,但是每次查询都是原子的

  • 表级锁,维护了一个总行数;

  • MyISAM表有三个文件:索引文件、表结构文件、数据文件;

  • 采用非聚簇索引,索引文件的数据域存储指向数据文件指针。辅索引与主索引基本一致,但是副索引不用保证唯一性。

InnoDB:

  • 支持ACID的事务,支持事务的四种隔离级别;

  • 支持行级锁外键约束:因此可以支持写并发;

  • 不存储总行数;

  • InnoDB引擎存储在一个文件空间(共享表空间,表大小不受操作系统控制,一个表可能分布在多个文件里),也有可能为多个(设置独立表空间,表的大小受操作系统文件大小限制,一般为2G)受操作系统文件大小的限制;

  • 主键索引采用聚集索引(索引的数据域存储数据文件本身),辅索引的数据域存储主键的值;因此从辅索引查找数据,需要通过辅索引找到主键值,再访问辅索引;最好用主键自增,防止插入数据时,为维持B+树结构而调整

  • MyISAM 存储引擎 非聚集索引
    三个文件: 表结构.frm 存储引擎数据.MYD 存储引擎索引.MYI 需要回表

  • Innodb 存储引擎 聚集索引
    两个文件: 表结构.frm 存储引擎数据,索引 .ibd

  • innodb引擎的4大特性
    插入缓冲(insert buffer),二次写(double write),
    自适应哈希索引(ahi),预读(read ahead)

索引类型对性能影响

普通索引:允许被索引的数据列包含重复的值。

唯一索引:可以保证数据记录的唯一性

主键:是一种特殊的唯一索引,在一张表中只能定义一个主键索引,主键用于唯一标识一条记录,使用关键字primarykey来矿建

联合索引:索引可以覆盖多个数据列,就是多个列

全文索引:通过建立倒排索引,可以极大的提升检索效率,解决判断字段是否包含的问题,是目前搜索引擎使用的一种关键技术。可以通过ALTER TABLE tablename add fulltext,创建全文索引,但是比较鸡肋,现在都用ElastictSearch

索引可以极大的提高数据的查询速度。

通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。

但是会降低插入,删除,更新表的速度,因为在执行这些写操作时,还要操作索引文件

索引需要占用物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大,如果非聚簇索引很多,一旦聚集索引改变,那么所有非聚簇索引都会跟着变。

数据结构与算法

排序本质就是进步的方式,进步就是无序到有序,有三种:加一减一法,分治策略,哈希函数(将无需映射成有序)

插入排序:
for(int i = 1; i < A.length; i++) {
    // 将A[i] 插入在卡片0,到卡片i之间
    // j代表抓到的牌,先放到最右侧,不断交换到对应的位置

    int c = A[i];
    int j = i;

    for(; j > 0 && A[j-1] > c;j--) {
        A[j] = A[j-1];
    }
    A[j] = c;
选择排序:从后往前遍历可以保证稳定性
for(int i = A.length - 1; i >= 0; i--) {
    
    // 0 - A[i]
    int j = maxIndex(A, 0, i+1);
    Helper.swap(A, i, j);
}
//不稳定 选择排序比插入排序写少
static private int maxIndex(int[] A, int i, int j) {
    int max = Integer.MIN_VALUE;

    int maxIndex = j-1;
    for(int k = i; k < j; k++) {
        if(max < A[k]) {
            max = A[k];
            maxIndex = k;
        }
    }
    return maxIndex;
}
//稳定
static private int maxIndex(int[] A, int i, int j) {
    int max = Integer.MIN_VALUE;

    int maxIndex = j-1;
    for(int k = j-1; k >= i; k--) {
        if(max < A[k]) {
            max = A[k];
            maxIndex = k;
        }
    }
    return maxIndex;
}

递归不终止StackOverflow(在栈上不停的push元素)

内存读取cpu缓存很快 ,写不能只写缓存

分治策略(Divide And Conquer):
 public void sort(int[] A) {
     mergeSort(A, 0, A.length);
 }
private void mergeSort(int[] A, int l, int r) {
    if(r - l <= 1) {
        return;
    }
    int mid = (l+r+1)/2;
    mergeSort(A, l, mid);
    mergeSort(A, mid, r);
    merge(A, l, mid, r);
}
private void merge(int[] A, int l, int mid, int r) {
    int[] B = Arrays.copyOfRange(A, l, mid+1);
    int[] C = Arrays.copyOfRange(A, mid, r+1);
    B[B.length-1] = C[C.length - 1] = Integer.MAX_VALUE;
    int i = 0, j = 0;
    for(int k = l; k < r; k++) {
        if(B[i] < C[j]) {
            A[k] = B[i++];
        } else {
            A[k] = C[j++];
        }
    }

合并排序设置哨兵

copyOfRange超出会补零

快速排序归纳:

流式编程

public List<Integer> sort(List<Integer> A) {
    return this.quickSort(A);
}
private List<Integer> quickSort(List<Integer> A) {
    if(A.size() <= 1) {
        return A;
    }
    // |---left---| x | ---right ---||
    var x = A.get(0);
    var left = A.stream().filter(a -> a < x)
        .collect(toList());
    var mid = A.stream().filter(a -> a == x)
        .collect(toList());
    var right = A.stream().filter(a -> a > x)
        .collect(toList());
    left = quickSort(left);
    right = quickSort(right);
    left.addAll(mid);
    left.addAll(right);
    return left;
}

实现 IMutableSorter

public void sort(int[] A) {
    this.quickSort(A, 0, A.length);
}

private void quickSort(int[] A, int l, int r) {
    if(r - l <= 1) {
        return;
    }
    // 选择最左边的元素构造子问题集合
    // 小于x的放到左边,大于x的放到右边
    // int x = A[l];
    // i代表x的位置
    int i = partition(A, l, r);
    // 省略计算x所在位置i
    // 以及将所有小于x的元素放到左边,大于x元素放到右边的
    quickSort(A, l, i);
    quickSort(A, i+1, r);
}
private int partition(int[] A, int l, int r) {
    int x = A[l];
    int i = l + 1;
    int j = r;
    while(i != j) {
        if(A[i] < x) {
            i++;
        } else {
            Helper.swap(A, i, --j);
        }
    }
    Helper.swap(A, i-1, l);
    return i-1;
}

桶排序要求对数据有了解

Coding:链表的表示?

static class Node<T> {
    Node<T> next = null;
    T data;
    public Node(T data){
        this.data = data;
    }
}
Node<T> head = null;

image-20210502100001112

Coding:增删改查? 相关复杂度?

// O(1) 头插法
    public void insert(T data) {
        var node = new Node<>(data);
        node.next = head;
        head = node;
    }

    // O(n) 查询用断言
    public Node<T> find(Predicate<T> predicate) {
        var p = head;
        while(p != null) {
            if(predicate.test(p.data)) {
                return p;
            }
            p = p.next;
        }
        return null;

    }

    public Integer size(){
        var p = head;
        Integer c = 0;
        while(p != null) { p = p.next; c++; }
        return c;
    }

    // O(n) 
    public void remove(Node<T> node){
        if(head == null) {
            return;
        }

        if(head == node) {
            head = head.next;
            return;
        }

        var slow = head;
        var fast = head.next;

        while(fast != node && fast != null) {
            slow = fast;
            fast = fast.next;
        }
        if(fast != null) {
            slow.next = fast.next;
//            fast.data = null; //删除时可以考虑回收问题,释放资源,data比较大
        }
    }

Coding:合并两个链表的操作? 用哨兵

翻转链表:三指针,递归

实现队列,栈?

Coding:判断链表中是否有环?

快慢指针是否相等

Coding:和CAS Loop结合?

HashTable的原理?

存取映射的数据结构

说一个Hash函数? 取余

Java的HashMap怎么实现的?

除了哈希表还有那些适合存Key/Value的数据结构?

ConcurrentHashMap是怎么回事?什么时候用?

两次hash

JavaObject的HashCode是如何计算的?

集合框架

集合的概念

集合的概念
  • 对象的容器,定义了对多个对象进行操作的常用方法。可实现数组的功能
集合和数组的区别
  • 数组长度固定,集合不固定
  • 数组可以存储基本数据类型和引用数据类型,但是集合只能存储引用数据类型
集合的位置
  • Java.util.*

Collection接口

List和Set接口都实现了Collection接口,Collection接口的体系如下:

image-20210509093854188

Collection特点
  • 代表一组任意类型的对象,无序,无下表,不能重复
Collection方法
返回值类型方法作用及描述
booleanadd(E e) 确保此集合包含指定的元素(可选操作)。
booleanaddAll(Collection<? extends E> c) 将指定集合中的所有元素添加到此集合(可选操作)。
voidclear() 从此集合中删除所有元素(可选操作)。
booleancontains(Object o) 如果此集合包含指定的元素,则返回 true
booleancontainsAll(Collection<?> c) 如果此集合包含指定 集合中的所有元素,则返回true。
booleanequals(Object o) 将指定的对象与此集合进行比较以获得相等性。
inthashCode() 返回此集合的哈希码值。
booleanisEmpty() 如果此集合不包含元素,则返回 true
Iterator<E>iterator() 返回此集合中的元素的迭代器。
default Stream<E>parallelStream() 返回可能并行的 Stream与此集合作为其来源。
booleanremove(Object o) 从该集合中删除指定元素的单个实例(如果存在)(可选操作)。
booleanremoveAll(Collection<?> c) 删除指定集合中包含的所有此集合的元素(可选操作)。
default booleanremoveIf(Predicate<? super E> filter) 删除满足给定谓词的此集合的所有元素。
booleanretainAll(Collection<?> c) 仅保留此集合中包含在指定集合中的元素(可选操作)。
intsize() 返回此集合中的元素数。
default Spliterator<E>spliterator() 创建一个Spliterator在这个集合中的元素。
default Stream<E>stream() 返回以此集合作为源的顺序 Stream
Object[]toArray() 返回一个包含此集合中所有元素的数组。

List接口与实现类

List特点
  • 有序、有下标、可以重复
List方法
  • voidadd(int index, Object o) 在第index位置插入
    booleanaddAll(int index, Collection c) 将指定集合中的所有元素插入到此列表中的指定位置
    Objectget(int index) 返回此列表中指定位置的元素。
    ListsubList(int fromIndex,int toIndex) 返回fromIndex (含)和 toIndex之间的集合元素
    ListIterator<E>listIterator(int index) 从列表中的指定位置开始,返回列表中的元素(按正确顺序)的列表迭代器,既可以从前往后也可以从后往前。
    booleanremove(Object o) 删除指定元素的第一个出现,传index可以删除第几个。
    Eset(int index, E element) 用指定的元素修改。
ArrayList
  • 数组实现,查询快,增删慢
  • 运行效率快,但是线程不安全
  • 当集合中有元素时默认大小10,当集合中没有元素时大小为0,扩容(每次的容量+容量右移一位)1.5
Vector
  • 数组实现,查询快,增删慢
  • 运行效率慢,线程安全
  • 和ArrayList相似 有枚举器
LinkedList
  • 双向链表结构实现,增删快,查询慢 双向链表
  • 有枚举器
LinkedList 和 ArrayList区别

ArrayList必须开辟连续的空间,查询快,增删慢

LinkedList 不需要开辟连续的空间,查询慢,增删快

Set接口与实现类

Set接口特点
  • 无序、无下标、元素不可重复
Set接口方法
  • 全部继承了Collection的方法
HashSet
  • 存储结构:哈希表 (之前是 数组+链表 ,1.8之后 数组+链表+红黑树)就是HashMap只存key
  • 基于Hashcode计算元素存放的位置
  • 当存入元素的哈希码相同时,调用equals进行确认,如结果为true,则拒绝存入
  • 重写hashcode 时 +31,31是一个质数,减少散列冲突、提高执行效率hashcode+31 * i = (i << 5) -i
TreeSet
  • 基于排列顺序实现元素不重复
  • 实现了SortedSet接口,对集合元素自动排序
  • 元素对象的类型必须实现Comparable接口,指定排序规则
  • CompareTo检测是否为重复元素
  • 可以重写 CompareTo
  • 用的就是TreeMap的key

Map接口与实现类

Map
  • 存储一对数据(Key-Value)、无序、无下标、键不可重复、值可以重复

  • 遍历方式:

    • 遍历用keyset先获取key,再通过map.get()获取value

    • 还可以使用entrySet() Set<Map.Entry<K,V>> entrys = map.entrySet() 返回此地图中包含的映射的Set视图。

      通过for遍历出所有单个的entry,再entry.getkey()entry.getvalue()

Vput(K key, V value) 将指定的值与该映射中的指定键相关联
Vget(Object key) 返回到指定键所映射的值,或 null如果此映射包含该键的映射。
Set<Map.Entry<K,V>>entrySet() 返回此地图中包含的映射的Set视图。
Collection<V>values() 返回此地图中包含的值的Collection视图。
Set<K>keySet() 返回此地图中包含的键的Set视图。
HashMap
  • 线程不安全,运行效率快,允许null作为key或者value
  • hashmap刚创建table是null,为了节省空间,在第一次插入容量调整为16,加载因子0.75,扩容容量左移一位,目的是减少调整元素的个数
  • 存储结构(数组链表红黑树)
  • 1.8后当每个链表的长度大于8时,并且数组元素个数大于等于64,自动转为红黑树,提高效率
  • 1.8后当链表长度小于6时,自动转为链表
  • 1.8以前,链表是头插法,之后是尾插法
TreeMap
  • 存储结构:红黑树
  • 操作时要实现comparable接口
HashTable
  • 线程安全,运行效率慢,不允许null作为key或者value
  • 初始容量11
properties
  • HashTable的子类要求key和value都是String。常用于配置文件的读取

collection常用工具方法

copy 复制 Collection的需要的空间要提前创建好

reserse翻转

shuffle 打乱

list.toArray()

Array.asList()

多线程

进程线程

进程和线程的区别?
  • 进程是程序执行的副本,线程是轻量级的进程,线程是并发(concorrent)的模型,一个进程里可以有很多个线程,线程共享整个进程的资源
为什么要有线程?
  • 线程(Thread)还被称为是轻量级的进程。
为什么要减少线程切换?
  • 因为有可能产生ABA问题
进程开销为什么比线程大?
  • 因为进程有资源,线程只有计算的能力。
JAVA线程有哪些状态?如何转换?
  • 基本状态:

    1. 运行态(Running)
    2. 就绪态(Ready)
    3. 休眠态(Sleeping)。我们通常说的阻塞态(Blocking)是休眠态的一种情况。
  • Java特有状态:

    1. NEW 构造
    2. TERMINATED 完成
    3. RUNAABLE 对应运行态和就绪态
    4. WAITING/TIME_WAITING/BLOCKED 超时等待
  • 保存当前线程的状态(CPU寄存器的值)
    重新执行调度程序(操作系统),重新选择其他线程执行

  • image-20210506162246123

ThreadLocal
  • 提供线程局部变量;一个线程局部变量在多个线程中,分别有独立的值。
  • 特点:简单(开箱即用)、快速(无额外开销)、 安全(线程安全)
  • 场景:多线程场景(资源持有、线程一致性、并发计算、线程安全等)
  • 实现原理:Java中用哈希表实现。
  • 应用范围:几乎所有提供多线程特征的语言。

排队很危险,

  • Quartz的SimpleSemaphore提供资源隔离
Java线程是内核级线程还是用户级线程?
  • 内核级线程由内核调度,用户级线程有应用自己调度。

  • jvm不调度,只维护

  • 在之前是用户级线程,后来jdk1为内核级线程,共享内核级线程分配的主线程。

  • 总结:Java采用用户-内核级线程映射(总体来说是内核级线程)

多线程和原子操作

竞争条件是怎么产生的?解决竞争条件有哪些方法?
  1. 两个线程发生竞争的区域(访问共享资源i )
  • 减少竞争
  • 实现原子操作
  • TAS指令
  • 互斥
原子操作是如何实现的?
  • CAS(Compare And Swap 或者 Compare And Set)
  • image-20210506191620817
  • cas操作:比较现在expectedValue和之前expectedValue的值是否相等,如果是,更新目标值
  • 作用:设置一个地址的值
  • 约束:要求指令的使用方必须知道这个地方原来的值

image-20210506191755861

Java的CAS是如何实现的?
  • 底层类Unsafe jdk.internal.misc.Unsafe 给cpu指令
CAS的ABA问题是怎么回事?
  • 线程1 cas(&A, A, B)
  • 线程2 cas(&A, A, B)cas(&B, B, A)
AtomicInterger类是如何实现的?**
  • 用AtomicInterger不会发生竞争条件

  • get set 都是原子的

  • 但是自增调用Unsafe类的cas

AtomicStampedReference解决什么问题?内部如何实现?
  • 解决了ABA问题

  • 版本号int stemp

同步器

什么是同步?
  • 执行同步
    • 多个参与方(多线程、进程)
    • 汇合(Join)
      • 例如:互相等待
    • 执行后续操作
  • 数据同步
    • 多份数据保持数据一致
      • 缓存和存储的同步
      • 不同机房订单数据的同步
简述下Java有哪些同步器?

基于Monitor JVM的实现

  • synchronized

基于AQS设计

  • Reentrant Lock
  • Read/Write Lock
  • CyclicBarrier
  • Semaphore
  • CountDownLatch
synchronized是如何实现的?
  • 依赖于Monitor JVM的实现
ReentrantLock和synchronized的区别?
  • synchronized完全支持Blocking算法,ReentrantLock支持Non-Blocking(可以tryLock) + 支持Timeout
  • ReentrantLock比较灵活,异常处理比较快,提供了中断能力,第一时间收到中断
  • synchronized 用 等待wait( )唤醒notify( ) , ReentrantLock 等待await( ) 唤醒 signal( )。
为什么需要AbstractQueuedSynchronizer?
  • Java提供的同步器开发框架
  • 需要一个框架来帮助程序员开发同步器,省的程序员在底层上浪费时间,降低心智负担
偏向锁、轻量级锁、重量级锁是怎么回事?
  • 轻量级锁用java抢占资源,重量级锁用操作系统抢占资源(开销更高)自旋锁很快,spinLock。

  • 没有资源竞争偏向锁,发生竞争就升级为轻量级锁,轻量级锁拿不到持有者权限就要升级为重量级锁。

  • 当设置偏向锁后,当资源没有持有者,可以跳过EntrySet直接参与竞争。

屏幕截图 2021-05-07 153333

Java实现生产者消费者问题

Monitor 提供wait,提供notify 所有的Object都有

AbstractQueuedSynchronizer提供await,signalAll。

// Producer 生产者
public void readDb() throws InterruptedException {
    lock.lock();
    if (queue.size() == MAX) {
        full.await();
        return;
    }
    var data = readData(); // 1s
    if(queue.size() == 1) {
        emtpy.signalAll();
    }
    queue.add(data);
    lock.unlock();
}

// Comsumer 消费者
public void calculate() throws InterruptedException {

    lock.lock();
    if (queue.size() == 0) {
        emtpy.await();
        return;
    }
    var data = queue.remove();
    System.out.println("queue-size:" + queue.size());
    if(queue.size() == MAX - 1) {
        full.signalAll();
    }

    data *= 100;
    lock.unlock();
}

AQS(AbstractQueuedSynchronizer)

区别于synchronized(build-in or intrinsic lock), Java提供的另一个实现同步的体系

半壁江山synchronized 另一半AQS

AbstractQueuedSynchronizer可以用来做什么?
  • 中断(InterruptedException)
  • tryLock(int a ,TimeUnit) 超时获取
  • tryAcquire 非阻塞获取锁
  • acquire 和 release 阻塞获取锁,释放锁
  • Condition 条件变量封装,条件变量是os提出的,条件变量的本质也是原子操作
  • 封装CAS操作
    • acquire/release都基于cas状态
    • cas失败触发在等待队列中等待
  • 内部封装高性能CLH队列(让进入队列成本变低)
    • CAS失败自动进入休眠队列
    • 条件等待自动进入队列
简述AbstractQueuedSynchronizer如何工作?
  • 在一个用户获取锁
  • image-20210508191929627
手写程序:如何用AbstractQueuedSynchronizer实现Mutex(互斥量)?
  • 继承AQS重写tryAcquire和tryRelease 用cas()

  • 线程调用时计算前上锁,退出后解锁

  • public class Mutex extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            return compareAndSetState(0,1);
        }
        @Override
        protected boolean tryRelease(int arg) {
            return compareAndSetState(1,0);
        }
    
        public static int i = 0;
        public static void main(String[] argv) throws InterruptedException {
            var mutex = new Mutex();
            Thread t1 = new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    mutex.acquire(0);
                    i ++;
                    mutex.release(0);
                }
            });t1.start();
            Thread t2 = new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    mutex.acquire(0);
                    i ++;
                    mutex.release(0);
                }
            });t2.start();
            t1.join();
            t2.join();
            System.out.println(i);
        }
    }
    
AQS如何实现公平性?
  • ReentrantLock(true)中公平锁实现了hasQueuedThreads()看有没有进程在排队,有排队的直接进入休眠队列。
CAS在AQS中的作用是什么?
  • 在进入队列时保证原子操作。
  • 为tryAcquire提供计算能力。
AQS内部的CHL算法工作原理是什么?
  • 实现一个性能很高的队列
  • 尾插头删

JVM

栈和堆的区别:

栈的作用:配合程序执行,函数调用用的,提供执行程序的必须内存空间,栈的数据结构像一个数组,因为是一个连续的数据结构,栈的内存分配是指针从高为到低位,从小地址向大地址增长

栈配合程序执行用的,配合程序方法调用,存储程序的临时数据 ,也可以存对象

每一个线程都有一个栈,因为线程是程序执行的最小单位。线程可以没有堆

堆的作用:用来存储数据,通过堆来存它不是一种数据结构,是一堆数据结构。堆通过引用来引用这些结构。从小地址到大地址增长,什么都有,像一个垃圾堆。

应用通过堆存储数据(申请,回收,托管)

image-20210526203940712

JVM的内存布局:

这是从Object的角度 hotspotimage-20210527091249757

从线程的角度image-20210527092007800

整体的架构
image-20210527092137751

JIT编译器一边执行程序,一边编译。GC执行引擎不只是垃圾回收器还是内存分配工具,来管理堆

ClassLoader将.class文件导入JVM,形成运行时的类
执行引擎执行bytecode和处理GC
执行引擎调用本地库接口,执行本地方法
本地方法执行时使用Native Stack(可能形成Native Memory)

JVM中Object都有哪些数据:

类的信息,常量,编译后的代码

JVM的运行时数据都有哪些:

Heap MethodArea Stack NativeStack ProgramCounter NativeMemory

为什么要GC?

应用在不断的弄乱空间,GC就负责整理

什么是STW?

Stop The World ,当GC忙不过来就要STW

如何提高吞吐量(Throughput)?

吞吐量: 程序执行时间的占比

-XX: GCTimeRatio=99 99:1 1%
-XX: GCTimeRatio=19 19:1 5%

  • 给更大的内存(Heap+++) -Xmx4G 最大4G Heap
  • 更改GC算法
高吞吐量、低延迟和低FootPrint,可以兼得吗? 谈谈你的理解?

低延时 Latency : 指GC造成的停顿(STW)时间

Pause Time: 一次STW的时间

FootPrint :指最终应用对内存的需求 当内存满了需要:STW回收。

引用计数法有什么问题?

循环引用

静态的 并发就不行了

为什么需要3色标记而不是双色?

双色,如果在标记和清除的中间有改动,会导致回收多了,所以这种状况就需要重新标记之后才能清楚。这时C为黑色,D,E会被清除。

image-20210527161859385

3色标记算法如何处理变化(mutation)?

当有一个不确定是否能清除时,要先进行标记,标记到最后,而且没有变化时,就可以清除了。然后GC需要遍历JavaHeap中所有的对象清除没有被标记的对象。

image-20210527162039646

Java堆分成哪些部分image-20210527164753200
新生代、存活代、老生代、永久代(元空间)是什么?如何工作的?

image-20210527165333878

新生代发生minorGC 有eden和 survivor from (s1) to (s2) 老年代发生fullGC(标记-清除),永久代(元空间)在方法区中。

什么时候发生复制和整理?

在新生代中 eden到survivor发生复制和整理

G1/CMS/Serial/Parallel/ParNew/ZGC的区别?

G1:目标:大内存,兼顾:Latency、Throughput,替代:CMS(尚未、部分)

ZGC:低延迟的GC 最大延迟时间几个ms 暂停时间不会随着堆大小、存活对象数目增加 内存范围8MB~16TB 太大容易挂

CMS(Concurrent Mark Sweep) :减少Pause Time,覆盖Serial/Parallel Collector的场景 ,需要减少 pause Time的场景

Parallel(并行):多线程, 提供最大的Throughput

Serial(连续的):Single Thread ,也能提供最大的Throughput

ParNew :多线程

如果对延迟要求高可以考虑哪种GC?

ZGC

内存空间很大的情况(TB)级别推荐哪种GC?

G1

类加载器有哪些

根加载器 BootStrapClassLoader

拓展加载器 ExtensionsClassLoader

程序加载器 ApplicationClassLoader

用户加载器 CustomClassLoader

说一下Object在内存中的格式?

有严格定义
不同虚拟机可能会不同
分成头部和数据,可能还有Padding
image-20210528145553265

mark word

hashcode age 标志位 LockRecordAddress MonitorAddress ForwardingAddress

klassOop: 指向Object的元数据

padding:对齐, Hotspot:8字节对齐

说一下初始化一个类的过程?

类的生命周期大致分为以下阶段:

load
类加载 disk -> memory ClassLoader
static initializers 初始化 static fields {}
触发原因:new/访问静态成员/loader加载等
加载完毕 loaded

create
allocate memory 分配内存
constructor 构造器
created 创建完毕就

live
in use Root Set可以找到(且被使用)
invisible 泄露,Root Set可以找到,但是没有使用
unreachable Root Set不可达(会被回收)

destroy/gc
collected 要被收集
finalize 终节资源
deallocated 回收完

空的Object多大?

肯定是8的倍数 64个字节(8bit)才能存的下,最少16字节

Class Header里都有什么?

markword

klassoop

arraylength

什么是双亲委派模型?

image-20210528180504240

说说双亲委派的过程?

image-20210528180507844

只有一个ClassLoader为什么不够用?
  • 需求不同
  • 可能存在版本问题 ,名字一样
  • 安全和缓存
  • MetaProgramming
如何打破双亲委派模型?

重写ClassLoader.loadClass()

Linux

usr unix system resouce

var variable data file

pwd 打印当前路径

more 打印一部分

less 打印一部分 有个界面

tail 尾部一行

head 头部一行

man 查看命令

cat 查看所有

tac 倒着查看所有

grep 加限定条件

find 查找

wc -l 查看文件数量

ip addr 可以查看IP地址

netstat 显示网络状态

host 查看dns

curl 开发联调工具

‘>’ 重定向

TCP/IP协议

互联网协议群:

  • 应用层
  • 传输层
  • 网络层
  • 链接层
  • 物理层

发送消息如何确认收到?

握手:

image-20210521170015876

挥手:

image-20210521170233302

Socket、IO模型、NIO

Socket的工作原理
  • socket是一种管道文件image-20210521175208328
  • Server Socket File中是Client Sockets’ 的FD
    • listen(80)
    • clientsocket = serversocket.accept()
  • Client Socket中是数据
I/O多路复用
BIO/NIO/AIO
N(new)IO

NoSQL四大分类

键值对存储:

  • 新浪:Redis
  • 美团:Redis + Tair
  • 阿里、百度:Redis + memecache

文档型数据库 (Bson)

传输形式,二进制的json

  • MongoDB
    • MongoDB是一个基于分布式文件存储的数据库,C++编写,主要用来处理大量的文档
    • MongoDB是一个介于关系型数据库和非关系型数据库中间的产品!MongoDB是非关系型数据库中功能最丰富,最像关系型数据库的!
  • ConthDB

列存储数据库

  • HBase
  • 分布式文件系统

图形数据库

  • 用来存关系的,类似朋友圈
  • Neo4j
  • InfoGrid

redis简介:

redis是一个开源的(BSD许可),内存中的数据结构存储系统,它可以作用数据库、缓存和消息中间件MQ。它支持多种类型的数据结构,如字符串,散列,列表,集合,有序集合与查询范围,bitmaps,hyperloglogs 和 地理空间索引半径查询。Redis内置了复制,LUA脚本,LRU驱动事件,事务和不同级别的磁盘持久化,并通过Redis哨兵和自动区分提供高可用性。

redis常用操作

移动键

move key db

启动Redis

redis-server [–port 6379]

连接Redis

./redis-cli [-h 127.0.0.1 -p 6379]

停止Redis

redis-cli shutdown || kill redis-pid

发送命令

给Redis发送命令有两种方式:

1、redis-cli带参数运行,如:

redis-cli shutdown

这样默认是发送到本地的6379端口。

2、redis-cli不带参数运行,如:

127.0.0.1:6379> shutdown

测试连通性

127.0.0.1:6379> ping

PONG

获取所有键

语法:keys pattern(*)

获取键总数

语法:dbsize

查询键是否存在

语法:exists key [key …]

删除键

语法:del key [key …]

查询键类型

语法: type key

移动键

语法:move key db

查询key的生命周期(秒)

秒语法:ttl key
毫秒语法:pttl key
-1:永远不过期。

设置过期时间

秒语法:expire key seconds
毫秒语法:pexpire key milliseconds

设置永不过期

语法:persist key

更改键名称

语法:rename key newkey

redis基本数据类型:

String (字符串)

存放键值

语法:set key value [EX seconds] [PX milliseconds] [NX|XX]

获取键值

语法:get key

增长字符串

语法:append key “a”

值递增/递减

如果字符串中的值是数字类型的,可以使用incr命令每次递增,不是数字类型则报错。

语法:incr key incrby key num 自动涨num个
语法:decr key decrby key num 自动减num个

获取部分字符

语法:getrange key start end

替换部分字符

语法:setrange key start “”

设置过期时间

setex key secound “a”

不存在再设置(分布式锁会用到)

setnx key " " (原子操作)

批量操作

mset k v k v k v

组合操作

getset key “value” 不是原子的

List(列表)

列表类型是一个有序的字段串列表,内部是使用双向链表实现,所有可以向两端操作元素,获取两端的数据速度快,通过索引到具体的行数比较慢。
列表类型的元素是有序且可以重复的。

存储值

左端存值语法:lpush key value [value …]

右端存值语法:rpush key value [value …]

索引存值语法:lset key index value

弹出元素

左端弹出语法:lpop key

右端弹出语法:rpop key

获取元素个数

语法:llen key

获取列表元素

两边获取语法:lrange key start stop

索引获取语法:lindex key index

删除元素

根据值删除语法:lrem key count value

范围删除语法:ltrim key start stop

移除最后一个元素,添加到头部

rpoplpush list list

插入值

linsert list before/after “value” “new value”

集合

没有重复的值

存储值

语法:sadd key member [member …]

弹出元素

语法:spop key [count]

获取元素

获取所有元素语法:smembers key

随机获取语法:srandmember key count

判断集合是否存在元素

语法:sismember key member

获取集合元素个数

语法:scard key

删除集合元素

语法:srem key member [member …]

移动集合元素

语法:smove set1 set2 value

不同集合元素

语法:sdiff key1 key2 (差集)

相同集合元素

语法:sinter key1 key2 (交集)

公共集合元素

语法:sunion key1 key2 (并集)

Hash (哈希)

Map集合,key—map
redis字符串类型键和值是字典结构形式,这里的散列类型其值也可以是字典结构。

存放键值

单个语法:hset key field value

多个语法:hmset key field value [field value …]

不存在时语法:hsetnx key field value

获取字段值

单个语法:hget key field

多个语法:hmget key field [field …]

获取所有键与值语法:hgetall key

获取所有字段语法:hkeys key

获取所有值语法:hvals key

判断字段是否存在

语法:hexists key field

获取字段数量

语法:hlen key

递增/减

语法:hincrby key field increment

删除字段

语法:hdel key field [field …]

有序集合(zset)

在set的基础上,增加了一个值,

存储值

语法:zadd key [NX|XX] [CH] [INCR] score member [score member …]

获取元素分数

语法:zscore key member

获取排序范围排名

语法:zrange key start stop [WITHSCORES]

获取指定分数范围排序

语法:zrangebyscore key min max [WITHSCORES] [LIMIT offset count]

增加指定元素分数

语法:zincrby key increment member

获取集合元素个数

语法:zcard key

获取指定范围分数个数

语法:zcount key min max

删除指定元素

语法:zrem key member [member …]

获取元素排名

语法:zrank key member

geospatial 地理位置

Hyperloglog 计数

Bitmaps 位存储

reids的事务:

Redis事务没有隔离级别的概念!
命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行!Exec
Redis单条命令式保存原子性的,但是事务不保证原子性!

  • 开启事务(multi)

  • 命令入队( … )

  • 执行事务( exec )

  • 正常执行事务!

multi
set k1 v1
set k1 v1
get k2
set k3 v3
exec
>ok
>ok
>v2
>ok
  • 放弃事务用 discard

  • 编译异常

所有的命令都不会被执行

  • 运行时异常

有错误的命令依旧可以正常运行

redis加锁

悲观锁:

乐观锁:

Mysql用version

redis用Watch

redis主从复制

主要作用:

  • 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
  • 故障恢复:当主节点出现问题式,从节点可以继续提供服务,实现快速的故障恢复;实际是一种服务的冗余
  • 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点负责读服务,分担服务器负载;尤其是在写少都多的情况,通过多个节点分担读,可以提高Redis服务器的并发量。
  • 高可用:除了上述的作用以外,主从复制还是哨兵和集群实施的基础,所以是高可用的基础

Spring

面试题

IOC AOP

“工厂模式”(DI)和“代理模式”(AOP)

1. Spring

Spring是一个免费的开源的框架;

Spring是一个轻量级的,非入侵的框架;

控制反转(IOC), 面向切面(AOP);

maven 导入jar

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.0</version>
</dependency>

延申

Spring Boot (build anything) 构建一切 快速开发单个微服务 约定大于配置;

Spring Cloud (coordinate anything) 协调一切 Spring Cloud 基于 Spring Boot实现;

Spring Cloud Data connect Flow 连接;

  1. IOC原理推理
以前的方法:
  1. Usermapper 接口;
  2. UserDaoImpl 实现类;
  3. Uservice 业务接口;
  4. UserServiceImpl 业务实现类;
现在的方法:
private UserDao userDao;

    //利用set方法进行动态实现值的注入
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

优点:
  • 之前的程序是主动创建对象,控制权在程序员手上;
  • 使用了set方法注入,程序不再有主动性,客制化接收对象
  • 这种思想让程序员不在主动创建对象了,被动接收
IOC本质

控制反转IOC(Inversion of Control), 是一种设计思想,DI依赖注入 是通过实现IOC的一种方法, 也有人认为DI是IOC的另一种说法.

没有IOC的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象转移给第三方,反转在于对象获取的方式。

采用xml方式配置Bean的时候,Bean的定义信息和实现是分离的,当采用注解方式是就可以把两者合到一起,Bean的定义信息直接以注释的形式定义到实现类中,从而达到了0配置的实现。

控制反转是一种通过描述(xml或注解)并通过第三方生产或获取特定对象的方式。 在Spring中控制反转实现的是IOC容器, 实现方法是依赖注入(Dependency Injection, DI)

3. IOC的创建方式
3.1. 使用无参构造创建对象,默认!
3.2.假设我们要使用有参构造创建对象
  • 下标赋值
<bean id="user" class="com.kuang.pojo.User">
        <constructor-arg index="0" value="zhaodi000"></constructor-arg>
    </bean>
  • 类型赋值

    <bean id="user" class="com.kuang.pojo.User">
            <constructor-arg type="java.lang.String" value="zhaodi2222"></constructor-arg>
        </bean>
    
  • 参数名直接赋值

    <!-- 参数名直接赋值   -->
        <bean id="user" class="com.kuang.pojo.User">
            <constructor-arg name="name" value="zhaodi33333"></constructor-arg>
        </bean>
    
    
  • 在配置文件加载时,容器中管理的对象就已经初始化了

4. 配置文件
4.1.别名
<alias name="以前的名字" alias="别名"></alias>
4.2.Bean的配置
<bean id="类名" class="路径" name="别名 可以是多种符号分隔">
	<property name="要注入的属性" value="传入的数据"></property>
</bean>
4.3. import

​ 将多个bean.xml 文件导入到一个总的ApplicattionContext.cml

    <import resource="bean2.xml"></import>
    <import resource="bean3.xml"></import>
    <import resource="beans.xml"></import>

内容不同根据所需类型自行创建

5. DI依赖注入
5.1 构造器注入
5.2 set注入
  • 依赖注入:set

    • 依赖: bean对象创建需要依赖容器;
    • 注入: bean对象中的所有属性由容器来注入;
  • 构造器注入

  • 其他注入

    c 命名

    p命名

  • bean作用域

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gSwrC61y-1625028583220)(…/…/…/…/25659/AppData/Roaming/Typora/typora-user-images/image-20201107092043519.png)]

    <bean id="accountService" class="com.something.DefaultAccountService"/>
    <!-- 单例模式 使用时只创建一个 spring 默认单例模式 -->
    <bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
    
    <!-- 原型模式 每次调用创建一个新的对象 -->
    <bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
    
6. Bean的自动装配
  • 自动装配是Spring满足bean依赖的一种方式
  • Spring会在上下文中找,并自动给bean装填

要使用约束

<!--注解支持-->
<context:annotation-config/>

在spring中有三种装配方式

  1. 在xml中显示的配置
  2. 在java中显示的配置
  3. 隐式的自动装配bean
6.1 byName自动装配
  • 容器会自动查找与bean id 相同的进行注入;
<bean id="people" class="com.kuang.pojo.People" autowire="byName">
    <property name="name" value="赵迪"></property>
</bean>
6.2 byType
  • 容器会自动查找与bean 相同属性的类型注入
  • 需要保证只创建一个时可以使用
<bean id="cat1" class="com.kuang.pojo.Cat"></bean>
<bean class="com.kuang.pojo.Dog"></bean>
<bean id="people" class="com.kuang.pojo.People" autowire="byType">
    <property name="name" value="赵迪"></property>
</bean>

小结:

  • byname时,需要保证所有的bean 的 id 唯一,并且这个bean需要和自动注入的属性的set方法一致;
  • bytype时, 需要bean的class唯一,并且这个bean需要和自动注入的属性类型一致,并且只能被容器创建一次;
6.3使用注解自动装配
  1. 如果@Autowired自动装配的环境比较复杂,自动装配无法通过一个注解完成时,我们可以使用@Qualifiler(value=”xxx“)去配置@Autowired使用,指定一个唯一的bean对象注入;
  2. @Nullable 使用这个注解可以让字段可以为null

@Resourse和@Autowired的区别:

  • @Autowired通过bytype方式实现,而且对象必须要存在,否则空指针可以手动添加(@Nullable);

  • @Resourse默认通过byname方式实现,如果找不到,通过bytype方式实现 ,当两种方式都不行时,会报错;

  • @Resourse默认通过byname方式实现,@Autowired通过bytype方式实现!

7. 使用注解开发

在spring4 之后使用注解开发,必须保证aop的包被导入

bean
属性注入
  1. //等价于    <bean id="name" class="com.kuang.pojo.User"/>
    @Component
    public class User {
    
        //相当于<property name="name" value="赵迪"></property>
        //当一个属性有set方法时也可以作用到set方法上
        @Value("赵迪")
        public String name ;
    }
    
衍生注解
  • @Component有几个衍申注解,在web开发中,会按照mvc三层架构分层!
    • ​ dao ---------@Repository
    • service--------@Service
    • controller----@Controller
自动装配域
@Autowired: 通过自动装配类型-名字
Autowired不能唯一自动装配需要,@Qualifiler(value=”xxx“)去配置;
@Nullable: 使用这个注解可以让字段可以为null
@Resourse:通过自动装配名字-类型
作用域

scrop(“sigleton”)||scrop(“prototype”)

小结

xml与注解:

  • xml更万能,适用于任何场合,维护方便
  • 注解 不是自己的类无法使用,维护发杂

xml与注解的最佳实践:

  • xml用来管理bean;

  • 注解只负责完成属性注入;

  • 我们在使用的过程中,只需要注意让注解生效

        <!--注解支持-->
        <context:annotation-config/>
    <!--    包的扫描机制-->
        <context:component-scan base-package="com.kuang"/>
    
8. aop

设计模式原则总结

  • 单一职责原则

    一个类只应该做一件事,职责要单一,一个类的职责过多,就会产生职责耦合,当一个职责发生变化的时候,其他的职责也会收到影响。

  • 开闭原则

    软件实体应该开发扩展,关闭修改,对拓展是开放的,对更改是关闭的。

    我们应该面向接口/抽象编程,在增加这个系统需求功能的时候,应该先想着扩展一个抽象的子类,而不是直接修改以前的代码。

  • 里氏替换原则

    凡是父类能够使用到父类的地方,将其替换成子类,程序也能正常运行,结果不会发生改变。

  • 依赖倒置原则

    高层模块不应该依赖于底层模块,两个模块都应该依赖于抽象类,细节也依赖于抽象,针对接口/抽象编程,不需要对具体的细节实现。

  • 接口隔离原则

    使用多个专一的接口,而不使用统一的接口,应该将一个接口分成多个专一的接口,从而将职责划分出来,降低耦合。

  • 合成/聚合复用原则

    尽量使用合成/聚合的方式来复用代码,而不推荐使用继承来复用代码。这样可以避免由于类的继承带来臃肿的功能。

  • 迪米特法则

    尽量与朋友通信,不与陌生人通信。朋友与朋友之间要保持距离。

单例模式:

饿汉式:

在类加载就创建一个静态的对象供应外部对象使用,重启系统才会使对象改变,所以本身就是线程安全。但是可以通过反射来破解构造方法,产生多个实例。

懒汉式:

懒汉式属于延迟加载,当第一次使用时才会进行实例化,但是在多线程下会产生多个对象。

通过加synchronized来保证线程安全,但是每次访问都上锁,降低效率。

双检锁:

当为空时,进行synchronized上锁实例化,当对象不为空时,直接返回对象。

枚举法:

枚举实例本身就是static final类型的,所以就只能被实例化一次。枚举本身就是线程安全的。枚举也提供了序列化的机制,其本身能够阻止默认的反序列化。

单例模式三大特点:
  • 线程安全
  • 延迟加载
  • 序列化与反序列化安全

工厂模式:

简单工厂:
模式缺点:

不符合开闭原则,当这个类无法工作时,其他功能也会有影响,系统扩展困难,要添加新的功能就要修改工厂逻辑,在功能较多的时候,可能会导致逻辑太复杂,简单工厂使用的静态工厂方法,使得工厂角色无法实现继承的等级结构。

适用场景:

工厂类负责创建的功能较少,客户端只知道传入工厂的参数,对创建对象细节不关心。

工厂方法:

对简单工厂抽象出一个抽象工厂,保留了简单工厂的优点,让扩展变得简单,让继承变得可行,增加了多态性

优点:

更符合开闭原则,新增一种产品,只需要增加相应的具体产品和相应的工厂,更符合单一职责原则,不适用静态工厂方法,可以形成基于继承的等级结构

缺点:

对系统开销大一些,添加产品时,开发新产品对工厂类还是要修改,一个具体的工厂只能创建一种具体的产品

抽象工厂:
优点:

可以在类的内部对产品簇进行管理,满足开闭原则

缺点:

增加新产品时,所有的工厂都要修改

总结:

无论是什么工厂模式,最终的目的就是为了解耦。只要解耦的目的达到了,就可以了

观察者模式:

优点:

降低了目标与观察者之间的耦合关系,两者之间是抽象耦合的关系,目标与观察者建立了触发机制,需要有一个通知方法

测略模式:

抽象策略,实现抽象策略,用一个上下文进行引用

适配器模式:

类适配器:一个类继承一个方法调用一个接口,则可以使用父类方法,调用接口方法

对象适配器:通过构造注入方式 ,不通过继承,通过注入的方式,实现解耦

优点:

复用已经有的类,将目标类和适配者类解耦,解决目标和适配者接口不一致问题 InputStream=>Reader用InputStreamReader

装饰模式:

动态的将功能加到对象上,比继承弹性更好。

在Java中装饰者应用于Java IO标准库

模板方法模式:

抽象类使用一个模板方法将若干个(抽象,具体,钩子)方法构成。

优点:

提供代码可用性,将相同的代码抽象到父类中,将不同的代码放入不同的子类。实现了反向控制用钩子方法,比较灵活,但是灵活的同时,由于子类影响了父类,违背了里氏替换原则,会给程序带来风险。这样对抽象类就要有更完善的设计。

缺点:

引入抽象类,每一个不同的类要具体实现,都要导致类的个数增加,从而增加复杂度。

静态代理:

优点:

不破坏源代码,不影响原来的功能

缺点:

当功能类复杂,代理类多时,整个类的结构就显得比较臃肿,难以维护

动态代理:

cglib动态代理和jdk动态代理(需要接口)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值