测试开发面试题

面试问题整理

  • 问题整理
    • 软件测试基础
      • testng有哪些注解
      • xml配置
      • @Test属性如下
      • 登陆页面怎么测试
      • 说一下工作中的测试流程
      • 测试用例的分类
      • 黑盒测试方法
      • 软件测试
      • 登录跳转到主页面,很慢,分析原因
      • 刷抖音出现闪退问题应该怎么测试
    • 性能测试指标
      • QPS:
      • TPS
      • 并发数
      • 吞吐量
    • Java基础
      • 多线程的实现
      • java设计模式-单例模式、工厂设计模式
      • 锁机制
      • Java 异常
      • JavaAOP
      • Java容器
      • Collections并不属于集合,是用来操作集合的工具类。
      • 可变参数
      • Comparable和Comparator区别?
      • HashMap底层实现原理解析
      • 栈和队列的区别
      • Java是一门面向对象的高级语言
      • 类包括哪些内容:
      • 构造器:
      • 面向对象的三大特征:封装、继承、多态
      • this关键字的作用:
      • static
      • 示例方法和静态方法访问机制
      • 继承
      • 继承间的引用
      • 方法重写
      • 继承后-构造器的特点
      • 什么会造成空指针
      • 继承的特点
      • 重写和重载有什么区别
      • 什么会造成死锁、死循环
      • 引用类型
      • 抽象方法
      • 抽象类的使用
      • 抽象类存在的意义有两点:
      • 引入:
      • final关键字
      • 单例模式
      • 枚举类 多例设计模式
      • 多态
      • 、引用类型
      • 内部类
      • 匿名内部类
      • 注意:
      • 一个类要么默认继承了Object类,要么间接继承了Object类。
      • Objects类与Object还是继承关系
      • Date类在Java中代表的是系统当前此刻日期时间对象。
      • Collection是集合的祖宗类
      • Math用于做数学运算。
      • BigDecimal大数据类
      • 可以把基本数据类型的值转换成字符串类型的值。(没啥用)
      • 正则表达式
      • 泛型
      • 集合类
      • 集合的遍历
      • 数据结构种类- Java
      • Map集合
      • Map集合的遍历方式有:3种。
      • LinkedHashMap的特点介绍。
      • Java内存溢出
      • Java反射机制
    • 网络协议
      • TCP/UDP
      • 五种常用的网络协议
      • 一个简单的HTTP请求会经过哪几个阶段
      • 计算机网络osi模型
      • TCP三次握手过程
      • Http和Https
      • DNS协议
      • DNS查询过程:
      • 拆箱与装箱
      • 垃圾回收机制
      • java中用于处理字符串常用的有三个类
    • Mysql(含sql语句)
      • 索引优缺点以及使用
      • sql语句
      • sql时间函数
      • 创建表sql
    • 算法题
      • 输出一个字符串中每个字符出现的次数
      • 冒泡排序
      • 二分查找
      • 回文数判断
      • 输入"hello world java",输出"java world hello"
    • Linnux命令
    • redis
      • redis特性
      • redis缓存穿透
      • 缓存击穿
      • redis雪崩效应

问题整理

软件测试基础

testng有哪些注解

@BeforeSuite:表示此注解的方法会在当前测试集合(Suite)中的任一测试用例开始之前执行。
@AfterSuite:表示此注解的方法会在当前测试集合(Suite)中的所有测试程序运行结束之后执行。
@BeforTest:表示此注解方法会在Test中任一测试用例开始运行前执行。
@AfterTest:表示此注解方法会在Test中所有测试用例运行结束后执行。
@BeforGroups:表示此注解的方法会在分组测试用例的任一测试用例开始前执行。
@AfterGroups:表示此注解的方法会在分组测试用例的所有测试用例运行结束后执行。
@BeforeClass:表示此注解的方法会在当前测试类的任一测试用例开始前执行。
@AfterClass:表示此注解的方法会在当前测试类的所有测试测试用例运行结束后执行。
@BeforeMethod:表示此注解的方法会在每个测试方法开始运行前执行。
@AfterMethod:表示此注解的方法会在每个测试方法运行结束后执行。
@Test:表示此注解的方法会被认为是一个测试方法,即一个测试用例
@Ignore :忽略该类
@Listeners --监听类
@Parameters–使用如下:
@Parameters({ “user” })
@Test
public void testcase1(String user) {
System.out.println(user);
}

xml配置

@Parameters({ “user” })
@Test
public void testcase1(@Optional(“yogouo”) String user) {
System.out.println(user);
}
@DataProvider

@Test属性如下

description属性
enabled属性 为false时该用例不在执行
groups属性 --测试方法进行分组,可在类级别或方法级别添加组,
dependsOnMethods属性
threadPoolSize属性 线程池的内线程的个数
dependsOnGroups属性
invocationCount属性 --用例执行的次数
alwaysRun属性–在添加依赖关系时,加上alwaysRun=“true”的属性,无论依赖是否成功,此方法都会继续执行。
priority 注解(优先级)值越大优先级越低

登陆页面怎么测试

1、功能测试
1、不输入账户信息直接点击登陆按钮是否会有相应的提示
2、输入正确的账户信息点击登陆时是否可以正常登录并成功的跳转到登陆后的页面
3、输入错误的账户信息喝是否有登陆失败的提示
4、用户名太长或太短是否会有相应的提示
5、用户名、密码是否有空格处理
6、密码是否加密显示
7、登陆切换另一账号时是否会清空另一账号信息
8、用户名密码是否大小写敏感
9、输入密码时,大写开启是否有相应提示
10、如果有验证码,验证码图片是否可以正常切换
11、输入用户名后按tap键是否可以切换到密码框,点击回车按钮是否可以登陆
12、如果有记录账号密码功能,下次打开时是否保存用户信息,点击登陆时是否可以正常登陆
13、用户首次登陆时是否有提示修改密码
14、如果验证码具有时效性,需要分别验证时效内和时效外验证码的有效性。
15、首次打开登录页面,默认焦点是否定位在用户名输入框中
16、如果有多种登录方式,各种登陆方式是否相互影响
17、若使用初始密码登录时是否会提示修改密码
性能测试
1、打开登录界面,加载完成需要的时间
2、登陆成功跳转到登陆成功的页面所需要的时间
3、网络延迟、弱网、断网情况下登陆页面的显示与提示
界面测试
1、界面控件布局是否合理,文本框是否对齐
2、文本框的长度和高度布局是否合理,按钮的大小是否易于点击
3、登录界面是否清晰合理美观,无乱码(文字简洁、无错别字)
登录界面是否清晰合理美观,无乱码(文字简洁、无错别字)

说一下工作中的测试流程

1、需求评审–有可能会进行二次需求评审
2、开发进行技术评审–测试确定是否进行接口自动化用例设计s
3、开发开发项目-测试开发接口
4、测试用例评审-之前要提高冒烟用例
5、开发完成后冒烟,提测,测试冒烟验证成功后提测成功
6、测试执行测试用例,测试完成后产品验收
7、产品验收成功后开发部署在预发环境,测试测试- 产品验收(如果涉及到业务方,业务方也要验收)
8、预发环境测试完成后部署到生产环境-测试测试- 产品验收(如果涉及到业务方,业务方也要验收)
9、生产测试完成后部署到公共环境,结束

测试用例的分类

1、横向分类
主要根据功能模块进行划分,一条测试用例都能追溯到一个功能需求
具有类似功能需求的测试用例会放在一起,形成一个功能模块的测试集
2、纵向分类
纵向分类主要根据测试的类型进行分类,主要有以下几种类型
BAT(Build Acceptance Test)
最基本的测试用例,不复杂但很重要
Core(Core Regression Test)
核心功能测试用例,和BAT测试用例很相似,测试用例会比较复杂,一般占整个总数的20%左右
Func
这类测试用例往往是对BAT和Core的补充。BAT和Core执行的主要路径的测试用例,那么分支的测试用例往往都设计在Func里面

黑盒测试方法

等价类、边界值、错误推测、场景法、因果图、正交试验、判定表、功能图

软件测试

按照软件的生命周期划分:单元测试、集成测试、系统测试、回归测试、验收测试。
按照测试关注点划分:功能测试、性能测试、稳定性测试、易用性测试、安全性测试。
按照测试实施者划分:开发方测试(α测试)、用户测试(β测试)、第三方测试。
按照技术/测试用例设计划分:白盒测试、黑盒测试、灰盒测试。
按照分析方法划分:静态测试、动态测试。
按照测试执行方式划分:手工测试、自动化测试。
按照测试对象划分:程序测试、文档测试。

登录跳转到主页面,很慢,分析原因

1、是否加载了大图片,音频等大数据的内容
2、查询数据库是否创建了索引
3、是否有网络原因

刷抖音出现闪退问题应该怎么测试

