目录
String,StringBuffer与StringBuilder的区别
synchronized 和 volatile和lock 区别
垃圾回收器的基本原理?可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?
1.面试官您好,我叫谢莫凡,是一名大四在校生,来自南京邮电大学通达学院,我学的专业是计算机科学与技术。 2.在校期间,担任心协活动部副部长,主要负责心协活动的策划和组织,确保活动顺利进行; 大二的时候,拿过学院篮球比赛冠军 ,所以我的沟通交流能力和团队合作能力还是不错的; 在暑假期间,我在教育机构做过助教,主要负责招生以及向学生家长反馈学生的学习情况,接触到不同的群体,也提高我的沟通交流能力以及面对各种问题的处理能力。 3.我在学校主修的课程主要是C语言、c++、数据库等。 4.在校期间,我报了艾瑞培训班,系统的学习了linux操作系统,数据库,java以及框架,并对数据库进行了巩固学习。在暑假的时候,我跟着导师做了电商后台管理项目,通过做项目我感觉还是对java开发比较感兴趣的,所以我的求职意向也是java开发。 5.我的自我介绍大概就是这样,谢谢!
分组做的,该系统模拟了电商后台管理,主要功能有用户管理、用户地址管理、商品分类管理、商品品牌管理,商品规格管理,商品规格属性管理,商品spu管理,商品sku管理,商品评论管理,商品订单管理。
我主要负责的是:1.用户登录页面:用户登录功能和身份校验以及注销 2.品牌管理模块:品牌信息的多条件分页查询功能
**用户登录和身份校验: 1.http 协议是无状态的 2.项目中设置了拦截器,然后拦截器设置了需要拦截的用户资源和放行的游客资源 通过拦截器配置类设置了需要拦截的用户资源 3.通过登录获取密钥的过程: 首先登录页面和登录接口都是属于游客资源不被拦截器拦截 用户请求登录页面,在登录页面中输入用户名和密码点击登录按钮后发起ajax请求登录接口 在后端的业务层中连接数据库进行用户名和密码的校验 校验失败返回错误报文,成功生成密钥,密钥存储在session和cookie中 session保存在服务器本地,cookie随着响应报文一起返回给浏览器 以后该用户的每一次http请求都会自动携带带有密钥的cookie 在拦截器中进行校验用户状态的时候,就能够通过拦截器的状态校验 4.拦截器的校验逻辑: 当用户没有登陆获取到带有密钥cookie的时候,或者是密钥过期的时候,他只能请求没有被拦截器拦截的游客资源 当他登录成功,得到带有密钥的cookie后,在以后的每一次请求就能够通过拦截器的身份校验 拦截器的校验逻辑就是从每一次的请求中取出cookie,然后找到是存储密钥的cookie,取出密钥 再取出当前请求所对应session 对象,从session中取出密钥,然后判断cookie中的密钥和session中的密钥不为空且一致时 则放行,否则被拦截 5.注销功能 当用户点击注销按钮的时候,先销毁浏览器中存储密钥的cookie,再发送ajax通知服务器销毁session中的密钥。 在用户注销成功后,用户的密钥失效,以后发出的http请求就会被拦截器拦截,返回到登录页面。用户只有重新登录后才能获取新的密钥 -------------------------------------------------------------------------------------- **多条件分页查询: 1.分页在service: 后端在业务层service中编写多条件分页查询逻辑 通过selectPage()方法实现多条件+分页查询。 2.条件构造器使用: 首先创建条件构造器对象QueryWrapper,条件构造器对象自动动态生成where条件语句; 必须要让用户在请求报文中把currentPage和pageSize传给后端 前端发起请求的时候需要携带分页参数和查询条件参数 查询条件参数可以有,也可以没有,因为业务层的querywrapper是根据条件子句是否为空来判断是否生成该条件的sql语句片段; 3.分页器使用: 再创建分页器对象Ipage,分页器自动查询一共有多少条数据,自动计算分页的开始和结束以及一共多少页,并自动生成分页语句,最后把所有的数据存储到分页器对象中返回。 Ipage是分页器接口,Page是Ipage实现类。 分页器对象必须指定两个属性:1.你要查询第几页2.每页查询第几条。 也可以通过构造方法,传入这两个参数;也可以通过set方法传入这两个参数。 4.把分页对象Ipage和条件构造器对象querywrapper传给selectPage()方法,它返回一个新的分页器对象。 selectpage()只能实现单表查询,如果需要多表查询需要自定义方法并自己绑定sql语句 5.在使用到Mybatis分页功能的时候,需要创建一个Mybatis配置类。 向IoC容器注册一个分页拦截器对象,就是告诉mybatis数据库是什么类型的数据库。 因为不同的数据库实现分页不一样,需要在分页拦截中指定数据库类型。 ------------------------------------------------------------------------------- 用户添加收货地址功能: 1.用户的id在用户表中是主键,在地址表中是外键,用户表对地址表是一个一对多的关系 2.再添加收获地址的时候,用户可以指定这个地址为默认地址,收货地址表中通过is_default字段来表达这个地址是不是默认收货地址,0是非默认,1是默认 3.用户可以有多个收货地址,但只可以有一个默认地址,因此在添加收获地址的时候,在业务层中,需要先将其他地址都改为非默认地址,即将其他地址的is_default字段改为0,然后再插入这个默认地址 因为这个业务功能中,既包含修改又包含插入操作,破坏了业务的原子性,因此在这个业务方法中必须开启事务 4.jdbc传统开启事务的方式是使用collection对象的setAutoCommit方法,然后判定所有的数据库操作都成功后,使用commit方法提交事务,当出现异常的时候,在catch块中调用rollback方法进行回滚,这种事务逻辑和业务逻辑是耦合在一起的 5.spring框架提供了声明式的事务处理注解,来实现将事务和业务代码进行解耦合,注解名是@Transactional,该注解会在业务层中开启事务后执行数据库操作,该注解必须指定一个rollbackFor属性来决定在何时回滚事务,属性的取值是指定一个异常类型,这个异常类型可以用Exception.class或者是他的子类.class,如果是Exception.class任何异常都会回滚,不出异常业务结束后就会提交事务
八种基本数据类型
四类八种-- 作为成员变量默认值 整型:byte(1)-2^7--2^7-1 0 short(2)-2^15--2^15-1 0 int(4)2^31 0 long(8)2^63 0L 浮点型:float(4)32位分为 1个符号位+8位指数位+23个尾数位 0.0F double(8)64位:1个符号位+11位指数位+52个尾数位 0.0D 字符型:char(2个字节) '\u0000' 布尔型:boolean(1个字节)true false false Java中整数,默认是int类型 Java中小数,默认是double类型的
访问修饰符
private : 在同一类内可见。 使用对象:变量、方法。 不能修饰类(外部类) default : 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。 protected : 对同一包内的类和所有子类可见。 使用对象:变量、方法 不能修饰类(外部类)。 public : 对所有类可见。 使用对象:类、接口、变量、方法
值传递和引用传递区别
值传递:在方法调用时,传递的是值的拷贝,传递后互不相关。 引用传递:在方法调用时,传递的参数是按引用进行传递,传递的引用的地址,即变量所对应的内存空间的地址。传递的是值的引用,传递前和传递后都指向同一个引用(也就是同一个内存空间)。
final:可以修饰类、变量、方法 修饰类 -该类不能被继承 修饰变量 -该变量是一个常量 ,且不能被重新赋值 被final修饰不可变的是变量的引用,而不是引用指向的内容,引用指向的内容是可以改变的 修饰方法 -该方法不可以被重写 finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码放在finally代码块 中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。 finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调用,当我们调用System.gc() 方法的时候,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的 最后判断。 ------------------------------------------------------------------------- this:表示当前对象(实例) this可以调用类中的成员变量、普通方法、构造方法。 this调用构造方法的时候,只能在构造方法中使用,且必须是第一行。 super:代表父类对象 super只能出现在子类的方法和构造方法中 在子类构造方法中调用父类构造方法,必须是第一句 super不可以访问父类中定义为private的属性和方法 static可以修饰成员变量、成员方法,用static修饰变量,称为静态变量,也称类变量 static修饰的变量可以通过类名直接访问 static修饰变量,可以被所有的实例(对象)共享,可以作为实例之间交流的共享数据 如果类的所有实例都包含一个相同的常量属性,可以将这个属性定义为静态的,从而节省内存空间。
1*静态变量和实例变量区别 静态变量:属于类,不属于实例对象,在内存中只会有一份,在类的加载过程中,JVM只为静态变量分配一次内存空间。 实例变量:属于实例对象,每次创建对象,都会为每个对象分配成员变量内存空间,在内存中,创建几次对象,就有几份成员变量。 -------------------------------------------------------------------- 2*静态变量与普通变量区别 静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。 static变量也称作静态变量 static成员变量的初始化顺序按照定义的顺序进行初始化。 非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。 ---------------------------------------------------------------------- 3**静态方法和实例方法有何不同? ①调用静态方法无需创建对象 调用静态方法: “类名.方法名” "对象名.方法名" 实例方法: "对象名.方法名" ②静态方法,只能访问静态成员(即静态成员变量和静态方法),不能访问实例成员变量和实例方法; 实例方法没有这个限制 * 静态方法内调用非静态成员为什么是非法的? 答:因为静态方法可以不通过对象进行调用
面向对象是模型化的,我们只需抽象出一个类,这是一个封闭的盒子,在这个盒子里,你拥有数据,也拥有解决问题的方法。需要什么功能直接使用就可以了,不用去一步一步的实现. 其实就是把面向过程抽象成类,然后封装,方便我们使用的就是面向对象。 面向对象三大特征 封装 继承 和多态 .抽象:抽象就是抽取,抽取共有的属性和功能 .封装:通过private关键字实现。 封装的目的在于 保护数据,将设计者与使用者分开,将数据私有化。 如果要想访问被封装的属性和方法,必须创建对外的访问接口 .继承:一个类继承另一个类,继承的类称为子类、派生类,被继承的类称为父类、基类、超类。 子类继承父类 继承了父类的所有属性和方法,包含私有的,私有的不能直接访问 继承的好处:*,提高了代码的复用性 维护性 *,让类和类之前产生了一个关系,是多态的前提 .多态性:一种声明多种表现形式。一个接口,多种方法。 Java中包含两种多态性: 编译时多态:通过方法重载实现。 运行时多态:通过方法重写实现。 ****实现多态有三个必要的条件: 继承、重写、向上转型。**** .继承:存在有继承关系的子类和父类。 .重写:子类对父类中某些方法进行重新定义,在调用这些方法的时候就会调用子类的方法 .向上转型:将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。 ***多态的实现原则:当父类对象引用变量引用子类对象时,被引用对象的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在父类中定义过的,也就是说被子类覆盖的方法。
重载:在同一个类中-- 方法名相同 ;
参数列表不同(参数类型不同、个数不同、顺序不同) ;
与方法返回值和访问修饰符无关
重写:在父子类中--方法名、参数列表必须相同,
返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类
如果父类方法访问修饰符为private则子类中就不是重写。
**构造器不能被重写,因为不能被继承,但可以被重载。
1、什么是抽象类?什么是接口?
抽象类是用来捕捉子类的通用特性的。(类的抽象)(是一种模板设计)
接口是抽象方法的集合。(行为的抽象),(是一种行为的规范)。
2、相同点:
接口和抽象类都不能实例化
都位于继承的顶端,用于被其他实现或继承
都包含抽象方法,其子类都必须覆写这些抽象方法
3、不同点:
声明上: 抽象类使用abstract关键字声明 接口使用interface关键字声明
实现: 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法实现
子类使用implements关键字来实现接口。它需要提供接口中所有声明的方法的实现
构造器: 抽象类可以有构造器 接口不能有构造器
访问修饰符: 抽象类中的方法可以是任意访问修饰符
接口方法默认修饰符是public。并且不允许定义为 private 或者 protected (注:Java8中接口中引入默认方法和静态方法)
多继承: 抽象类一个类最多只能继承一个抽象类 接口一个类可以实现多个接口
字段声明: 抽象类的字段声明可以是任意的 接口的字段默认都是 static 和 final 的
String类底层使用字符数组保存字符串--string对象是不可变的---线程安全。 每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。 如果要操作少量的数据用 = String StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,这两种对象都是可变的。 AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。 StringBuffer对方法加了同步锁或者对调用的方法加了同步锁---是线程安全的。 StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。 多线程操作字符串缓冲区 下操作大量数据 = StringBuffer StringBuilder并没有对方法进行加同步锁---非线程安全的。 StirngBuilder 相比使用StringBuffer 仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。 单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
(1)在类中的位置不同
成员变量:类中方法外
局部变量:方法定义中或者方法声明上
(2)在内存中的位置不同
成员变量:在堆中
局部变量:在栈中
(3)生命周期不同
成员变量:随着对象的创建而存在,随着对象的消失而消失
局部变量:随着方法的调用而存在,随着方法的调用完毕而消失
(4)初始化值不同
成员变量:有默认值
局部变量:没有默认值,必须定义,赋值,然后才能使用
要使用面向对象 ,必须要创建对象,并且指定他们的初始状态,然后通过创建好的对象,调用属性和方法。 创建对象必须要使用构造函数来构造一个新的实例。 构造函数的作用就是用来构造对象,并初始化的 构造函数的特点: 1.构造函数的方法名和类名相同 2.构造函数没有返回值类型 3.构造函数可以是0个,1个,多个参数 4.类中如果没有构造函数,系统会默认的提供一个无参构造函数 5.类中一旦写了其他的构造函数,系统默认的无参构造函数就失效了,如果使用无参构造,需要自己创建 6.构造函数主要作用就是完成类对象的初始化工作的 7.构造函数不能由编程人员显示的直接调用,使用new关键字调用 8.一个类中可以多个构造函数
1、String类是一个最终类,不能被继承 是固定长度的字符串,一旦创建不可更改 2、String类的常用方法: charAt(int index):返回指定的下标的字符 indexOf():返回字符或字符串第一次在字符串中的出现的下标位置 没有返回-1 valueOf():把其他数据类型变为String类型 toCharArray():把字符串变成字符数组 substring():截取指定下标位置的字符或字符串
按照流的流向分: 输入流 输出流
操作单元: 字节流 字符流
角色 : 节点流 处理流
InputStream/Reader: 所有的输入流的基类,
InputStream是字节输入流,Reader是字符输入流。
OutputStream/Writer: 所有输出流的基类,
OutputStream是字节输出流,Writer是字符输出流。
NIO:同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
== : 判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。
(基本数据类型 == 比较的是值,
引用数据类型 == 比较的是内存地址)
equals() : 判断两个对象是否相等。但它一般有两种使用情况:
情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。
情况2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。
运行时异常: 运行时异常都是RuntimeException类及其子类,
是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的。
NullPointerException空指针异常
ArrayIndexOutBoundException数组下标越界异常
ClassCastException类型转换异常
ArithmeticExecption算术异常
检查(编译)时异常: 程序本身没有问题由于外部原因导致的异常
ClassNotFoundException(没有找到指定的类异常)
IOException(IO流异常),
throw 写在方法体内 后面跟一个异常对象,表示此处抛出一个异常对象
throw是一个流程控制关键字
一个方法中走到了throw以后,后面的代码都不会执行了,直接抛出异常对象
所以,throw是跟return一个级别的存在
方法中如果执行了throw,连return都不可能再执行了
throws写在方法签名上 后面跟的是异常的类型,表示这个方法抛出了一个什么类型的异常 用来告知上层调用者
throws只是一个方法声明的关键字
1、什么是反射机制? 可以不用去new对象,去实现类中方法的调用、属性的赋值 对于任意一个类,都能够知道这个类的所有属性和方法; 对于任意一个对象,都能够调用它的任意一个方法和属性; 2、获取反射的三种方法 1.new一个对象 2.路径class.forName 3.类名 3、反射机制优缺点 优点: 运行期类型的判断,动态加载类,提高代码灵活度。 缺点: 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的java代码要慢很多。
1.加载驱动 2.通过drivermanager获取连接 3.编写sql 4.操作sql 启动sql的对象 5.查询返回结果集 6.关闭资源 JDBC中的Statement 和PreparedStatement的区别? PreparedStatement 继承于 Statement Statement 一般用于执行固定的没有参数的SQL PreparedStatement 一般用于执行有?参数预编译的SQL语句。 PreparedStatement支持?操作参数,相对于Statement更加灵活。 PreparedStatement可以防止SQL注入,安全性高于Statement。
序列化就是指将Java对象转换为字节码的过程,Serializable可序列化接口 transient 让数据不能序列化 ,对没有序列化的数据赋予初始值 反序列化就是将字节流还原成对象
内存溢出是指申请内存时,没有足够大的存储空间供对象使用 内存泄漏是对象申请内存后,无法释放申请的内存空间
(数组,栈,链表,队列,树,图,堆,散列表) *数组是一种连续存储线性结构,元素类型相同,大小相等,数组是多维的,通过使用整型索引值来访问他们的元素,数组尺寸不能改变。 数组的优点: 存取速度快 数组的缺点: 事先必须知道数组的长度 插入删除元素很慢 空间通常是有限制的 需要大块连续的内存块 插入删除元素的效率很低 *链表 链表优点 空间没有限制 插入删除元素很快 链表缺点 存取速度很慢 *栈是一种特殊的线性表,只能在线性表的一端操作,栈顶允许操作,栈底不允许操作。 栈的特点是:先进后出,或者说是后进先出,从栈顶放入元素的操作叫入栈,取出元素叫出栈。 栈的结构就像一个集装箱,越先放进去的东西越晚才能拿出来,所以,栈常应用于实现递归功能方面的场景,例如斐波那契数列队列是先进先出
冒泡排序 快速 选择 插入 1.冒泡: 重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。 走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。 (1)比较相邻的元素。如果第一个比第二个大,就交换它们两个; (2)对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数; (3)针对所有的元素重复以上的步骤,除了最后一个; 重复步骤1~3,直到排序完成。 2.快速排序: 通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。 快速排序使用分治法来把一个串分为两个子串。 (1)从数列中挑出一个元素,称为 “基准”; (2)重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区操作; (3)递归地把小于基准值元素的子数列和大于基准值元素的子数列排序。
单例模式: 定义:单例对象的类必须保证只有一个实例存在,整个系统只能使用一个对象实例 优点:不会频繁地创建和销毁对象,浪费系统资源 单例模式有多种写法: (1)饿汉式:提前就已经加载好的静态static对象 线程安全 (2)懒汉式:用的时候再去创建对象 非线程安全 (3)双检锁:两次检查避免多线程造成创建了多个对象 线程安全 懒汉式和饿汉式的区别: 1.懒汉式不到必要的时候不出案件单例实例,饿汉式在类加载的时候就创建了单例 2.懒汉式线程不安全,饿汉式线程安全 线程安全值得是多线程环节下确保对象仅被创建一次 3.懒汉式当方法被调用时才会创建实例,如果程序由始至终都没有调用该方法,那么单例实例就永远不会被创建,节省了内存的空间和资源 4.饿汉式在类加载的是hi就创建单例对象,并且把单例变量一直存放在内存的方法区中,直到程序结束才会把单例对象销毁,如果程序由始至终都没使用该实例,将会造成资源浪费
list、set、map区别
List , Set 都是继承自Collection 接口,都是单列集合,只存储数据, map不继承collection接口
/*List :一个有序容器 元素可以重复 可以插入多个null元素。 最大的特点是元素都有索引。
(常用的实现类有 ArrayList、LinkedList 和 Vector。)
List 支持for循环,也就是通过下标来遍历,也可以用迭代器
List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变
/*Set :无序 不可以存储重复元素 只允许存入一个null元素,必须保证元素唯一性。
(Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。)
set只能用迭代,因为他无序,无法用下标来取得想要的值
Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变
/*Map :是一个键值对集合,存储键和值之间的映射。 Key无序,唯一;value 不要求有序,允许重复。
从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。
(Map 的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、ConcurrentHashMap)
ArrayList 基于动态数组的 不同步 ---不保证线程安全
随机访问的时候效率高,( 因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。)
LinkedList基于双向链表的 不同步---不保证线程安全
在非首尾的增加和删除操作的时候效率高一些,(因为 ArrayList 增删操作影响数组内的其他数据的下标。)
占内存,(因为 LinkedList 的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素)
Vector:底层的数据结构是数组,线程同步的,Vector无论查询和增删都巨慢
1、HashMap概述: 基于哈希表的Map接口的非同步实现。
继承Abstractmap类,是散列分布存储的,
通过key/value结构实现,允许使用null值和null键。不保证映射的顺序---不保证该顺序恒久不变。
2、HashMap的数据结构:
1.7是基于数组+链表的,
1.8做了优化,当链表中的节点数据超过8个之后,该链表会转为"红黑树"来提高查询效率(数组+链表+红黑二叉树)
3、HashMap 基于 Hash 算法实现的 :
当我们往Hashmap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标
存储时,如果出现hash值相同的key,此时有两种情况。(1)如果key相同,则覆盖原始值;(2)如果key不同(出现冲突),则将当前的key-value放入链表中
获取时,直接找到hash值对应的下标,再进一步判断key是否相同,从而找到对应值。
1.数据结构不同: 1.7基于数组+链表 1.8基于数组+链表+红黑二叉树 2.插入方式的改变: 1.7是头插法 1.8是尾插法 3.扩容方式: 1.7是先扩容 再添加 1.8是先添加 再扩容 4.hash函数的变化: 1.7 hash函数经过四次位运算 1.8只有一次位运算 使用了扰动函数使hash碰撞更均匀 5.扩容后的元素位置: 1.7是需要重新计算元素的hash和位置 1.8要么是当前下标 要么是当前下标+扩容的长度
1.HashMap 是非线程安全的,Hashtable是线程安全的。(HashTable 内部的方法基本都经过 synchronized 修饰。 2.Hashmap 比hashtable效率高一点,hashtable基本不使用,淘汰了 3.HashMap 允许 key 和 value 为 null,并且这样的null键只有一个,null值可以有一个或者多个。 Hashtable 不允许键和值为null。(HashTable 中 put 进的键值只要有一个 null,直接抛空指针异常。) 4.HashMap 的默认初始容量为 16,Hashtable 为 11。 HashMap 的扩容为原来的 2 倍,Hashtable 的扩容为原来的 2 倍加 1。 创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小。 HashMap 总是使用2的幂作为哈希表的大小. 5底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。 6.推荐使用:在 Hashtable 的类注释可以看到,Hashtable 是保留类不建议使用,推荐在单线程环境下使用 HashMap 替代,如果需要多线程使用则用 ConcurrentHashMap 替代。 7.HashMap 的 hash 值重新计算过,Hashtable 直接使用 hashCode。 HashMap 去掉了 Hashtable 中的 contains 方法。 HashMap 继承自 AbstractMap 类,Hashtable 继承自 Dictionary 类。
HashMap 实现了Map接口 存储键值对 调用put()向map中添加元素 HashMap使用键(Key)计算Hashcode HashMap相对于HashSet较快,因为它是使用唯一的键获取对象 HashSet 实现Set接口 仅存储对象 调用add()方法向Set中添加元素 HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,如果两个对象不同的话,那么返回false HashSet较HashMap来说比较慢
*线程和进程区别 *
进程 :一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程。
线程 :进程中的一个执行任务(控制单元),负责当前进程中程序的执行。
一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。
根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位
资源开销:每个进程都有独立的代码和数据空间,程序之间的切换会有较大的开销;
线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;
线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。
内存分配:同一进程的线程共享本进程的地址空间和资源,
而进程之间的地址空间和资源是相互独立的
影响关系:多进程要比多线程健壮。
因为一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。
执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。
但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行
***线程安全:指某个方法在多线程环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。
多线程:一个进程中同时运行多个线程,用于完成不同的工作 多线程的好处: 可以提高 CPU 的利用率。在多线程程序中,一个线程必须等待的时候,CPU 可以运行其它的线程而不是等待,这样就大大提高了程序的效率。也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。 多线程的劣势: 线程也是程序,所以线程需要占用内存,线程越多占用内存也越多; 多线程需要协调和管理,所以需要 CPU 时间跟踪线程; 线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题。 多线程设计模式: Producer-Consumer模式(生产者-消费者) 在多线程编程中经常有一个生成数据以及一个消费数据的两个模块,这两者的速率通常是不相等的,所以为了避免等待,该模式中引入了通道的概念。 生产者将产生的数据放入一个队列,消费者则从队列中拿数据。并且生产者与消费者是完全解耦的。 改进版本中也有用多个通道进行消费,减少锁竞争,再者也可以用工作窃取的方式进行负载均衡
A.什么是死锁? 当线程 A 持有独占锁a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的情况下,就会发生 AB 两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。 某一个同步块,同时拥有两个以上的对象的锁,就可能会发生死锁 出现死锁后,不会有异常提示,只是所有线程处于阻塞状态,无法继续。 多线编程的时候,需要避免死锁发生 B.出现死锁的必要条件: 1,互斥条件:一个资源每次只能被一个进程使用 2,请求和保持条件:一个进程因为请求资源阻塞的时候,对获得的资源保持不放 3,不剥夺条件:进程已经获得资源,未使用之前,不能强行剥夺 4,循环等待条件:若干进程之间形成头尾详解的循环等待资源关系 C.如何避免线程死锁? 破坏产生死锁的四个条件中的其中一个就可以了。
继承 Thread 类; 实现 Runnable 接口; 实现 Callable 接口; 用 Executors 工具类创建线程池; 其中,Thread 其实也是实现了 Runnab1e 接口。Runnable 和 Callable 的主要区别在于是否有返回值。 run 方法无返回值; call 方法有返回值 run 方法只能抛出运行时异常,且无法捕获处理; call 方法允许抛出异常,可以获取异常信息
1.新建:新创建了一个线程对象。 2.可运行(就绪):线程对象创建后,当调用线程对象的 start()方法,该线程处于就绪状态,等待被线程调度选中,获取cpu的使用权。 3.运行:可运行状态(runnable)的线程获得了cpu时间片,执行程序代码。 线程要想进入运行状态执行,首先必须处于就绪状态中; 4.阻塞:处于运行状态中的线程由于某种原因,暂时放弃对 CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被 CPU 调用以进入到运行状态。 阻塞的情况分三种: (一). 等待阻塞:运行状态中的线程执行 wait()方法,JVM会把该线程放入等待队列中,使本线程进入到等待阻塞状态; (二). 同步阻塞:线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用),则JVM会把该线程放入锁池中,线程会进入同步阻塞状态; (三). 其他阻塞: 通过调用线程的 sleep()或 join()或发出了 I/O 请求时,线程会进入到阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。 5.死亡:线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。run() 可以重复调用,而 start() 只能调用一次。 start()方法来启动一个线程,真正实现了多线程运行。调用start()方法无需等待run方法体代码执行完毕,可以直接继续执行其他的代码; 此时线程是处于就绪状态,并没有运行。 然后通过此Thread类调用方法run()来完成其运行状态, run()方法运行结束, 此线程终止。然后CPU再调度其它线程。 run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接调用用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。 ***为什么我们不能直接调用 run() 方法? 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。
wait会释放对象锁 sleep不会释放对象锁 wait是Object类下的普通方法 sleep是Thread线程类下的静态方法 Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。 wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用wait(long timeout)超时后线程会自动苏醒。
并发:两个或多个事件在同一时间间隔发生。多个线程同时操作一个对象 并行:两个或者多个事件在同一时刻发生。 并行是真正意义上,同一时刻做多件事情,而并发在同一时刻只会做一件事件,只是可以将时间切碎,交替做多件事情。 网上有个例子挺形象的: 你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。 你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。 你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。
线程同步:多个线程之间同时执行一段代码,是顺序执行,执行完一个再执行下一个需要等待协调运行。
线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。
对于同步,在具体的代码中需要完成以下两种操作:
--把竞争访问的资源标识为private
--同步那些修改变量的代码,会用synchronized关键字同步方法和代码
线程异步:和同步是相对的,多个线程之间彼此是独立的。多线程就是实现异步的一种方式。
*修饰实例方法: 作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁 *修饰静态方法: 也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。 *修饰代码块: 指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。 总结: synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。synchronized 关键字加到实例方法上是给对象实例上锁。尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓存功能!
synchronized 表示只有一个线程可以获取作用对象的锁,执行代码,阻塞其他线程。 volatile 表示变量在 CPU 的寄存器中是不确定的,必须从主存中读取。保证多线程环境下变量的可见性;禁止指令重排序。 *synchronized 和 volatile区别: volatile 是变量修饰符;synchronized 可以修饰类、方法、变量。 volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。 volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。 volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。 volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。但是volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,实际开发中使用 synchronized 关键字的场景还是更多一些。 *synchronized 和 Lock 有什么区别? synchronized是Java内置关键字,在JVM层面,Lock是个Java类; synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。 synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
servlet的生命周期 执行原理
*生命周期: 1,被创建阶段 : 执行init()方法,这个方法只执行一次 Servlet默认情况下,第一次被访问的时候创建 init()主要用来加载资源 2,提供服务阶段: 执行service() 方法,可以执行多次,每次访问servlet,service()方法,都会被调用一次 3,被销毁阶段:执行destroy()方法,只执行一次,servlet被销毁时执行,也就是服务关闭的时候,servlet被销毁,只有服务器正常关闭,才会执行此方法。destroy()方法,在servlet被销毁之前执行,一般用于资源释放 *servlet执行原理: 1,当服务器接收到客户端浏览器的请求后,会解析请求的url路径,获取访问servlet资源路径 2,查找web.xml文件 ,是否存在对应的标签体内容 3,如果有,找到对应的 中的全类名 4,tomcat会自动将全类名对应的字节码文件加载进内存,通过反射,创建对象 5,调用方法
1,cookie是存放在客户端 session是存放在服务器端
2,cookie大小有限制4k session存放数据大小没有限制
3,cookie相对来说不安全,Session数据相对安全
*最大的区别:生存周期,一个是IE启动到IE关闭.(浏览器页面一关 ,session就消失了),
cookie是预先设置的生存周期,或永久的保存于本地的文件。
转发的特点: 1,转发的url地址不会改变 2,转发是一次请求,可以使用request域对象来共享数据 3,转发只能访问当前服务器资源 重定向的特点: 1,重定向的url地址会发生改变 2,重定向是两次请求,且不能使用request域对象来共享数据 3,重定向可以访问其他服务器的资源,比如访问百度
getParameter()是获取POST/GET传递的参数值; getInitParameter获取Tomcat的server.xml中设置Context的初始化参数 getAttribute()是获取对象容器中的数据值; getRequestDispatcher是请求转发。
四种请求方式: get 请求资源(从服务器)-查询 post 发送资源 (向服务器)-添加修改删除 put 更新资源(向服务器)-修改 delete 删除资源(向服务器)-删除 常见状态码: 200:请求成功,302(表示重定向),304(表示访问缓存),404 请求路径没有对应的资源, 405:请求方式没有对应的doxxx方法, 500:服务器内部错误
携带的数据的存放位置不一样: get请求数据拼接在url后面 post请求数据放在请求体中 1.安全性:get请求url暴露在外面,例如密码敏感信息,不安全,post安全性高于get 2.数据的大小:get请求在url中传送的参数是有长度限制的(2KB 4KB) post没有(16MB) 3.效率:get请求比post请求速度快,因为get请求数据在url中,解析快
ajax是jquery封装的一个方法,这个方法就是实现http协议的异步请求方法。 AJAX最大的特点是:可以实现动态不刷新(局部刷新),就是能在不更新整个页面的前提下维护数据。 ajax实现方式: 1.原生的JavaScript方式实现--创建XMLHttpRequest对象 2.使用Jquery来实现 (1,$.ajax() -2,$.get() :发送get请求 -3,$.post() :发送post请求) 如何使用Ajax发起一个http请求: 1.引入jquery 2.使用$对象调用ajax函数 3.在url参数中指定请求地址 4.在method参数中指定请求方式 5.在data参数中指定携带的数据 6.在dataType参数中约定返回的响应报文中的响应体中的数据类型 7.在success回调函数中编写响应成功的逻辑 8.在error回调函数中编写响应失败的逻辑
建立在客户端和服务端相互通信的基础上 同步:客户端必须要等待服务器响应,在等待期间,客户端不能做其他事 异步:客户端不必去等待服务端响应,在服务器端处理请求的过程中,客户端可以进行其他操作
JVM的内存结构
*程序计数器:当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成; *java虚拟机栈:用于存储局部变量表、操作数栈、动态链接、方法出口等信息; *本地方法栈:与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的; *java 堆:Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存; *方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。
1.标记-清除算法:分为标记和清除两个阶段,首先从跟集合进行扫描,对存活的对象进行标记,然后对堆内存从头到尾进行线性遍历,回收不可达对象的内存 2.复制算法:原理是将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了,就将还存活的对象复制到另一块上,然后再把已经使用过的内存空间一次清理掉 3.标记-整理算法:标记过程跟标记-清除算法一致,都是对存活对象进行标记,但是后续步骤不是直接对可回收对象进行清理,而是移动所有的存活对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收,适用于存活率高的场景 4.分代收集算法:分代收集算法是根据对象的存活周期的不同,将内存划分为不同的区域,然后针对不同区域采用不同的垃圾回收算法。比如新生代存活率低,采用复制算法,老年代存活率高,使用标记清除或者标记整理算法
优点: 使java程序员在编写程序时不再考虑内存管理的问题。
java中的对象不再有“作用域”的概念,只有引用的对象才有“作用域”。
有效的防止了内存泄露,可以有效的使用可使用的内存。
垃圾回收器通常作为一个单独的低级别的线程运行,在不可预知的情况下对内存堆中已经死亡的或很长时间没有用过的对象进行清除和回收。
程序员不能实时的对某个对象或所有对象调用垃圾回收器进行垃圾回收。
垃圾回收有:分代复制垃圾回收、标记垃圾回收、增量垃圾回收。
对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。 通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空间。 可以。程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。
启动类加载器 扩展类加载器 应用程序类加载器 自定义类加载器
加载:根据查找路径找到相应的 class 文件然后导入; 验证:检查加载的 class 文件的正确性; 准备:给类中的静态变量分配内存空间; 解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址; 初始化:对静态变量和静态代码块执行初始化工作。
关系型数据库和非关系型数据库
-关系型数据库
MySQL Oracle DB2 SQLServer
基于二维表结构存储数据的文件型磁盘数据库
缺点: 因为数据库的特征是磁盘文件型数据库, 就造成每次查询都有IO操作, 海量数据查询速度较慢
-NoSQL数据库 (Not Only SQL)
Redis MongoDB
基于 key value 结构存储数据的内存数据库
优点: 因为数据库的特征是内存型数据库, 数据查询不需要进行文件IO操作, 因此这种数据库的读写速度极快
整数类型:tinyInt smallint mediumint int(integer) 小数类型:float double 日期类型:date year time 文本、二进制类型:char varchar *varchar与char的区别: -char表示定长字符串,长度是固定的; 如果插入数据的长度小于char的固定长度时,则用空格填充; 因为长度固定,所以存取速度要比varchar快很多,甚至能快50%,但正因为其长度固定,所以会占据多余的空间,是空间换时间的做法; 对于char来说,最多能存放的字符个数为255,和编码无关 -varchar表示可变长字符串,长度是可变的; 插入的数据是多长,就按照多长来存储; varchar在存取方面与char相反,它存取慢,因为长度不固定,但正因如此,不占据多余的空间,是时间换空间的做法; 对于varchar来说,最多能存放的字符个数为65532 总之,结合性能角度(char更快)和节省磁盘空间角度(varchar更小)
数据定义语言DDL CREATE,DROP,ALTER 数据查询语言DQL SELECT,FROM,WHERE,GROUP BY,ORDER BY 数据操纵语言DML INSERT,UPDATE,DELETE 数据控制功能DCL GRANT,REVOKE,COMMIT,ROLLBACK
范式的引入,主要是为了解决数据冗余、插入异常、删除异常、更新异常等问题 第一范式:每个列都不可以再拆分。 第二范式:在第一范式的基础上,非主键列完全依赖于主键,而不能是依赖于主键的一部分。 第三范式:在第二范式的基础上,非主键列只依赖于主键,不依赖于其他非主键。
非空约束NOT NULL: 用于控制字段的内容一定不能为空(NULL)。 唯一约束UNIQUE: 控件字段内容不能重复,一个表允许有多个 Unique 约束。 主键约束PRIMARY KEY: 也是用于控件字段内容不能重复,但它在一个表只允许出现一个。 外键约束FOREIGN KEY: 用于预防破坏表之间连接的动作,也能防止非法数据插入外键列,因为它必须是它指向的那个表中的值之一。 检测约束CHECK: 用于控制字段的值范围。
内连接: 只连接匹配的行 *左外连接: 以左表为主,先查询出左表,按照ON后的关联条件匹配右表,没有匹配到的用NULL填充,(LEFT JOIN) *右外连接: 以右表为主,先查询出右表,按照ON后的关联条件匹配左表,没有匹配到的用NULL填充,(RIGHT JOIN) 全外连接: 包含左、右两个表的全部行,不管另外一边的表中是否存在与它们匹配的行。 交叉连接: 生成笛卡尔积-它不使用任何匹配或者选取条件,而是直接将一个数据源中的每个行与另一个数据源的每个行都一一匹配
delete 属于DML 可回滚 表结构还在,删除表的全部或者一部分数据行 删除速度慢,需要逐行删除 truncate 属于DDL 不可回滚 表结构还在,删除表中的所有数据 删除速度快 drop 属于DDL 不可回滚 从数据库中删除表,所有的数据行,索引和权限也会被删除 删除速度最快 不再需要一张表---drop; 删除部分数据行---delete; 保留表而删除所有数据---truncate。
1.存储过程是一个预编译的SQL语句--优点是允许模块化的设计 只需要创建一次,以后在该程序中就可以调用多次。 如果某次操作需要执行多次SQL,使用存储过程比单纯SQL语句执行要快。 2.优点 1)存储过程是预编译过的,执行效率高。 2)存储过程的代码直接存放于数据库中,通过存储过程名直接调用,减少网络通讯。 3)安全性高,执行存储过程需要有一定权限的用户。 4)存储过程可以重复使用,减少数据库开发人员的工作量。 3.缺点 1)调试麻烦,但是用 PL/SQL Developer 调试很方便!弥补这个缺点。 2)移植问题,数据库端代码当然是与数据库相关的。但是如果是做工程型项目,基本不存在移植问题。 3)重新编译问题,因为后端代码是运行前编译的,如果带有引用关系的对象发生改变时,受影响的存储过程、包将需要重新编译(不过也可以设置成运行时刻自动编译)。 4)如果在一个程序系统中大量的使用存储过程,到程序交付使用的时候随着用户需求的增加会导致数据结构的变化,接着就是系统的相关问题了,最后如果用户想维护该系统可以说是很难很难、而且代价是空前的,维护起来更麻烦。
索引相当于是字典的目录,可以加快检索的速度,其实就是把无序的数据变成有序的查询。索引是一种数据结构, 数据量很大的情况下,能够大幅提高查询速度 1.索引的优点缺点 优点: 大幅度提升了检索速度 创建唯一索引,保证数据的唯一性 加快表和表之间的连接 使用分组和排序的时候,可以减少时间 缺点: 占据物理空间 对表中的数据进行DML操作的时候,索引需要动态的维护,提升了工作量 索引虽然好用,但也不能无限制使用。尽量在数据量大的情况下使用索引。 2.索引的建立原则 1.用于where判断 order排序和join的(on)、group by的字段上创建索引 2.索引的个数不能过多,一般6个,不超过8个,过多会浪费空间,更新变慢 3.过长的字段,建立前缀索引 4.离散度太低的字段(例如性别)不能建立索引** 5.频繁更新的值,不要作为索引或者主键 6.随机无序的值,不建议作为索引,例如身份证 7.联合索引把散列度高的值放在前面 (联合索引:使用多个字段同时建立一个索引) 8.创建复合索引而不是修改单列索引 9.**最左匹配原则/最左前缀原则----联合索引中覆盖的字段,最左边的字段必须出现在条件中,其他字段可以出现也可以不出现,如果出现了,那么他左边的字段也必须出现,否则B+树无法判断他前面的字段在左边还是右边。 3.索引的数据结构 索引是按照B+tree的结构进行存储的,非叶子节点不存储数据 (由于B+树的内部节点只存放键,不存放值,因此,一次读取,可以在内存页中获取更多的键,有利于更快地缩小查找范围。 B+树的叶节点由一条链相连,因此,当需要进行一次全数据遍历的时候,B+树只需要使用O(logN)时间找到最小的一个节点,然后通过链进行O(N)的顺序遍历即可。而B树则需要对树的每一层进行遍历,这会需要更多的内存置换次数,因此也就需要花费更多的时间) 4.索引有哪几种类型? 主键索引: 数据列不允许重复,不允许为NULL,一个表只能有一个主键。 唯一索引: 数据列不允许重复,允许为NULL值,一个表允许多个列创建唯一索引。 普通索引: 基本的索引类型,没有唯一性的限制,允许为NULL值。 全文索引: 是目前搜索引擎使用的一种关键技术。 5.聚簇索引和非聚簇索引的区别: 每张表中必然有一棵聚簇索引树,默认是主键字段,如果没有主键,会使用隐藏字段rowid创建聚簇索引树 其他的非主键字段创建索引都是非聚簇索引 非聚簇索引可以有 也可以没有 有的话 可以是一个也可以是多个 聚簇索引的最底层的叶子节点中绑定的是主键的值和对应的整行数据的磁盘地址 非聚簇索引的最底层的叶子节点中绑定的是数据和对应的主键
什么是回表查询? 当查询语句命中非聚簇索引,在非聚簇索引中得到主键的值 然后拿着主键的值去聚簇索引中查询整行数据的磁盘地址的过程称为回表查询 回表查询是导致索引变慢的原因: 如果查询语句发生了回表操作,即使有索引,也会很慢 先走非聚簇索引-拿到id->聚簇索引 怎么设计索引和SQL语句,才能避免回表查询? 1.如果要查询整行数据,使用主键字段作为条件,直接走聚簇索引查询,速度超快 2.如果不是查询整行数据,禁止使用select*,而是查什么字段写什么字段
事务是对数据库中一系列操作进行统一的回滚或者提交的操作,主要用来保证数据的完整性和一致性。 1.原子性:事务工作是原子单元,事务中的操作,要么全部执行,要么全部不执行,不能只完成部分操作。 2.一致性:事务开始之前,数据库处于一致的状态,事务结束之后,数据仍然要处于一致状态。比如:银行转账的时候,要保证两个账户的金额之和不变。 3.隔离性:系统必须保证事务不受其他的并发事务的影响,就是当同时有多个事务运行时,每个事务之间都是相互隔离的,不可以互相干扰。 4.持续性:一个已经完成的事务对数据所做的任何变动,在系统中是永久有效的,即使事务产生了错误的结果,这个错误也将一直保存。
READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。 READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。 REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。 SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。 **注意:Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别
1.在读未提交级别下,读取数据不需要加共享锁,这样就不会跟被修改的数据上的排他锁冲突 2.在读已提交级别下,读操作需要加共享锁,但是在语句执行完以后释放共享锁; 3.在可重复读级别下,读操作需要加共享锁,但是在事务提交之前并不释放共享锁,也就是必须等待事务执行完毕以后才释放共享锁。 4.串行化 是限制性最强的隔离级别,因为该级别锁定整个范围的键,并一直持有锁,直到事务完成。
*对MySQL的锁了解吗? 当数据库有并发事务的时候,可能会产生数据的不一致,这时候需要一些机制来保证访问的次序,锁机制就是这样的一个机制。 -- 就像酒店的房间,如果大家随意进出,就会出现多人抢夺同一个房间的情况,而在房间上装上锁,申请到钥匙的人才可以入住并且将房间锁起来,其他人只有等他使用完毕才可以再次使用。 *锁的类型 共享锁(读锁),共享锁可以与共享锁共存 其他事务只能读 不能改 排它锁(写锁),排它锁不能与其他锁共存 在事务结束之前,其他事务不能读也不能改 *锁的粒度 行锁,锁定一行或多行 表锁,锁定整张表
*死锁形成的条件/死锁的定义
两个事务在同一个时刻,互相想要获取对方手里锁住的资源,同时放不开自己手中锁住的资源,那么此时一个死锁产生,两个事务互不相让。
*怎么解决死锁?
1.提升锁的粒度(把行锁升级为表锁)
这样可以避免死锁,但是会影响系统的并发度--影响性能
所以这种方式不可取
2.约定好操作资源的顺序
3.一次性锁定本次功能中所需的所有资源(与第一个道理相同)
升级为表锁,代价太大了(锁起来慢慢去改)
数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。 悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。在查询完数据的时候就把事务锁起来,直到提交事务。实现方式:使用数据库中的锁机制 乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。在修改数据的时候把事务锁起来,通过version的方式来进行锁定。实现方式:乐一般会使用版本号机制或CAS算法实现。 乐观锁适用于写比较少的情况 悲观锁多写的场景下用比较合适。
1.ls+[选项]+[参数] 例如:ls -a 显示当前路径下的所有文件 2.pwd 显示当前所在目录 3.cd +[路径名] cd /root/Docement 表示切换到目录cd /root/Docement 4.cp 表示复制 5.rm 删除 rm -f 不会出现警告消息 6.mv 移动文件 rm -f 若目标文件已经存在,强制覆盖 7.tar-xvf 打包 tar-zcvf打包并压缩 8.mkdir+[选项]+[目录名] 创建目录 9.rmdir+[选项]+[目录名] 删除目录 10.tail 显示文件后几行内容 tail -n 1000:显示最后1000行 11.grep 查找字符串 12.touch 创建空文件 13.kill 结束进程 14.chmod 修改文件权限 15.useradd 建立用户账号 16.sudo 用其他的身份来执行命令 17.passwd 设置密码 18.vim+文件路径 打开路径并编辑 19.cat 连接文件或标准输入并打印 20.ipconfig 查看IP地址等信息
TCP/IP基于TCP和IP这两个最初的协议上的的不同的通信协议大合集,能够在多个不同网络间实现信息传输。是Internet最基本的协议。 四个协议层: 1.应用层:应用层 表示层 会话层 --应用程序通过这一层访问网络。 2.传输层:传输层 --传输协议在计算机之间提供通信会话。传输协议的选择根据数据传输方式而定。 两个传输协议: 传输控制协议TCP:为应用程序提供可靠的通信连接。适合于一次传输大批数据的情况。并适用于要求得到响应的应用程序。 用户数据报协议UDP:提供了无连接通信,且不对传送包进行可靠的保证。适合于一次传输小量数据,可靠性则由应用层来负责。 3.网间层:网间层 --互联协议将数据包封装成internet数据报,并运行必要的路由算法。 4.网络接口层:数据链路层 物理层 --模型的基层是网络接口层。负责数据帧的发送和接收,帧是独立的网络信息传输单元。网络接口层将帧放在网上,或从网上把帧取下来。
UDP:在IP服务的基础上,仅增加复用、分用,差错检验,无连接不可靠;面向报文(依次交付一个完整报文);首部长度:8B TCP:面向连接,点对点,全双工通信;可靠(差错检验,保证有序到达,无丢失);面向字节流;首部长度:20B
三次握手:是客户端发起的(客户端2次,服务器1次) 第一次:客户端试探服务端是否愿意建立连接 第二次:服务端返回愿意建立连接的消息 第三次:客户端发送收到了服务器的同意的信息,准备连接 连接建立成功,客户端发送请求报文,服务端返回响应报文 四次挥手:也是客户端发起的,这时请求和响应已经结束(客户端2次,服务器2次) 第一次:客户端通知服务器要断开连接 第二次:服务器同意断开连接,但要等他释放完关于客户端的所有资源 第三次:服务器释放资源完毕,通知客户端,可以断开连接了 第四次:客户端:拜拜,连接中断 为什么挥手比握手多一次: 服务器多一次,需要释放资源 等释放完通知客户端