1.空指针:对象没有初始化就使用了;
2.空函数:有事件源但没有对应的事件对象;
3死循环:没有合理的循环控制流;
4.内存泄露:同一个对象不停地开辟,而且没有释放;
5.内存溢出:程序所消耗的内存大于硬件提供的内存;
6.网络:异步加载时提前调用了数据(现象是在弱网时,根源是空指针);
7.界面UI:像拍照没有附加于ControlView;
8.主线程:需要主线程执行的,放于子线程里执行了。比如:网络电话——网络电话是用C语言写的,在高级语音中要调用中,需要加线程转换。

性能测试指标

QPS:

Queries Per Second意思是“每秒查询率”,是一台服务器每秒能够相应的查询次数,对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。
主要是强调服务器的处理能力

TPS

即 Transactions Per Second 的缩写,每秒处理的事务数目。一个事务是指一个客户机向服务器发送请求然后服务器做出反应的过程。
TPS 的过程包括:客户端请求服务端、服务端内部处理、服务端返回客户端
Qps 基本类似于 Tps,但是不同的是,对于一个页面的一次访问,形成一个 Tps;但一次页面请求,可能产生多次对服务器的请求,服务器对这些请求,就可计入“Qps”之中。
例如,访问一个 Index 页面会请求服务器 3 次,包括一次 html,一次 css,一次 js,那么访问这一个页面就会产生一个“T”,产生三个“Q”。

并发数

并发数是指系统同时能处理的请求数量

吞吐量

指单位时间内系统能处理的请求数量

Java基础

多线程的实现

1、继承Thread类,重写run方法

public class MyThread extends Thread {
    @Override
    public void run(){
        super.run();
        System.out.println("执行子线程...");
    }
}

注:
(1)、结果不代表线程的执行顺序,线程是并发执行的,如果多运行几次,执行顺序可能会不同
(2)、多线程的运行过程中,CPU是以不确定的方式去执行线程的,故运行结果与代码的执行顺序或者调用顺序无关
(3)、调用时应为myThread的start方法,而不是run()方法。调用start()方法是告诉CPU此线程已经准备就绪可以执行,进而系统有时间就会来执行其run()方法。而直接调用run()方法,则不是异步执行,而是等同于调用函数般按顺序同步执行,这就失去了多线程的意义了
2、实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("执行子线程...");
    }
}
//调用方法如下
public class Test {
    public static void main(String[] args) {

        Runnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
        System.out.println("主线程运行结束!");
    }
}

这一步Thread类的作用就是把run()方法包装成线程执行体,然后依然通过start去告诉系统这个线程已经准备好了可以安排执行。
上面的两种方式都有这两个问题:
无法获取子线程的返回值
run方法不可以抛出异常
3、通过Callable和FutureTask创建线程
流程如下:
a、创建Callable接口的实现类 ,并实现Call方法,与run()方法不同的是,call()方法具有返回值
b、使用FutureTask类包装Callable对象,该FutureTask对象封装了Callable对象的Call方法的返回值
c:使用FutureTask对象作为Thread对象的target创建并启动线程
d:调用FutureTask对象的get()来获取子线程执行结束的返回值

public class ThreadTest {

    public static void main(String[] args) {

        Callable<Integer> myCallable = new MyCallable();    // 创建MyCallable对象
        FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask来包装MyCallable对象

        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 30) {
                Thread thread = new Thread(ft);   //FutureTask对象作为Thread对象的target创建新的线程
                thread.start();                      //线程进入到就绪状态
            }
        }

        System.out.println("主线程for循环执行完毕..");

        try {
            int sum = ft.get();            //取得新创建的新线程中的call()方法返回的结果
            System.out.println("sum = " + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}


class MyCallable implements Callable<Integer> {
    private int i = 0;

    // 与run()方法不同的是,call()方法具有返回值
    @Override
    public Integer call() {
        int sum = 0;
        for (; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            sum += i;
        }
        return sum;
    }

}

java设计模式-单例模式、工厂设计模式

Java包含23种设计模式,是一套代码经验的总结,为了提高代码的可读性,可扩展性以及代码的复用性
单例模式-懒汉单例、饿汉单例
工厂方法模式
创建对象的过程不再由当前类实例化,而是由工厂类完成,在工厂类中只需要告知对象类型即可
工厂方法模式分为三种:
1、普通工厂模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建
2、多个工厂方法模式,是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。

锁机制

一种保护机制,在多线程的情况下,保证操作数据的正确性与一致性
悲观锁、乐观锁、独占锁、共享锁、公平锁、非公平锁、分布式锁、自旋锁
再谈谈独占锁、共享锁:

Java 异常

1) java.lang.NullPointerException 空指针异常;出现原因:调用了未经初始化的对象或者是不存在的对象。
2) java.lang.ClassNotFoundException 指定的类找不到;出现原因:类的名称和路径加载错误;通常都是程序试图通过字符串来加载某个类时可能引发异常。
3) java.lang.NumberFormatException 字符串转换为数字异常;出现原因:字符型数据中包含非数字型字符。
4) java.lang.IndexOutOfBoundsException 数hao d常,常见于操作数组对象时发生。
5) java.lang.IllegalArgumentException 方法传递参数错误。
6) java.lang.ClassCastException 数据类型转换异常。
7) java.lang.NoClassDefFoundException 未找到类定义错误。
8)SQLException SQL 异常,常见于操作数据库时的 SQL 语句错误。
9) java.lang.InstantiationException 实例化异常。
10) java.lang.NoSuchMethodException 方法不存在异常。

JavaAOP

AOP 即面向切面编程
首先,在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能。

所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务
所谓的周边功能,比如性能统计,日志,事务管理等等
周边功能在 Spring 的面向切面编程AOP思想里,即被定义为切面

–核心业务功能和切面功能分别独立进行开发,然后把切面功能和核心业务功能 “编织” 在一起,这就叫AOP
AOP能够将那些与业务无关,却为业务模块调用的代码封装起来(例如事务处理、日志管理、权限控制等)
测试开发面试题_第1张图片

Java容器

Java容器又称Java集合,分为 Collection 和 Map 两大类,两大类下面有很多子类
1、Collection-
List-arraylist、linkedlist、vector、stack
set-hashset、linkedhashset、treeset
2、Map
hashMap-linkedhashmap
treeMap
ConcurrentHashMap
Hashtable
arraylist(数组结构) 查询速度快,但是修改 、插入、删除的速度慢和
linked list(链表结构)查询速度满,但是更改速度快(插入修改)
list、set、map区别体现在是否元素有序一级元素是否可重复
list元素有序且允许重复
hashset和hashmap元素无序且不允许重复,hashmap中value可以重复,key不可以
tree set、和treemap元素有序但是元素不允许重复,treemap中value可以重复,key不可以
HashMap 和 Hashtable 有什么区别?
存储:HashMap 运行 key 和 value 为 null,而 Hashtable 不允许。
线程安全:Hashtable 是线程安全的,而 HashMap 是非线程安全的。
推荐使用:在 Hashtable 的类注释可以看到,Hashtable 是保留类不建议使用,推荐在单线程环境下使用 HashMap 替代,如果需要多线程使用则用 ConcurrentHashMap 替代。

Collections并不属于集合,是用来操作集合的工具类。

Collections有几个常用的API:
     - public static  boolean addAll(Collection c, T... elements)
         给集合对象批量添加元素!
       Collections.addAll(names,"曹操","贾乃亮","王宝强","陈羽凡");
     - public static void shuffle(List list) :打乱集合顺序。
     -  Collections.shuffle(newnames); // 打乱顺序
     - public static  void sort(List list):将集合中元素按照默认规则排序。
     - public static  void sort(List list,Comparator ):将集合中元素按照指定规则排序。
    Collections.shuffle(newnames); // 打乱顺序
    引用数据类型的排序
     自定义类型的比较方法API:
     - public static  void sort(List list):
           将集合中元素按照默认规则排序。
           对于自定义的引用类型的排序人家根本不知道怎么排,直接报错!
           如果希望自定义的引用类型排序不报错,可以给类提供比较规则:Comparable。

     - public static  void sort(List list,Comparator c):
            将集合中元素按照指定规则排序,自带比较器
            注意:如果类有比较规则,而这里有比较器,优先使用比较器。

测试开发面试题_第2张图片

可变参数

可变参数的作用:传输参数非常灵活,方便。
可变参数的注意事项:
1.一个形参列表中可变参数只能有一个!!
2.可变参数必须放在形参列表的最后面!!
测试开发面试题_第3张图片

Comparable和Comparator区别?

1、Comparable接口
实现Comparable接口类表示这个类型的对象可以进行比较大小的。 这种可以比较大小的对象可以进行自然排序。
Integer,Character,String,Date都实现了Comparable接口
2、Comparator接口
比较器用于实现对象任意属性进行比较大小。
在排序时候可以通过指定属性比较器实现任意属性排序。
在排序时候Comparable接口用于进行自然排序,而Comparator接口进行自定义排序,自定义排序更加灵活方便而常用。
设计上Comparable不推荐使用,因为对程序本身具有侵入性
进一步解释:
从源码上来看,Comparable接口内定义了一个compareTo方法,接收一个对象作为参数,返回一个int值。
而Comparator接口内则定义了一个compare方法,也是返回一个int值,但是接收的参数是两个同类型的参数。
从两个接口的命名我们可以大概看出二者的作用。
Comparable,中文意思为“可比较的”;
Comparator,中文意思则是“比较器”。
实现Comparable的类,实例化的对象是可以和同类型对象比较的。所以他的compareTo方法是一个参数,一个用来和自己进行比较的对象。
实现Comparator的类,实例化的对象则是用来作为裁判比较其他对象的。所以他的compare方法是两个参数,我们的裁判就是要比较这两个对象
 Comparable相当于“内部比较器”,而Comparator相当于“外部比较器”。
可查看:https://baijiahao.baidu.com/s?id=1672294154905165112&wfr=spider&for=pc

HashMap底层实现原理解析

1、常见的数据结构有三种
(1)数组:存储区间连续,内存占用较多,空间复杂度大
优点:随机访问性强、查找速度快
缺点:插入和删除数据效率低,因插入数据,这个位置后面的数据在内存中都要往后移动,且大小固定不易动态扩展。
(2)链表:存储区间离散、占用内存小、空间复杂度小
优点:插入删除速度快,内存利用率高,没有固定大小,扩展灵活
缺点:不能随机查找,每次都是从第一个开始遍历,查找速度慢
(3)哈希表:结合数组结构和链表结构的优点,查找、修改、插入删除效率都很快。
哈希表根据关键码去寻找值的数据映射结构
哈希表左边是数组,数组的每个成员都指向一个链表的头,这个链表可能为空,也可能元素很多(根据元素的一些特征把这些元素分配到不同的链表中去,也是根据这些特征找到正确的链表,在从这个链表找到这个元素)
元素特征转变为数组下表的方法就是散列法,散列法有很多种
(1)除法散列法
最直观的一种,上图使用的就是这种散列法,公式:
index = value % 16
学过汇编的都知道,求模数其实是通过一个除法运算得到的,所以叫“除法散列法”
(2)平方散列法
求index是非常频繁的操作,而乘法的运算要比除法来得省时(对现在的CPU来说,估计我们感觉不出来),所以我们考虑把除法换成乘法和一个位移操作。公式:
index = (value * value) >> 28 (右移,除以2^28。记法:左移变大,是乘。右移变小,是除。)
如果数值分配比较均匀的话这种方法能得到不错的结果,但我上面画的那个图的各个元素的值算出来的index都是0——非常失败。也许你还有个问题,value如果很大,value * value不会溢出吗?答案是会的,但我们这个乘法不关心溢出,因为我们根本不是为了获取相乘结果,而是为了获取index。
3,斐波那契(Fibonacci)散列法
平方散列法的缺点是显而易见的,所以我们能不能找出一个理想的乘数,而不是拿value本身当作乘数呢?答案是肯定的。
1,对于16位整数而言,这个乘数是40503
2,对于32位整数而言,这个乘数是2654435769
3,对于64位整数而言,这个乘数是11400714819323198485
这几个“理想乘数”是如何得出来的呢?这跟一个法则有关,叫黄金分割法则,而描述黄金分割法则的最经典表达式无疑就是著名的斐波那契数列,即如此形式的序列:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946,…。另外,斐波那契数列的值和太阳系八大行星的轨道半径的比例出奇吻合。

对我们常见的32位整数而言,公式: 
        index = (value * 2654435769) >> 28

https://blog.csdn.net/cyongxue/article/details/19544107
JDK1.7及之前:数组+链表
JDK1.8:数组+链表+红黑树
4、队列:一种数据结构,最明显的特性是只允许队头删除,队尾插入。
-----栈:同样是一种数据结构,特性是插入删除都在栈的顶部。
5、Java里面的LinkList使用链表实现
Java里面的ArrayList就是使用数组实现的
6、hashcode定义:通过字符串算出它的ascii码,进行mod(取模),算出哈希表中的下标。注意:这里的取模多少,具体是跟数组的长度相关的。

栈和队列的区别

存储方式:栈存储时是先进后出,后进先出。队列存储为:先进先出,
栈和队列的共同点:
1、都是一种线性表,
2、都是在端点处插入和删除元素-栈是仅限定在表尾进行插入和删除操作的线性表,而队列插入操作在队尾,删除操作在队头
3、都可以通过顺序结构和链式结构实现,顺序结构-数组,链式结构-指针域和数据域,头节点记录链表的基地址,这样我们就可以遍历得到整个结点,尾结点是最后一个结点,它的指针指向一个空地址NULL
双链表既有后继指针next,也有前驱节点prev
4、插入与删除的时间复杂度都是O(1),在空间复杂度上两者也一样。
5、顺序栈能实现空间共享,但是顺序队列不能

Java是一门面向对象的高级语言

面向对象:用代码描述现实世界
面向对象最重要的两个概念:类和对象。
类是相同事物共同特征的描述。是抽象出来的
对象:是真实存在的实例
注意:1.类名的首字母建议大写。满足驼峰模式。 StudentNameCode
2.一个Java代码文件中可以定义多个类。但是按照规范还是建议一个Java文件定义一个类。
3.一个Java代码文件中,只能有一个类是用public修饰的,
而且public修饰的类名必须成为当前Java代码的文件名称

类包括哪些内容:

        // 1.成员变量(Field):  描述类或者对象的属性信息的。
        // 2.成员方法(Method): 描述类或者对象的行为信息的。
        // 3.构造器(Constructor): 初始化一个对象返回。
        // 4.代码块(后面学习的)
        // 5.内部类(后面学习的)

构造器:

    格式:修饰符 类名(形参列表){
         }
    作用:初始化类的一个对象返回。
    构造器的分类:无参数构造器,有参数构造器。
    构造器的注意点:一个类默认自带一个无参数构造器,但是如果写了有参数构造器那么
        默认的无参数构造器就消失了,此时如果还需要用无参数构造器就需要自己从新写一个。
    构造器初始化对象的格式:
        类名 对象名称 = new 构造器;
        Student s = new Student();
    无参数构造器的作用:初始化一个类的对象(使用对象的默认值初始化)返回。
    有参数构造器的作用:初始化一个类的对象(可以在初始化对象的时候为对象赋值)返回。

面向对象的三大特征:封装、继承、多态

 封装最初的目的:提高代码的安全性和复用性,组件化。
 封装的步骤: 成员变量私有,提供getter+setter方法。
    1.成员变量应该私有。用private修饰,只能在本类中直接访问。
    2.提供成套的getter和setter方法暴露成员变量的取值和赋值。

this关键字的作用:

    this代表了当前对象的引用。
    this关键字可以用在实例方法和构造器中。
    this用在方法中,谁调用这个方法,this就代表谁。
    this用在构造器,代表了构造器正在初始化的那个对象的引用

static

我们之前定义了很多成员变量(name , age , sex)
其实我们只写了一份,但是发现每个对象都可以用,就说明
Java中这些成员变量或者方法是存在所属性的。
有些是属于对象的,有些是属于类本身的。
Java是通过成员变量是否有static修饰来区分是类的还是属于对象的,只能修饰类
小结:
成员变量有2种
– 有static修饰的属于类叫静态成员变量,与类一起加载一次,直接用类名调用即可。
– 无static修饰的属于类的每个对象的叫实例成员变量,
与类的对象一起加载,对象有多少个,实例成员变量就加载多少份。必须用类的对象调用。

    成员方法有2种:
         -- 有static修饰的属于类叫静态方法,直接用类名调用即可。
         -- 无static修饰的属于类的每个对象的叫实例方法,必须用类的对象调用。

注意:同一个类中访问静态成员变量可以省略类名不写

示例方法和静态方法访问机制

测试开发面试题_第4张图片

继承

继承是Java中一般到特殊的关系,是一种子类到父类的关系。提高代码复用,子类更强大,子类不仅得到了父类的功能,它还有自己的功能。
例如:学生类继承了人类。 猫类继承了动物类。
子类继承了一个父类,子类就可以直接得到父类的属性(成员变量)和行为(方法)了。
子类不能继承父类的构造器:子类有自己的构造器。(没有争议的)
有争议的观点(拓展):
子类是否可以继承父类的私有成员(私有成员变量,私有成员方法)?
– 我认为子类是可以继承父类的私有成员的,只是不能直接访问而已。
– 以后可以暴力去访问继承自父类的私有成员~~~
子类是否可以继承父类的静态成员?
– 我认为子类是不能继承父类的静态成员的,
– 子类只是可以访问父类的静态成员,父类的静态成员只有一份可以被子类共享访问。
共享并非继承。

继承间的引用

测试开发面试题_第5张图片
子类访问成员变量的原则:就近原则。
如果一定要访问父类的成员变量可以使用super关键字。
目标:继承后-成员方法的访问特点。
就近原则:
子类有找子类,子类没有找父类,父类没有就报错。
小结:
子类对象优先使用子类已有的方法。

方法重写

方法重写是子类重写一个与父类申明一样的方法覆盖父类的方法。
方法重写建议加上@Override注解。
方法重写的核心要求:方法名称形参列表必须与被重写方法一致!!
建议“申明不变,重新实现”。
super调用父类被重写的方法。

继承后-构造器的特点

特点:
子类的全部构造器默认一定会先访问父类的无参数构造器,再执行子类自己的构造器。
为什么子类构造器会先调用父类构造器?
1.子类的构造器的第一行默认有一个super()调用父类的无参数构造器,写不写都存在!
2.子类继承父类,子类就得到了父类的属性和行为。
当我们调用子类构造器初始化子类对象数据的时候,必须先调用父类构造器初始化继承自父类的属性和行为啊。
*/
注意:
this(…)借用本类其他构造器。
super(…)调用父类的构造器。
this(…)和super(…)必须放在构造器的第一行,否则报错!
所以this(…)和super(…)不能同时出现在构造器中!!!

什么会造成空指针

因为用空(null)去调用属性或方法,null表示没有这个对象,既然没有这个对象,那么去调用他的属性和方法,就会报异常
当一个对象不存在时又调用其方法会产生异常obj.method() // obj对象不存在。(当获取接口数据时,没有进行一个盼空,直接获取他的body,就会产生空指针异常)

继承的特点

1.单继承:一个类只能继承一个直接父类。
2.多层继承:一个类可以间接继承多个父类。(家谱)
3.一个类可以有多个子类。
4.一个类要么默认继承了Object类,要么间接继承了Object类,Object类是Java中的祖宗类!!

重写和重载有什么区别

重载(Overload):首先是位于一个类之中或者其子类中,具有相同的方法名,但是方法的参数不同,返回值类型可以相同也可以不同。
(1):方法名必须相同
(2):方法的参数列表一定不一样。
(3):访问修饰符和返回值类型可以相同也可以不同。
其实简单而言:重载就是对于不同的情况写不同的方法。 比如,同一个类中,写不同的构造函数用于初始化不同的参数。
重写(override):一般都是表示子类和父类之间的关系,其主要的特征是:方法名相同,参数相同,但是具体的实现不同。
重写的特征:
(1):方法名必须相同,返回值类型必须相同 ,参数列表必须相同
(2):访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected
(3):子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法。
(5):构造方法不能被重写,
简单而言:就是具体的实现类对于父类的该方法实现不满意,需要自己在写一个满足于自己要求的方法。

什么会造成死锁、死循环

死锁:各进程互相等待对方手里的资源,导致各进程都阻塞,无法向前推进的现象。
每个人都占有一个资源,同时又在等待另一个人手里的资源。发生“死锁”。
死锁产生的原因
1.竞争资源
系统中的资源可以分为两类:一类是可剥夺资源,CPU和主存均属于可剥夺性资源;另一类资源是不可剥夺资源,如磁带机、打印机等
  产生死锁中的竞争资源之一指的是竞争不可剥夺资源(例如:系统中只有一台打印机,可供进程P1使用,假定P1已占用了打印机,若P2继续要求打印机打印将阻塞)
  产生死锁中的竞争资源另外一种资源指的是竞争临时资源(临时资源包括硬件中断、信号、消息、缓冲区内的消息等),通常消息通信顺序进行不当,则会产生死锁
  2.进程间推进顺序非法 
  若P1保持了资源R1,P2保持了资源R2,系统处于不安全状态,因为这两个进程再向前推进,便可能发生死锁。例如,当P1运行到P1:Request(R2)时,将因R2已被P2占用而阻塞;当P2运行到P2:Request(R1)时,也将因R1已被P1占用而阻塞,于是发生进程死锁
死循环:某进程执行过程中一直跳不出某个循环的现象。有时是因为程序逻辑bug导致的,有时是程序员故意设计的
产生死锁的四个必要条件:
互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。

请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。

引用类型

引用类型作为Java的数据类型,自然可以作为方法的参数类型和返回值类型。
除了基本数据类型都是引用数据类型了。
引用类型作为数据类型可以在一切可以使用类型的地方使用
测试开发面试题_第6张图片
测试开发面试题_第7张图片
测试开发面试题_第8张图片

抽象方法

父类知道子类一定要完成某个功能,但是每个子类实现的情况都不一样
而且子类都会用自己的功能了,父类的该功能就可以定义成抽象的方法。
拥有抽象方法的类必须定义成抽象类。
抽象方法:没有方法体,只有方法签名,必须用abstract修饰的方法就是抽象方法。
抽象类:拥有抽象方法的类必须定义成抽象类,必须用abstract修饰

抽象类的使用

抽象类是为了被继承。
总结:
一个类继承了抽象类,必须重写完抽象类的全部抽象方法,否则这个类必须定义成抽象类。
因为拥有抽象方法的类必须定义成抽象
面试题:抽象类是否有构造器,是否可以创建对象,为什么?
答:抽象类作为类一定有构造器,而且必须有构造器。
提供给子类继承后调用父类构造器使用的。

     抽象类虽然有构造器,但是抽象类绝对不能创建对象。
     抽象类中可能存在抽象方法,抽象方法不能执行。
     抽象在学术上本身意味着不能实例化。

抽象类存在的意义有两点:

    (1)被继承,抽象类就是为了被子类继承,否则抽象类将毫无意义。(核心意义)
    (2)抽象类体现的是"模板思想":部分实现,部分抽象。(拓展)
        -- 可以使用抽象类设计一个模板模式。
        目标:抽象类的注意事项和总结

 1. 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
     理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

 2. 抽象类一定有而且是必须有构造器,是供子类创建对象时,初始化父类成员使用的。
     理解:子类的构造器中,有默认的super(),需要访问父类构造器。

 3. 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

 4. 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则子类也必须定义成抽象类。

 5. 抽象类存在的意义是为了被子类继承,抽象类体现的是模板思想。
    理解:抽象类中已经实现的是模板中确定的成员,
    抽象类不确定如何实现的定义成抽象方法,交给具体的子类去实现。

引入:

    类与类是继承关系:一个类只能直接继承一个父类。
    类与接口是实现关系:一个类可以实现多个接口。
    实现接口的类称为“实现类”。
     注意:一个类实现接口必须重写完接口中全部抽象方法,否则这个类必须定义成抽象类!!
      小结:
    一个类可以实现多个接口。
    一个类如果实现了多个接口,必须重写完全部接口中的全部抽象方法
    否则这个类必须定义抽象类。
      引入:
    类与类是单继承关系:一个类只能继承一个直接父类。
    类与接口是多实现关系:一个类可以实现多个接口。
    接口与接口是多继承关系:一个接口可以继承多个接口。

拓展:实现多个接口的使用注意实现。(非常偏的语法,理解和了解即可)

1.如果实现了多个接口,多个接口中存在同名的静态方法并不会冲突,
    原因是只能通过各自接口名访问静态方法。

2.当一个类,既继承一个父类,又实现若干个接口时,(重点)
    父类中的成员方法与接口中的默认方法重名,子类就近选择执行父类的成员方法。

3.当一个类实现多个接口时,多个接口中存在同名的默认方法。
    实现类必须重写这个方法。

4.接口中,没有构造器,不能创建对象。(重点)
    接口是更彻底的抽象,连构造器都没有,自然不能创建对象

final关键字

1.final修饰类,类不能被继承了。
2.final可以修饰方法,方法就不能被重写了。
3.final修饰变量总规则:变量有且仅能被赋值一次。
final和abstract的关系?
互斥关系,不能同时修饰类或者同时修饰方法!!
变量有几种?
成员变量
– 静态成员变量:有static修饰,属于类,只加载一份。
– 实例成员变量:无static修饰,属于每个对象,与对象一起加载。
局部变量
– 只能方法中,构造器中,代码块中,for循环中,用完作用范围就消失了。
final修饰局部变量:
– 让值被固定或者说保护起来,执行的过程中防止被修改。
拓展:
final修饰静态成员变量可以在哪些地方赋值一次:
1.定义的时候赋值一次。
2.可以在静态代码块中赋值一次。
3.可以在每个构造器中赋值一次。

单例模式

单例模式的含义: 单例模式,是一种常用的软件设计模式。通过单例模式可以保证系统中,
应用该模式的这个类永远只有一个实例。即一个类永远只有一个对象实例。
单例是为了节约内存,单例在有些业务场景下还必须用到!!

 单例的应用场景:在实际开发中,有很多业务对象永远只需要一个,无论启动多少次
 我们只需要一个对象,例如任务管理对象,只需要一个对象。节约内存和性能。
 因为对象越多内存占用越大,极有可能出现内存溢出!

 实现单例模式目前提供两种方式:
        1.饿汉单例设计模式
             在用类获取对象的时候,对象已经提前为你创建好了。
             设计步骤:
             a.定义一个类,把构造器私有。
             b.定义一个静态变量存储一个对象。
             c.提供一个返回单例对象的方法。

        2.懒汉单例设计模式
         在真正需要该对象的时候,才去创建一个对象(延迟加载对象)。
             设计步骤:
             a.定义一个类,把构造器私有。
             b.定义一个静态变量存储一个对象。
             c.提供一个返回单例对象的方法。

枚举类 多例设计模式

枚举的特点:
     1.枚举类是用final修饰的,枚举类不能被继承!
     2.枚举类默认继承了java.lang.Enum枚举类。
     3.枚举类的第一行都是常量,存储都是枚举类的对象。
     4.枚举类的第一行必须是罗列枚举类的实例名称。
     所以:枚举类相当于是多例设计模式。
小结:
     枚举类的特点:
     1.枚举类是用final修饰的,枚举类不能被继承!
     2.枚举类默认继承了java.lang.Enum枚举类。
     3.枚举类的第一行都是常量,存储都是枚举类的对象。
     4.枚举类的第一行必须是罗列枚举类的实例名称。
     所以:枚举类相当于是多例设计模式。

*/
public class EnumDemo {
}

enum Sex{
BOY , GIRL;
}

// 枚举
enum Season {
SPRING , SUMMER , AUTUMN , WINTER;
}
public final class Season extends java.lang.Enum {
public static final Season SPRING = new Season();
public static final Season SUMMER = new Season();
public static final Season AUTUMN = new Season();
public static final Season WINTER = new Season();

         public static Season[] values();
         public static Season valueOf(java.lang.String);
     }

测试开发面试题_第9张图片

多态

多态的概念:
同一个类型的对象,执行同一个行为,在不同的状态下会表现出不同的行为特征
多态的概念:
同一个类型的对象,执行同一个行为,在不同的状态下会表现出不同的行为特征。

多态的识别技巧:
    对于方法的调用:编译看左边,运行看右边。
    对于变量的调用:编译看左边,运行看左边。

测试开发面试题_第10张图片

多态的使用前提:
    (1)  必须存在继承或者实现关系。
    (2)  必须存在父类类型的变量引用子类类型的对象。
    (3)  需要存在方法重写。

父类类型的范围 > 子类类型范围的
测试开发面试题_第11张图片

、引用类型

在基础班学过了基本数据类型的转换。
1.小范围类型的变量或者值可以直接赋值给大范围类型的变量。
2.大范围类型的变量或者值必须强制类型转换给小范围类型的变量。
父类类型的变量或者对象必须强制类型转换成子类类型的变量,否则报错
小结:
有继承/实现关系的两个类型就可以进行强制类型转换,编译阶段一定不报错!
但是运行阶段可能出现:类型转换异常 ClassCastException
Java建议在进行强制类型转换之前先判断变量的真实类型,再强制类型转换!
变量 instanceof 类型: 判断前面的变量是否是后面的类型或者其子类类型才会返回true,

内部类

内部类是类的五大成分之一:成员变量,方法,构造器,代码块,内部类。

什么是内部类?
    定义在一个类里面的类就是内部类。
内部类有什么用?
    可以提供更好的封装性, 
    可以体现出组件的思想。

内部类的分类:
    (1)静态内部类。
    (2)实例内部类。(成员内部类)
    (3)局部内部类。
    (4)匿名内部类。(重点)

小结:
    匿名内部类是我们的重点。

匿名内部类

1.匿名内部类是一个没有名字的内部类。
2.匿名内部类一旦写出来,就会立即创建一个匿名内部类的对象返回。
3.匿名内部类的对象的类型相当于是当前new的那个的类型的子类类型。
且当该类中只有一个方法,就可以省略

注意:

    相同包下的类可以直接访问。
    不同包下的类必须导包,才可以使用!
       导包格式:import 包名.类名;

一个类要么默认继承了Object类,要么间接继承了Object类。

(1)public String toString():
– 默认是返回当前对象在堆内存中的地址信息:com.itheima._12Object类的详细使用.Student@735b478
– 默认的地址信息格式:类的全限名@内存地址
– 直接输出对象名称,默认会调用toString()方法,所以直接输出对象可以省略toString()不写。
– 实际开发中直接输出对象,输出对象的地址其实是没有意义的。
所以toString方法存在的意义是为了被子类重写。
以便能够返回对象的数据内容输出。因为实际开发中我们
输出对象更多的时候是希望看到对象的数据内容信息。

            小结:开发中如果希望输出对象看到对象的内容,
                 只需要重写toString()方法即可。
                 所以toString方法存在的意义是为了被子类重写。

    (2)public boolean equals(Object o):
            -- 默认是比较两个对象的地址是否相同。相同返回true,反之。
            -- 直接比较两个对象的地址是否相同完全可以用“==”替代equals。
               所以equals存在的意义是为了被子类重写,以便程序员可以
                自己来定制比较规则。
            -- 需求:只要两个对象的内容一样,我们就认为他们是相等的。
        小结:
             equals存在的意义是为了被子类重写,以便程序员可以
             自己来定制比较规则。

Objects类与Object还是继承关系

Objects的方法:
1.public static boolean equals(Object a, Object b)
– 比较两个对象的。
– 底层进行非空判断,从而可以避免空指针异常。更安全!!推荐使用!!
public static boolean equals(Object a, Object b) {
return a == b || a != null && a.equals(b);
}
2.public static boolean isNull(Object obj)
– 判断变量是否为null ,为null返回true ,反之!

Date类在Java中代表的是系统当前此刻日期时间对象。

Date可以代表系统当前此刻日期时间对象。
时间记录的两种方式:
Date日期对象。
时间毫秒值:从1970-01-01 00:00:00开始走到此刻的总的毫秒值。 1s = 1000ms
时间毫秒值可以用于做时间的计算:例如代码的执行性能分析。
流程:
Date日期对象 -> getTime() -> 时间毫秒值
时间毫秒值 -> new Date(时间毫秒值) -> Date日期对象
小结:
public Date(long time):把时间毫秒值转换成日期对象。
– public String format(Date date):可以把日期对象格式化成我们喜欢的时间形式,返回的是字符串!
– public String format(Object time):可以把时间毫秒值格式化成我们喜欢的时间形式,返回的是字符串!
– public Date parse(String date) throws ParseException:把字符串的时间解析成日期对象

Collection是集合的祖宗类

- public boolean add(E e):  把给定的对象添加到当前集合中 。
- public void clear() :清空集合中所有的元素。
- public boolean remove(E e): 把给定的对象在当前集合中删除。
- public boolean contains(Object obj): 判断当前集合中是否包含给定的对象。
- public boolean isEmpty(): 判断当前集合是否为空。
- public int size(): 返回集合中元素的个数。
- public Object[] toArray(): 把集合中的元素,存储到数组中

HashSet:添加的元素是无序,不重复,无索引。
集合重写了toString()方法,默认打印出内容信息

// HashSet:添加的元素是无序,不重复,无索引。
Collection sets = new HashSet<>()

Math用于做数学运算。

Math类中的方法全部是静态方法,直接用类名调用即可。
方法:
      方法名                                        说明                
      public static int abs(int a)                  获取参数a的绝对值:
      public static double ceil(double a)           向上取整
      public static double floor(double a)          向下取整
      public static double pow(double a, double b)  获取a的b次幂        
      public static long round(double a)            四舍五入取整

Calendar的方法:
1.public static Calendar getInstance(): 返回一个日历类的对象。
2.public int get(int field):取日期中的某个字段信息。
3.public void set(int field,int value):修改日历的某个字段信息。
4.public void add(int field,int amount):为某个字段增加/减少指定的值
5.public final Date getTime(): 拿到此刻日期对象。
6.public long getTimeInMillis(): 拿到此刻时间毫秒值

BigDecimal大数据类

包:java.math.
创建对象的方式(最好的方式:)
public static BigDecimal valueOf(double val) :包装浮点数成为大数据对象。
方法声明
public BigDecimal add(BigDecimal value) 加法运算
public BigDecimal subtract(BigDecimal value) 减法运算
public BigDecimal multiply(BigDecimal value) 乘法运算
public BigDecimal divide(BigDecimal value) 除法运算
public double doubleValue():把BigDecimal转换成double类型。

可以把基本数据类型的值转换成字符串类型的值。(没啥用)

        -- 调用toString()方法。
        -- 调用Integer.toString(基本数据类型的值)得到字符串。
        -- 直接把基本数据类型+空字符串就得到了字符串。

    2.把字符串类型的数值转换成对应的基本数据类型的值。(真的很有用)
        -- Xxx.parseXxx("字符串类型的数值")
        -- Xxx.valueOf("字符串类型的数值"):推荐使用!
小结:
    包装类可以把字符串类型的数值转换成对应的基本数据类型的值(真的很有用)

正则表达式

是一些特殊字符组成的校验规则,可以校验信息的正确性,校验邮箱是否合法
电话号码,金额等。
字符类
[abc] a、b 或 c(简单类)
[^abc] 任何字符,除了 a、b 或 c(否定)
[a-zA-Z] a 到 z 或 A 到 Z,两头的字母包括在内(范围)
[a-d[m-p]] a 到 d 或 m 到 p:[a-dm-p](并集)
[a-z&&[def23]] d、e 或 f(交集)
[a-z&&[^bc]] a 到 z,除了 b 和 c:[ad-z](减去)
[a-z&&[^m-p]] a 到 z,而非 m 到 p:[a-lq-z](减去)

 预定义字符类
     . 任何字符
     \d 数字:[0-9]
     \D 非数字: [^0-9]
     \s 空白字符:[ \t\n\x0B\f\r]
     \S 非空白字符:[^\s]
     \w 单词字符:[a-zA-Z_0-9]
     \W 非单词字符:[^\w]
     Greedy 数量词
     X? X,一次或一次也没有
     X* X,零次或多次
     X+ X,一次或多次
     X{n} X,恰好 n 次
     X{n,} X,至少 n 次
     X{n,m} X,至少 n 次,但是不超过 m 次

泛型

泛型和集合都只能支持引用数据类型,不支持基本数据类型。
泛型在编译阶段约束了操作的数据类型,从而不会出现类型转换异常。
体现的是Java的严谨性和规范性,数据类型,经常需要进行统一!
泛型变量建议使用 E , T , K , V
自定义泛型的核心思想:是把出现泛型变量的地方全部替换成传输的真实数据类型。
什么是泛型方法?
定义了泛型的方法就是泛型方法。
泛型方法的定义格式:
修饰符 <泛型变量> 返回值类型 方法名称(形参列表){

    }
    注意:方法定义了是什么泛型变量,后面就只能用什么泛型变量。
    泛型类的核心思想:是把出现泛型变量的地方全部替换成传输的真实数据类型。

泛型方法和泛型类可以做通用技术架构。
测试开发面试题_第12张图片

集合类

什么是集合?
集合是一个大小可变的容器。
容器中的每个数据称为一个元素。数据==元素。
集合的特点是:类型可以不确定,大小不固定。集合有很多种,不同的集合特点和使用场景不同。
数组:类型和长度一旦定义出来就都固定了。

集合有啥用?
    在开发中,很多时候元素的个数是不确定的。
    而且经常要进行元素的增删该查操作,集合都是非常合适的。
    开发中集合用的更多!!

测试开发面试题_第13张图片
Collection API如下:
- public boolean add(E e): 把给定的对象添加到当前集合中 。
- public void clear() :清空集合中所有的元素。
- public boolean remove(E e): 把给定的对象在当前集合中删除。
- public boolean contains(Object obj): 判断当前集合中是否包含给定的对象。
- public boolean isEmpty(): 判断当前集合是否为空。
- public int size(): 返回集合中元素的个数。
- public Object[] toArray(): 把集合中的元素,存储到数组中

集合的遍历

遍历就是一个一个的把容器中的元素访问一遍。
Collection集合的遍历方式是全部集合都可以直接使用的,
Collection集合的遍历方式有三种:
(1)迭代器。
(2)foreach(增强for循环)。
(3)JDK 1.8开始之后的新技术Lambda表达式(了解)
a.迭代器遍历集合。
– 方法:
public Iterator iterator(): 获取集合对应的迭代器,用来遍历集合中的元素的
E next():获取下一个元素值!
boolean hasNext():判断是否有下一个元素,有返回true ,反之。
–流程:
1.先获取当前集合的迭代器
Iterator it = lists.iterator();
2.定义一个while循环,问一次取一次。
通过it.hasNext()询问是否有下一个元素,有就通过
it.next()取出下一个元素。

foreach(增强for循环)遍历集合。
foreach是一种遍历形式,可以遍历集合或者数组。
foreach遍历集合实际上是迭代器遍历的简化写法。
foreach遍历的关键是记住格式:
for(被遍历集合或者数组中元素的类型 变量名称 : 被遍历集合或者数组){

    小结:
        foreach遍历集合或者数组很方便。
        缺点:foreach遍历无法知道遍历到了哪个元素了,因为没有索引。

数据结构种类- Java

数据存储的常用结构有:栈、队列、数组、链表和红黑树
a.队列(queue)
– 先进先出,后进后出。
– 场景:各种排队。叫号系统。
– 有很多集合可以实现队列。

    b.栈(stack)
       -- 后进先出,先进后出
       -- 压栈 == 入栈
       -- 弹栈 == 出栈
       -- 场景:手枪的弹夹。


    c.数组
       -- 数组是内存中的连续存储区域。
       -- 分成若干等分的小区域(每个区域大小是一样的)
       -- 元素存在索引
       -- 特点:查询元素快(根据索引快速计算出元素的地址,然后立即去定位)
               增删元素慢(创建新数组,迁移元素)

    d.链表
       -- 元素不是内存中的连续区域存储。
       -- 元素是游离存储的。每个元素会记录下个元素的地址。
       -- 特点:查询元素慢
               增删元素快(针对于首尾元素,速度极快,一般是双链表)

    e.红黑树
         二叉树:binary tree 永远只有一个根节点,是每个结点不超过2个节点的树(tree) 。
         查找二叉树,排序二叉树:小的左边,大的右边,但是可能树很高,性能变差。
         为了做排序和搜索会进行左旋和右旋实现平衡查找二叉树,让树的高度差不大于1
         红黑树(就是基于红黑规则实现了自平衡的排序二叉树):
            树尽量的保证到了很矮小,但是又排好序了,性能最高的树。

         红黑树的增删查改性能都好!!!

Map集合

Map集合是另一个集合体系。
Collection是单值集合体系。
Map集合每个元素包含两个值,也被称为“键值对集合”
Map集合的体系:
Map(接口,Map集合的祖宗类)
/
TreeMap HashMap(实现类,经典的,用的最多)

LinkedHashMap(实现类)
Map集合的特点:
1.Map集合的特点都是由键决定的。
2.Map集合的键是无序,不重复的,无索引的。
Map集合后面重复的键对应的元素会覆盖前面的整个元素!
3.Map集合的值无要求。
4.Map集合的键值对都可以为null。
HashMap:元素按照键是无序,不重复,无索引,值不做要求。
LinkedHashMap:元素按照键是有序,不重复,无索引,值不做要求。
Map常用方法
目标:Map集合的常用API(重点中的重点)
- public V put(K key, V value): 把指定的键与指定的值添加到Map集合中。
- public V remove(Object key): 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。
- public V get(Object key) 根据指定的键,在Map集合中获取对应的值。
- public Set keySet(): 获取Map集合中所有的键,存储到Set集合中。
- public Set> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)。
- public boolean containsKey(Object key):判断该集合中是否有此键,containsValue
// Map集合的键是无序不重复的,所以返回的是一个Set集合。
Set keys = maps.keySet();
// 9.获取全部值的集合:Collection values();
// Map集合的值是不做要求的,可能重复,所以值要用Collection集合接收!
Collection values = maps.values();
for (Integer value : values) {
System.out.println(value);
}

Map集合的遍历方式有:3种。

    (1)“键找值”的方式遍历:先获取Map集合全部的键,再根据遍历键找值。
    (2)“键值对”的方式遍历:难度较大。
    (3)JDK 1.8开始之后的新技术:Lambda表达式。(暂时了解)
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/5c98b70528ae4ac28e1e9a38b5d51b26.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Y-25Y-2772e,size_20,color_FFFFFF,t_70,g_se,x_16)
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/f79fc78ed2604c72908722f8ae862431.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Y-25Y-2772e,size_20,color_FFFFFF,t_70,g_se,x_16)
注:
    Map集合的键和值都可以存储自定义类型。
    如果希望Map集合认为自定义类型的键对象重复了,必须重写对象的hashCode()和equals()方法

LinkedHashMap的特点介绍。

LinkedHashMap是HashMap的子类。
    -- 添加的元素按照键有序,不重复的。
HashSet集合相当于是HashMap集合的键都不带值。
LinkedHashSet集合相当于是LinkedHashMap集合的键都不带值。
底层原理完全一样,都是基于哈希表按照键存储数据的,
只是HashMap或者LinkedHashMap的键都多一个附属值。

TreeMap集合按照键是可排序不重复的键值对集合。(默认升序)
TreeMap集合按照键排序的特点与TreeSet是完全一样的。
小结:
TreeMap集合和TreeSet集合都是排序不重复集合
TreeSet集合的底层是基于TreeMap,只是键没有附属值而已。
所以TreeMap集合指定大小规则有2种方式:
a.直接为对象的类实现比较器规则接口Comparable,重写比较方法(拓展方式)
b.直接为集合设置比较器Comparator对象,重写比较方法

Java内存溢出

内存泄露:
因为执行程序指令,引入数据,装载数据等等,都是需要内存空间的,内存空间也是有限的
正常的程序执行,肯定是开辟内存空间,运行程序指令(输入、输出),程序结束,释放空间。这样可以保证内存的往复利用
但是在程序设计时可能会出现漏洞, 导致占用的内存没有得到释放,那么这一块内存就没办法再利用了,这就是内存泄露。
当不断出现内存泄露的时候,被占用的内存空间越来越多,最终到下一次需要使用内存空间的时候就会这样——空间不够了,溢出了
对于Java,内存溢出分三种情况。
1、OutOfMemoryError: PermGen space
Permanent Generation space 这个区域主要用来保存加载的Class的一些信息,在程序运行期间属于永久占用的,Java的GC不会对他进行释放,所以如果启动的程序加载的信息比较大,超出了这个空间的大小,就会发生溢出错误;

解决的办法无非就是增加空间分配了——增加java虚拟机中的XX:PermSize和XX:MaxPermSize参数的大小,其中XX:PermSize是初始永久保存区域大小,XX:MaxPermSize是最大永久保存区域大小。

2、OutOfMemoryError:Java heap space

heap 是Java内存中的堆区(堆内存),主要用来存放对象,当对象太多超出了空间大小,GC又来不及释放的时候,就会发生溢出错误。

Java中对象的创建是可控的,但是对象的回收是由GC自动的,一般来说,当已存在对象没有引用(即不可达)的时候,GC就会定时的来回收对象,释放空间。但是因为程序的设计问题,导致对象可达但是又没有用(即前文提到的内存泄露),当这种情况越来越多的时候,问题就来了。

针对这个问题,我们需要做一下两点:

1、检查程序,减少大量重复创建对象的死循环,减少内存泄露。

2、增加Java虚拟机中Xms(初始堆大小)和Xmx(最大堆大小)参数的大小。

3、StackOverFlowError

stack是Java内存中的栈空间,主要用来存放方法中的变量,参数等临时性的数据的,发生溢出一般是因为分配空间太小,或是执行的方法递归层数太多创建了占用了太多栈帧导致溢出。
针对这个问题,除了修改配置参数-Xss参数增加线程栈大小之外,优化程序是尤其重要。

Java反射机制

在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。

网络协议

TCP/UDP

TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接
UDP是一个无状态的传输协议,所以它在传递数据时非常快
1、TCP基于连接,而UDP基于无连接的;
2、对系统资源的要求:TCP较多(TCP有20个字节信息包),UDP少(UDP信息包只有8个字节);
3、UDP程序结构较简单;
4、TCP是字节流模式,而UDP是数据报文模式 ;
5、TCP保证数据正确性,安全可靠,并且保证数据顺序,而UDP可能丢包,而且UDP不保证数据顺序。

五种常用的网络协议

TCP/IP、HTTP、FTP协议、OSPF、IGP协议。

一个简单的HTTP请求会经过哪几个阶段

1、域名解析
2、发起TCP的3次握手
3、建立TCP链接后发起http请求
4、服务端响应http请求,浏览器得到html代码
5、浏览器解析html代码并请求代码中的资源
6、浏览器对页面进行渲染呈现给用户

计算机网络osi模型

https://zhuanlan.zhihu.com/p/116436431

TCP三次握手过程

第一次握手:主机A通过向主机B 发送一个含有同步序列号的标志位的数据段给主机B,向主机B 请求建立连接,通过这个数据段, 主机A告诉主机B 两件事:我想要和你通信;你可以用哪个序列号作为起始数据段来回应我。
第二次握手:主机B 收到主机A的请求后,用一个带有确认应答(ACK)和同步序列号(SYN)标志位的数据段响应主机A,也告诉主机A两件事:我已经收到你的请求了,你可以传输数据了;你要用那个序列号作为起始数据段来回应我
第三次握手:主机A收到这个数据段后,再发送一个确认应答,确认已收到主机B 的数据段:"我已收到回复,我现在要开始传输实际数据了,这样3次握手就完成了,主机A和主机B 就可以传输数据了。
https://zhuanlan.zhihu.com/p/24860273
TCP建立连接要进行3次握手,而断开连接要进行4次
第一次: 当主机A完成数据传输后,将控制位FIN置1,提出停止TCP连接的请求 ;
第二次: 主机B收到FIN后对其作出响应,确认这一方向上的TCP连接将关闭,将ACK置1;
第三次: 由B 端再提出反方向的关闭请求,将FIN置1 ;
第四次: 主机A对主机B的请求进行确认,将ACK置1,双方向的关闭结束.。

1、ACK 是TCP报头的控制位之一,对数据进行确认。确认由目的端发出, 用它来告诉发送端这个序列号之前的数据段都收到了。 比如确认号为X,则表示前X-1个数据段都收到了,只有当ACK=1时,确认号才有效,当ACK=0时,确认号无效,这时会要求重传数据,保证数据的完整性。
2、SYN 同步序列号,TCP建立连接时将这个位置1。
3、FIN 发送端完成发送任务位,当TCP完成数据传输需要断开时,,提出断开连接的一方将这位置1。

Http和Https

HTTP:是应用层协议,是一个客户端和服务器端请求和应答的标准(TCP)
 HTTPS:简单讲是HTTP的安全版,即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。
 HTTPS协议的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全;另一种就是确认网站的真实性。
 HTTPS和HTTP的区别主要如下:
  1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
  2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
  3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
  4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
  测试开发面试题_第14张图片

DNS协议

域名解析协议-从域名转换到IP地址的服务
域名:如www.baidu.com。优点是人们喜欢,容易记;缺点是机器不喜欢,路由器无法处理。
IP地址:如39.156.69.79。优点是机器喜欢,容易处理;缺点是人们不喜欢,不好记。
DNS协议位于五层网络模型中的应用层,在进行域名解析时其传输层采用UDP协议,其知名端口号为53。
DNS协议由两部分组成:

域名解析,用于执行对DNS特定名称查询的查询/响应协议;
区域传输:用于交换数据库记录的协议。
对于域名解析,由于数据量小,DNS协议采用UDP协议,知名端口号为53;

对于区域传输,由于数据量大,DNS协议采用TCP协议,知名端口号为53。

DNS查询过程:

递归:DNS服务器可使用其自身的资源记录信息缓存来应答查询,也可代表请求客户机来查询和联系其他DNS服务器,以完全解析该名称,并随后将应答返回至客户机。

迭待:客户机自己也可尝试联系其它的DNS服务器来解析名称,如果客户机这么做,它会使用基于服务器应答的独立和附加的查询。下面看一下迭待的具体过程:

浏览器输入域名后,就会先在本地的HOSTS文件中找有木有相应域名跟ip地址的对应关系,如果有则调用该ip地址完成域名解析;如果说本地HOSTS文件中木有映射的话那就到“本地DNS解析缓存”去找,找到对应的映射信息最终返回给浏览器;如果也没有找到则就进行第三部去“本地DNS”查找,而它是根据TCP/IP首选设置的服务器,如果能在本地DNS区域中找到就会返回解析结果给客户端完成域名解析,注意:这个解析是有权威性的,而不像第四步,所以这里总结一下:

1、在浏览器中输入域名,操作系统会先检查自己本地的HOSTS文件是否有这个网址映射关系。

2、如果HOSTS里没有这个域名的限制,则查找本地DNS解析器缓存。

3、如果本地HOSTS与本地DNS解析器缓存都没有相应的网址映射关系,首先会找TCP/IP参数中设置的首选DNS服务器。注意:这个查询是具有权威性的。

4、如果要查询的域名,不由本地DNS服务器区域解析,但该DNS服务器已经缓存了此网址映射关系,则调用这个IP地址映射,完成域名解析。注意:这个区域查询是没有权威性的。

5、本地DNS就把请求发至13台根DNS,根DNS服务器收到请求之后会判断这个域名(.com)是谁来授权管理的,并会返回一个负责该顶级域名服务器的一个IP。本地DNS服务器收到IP信息后,将会联系负责.com域的这台服务器。注意:它是没有用转发模式。

6、如果用的是转发模式,此DNS服务器就会把请求转发至上一级DNS服务器,由上一级服务器进行解析。需要注意:如果上一级服务器不能解析,还会继续找根DNS,或者把请求转发给上上级,以此循环来进行DNS解析。

拆箱与装箱

自动拆箱与自动装箱是相反的操作。装箱是将一个原始数据类型赋值给相应封装类的变量。而拆箱则是将一个封装类的变量赋值给相应原始数据类型的变量。装箱、拆箱的名字也取得相当贴切。

垃圾回收机制

在Java中,程序员不需要去关心内存动态分配和垃圾回收的问题,这一切都交给了JVM来处理。顾名思义,垃圾回收就是释放垃圾占用的空间
在java中是通过引用来和对象进行关联的,也就是说如果要操作对象,必须通过引用来进行。那么很显然一个简单的办法就是通过引用计数来判断一个对象是否可以被回收。不失一般性,如果一个对象没有任何引用与之关联,则说明该对象基本不太可能在其他地方被使用到,那么这个对象就成为可被回收的对象了。这种方式成为引用计数法。
优点:
  引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。
缺点:
  无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0.
   根搜索算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。
   标记-清除算法采用从根集合进行扫描,对存活的对象对象标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收,如上图所示。标记-清除算法不需要进行对象的移动,并且仅对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片。

java中用于处理字符串常用的有三个类

1、java.lang.String
2、java.lang.StringBuffer
3、java.lang.StrungBuilder
相同点:final修饰,不允许被继承
不同点:
运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String
Java中对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢。
StringBuilder和StringBuffer的对象是变量,对变量进行操作就是直接对该对象进行更改,而不进行创建和回收的操作,所以速度要比String快很多
在线程安全上,StringBuilder是线程不安全的,而StringBuffer(synchronized)是线程安全的
所以StringBuilder效率更高,锁的获取和释放会带来开销。

Mysql(含sql语句)

索引优缺点以及使用

优点
1、大大加快查找数据的速度
2、使用分组和排序子句进行数据检索时,可以显著减少查询中分组和排序的时间
缺点
1、占用物理空间
2、当对数据表进行增加、删除和修改时,索引也需要动态维护,维护时间随着数据量的增加而增加
使用
1、对于经常更新的表不要建立过多的索引,在经常查询的列可以适当创建索引
2、数据量小的表不需要建立索引
3、值重复较多的列不要建立索引
索引类型
普通索引:最基本的一种索引,允许在定义索引的列中有重复值和空值,该索引存粹是为了加快查询。
mysql>ALTER TABLE table_name ADD INDEX index_name ( column )
mysql> CREATE INDEX ind_1 ON t_index(col4);
唯一索引:列中的值必须是唯一的,可以有空值
mysql>ALTER TABLE table_name ADD UNIQUE (
column
)
主键索引:是一种特殊的唯一索引,不可以有空值,一个表中只能有一个。
mysql>ALTER TABLE table_name ADD PRIMARY KEY ( column )
组合索引:在表中的多个字段组合上创建的索引,遵循最左前缀原则。
全文索引:只有在MyISAM搜索引擎中的CHAR、VARCHAR、TEXT字段上才能设置全文索引。在使用时,需要借助MATCH函数,并且最少要4个字符,如果太短就会被忽略掉。
mysql>ALTER TABLE table_name ADD FULLTEXT ( column)

mysql> CREATE TABLE t_index(
col1 int PRIMARY KEY,
col2 int NOT null,
col3 int NOT null,
col4 VARCHAR(20),
INDEX (col2,col3)
);
测试开发面试题_第15张图片

sql语句

计算单科成绩最高的
测试开发面试题_第16张图片
select stid,subject,score from ac as t1,
(select name,max(score) as maxscr from ac group by name) as t2 where t1.stid=t2.stid and
t1.score=t2.score
计算平均成绩
select stid,name,AVG(score) from ac group by stid,name
计算单科成绩最高学生的成绩
select t1.stuid,t1.name,t1.subject,t1.score from stuscore t1,(select subject, max(score) from ac group by subject)t2 WHEN
t1.subject=t2.subject AND t1.score=t2.score
测试开发面试题_第17张图片
测试开发面试题_第18张图片
查找平均成绩大于60的学号
select sno,avg(score) from sc
group by sno
having avg(score)>60

sql时间函数

1、当前系统日期、时间
select getdate()
2、dateadd 在向指定日期加上一段时间的基础上,返回新的 datetime 值,例如:向日期加上2天

select dateadd(day,2,‘2004-10-15’) --返回:2004-10-17 00:00:00.000
3、datediff 返回跨两个指定日期的日期和时间边界数

select datediff(day,‘2004-09-01’,‘2004-09-18’) --返回:17
4、datepart 返回代表指定日期的指定日期部分的整数

SELECT DATEPART(month, ‘2004-10-15’) --返回 10
5、datename 返回代表指定日期的指定日期部分的字符串

SELECT datename(weekday, ‘2004-10-15’) --返回:星期五

创建表sql

CREATE TABLE 表名称
(
列名称1 数据类型,
列名称2 数据类型,
列名称3 数据类型,
)
插入表数据sql
insert into tb_user(name, password) values(‘jim’, 123)
修改表字段
举例:ALTER TABLE employee ADD spbh varchar(20) NOT NULL Default 0
修改表数据
mysql> UPDATE tb_courses_new
-> SET course_name=‘DB’,course_grade=3.5
-> WHERE course_id=2;

算法题

输出一个字符串中每个字符出现的次数

for(int i = 0 ; i < datas.length() ; i++ ){
// 取出当前索引的字符
char ch = datas.charAt(i);
// (4)拿着这个字符去Map集合中看是否有这个字符键,有说明之前统计过,其值+1
// 没有这个字符键,说明该字符是第一次统计,直接存入“该字符=1”
if(infos.containsKey(ch)){
// 说明这个字符之前已经出现过,其值+1
infos.put(ch , infos.get(ch) + 1);
}else{
// 说明这个字符第一次出现,直接存入 a=1
infos.put(ch , 1);
}
}

冒泡排序

1.定义一个循环控制总共需要冒泡几轮:数组的长度-1
for(int i = 0 ; i < arr.length - 1 ; i++ ){
// i = 0 j = 0 1 2
// i = 1 j = 0 1
// i = 2 j = 0
// 2.控制每轮比较几次。
for(int j = 0 ; j < arr.length - i - 1 ; j++ ){
// 如果当前元素大于后一个元素
if(arr[j] > arr[j+1]){
// 交换位置。大的元素必须后移!
// 定义一个临时变量存储后一个元素
int temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
}
}

二分查找

public static int binarySerach(int[] arr , int number){
// 3.记录当前区间搜索的开始索引和结束索引。
int start = 0 ;
int end = arr.length - 1;
// 4.定义一个循环,反复去循环元素。
while(start <= end){
// 5.取中间索引位置
int middleIndex = (start + end) / 2 ;
// 6.判断当前元素与中间元素的大小
if(number < arr[middleIndex]){
// 7.往左边继续寻找,结束索引应该-1
end = middleIndex - 1;
}else if(number > arr[middleIndex]){
start = middleIndex + 1;
}else if(number == arr[middleIndex]){
return middleIndex;
}
}

回文数判断

判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
//示例1,输入: 121,输出: true
//示例2,输入: -121,输出: false,解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
//示例3,输入: 10,输出: false,解释: 从右向左读, 为 01 。因此它不是一个回文数
StringBuffer str=new StringBuffer(strSc)
Boolean result=true;
for(int i=0;i if(Integer.Valueof(str.charAT(i))!=Integerstr.valueof(charAT(str.length-1-i)))(
result=false
)
}

输入"hello world java",输出"java world hello"

String [] str=stringBuffer.split(" “);
for(int i=str.length-1;i>0;i–){
outstr.append(str[i]+” ")
}
outstr.append(str[0]);

Linnux命令

在一个log文件中,统计包含"ERROR"的次数
cat XXX.log|grep ‘’|grep ‘ERROR’| wc -l
查找当前端口号是否被占用
1.netstat -anp |grep 端口号
查看当前所有已经使用的端口情况
2.netstat -nultp(此处不用加端口号)测试开发面试题_第19张图片
chmod ugo+r file1.txt
测试开发面试题_第20张图片

redis

redis特性

1、执行速度快
redis数据读写速度非常快,因为它把数据都读取到内存当中操作,而且redis是用C语言编写的,是最“接近“”操作系统的语言,所以执行速度相对较快。Redis能读的速度是110000次/s,写的速度是81000次/s。
2、持久化:
redis虽然数据的读取都存在内存当中,但是最终它是支持数据持久化到磁盘当中
3、操作的数据结构丰富
redis提供了丰富的数据结构,Redis的值不仅可以是字符串,它还可以是其他五中数据机构中的任意一种
字符串、列表、散列、集合、有序集合
字符串、hash、set、zset、list。
redis的所有操作都是原子性,支持事务,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
redis支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。
复制功能是分布式Redis的基础。

redis缓存穿透

前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果
描述:
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求。这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义
在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。
当我们查找不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大
解决方案
从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击

缓存击穿

描述:
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
解决方案:
1、设置热点数据永远不过期。
2、接口限流与熔断,降级。重要的接口一定要做好限流策略,防止用户恶意刷接口,同时要降级准备,当接口中的某些 服务 不可用时候,进行熔断,失败快速返回机制。
3、布隆过滤器。bloomfilter就类似于一个hash set,用于快速判某个元素是否存在于集合中,其典型的应用场景就是快速判断一个key是否存在于某容器,不存在就直接返回。布隆过滤器的关键就在于hash算法和容器大小,
4、加互斥锁,互斥锁参考代码如下:

redis雪崩效应

描述:
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是, 缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决:
缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
设置热点数据永远不过期。

你可能感兴趣的:(面试,mysql,java,数据库,面试)