0%

XML

xml数据或者xml文档只用于组织、存储数据。

  • 为xml定规则:使用DTD或Schema技术
  • 解析xml数据:使用DOM或SAX技术
  • 提供样式:XSLT可扩展样是转换语言

语法

文档声明

  • XML声明放在XML的第一行
  • version—-版本
  • encoding–编码
  • standalone–独立使用–默认是no。standalone表示该xml是不是独立的,如果是yes,则表示这个XML文档时独立的,不能引用外部的DTD规范文件;如果是no,则该XML文档不是独立的,表示可以引用外部的DTD规范文档。
  • 正确的文档声明格式,属性的位置不能改变!
1
<?xml version="1.0" encoding="utf-8" standalone="no"?>

元素

首先在这里说明一个概念:在XML中元素和标签指的是同一个东西!不要被不同的名称所迷惑了!

元素中需要值得注意的地方:

  • XML元素中的出现的空格和换行都会被当做元素内容进行处理
  • 每个XML文档必须有且只有一个根元素
  • 元素必须闭合
  • 大小写敏感
  • 不能交叉嵌套
  • 不能以数字开头

看起来好像有很多需要值得注意的地方,其实只需要记住:XML的语法是规范的!不要随意乱写!

属性

属性是作为XML元素中的一部分的,命名规范也是和XML元素一样的!

1
2
3
4
<!--属性名是name,属性值是china-->
<中国 name="china">

</中国>

注释

注释和HTML的注释是一样的

1
<!---->

CDATA

在编写XML文件时,有些内容可能不想让解析引擎解析执行,而是当作原始内容处理。遇到此种情况,可以把这些内容放在CDATA区里,对于CDATA区域内的内容,XML解析程序不会处理,而是直接原封不动的输出

语法:

1
2
3
<![CDATA[
...内容
]]>

处理指令

处理指令,简称PI (processing instruction)。处理指令用来指挥解析引擎如何解析XML文档内容。

例如:

在XML文档中可以使用xml-stylesheet指令,通知XML解析引擎,应用css文件显示xml文档内容。

1
<?xml-stylesheet type="text/css" href="1.css"?>
  • XML代码:
1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/css" href="1.css"?>

<china>
<guangzhou>
广州
</guangzhou>
<shenzhen>
深圳
</shenzhen>
</china>
  • CSS代码:
1
2
3
guangzhou{
font-size: 40px;
}
  • 效果:

img

JDK中的XML API

①:JAXP(The Java API For XML Processing):主要负责解析XML

②:JAXB(Java Architecture for XML Binding):主要负责将XML映射为Java对象

XML解析

XML解析就是读取XML的数据!

解析方式

XML解析方式分为两种:

①:dom(Document Object Model)文档对象模型,是W3C组织推荐解析XML的一种方式

②:sax(Simple API For XML),它是XML社区的标准,几乎所有XML解析器都支持它!

常用的解析器和解析开发包的关系如下所示:

  • jaxp是jdk自带的,无需导入开发包
  • jdom需要导入开发包
  • dom4j是用的最多的,需要导入开发包

jaxp

DOM解析操作

DOM解析是一个基于对象的API,它把XML的内容加载到内存中,生成与XML文档内容对应的模型!当解析完成,内存中会生成与XML文档的结构与之对应的DOM对象树,这样就能够根据树的结构,以节点的形式对文档进行操作!

在DOM解析中有几个核心的操作接口:

  • Document【代表整个XML文档,通过Document节点可以访问XML文件中所有的元素内容!】
  • Node【Node节点几乎在XML操作接口中几乎相当于普通Java类的Object,很多核心接口都实现了它!】
  • NodeList【代表着一个节点的集合,通常是一个节点中子节点的集合!】
  • NameNodeMap【表示一组节点和其唯一名称对应的一一对应关系,主要用于属性节点的表示】

XML就是这么简单 - SegmentFault 思否

JSON

JSON:JavaScript Object Notation 【JavaScript 对象表示法】

JSON 是存储和交换文本信息的语法。类似 XML。

JSON采用完全独立于任何程序语言的文本格式,使JSON成为理想的数据交换语言。

JSON 比 XML 更小、更快,更易解析

  • javaScript原生支持JSON,解析速度会很快
  • XML解析成DOM对象的时候,浏览器【IE和fireFox】会有差异
  • 使用JSON会更简单

语法

JSON所表示的数据要么就是对象,要么就是数据

JSON语法是javaScript语法的子集,javaScript用[]中括号来表示数组,用{}大括号来表示对象,JSON亦是如此。

JSON数组

1
2
3
4
5
var employees = [
{ "firstName":"Bill" , "lastName":"Gates" },
{ "firstName":"George" , "lastName":"Bush" },
{ "firstName":"Thomas" , "lastName": "Carter" }
];

JSON对象

1
2
3
4
5
6
7
8
9
var obj = {

age: 20,
str: "zhongfucheng",
method: function () {
alert("我爱学习");
}

};

当然啦,数组可以包含对象,在对象中也可以包含数组

解析JSON

使用eval()函数来解析JSON,把JSON文本数据转换成一个JavaScript对象。

1
2
3
4
5
6
7
8
9
10
function test() {
//在写JOSN的时候,记得把带上逗号
var txt = "{a:123," +
"b:'zhongfucheng'}";

//使用eval解析JSON字符串,需要增添()
var aa = eval("(" + txt + ")");
alert(aa);

}

AJAX

AJAX即“Asynchronous Javascript And XML”,是指一种创建交互式网页应用的网页开发技术。AJAX 是一种用于创建快速动态网页的技术。它可以令开发者只向服务器获取数据(而不是图片,HTML文档等资源),互联网资源的传输变得前所未有的轻量级和纯粹。

img

Ajax实际上是下面这几种技术的融合:

  1. XHTML和CSS的基于标准的表示技术
  2. DOM进行动态显示和交互
  3. XML和XSLT进行数据交换和处理
  4. XMLHttpRequest进行异步数据检索
  5. Javascript将以上技术融合在一起

客户端与服务器,可以在【不必刷新整个浏览器】的情况下,与服务器进行异步通讯的技术

————可以进行局部刷新

XMLHttpRequest

浏览器是先把请求发送到XMLHttpRequest异步对象之中,异步对象对请求进行封装,然后再发送给服务器。服务器并不是以转发的方式响应,而是以流的方式把数据返回给浏览器

XMLHttpRequest异步对象会不停监听服务器状态的变化,得到服务器返回的数据,就写到浏览器上【因为不是转发的方式,所以是无刷新就能够获取服务器端的数据】

api

  • open()(String method,String url,boolean asynch,String username,String password)
  • send(content)
  • setRequestHeader(String header,String value)
  • getAllResponseHeaders()
  • getResponseHeader(String header)
  • abort()

常用的方法就是黑色粗体的前三个

  • open():该方法创建http请求
    • 第一个参数是指定提交方式(post、get)
    • 第二个参数是指定要提交的地址是哪
    • 第三个参数是指定是异步还是同步(true表示异步,false表示同步)
    • 第四和第五参数在http认证的时候会用到。是可选的
  • setRequestHeader(String header,String value):设置消息头(使用post方式才会使用到,get方法并不需要调用该方法)
    • xmlhttp.setRequestHeader(“Content-type”,”application/x-www-form-urlencoded”);
  • send(content):发送请求给服务器
    • 如果是get方式,并不需要填写参数,或填写null
    • 如果是post方式,把要提交的参数写上去

属性

  • onreadystatechange:请求状态改变的事件触发器(readyState变化时会调用此方法),一般用于指定回调函数

  • readyState:请求状态readyState一改变,回调函数被调用,它有5个状态

    • 0:未初始化
    • 1:open方法成功调用以后
    • 2:服务器已经应答客户端的请求
    • 3:交互中。Http头信息已经接收,响应数据尚未接收。
    • 4:完成。数据接收完成
  • responseText:服务器返回的文本内容

  • responseXML:服务器返回的兼容DOM的XML内容

  • status:服务器返回的状态码

  • statusText:服务器返回状态码的文本信息

ajax的使用

创建XMLHttpRequest对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script type="text/javascript">

var httpRequest;

if(window.XMLHttpRequest) {

//在IE6以上的版本以及其他内核的浏览器(Mozilla)等
httpRequest = new XMLHttpRequest();
}else if(window.ActiveXObject) {

//在IE6以下的版本
httpRequest = new ActiveXObject();
}

</script>

向服务器发送请求

1
2
3
4
5
6
7
8
    1. xhr.open(method,url,async);  
2. send(string);//post请求时才使用字符串参数,否则不用带参数。

method:请求的类型;GETPOST

url:文件在服务器上的位置

asynctrue(异步)或 false(同步) 注意:post请求一定要设置请求头的格式内容
1
2
3
xhr.open("POST","test.html",true);  
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xhr.send("fname=Henry&lname=Ford"); //post请求参数放在send里面,即请求体

服务器响应处理

responseText 获得字符串形式的响应数据。

responseXML 获得XML 形式的响应数据。

①同步处理

1
2
3
xhr.open("GET","info.txt",false);  
xhr.send();
document.getElementById("myDiv").innerHTML=xhr.responseText; //获取数据直接显示在页面上

②异步处理

相对来说比较复杂,要在请求状态改变事件中处理。

1
2
3
4
5
xhr.onreadystatechange=function()  { 
if (xhr.readyState==4 &&xhr.status==200) {
document.getElementById("myDiv").innerHTML=xhr.responseText;
}
}

跨域

AJAX跨域完全讲解 - SegmentFault 思否

面试题

AJAX面试题都在这里 - SegmentFault 思否

深蹲

主要要点:

  • 重心:杠铃重心应该始终保持在脚中心的正上方位置
  • 站姿:脚跟距离与肩同宽;脚尖外展约30度
  • 握距:越窄越好,越窄越能将杠铃控制在肌肉上。大拇指应该在杠铃上方以确保握杠时手腕和前臂在一条直线上。
  • 肘向上抬
  • 全程保持头向下看
  • 发力方向:髋部发力应该是往上不是往前,往前的话会导致重心往脚尖压
  • 反弹:反弹必须作为发力过程的启动部分而存在
  • 呼吸:深吸一口气并屏住呼吸

5X5训练计划

一周在非连续的日子里健身3次,并把AB部分轮流使用:

周一:

A:深蹲5X5(起始重量)、推举5X5(起始重量)、硬拉1X5(起始重量)

周三:

B:深蹲5X5(+2.5kg)、卧推5X5(起始重量)、划船5X5(起始重量)

周五:

A:深蹲5X5(+2.5kg)、推举5X5(+2.5kg)、硬拉1X5(+5kg)

周一:

B:深蹲5X5(+2.5kg)、卧推5X5(+2.5kg)、划船5X5(+2.5kg)

热身

一般会做三个热身组,先用50%今天要做的重量做一组,65%今天要做的重量做一组,80%今天要做的重量做一组。

注意事项

1、如不能完成5组任意一个,争取做完全部5组(能做几个做几个),下次继续,如果三次训练不能做完,减重10%继续5*5,只要你还没达到瓶颈,就加重下去。

2、至于推举和划船的重量,你也应该尝试每次增加5磅重量,但是由于这两个锻炼所要求的肌肉力量会少于深蹲、硬拉、和卧推(推举所用的肩部力量远远少于你大腿的伸膝力量),每次增加5磅重量不一定现实。

比如:我用160磅卧推5X5时第三组时失败了,只做了4下。那么我第四组时会拿155磅的重量企图完成,如果我还是败了就用150磅完成第五组。
如果失败的地方是在第五组,那么不要纠结企图多做一组了。下次再说吧。注意如果你出现失败的情况,那么下次不能增加重量。

3、如果你加重失败,5组没做完出现完成不了的情况,你可以选择多休息(5+分钟)再挑战这个重量,或者在把重量减少到上次的重量后,再把剩下几组做完。

正常的话,组间休息在90秒左右;训练初期重量较轻,组间休息时间可以适当减少,到后期快到达极限时需要酌情延长。一般休息3分钟可以让ATP恢复到80%左右,5分钟可以恢复到95%。

deload:

全体deload的方法很简单。花一周时间,啥运动都不做,就休息。每健身的第九周,全体deload一周。当然如果你没有健身疲劳也没必要什么运动都不做。你可以照样跟平时一样健身,组数次数啥的都一样,但就把重量减轻40%或更多,然后专门注意纠正改善你的姿势。
有的人会问deload完之后怎么办。之后就当啥事没发生,之前该干啥还干啥。该做什么计划就继续做什么计划。

半deload一般只适合在用线性计划、长时间某项锻炼无法提高的朋友。
如果你在做5X5,并且已经有两周你的卧推停滞在80KG上涨不了,那么你可以半deload。方法很简单,别的锻炼照样做,卧推重量减少15磅(或者更多),设这个为你的新的RM。然后从这里开始再慢慢把重量加回去。
一般来说,在开始一项新的计划之前,你应该把你的所有锻炼都半deload,以较轻的新的RM开始这个计划(像5/3/1会专门求你设原来的1RM的90%为1RM)。原因很简单:你从来不想让自己用一个新的计划的第一天就拼尽全力。用一个新的计划的前两周你应该处于一种适应的状态,如果你连这个计划的频率、训练量都没适应,就直接push你自己,只会导致自己肌肉恢复跟不上、然后各种失败。

辅助训练(最好不加):

引体向上 (最多3组)或 绳索下拉(最多3组)
孤立三头锻炼 (最多3组)或 双杠臂屈伸(最多3组)
孤立二头肌锻炼(最多3组)
后侧肌肉群锻炼 如山羊挺身、罗马尼亚硬拉、直腿硬拉(最多3组)

ICF冰淇淋5X5:

A部分
深蹲 5X5
卧推 5X5
划船 5X5
杠铃或哑铃耸肩 3X8-12
三头肌下压 3X8-12
二头肌弯举 3X8-12
逆向曲腿3X8-12
腹肌锻炼自己加

B部分
深蹲5X5
推举5X5
硬拉1X5
划船(用比A部分少10%的重量做) 5X5
窄距卧推 或 双杠臂屈伸 3X8-12
二头肌弯举 3X8-12
腹肌锻炼自己加

这个计划在5X5的基础上添加了很多辅助增肌的锻炼,因此如果你并不是特别对力量有兴趣,更追求增肌的话,这个计划在5X5的计划里算是最适合健美增肌的了(而5X5本身就比99%的其他计划更适合健美增肌。。。)。
如果你在减脂,却还想使用这个计划的话,也不是不可以,把所有5组的锻炼做3组,所有3组的锻炼做2组就可以了。你如果减脂期间坚持做完整个计划的话,你会发现你的肌肉恢复会可能跟不上。

拉伸

每次拉伸至少要拉30秒以上

1、髖屈肌群拉伸

2、

madcow5X5

当你可以深蹲体重1.5到2倍左右、硬拉体重2到2.5倍左右、卧推体重1.25倍到1.5倍左右的时候

每周第一天
深蹲 5X5 (除了最后1组之外,前面4组分别用最后一组的60%,70%,80%,90%完成)
卧推 5X5 (除了最后1组之外,前面4组从60%开始增加重量)
俯身划船 5X5 (除了最后1组之外,前面4组从60%开始增加重量)
可选辅助锻炼: 腹肌锻炼、3组二头肌弯举、三组三头肌下压

第二天 休息

第三天
深蹲 4X5 (和第一天一样,只不过你不做最后一组正式组)
推举 或 上斜卧推 4X5 (前3组从60%开始增加重量,第四组为正式组)
硬拉 4X5 (前3组从60%开始增加重量,第四组为正式组)
可选辅助锻炼:3组哑铃飞鸟、2组窄握双杠臂屈伸、3组二头肌弯举、腹肌锻炼

第四天 休息

第五天
深蹲 4X5 1X3 1X8 (前4组和第一天第三天一样,第五组1X3用周一的重量提高2.5%完成,第六组1X8用前四组中的第三组完成)
卧推 4X5 1X3 1X8(前4组和第一天第三天一样,第五组1X3用周一的重量提高2.5%完成,第六组1X8用前四组中的第三组完成)
俯身划船 4X5 1X3 1X8(前4组和第一天第三天一样,第五组1X3用周一的重量提高2.5%完成,第六组1X8用前四组中的第三组完成)
可选辅助锻炼:3组窄握双杠臂屈伸、3组脸拉、3组二头肌弯举、腹肌锻炼

第六天第七天休息,然后下周第一天时,正式组都用上一周的1X3来做。
也就是说:这周周一我第五组正式组是1X5 200磅卧推,这周周五的我的第五组正式组是1X3 205磅卧推,那么下周周一我的第五组正式组是1X5 205磅卧推。
至于第三天的推举和硬拉,你只需要跟着每周正式组增加2.5%的重量走就可以了。

Canditto LP坎底托直线提高计划介绍

本周第一天 上身力量
杠铃卧推 3X6
背部主要锻炼 3X6
肩部主要锻炼 1X6
背部次要锻炼 1X6
可选上身锻炼1 3X8-15
可选上身锻炼2 3X8-15

本周第二天 下身力量
后端深蹲(高杠或低杠)3X6
硬拉 2X5
可选下身锻炼1 3X8-15
可选下身锻炼2 3X8-15
可选下身锻炼3 3X8-15
本周第三天 休息

本周第四天 上身增肌
杠铃或哑铃卧推 3X8-12
背部主要锻炼 4X8-12
肩部主要锻炼 3X8-12
背部次要锻炼 4X8-12
上斜杠铃或哑铃卧推 3X8-12
二头肌弯举 3X8-12
可选上身锻炼 3X8-12

本周第五天 下身增肌
深蹲(前端或后端) 5X8
可选花式硬拉 2X6
腘绳肌锻炼 3X8-12
小腿提锺 3X8-12
可选下身锻炼1 3X8-12
可选下身锻炼2 3X8-12

本周第六天 第七天 休息

这个模板可以说是相当地灵活,你可以自己随便加锻炼进去。当然,我个人推荐加如下的锻炼:
背部主要锻炼 - 俯身划船 或 负重引体向上 或 背部下拉
背部次要锻炼 - 俯身划船 或 负重引体向上 或 背部下拉
肩部主要锻炼 - 直立杠铃推举 或 坐姿哑铃推举
可选上身锻炼 - 二头肌弯举 三头肌下压 俯身哑铃飞鸟 杠铃或哑铃耸肩
可选花式硬拉 - 罗马尼亚硬拉 直腿硬拉
可选下身锻炼 - 跳箱 前端深蹲 前屈弓步 (这个选择面比较广)
腘绳肌锻炼 - 双腿伸屈(机器)

增重原则
所有锻炼,每周尝试增加0-10磅的重量。
因为真正的力量日上身下身一周各只有一天,所以增重的尝试只有一次。很多人因此觉得这个计划的增重规律实在太慢,但我个人认为0-10磅的增重设计反而非常不错,因此不会硬逼着你加重,你如何加重完全是看感觉。新手,特别是动作姿势还有问题的新手,被逼着加重时很可能出现姿势不正确、不完全完成动作的行为(比如深蹲时就做个半蹲),因此这种灵活的增重设计我是赞成的。

GZCL训练理念

【最良心的干货】手把手教你周期力量计划的设计 - 知乎 (zhihu.com)

每次训练你把你的锻炼要分为三个不同的区域之内,分别是:

一级主项,二级副项,三级辅助

ZCL理念即是:每天的训练应该安排成一个金字塔。金字塔的厚度为训练量,高为训练强度。最高的位置,训练强度最高、训练量越低,这也是对于你发展力量最重要的训练部分(一级主项);稍微往下,训练强度、训练量都为中等,这是你攻克自己的弱点、直接性辅助你力量提高的训练部分(二级副项);最下方,训练强度最低,训练量最高,这是你做大量的次数和组数好得到增肌效果、以助于你以后力量的提高(三级辅助)。

一级主项的锻炼安排:一般来说这是你最重要的大三项,深蹲硬拉卧推。根据你目标的不同,也可以包括别的主项锻炼,比如实力推。你每次训练的目的是把最多的精力和力量安排到一级主项上面。一般而言,你每次训练只会安排一个主项,训练强度一般都高于80%1RM。

二级副项的锻炼安排:一般来说这是你的三大项的专项性辅助。深蹲的包括:暂停深蹲、其他深蹲方式(主蹲低杠的,可以安排高杠或者前蹲);硬拉的包括:超程硬拉、架上拉、其他硬拉方式(主拉相扑拉的,可以安排传统拉);卧推的包括:暂停卧推、窄距卧推。这都只是一些例子。要注意的是你也可以把主项放入副项里练。副项的目标,除了攻克弱点、直接性辅助你的主项之外,也可以发展其他的主项;但是重要性会低于当天的一级主项。具体这是什么意思,我会在底下说明白。一般而言,你每次训练只会安排一个或者两个副项,训练强度一般在65%-85%1RM之间。

三级辅助的锻炼安排:一般来说这是任何其它的辅助锻炼,包括(不限制于):背部锻炼(划船、引体向上)、哑铃锻炼(哑铃卧推)、增肌性质的三大项辅助(硬拉:罗马尼亚硬拉、直腿硬拉;卧推:哑铃卧推、俯卧撑、甚至胸推机器;深蹲:哑铃弓箭步、箱跳、腿弯举)、肩部锻炼(推举这边也可以练)。你要通过三级辅助,把一级和二级锻炼里还没有压榨完的精力给全部花出去。一般而言,每次训练会安排两个以上的三级辅助,每个的组数锻炼至少3组以上、每组10-20次(Cody喜欢做每组20下的三级辅助,走他的黑又壮计划的朋友们应该都明白,哭死人)。三级辅助的目标是:提高训练容量(高组数高次数)、达到增肌效果、提高综合耐力。

安装教程

参考文章

参考文章

概念

Oracle数据库服务器由两部分组成:

  • 实例:理解为对象,看不见的
  • 数据库:理解为类,看得见的

Oracle数据库把表、视图等都看成是对象。

null值

Oracle中如果存在字段是null值的话,那么在sqlplus中它是不会显示出来的….如果我们使用null值的数据与其他数据进行运算…那么最终得出的结果都是null值

因此,Oracle提供了NVL(表达式1,表达式2)函数供我们使用,如果表达式1的值为null值,那么就取表达式2的值…当然了,如果表达式1不是null,取的就是表达式1的值

还有值得注意的是:null值不能参数=号运算,null能参数number/date/varchar2类型运算

Oracle提供了 is null关键字来代替=号运算的问题

别名

Mysql中如果要用别名的话,需要使用as关键字 ,后面跟着别名就行了….Oracle可以省略as关键字…

并且,一般地,我们使用别名都是用双引号””把别名括起来,Oracle也支持我们直接写别名,但是呢,如果我们不写双引号,那么我们的别名是不能有空格的

还有一点的是:Oracle的别名是不能使用单引号来括起来的,Oracle默认认为单引号是字符串类型和日期类型的。

操作表细节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
进入回收站
drop table users;

查询回收站中的对象
show recyclebin;

闪回,即将回收站还原
flashback table 表名 to before drop;
flashback table 表名 to before drop rename to 新表名;

彻底删除users表
drop table users purge;

清空回收站
purge recyclebin;

为emp表增加image列,alter table 表名 add 列名 类型(宽度)
alter table emp
add image blob;

修改ename列的长度为20个字节,alter table 表名 modify 列名 类型(宽度)
alter table emp
modify ename varchar2(20);

删除image列,alter table 表名 drop column 列名
alter table emp
drop column image;

重名列名ename为username,alter table 表名 rename column 原列名 to 新列名
alter table emp
rename column ename to username;

将emp表重命名emps,rename 原表名 to 新表名
rename emp to emps;

值得注意的是:修改表的时候,是不能回滚的!

Oracle中的级联操作:

  • 【on delete cascade】级联删除
  • 【on delete set null】将外键一方设置为null

分页

Oracle中的分页是依靠着rownum这个伪列来实现的,由于rownum只能使用的是<=或者<来获取数据。。。因为rownum的值可能会经常变【加入一条数据,那么rownum就+1,讲道理rownum可以是无穷大的,因此不能使用>来进行操作】….

那么Oracle分页的思路是这样子的:

  • 先在子查询中获取前n条记录
  • 由于返回的是多行多列,因此我们可以看做成一张表
  • 那么将查询出来的数据放在from字句的后边
  • 外套的查询可以通过where字句来对子查询出来的数据进行过滤
  • 那么我们就可以查询出想要的数据了…

公式:

  • Mysql从(currentPage-1)*lineSize开始取数据,取lineSize条数据
  • Oracle先获取currentPage*lineSize条数据,从(currentPage-1)*lineSize开始取数据

语法

批量操作

我们查询出来的数据可看成是一张表,那么我们在插入数据的时候,可以根据查询出来的数据进行插入…这就可以看成是批量操作…

值得注意的是,如果没有指定插入哪些字段的话,那么查询出来的全部字段均会插入表中..

1
2
3
4
5
将xxx_emp表中所有20号部门的员工,复制到emp表中,批量插入,insert into 表名 select ...语法
insert into emp
select *
from xxx_emp
where deptno=20;

删除操作对比

我们的删除语法有三种:

  • delete from
  • truncate from
  • drop from

我们来对比一下他们的区别:

drop table

  • 1)属于DDL
  • 2)不可回滚
  • 3)不可带where
  • 4)表内容和结构删除
  • 5)删除速度快

truncate table

  • 1)属于DDL
  • 2)不可回滚
  • 3)不可带where
  • 4)表内容删除
  • 5)删除速度快

delete from

  • 1)属于DML
  • 2)可回滚
  • 3)可带where
  • 4)表结构在,表内容要看where执行的情况
  • 5)删除速度慢,需要逐行删除

访问其他用户的对象

我们只要在访问表的时候,指定具体的用户.数据库表就行了,但是呢,还要看看该用户有没有权限查询别的用户的数据表,于是就需要赋予权限了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
声明:scott或hr叫用户名/方案名/空间名
scott--tiger
hr-----lion

查询当前用户是谁
show user;

查询scott自己表空间下的所有对象时,可加,或不加用户名select * from emp;
select * from emp;

select * from scott.emp;

以sysdba身份解锁hr普通帐户
alter user hr account unlock;

以sysdba身份设置hr普通帐户的密码
alter user hr identified by lion;

当scott查询hr表空间下的所有表时,必须得加用户名
select * from hr.jobs;

在默认情况下,每个用户只能查询自已空间下的对象的权限,不能查询其它用户空间下的对象

以sysdba身份角色,授予scott用户查询所有用户空间下的对象权限
grant select any table to scott;

以sysdba身份,撤销scott用户查询所有用户空间下的对象权限
revoke select any table from scott;

scott自已查看自己所拥有的权限
select * from user_sys_privs;

从scott用户空间导航到sysdba用户空间
conn / as sysdba;

从sysdba用户空间导航到scott用户空间
conn scott/tiger;

从scott用户空间导航到hr用户空间
conn hr/lion;

查询hr用户空间中的所有对象
select * from tab;

从hr用户空间导航到scott用户空间
conn scott/tiger;

在scott用户空间下,查询hr用户空间下的jobs表,必须加上hr用户空间名
select * from hr.jobs;

视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
基于emp表所有列,创建视图emp_view_1,create view 视图名 as select对一张或多张基表的查询
create view emp_view_1
as
select * from emp;

默认情况下,普通用户无权创建视图,得让sysdba为你分配creare view的权限

以sysdba身份,授权scott用户create view权限
grant create view to scott;

以sysdba身份,撤销scott用户create view权限
revoke create view from scott;

基于emp表指定列,创建视图emp_view_2,该视图包含编号/姓名/工资/年薪/年收入(查询中使用列别名)
create view emp_view_2
as
select empno "编号",ename "姓名",sal "工资",sal*12 "年薪",sal*12+NVL(comm,0) "年收入"
from emp;

基于emp表指定列,创建视图emp_view_3(a,b,c,d,e),包含编号/姓名/工资/年薪/年收入(视图中使用列名)
create view emp_view_3(a,b,c,d,e)
as
select empno "编号",ename "姓名",sal "工资",sal*12 "年薪",sal*12+NVL(comm,0) "年收入"
from emp;

查询emp_view_3创建视图的结构
desc emp_view_3;

修改emp_view_3(id,name,salary,annual,income)视图,create or replace view 视图名 as 子查询
create or replace view emp_view_3(id,name,salary,annual,income)
as
select empno "编号",ename "姓名",sal "工资",sal*12 "年薪",sal*12+NVL(comm,0) "年收入"
from emp;

查询emp表,求出各部门的最低工资,最高工资,平均工资
select min(sal),max(sal),round(avg(sal),0),deptno
from emp
group by deptno;

创建视图emp_view_4,视图中包含各部门的最低工资,最高工资,平均工资
create or replace view emp_view_4
as
select deptno "部门号",min(sal) "最低工资",max(sal) "最高工资",round(avg(sal),0) "平均工资"
from emp
group by deptno;

创建视图emp_view_5,视图中包含员工编号,姓名,工资,部门名,工资等级
create or replace view emp_view_5
as
select e.empno "编号",e.ename "姓名",e.sal "工资",d.dname "部门名",s.grade "工资等级"
from emp e,dept d,salgrade s
where (e.deptno=d.deptno) and (e.sal between s.losal and s.hisal);

删除视图emp_view_1中的7788号员工的记录,使用delete操作,会影响基表吗
delete from emp_view_1 where empno=7788;写法正确,会影响基表

修改emp_view_1为只读视图【with read only】,再执行上述delete操作,还行吗?
create or replace view emp_view_1
as
select * from emp
with read only;
不能进行delete操作了

删除视图中的【某条】记录会影响基表吗?
会影响基表

将【整个】视图删除,会影响表吗?
不会影响基表

删除视图,会进入回收站吗?
不会进入回收站

删除基表会影响视图吗?
会影响视图

闪回基表后,视图有影响吗?
视图又可以正常工作了

序列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

为emp表的empno字段,创建序列emp_empno_seq,create sequence 序列名
create sequence emp_empno_seq;

删除序列emp_empno_seq,drop sequence 序列名
drop sequence emp_empno_seq;

查询emp_empno_seq序列的当前值currval和下一个值nextval,第一次使用序列时,必须选用:序列名.nextval
select emp_empno_seq.nextval from dual;
select emp_empno_seq.currval from dual;

使用序列,向emp表插入记录,empno字段使用序列值
insert into emp(empno) values(emp_empno_seq.nextval);
insert into emp(empno) values(emp_empno_seq.nextval);
insert into emp(empno) values(emp_empno_seq.nextval);

修改emp_empno_seq序列的increment by属性为20,默认start with1alter sequence 序列名
alter sequence emp_empno_seq
increment by 20;

修改修改emp_empno_seq序列的的increment by属性为5
alter sequence emp_empno_seq
increment by 5;

修改emp_empno_seq序列的start with属性,行吗
alter sequence emp_empno_seq
start with 100;

有了序列后,还能为主健手工设置值吗?
insert into emp(empno) values(9999);
insert into emp(empno) values(7900);

删除表,会影响序列吗?
你无法做insert操作,表真正亡,序列亡

删除序列,会影响表吗?

不会


在hibernate中,如果是访问oracle数据库服务器,那么User.hbm.xml映射文件中关于<id>标签如何配置呢?
<id name="id" column="id">
<generator class="increment/identity/uuid/【sequence】/【native】"/>
</id>

索引

(1)是一种快速查询表中内容的机制,类似于新华字典的目录
(2)运用在表中某个/些字段上,但存储时,独立于表之外

为什么要用索引

  • (1)通过指针加速Oracle服务器的查询速度
  • (2)通过rowid快速定位数据的方法,减少磁盘I/O
    • rowid是oracle中唯一确定每张表不同记录的唯一身份证

rowid特点

  1. 位于每个表中,但表面上看不见,例如:desc emp是看不见的
  2. 只有在select中,显示写出rowid,方可看见
  3. 它与每个表绑定在一起,表亡,该表的rowid亡,二张表rownum可以相同,但rowid必须是唯一的
  4. rowid是18位大小写加数字混杂体,唯一表代该条记录在DBF文件中的位置
  5. rowid可以参与=/like比较时,用’’单引号将rowid的值包起来,且区分大小写
  6. rowid是联系表与DBF文件的桥梁

索引特点

  1. 索引一旦建立, Oracle管理系统会对其进行自动维护, 而且由Oracle管理系统决定何时使用索引
  2. 用户不用在查询语句中指定使用哪个索引
  3. 在定义primary key或unique约束后系统自动在相应的列上创建索引
  4. 用户也能按自己的需求,对指定单个字段或多个字段,添加索引

需要注意的是:Oracle是自动帮我们管理索引的,并且如果我们指定了primary key或者unique约束,系统会自动在对应的列上创建索引..

什么时候【要】创建索引

(1)表经常进行 SELECT 操作

(2)表很大(记录超多),记录内容分布范围很广

(3)列名经常在 WHERE 子句或连接条件中出现

什么时候【不要】创建索引

(1)表经常进行 INSERT/UPDATE/DELETE 操作

(2)表很小(记录超少)

(3)列名不经常作为连接条件或出现在 WHERE 子句中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
为emp表的empno单个字段,创建索引emp_empno_idx,叫单列索引,create index 索引名 on 表名(字段,...)
create index emp_empno_idx
on emp(empno);

为emp表的ename,job多个字段,创建索引emp_ename_job_idx,多列索引/联合索引
create index emp_ename_job_idx
on emp(ename,job);
如果在where中只出现job不使用索引
如果在where中只出现ename使用索引
我们提倡同时出现ename和job

注意:索引创建后,只有查询表有关,和其它(insert/update/delete)无关,解决速度问题

删除emp_empno_idx和emp_ename_job_idx索引,drop index 索引名
drop index emp_empno_idx;
drop index emp_ename_job_idx;

权限和用户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
一)用户
Oracle中的用户分为二大类
1)Oracle数据库服务器创建时,由系统自动创建的用户,叫系统用户,如sys。
2)利用系统用户创建的用户,叫普通用户,如scott,hr,c##tiger,zhaojun,...

》用sys登录,查询当前Oracle数据库服务器中已有用户的名字和状态
username表示登录名
expired&locked表示帐号过期和锁定
open表示帐号现在可用
sqlplus / as sysdba;
col username for a30;
col account_status for a30;
set pagesize 100;
select username,account_status from dba_users;

查询Oracle中有哪些用户
select * from all_users;



二)创建与删除普通用户
可以在Oracle中创建新的普通用户,创建普通用户命令是:create user,在创建普通用户的同时,应该为其分配一个具体的表空间,通常叫users。

》用sys登录,查询Oracle中有哪些可用存储空间,所有普通用户默认为users存储空间
select * from v$tablespace;

》用sys登录,创建普通用户c##tiger,密码为abc,默认使用users存储空间,即对应硬盘上的一个DBF二进制文件
sqlplus / as sysdba;
create user c##tiger identified by abc default tablespace users;

》用sys登录,为c##tiger分配users空间无限制使用,即数据库中DBF文件可以无限增加,一个DBF文件不够,会创建第二个DBF文件
sqlplus / as sysdba;
alter user c##tiger quota unlimited on users;

》用c##tiger登录,能进orcl数据库吗?
sqlplus c##tiger/abc
进不去orcl数据库

》用sys登录,删除普通用户c##tiger
sqlplus / as sysdba;
drop user c##tiger cascade;



三)了解系统用户
sys是Oracle中一个重要的系统用户,sys是Oracle中最高权限用户,其角色为SYSDBA(系统管理员)
sqlplus / as sysdba



四)权限
权限的最终作用于用户。即所有用户在数据库内的操作对象和可执行的动作都是受到限制的。
Oracle中权限分为二大类:
1)系统权限
2)对象权限



五)系统权限
针对数据库中特定操作的许可,例如:让c##tiger能登录到orcl数据库,能在orcl数据库中创建表

》用sys登录,获取系统权限的相关信息,例如:select any table表示针对所有表的select权限
sqlplus / as sysdba;
select distinct privilege from dba_sys_privs;

》用sys登录,为c##tiger分配create session与数据库建立会话的权限,即允许该用户登录
sqlplus / as sysdba;
grant create session to c##tiger;

》用c##tiger登录,能进orcl数据库吗?
sqlplus c##tiger/abc
能进去orcl数据库

》用c##tiger登录,创建一张tiger的表,能创建吗?
sqlplus c##tiger/abc
create table tiger(
name varchar2(20)
);
这时c##tiger没有权限创建表

》用sys登录,为c##tiger分配create table权限,即允许创建表
sqlplus / as sysdba;
grant create table to c##tiger;

》用c##tiger登录,创建一张tiger的表,能创建吗?
sqlplus c##tiger/abc
create table tiger(
name varchar2(20)
);
可以创建c##tiger表

》用sys登录,查询c##tiger所拥有的系统权限
sqlplus / as sysdba;
select grantee,privilege from dba_sys_privs where lower(grantee) = 'c##tiger';
grantee表示普通用户名
privilege权限名

》用sys登录,撤销c##tiger的create table权限
sqlplus / as sysdba;
revoke create table from c##tiger;



六)对象权限
用户对已有对象的操作权限,包括:
1select可用于表,视图和序列
2insert向表或视图中插入新的记录
3update更新表中数据
4delete删除表中数据
5execute函数,过程的执行
6)index为表创建索引
7references为表创建外健
8alter修改表或者序列的属性

》用sys登录,查询c##tiger所拥有的对象权限
sqlplus / as sysdba;
col grantee for a10;
col table_name for a10;
col privilege for a20;
select grantee,table_name,privilege from dba_tab_privs where lower(grantee) = 'c##tiger';

》用sys登录,为c##tiger分配对tiger表的所有权限,即增删改查操作
sqlplus / as sysdba;
grant all on c##tiger.tiger to c##tiger;
注意:c##tiger表示空间名
tiger表示该空间下的表名
C##TIGER TIGER FLASHBACK
C##TIGER TIGER DEBUG
C##TIGER TIGER QUERY REWRITE
C##TIGER TIGER ON COMMIT REFRESH
C##TIGER TIGER REFERENCES
C##TIGER TIGER UPDATE
C##TIGER TIGER SELECT
C##TIGER TIGER INSERT
C##TIGER TIGER INDEX
C##TIGER TIGER DELETE
C##TIGER TIGER ALTER

》用c##tiger登录,对tiger表进行增删改查操作
sqlplus c##tiger/abc;
insert into tiger(name) values('AA');
update tiger set name = 'BB';
delete from tiger where rownum = 1;
select * from tiger;


PLSQL学习

PLSQL是Oracle对SQL99的一种扩展

  • 是专用于Oracle服务器,在SQL基础之上,添加了一些过程化控制语句,叫PLSQL
  • 过程化包括有:类型定义,判断,循环,游标,异常或例外处理。。。
  • PLSQL强调过程

为什么要用PLSQL

  • 因为SQL是第四代命令式语言,无法显示处理过程化的业务,所以得用一个过程化程序设计语言来弥补SQL的不足之处
  • SQL和PLSQL不是替代关系,是弥补关系

语法

参考文章

面试题

参考文章

有关mysql命令

面试前必须知道的MySQL命令【explain】 - SegmentFault 思否

1、文件是对I/O设备的抽象表示;虚拟内存是对主存和磁盘I/O的抽象表示;进程是对处理器、主存和I/O设备的抽象表示。

2、shell进程:shell做为一种和Linux系统的特殊交互式工具,为用户提供了启动程序、管理文件系统中的文件及运行在Linux上的进程的途径。shell通过解析输入的文本命令,在内核中执行来达到与系统交互的功能。shell包含了一组内部命令,通过这些命令可以进行文件管理、程序管理及运行等操作。

【什么是shell】

简单点理解,就是系统跟计算机硬件交互时使用的中间介质,它只是系统的一个工具。实际上,在shell和计算机硬件之间还有一层东西那就是系统内核了。打个比方,如果把计算机硬件比作一个人的躯体,而系统内核则是人的大脑,至于shell,把它比作人的五官似乎更加贴切些。回到计算机上来,用户直接面对的不是计算机硬件而是shell,用户把指令告诉shell,然后shell再传输给系统内核,接着内核再去支配计算机硬件去执行各种操作。

1、事务的四大特性是什么?

  • 原子性,要么执行,要么不执行
  • 隔离性,所有操作全部执行完以前其它会话不能看到过程
  • 一致性,事务前后,数据总额一致
  • 持久性,一旦事务提交,对数据的改变就是永久的

PS:sql语句

sql对大小写不敏感。

1.SQL DML 和 DDL

可以把 SQL 分为两个部分:数据操作语言 (DML) 和 数据定义语言 (DDL)。

SQL (结构化查询语言)是用于执行查询的语法。但是 SQL 语言也包含用于更新、插入和删除记录的语法。

查询和更新指令构成了 SQL 的 DML 部分:

  • SELECT - 从数据库表中获取数据
  • UPDATE - 更新数据库表中的数据
  • DELETE - 从数据库表中删除数据
  • INSERT INTO - 向数据库表中插入数据

SQL 的数据定义语言 (DDL) 部分使我们有能力创建或删除表格。我们也可以定义索引(键),规定表之间的链接,以及施加表间的约束。

SQL 中最重要的 DDL 语句:

  • CREATE DATABASE - 创建新数据库
  • ALTER DATABASE - 修改数据库
  • CREATE TABLE - 创建新表
  • ALTER TABLE - 变更(改变)数据库表
  • DROP TABLE - 删除表
  • CREATE INDEX - 创建索引(搜索键)
  • DROP INDEX - 删除索引

select语句

从表中选取数据

1
2
3
SELECT 列名称,列名称 FROM 表名称
SELECT * FROM 表名称//星号(*)是选取所有列的快捷方式。

distinct语句

关键词 DISTINCT 用于返回唯一不同的值。

1
SELECT DISTINCT 列名称 FROM 表名称

where

WHERE 子句用于规定选择的标准

1
SELECT 列名称 FROM 表名称 WHERE 列 运算符 值
操作符 描述
= 等于
<> 不等于
> 大于
< 小于
>= 大于等于
<= 小于等于
BETWEEN 在某个范围内
LIKE 搜索某种模式

and和or运算符

AND 和 OR 可在 WHERE 子语句中把两个或多个条件结合起来。

如果第一个条件和第二个条件都成立,则 AND 运算符显示一条记录。

如果第一个条件和第二个条件中只要有一个成立,则 OR 运算符显示一条记录。

1
2
SELECT * FROM Persons WHERE (FirstName='Thomas' OR FirstName='William')
AND LastName='Carter'/*使用圆括号来组成复杂的表达式*/

order by

ORDER BY 语句用于对结果集进行排序。

例如:

1
2
3
4
5
6
/*按照company名字进行升序排序*/
SELECT Company, OrderNumber FROM Orders ORDER BY Company
/*以字母顺序显示公司名称(Company),并以数字顺序显示顺序号(OrderNumber*/
SELECT Company, OrderNumber FROM Orders ORDER BY Company, OrderNumber
/*desc是降序*/
SELECT Company, OrderNumber FROM Orders ORDER BY Company DESC, OrderNumber ASC

insert into

INSERT INTO 语句用于向表格中插入新的行。

1
2
3
4
5
/*(值1, 值2,....)构成新的一行*/
INSERT INTO 表名称 VALUES (值1, 值2,....)
/*指定所要插入数据的列*/
INSERT INTO table_name (列1, 列2,...) VALUES (值1, 值2,....)

update

修改列值

1
UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值

delete

删除表中的

1
2
3
DELETE FROM 表名称 WHERE 列名称 =
/*删除所有行*/
DELETE * FROM table_name

top

1
2
3
4
/*选取头两条记录*/
SELECT TOP 2 * FROM Persons
/*选取50%记录*/
SELECT TOP 50 PERCENT * FROM Persons

like

用在where子句中搜索列中的指定模式

1
2
3
/*从 "Persons" 表中选取居住在以 "N" 开始的城市里的人*/
SELECT * FROM Persons
WHERE City LIKE 'N%'

“%” 可用于定义通配符(模式中缺少的字母)。

SQL 通配符必须与 LIKE 运算符一起使用。

通配符 描述
% 替代一个或多个字符
_ 仅替代一个字符
[charlist] 字符列中的任何单一字符
[^charlist]或者[!charlist] 不在字符列中的任何单一字符
1
2
3
/*从 "Persons" 表中选取居住的城市以 "A" 或 "L" 或 "N" 开头的人*/
SELECT * FROM Persons
WHERE City LIKE '[ALN]%'

in

IN 操作符允许我们在 WHERE 子句中规定多个值。

1
2
3
/*选取姓氏为 Adams 和 Carter 的人*/
SELECT * FROM Persons
WHERE LastName IN ('Adams','Carter')

alias(别名-AS)

1
2
SELECT column_name AS alias_name
FROM table_name

join

SQL join 用于根据两个或多个表中的列之间的关系,从这些表中查询数据。

  • JOIN: 如果表中有至少一个匹配,则返回行
  • LEFT JOIN: 即使右表中没有匹配,也从左表返回所有的行
  • RIGHT JOIN: 即使左表中没有匹配,也从右表返回所有的行
  • FULL JOIN: 只要其中一个表中存在匹配,就返回行

inner join(内连接)

INNER JOIN 与 JOIN 是相同的。

1
2
3
4
SELECT column_name(s)
FROM table_name1
INNER JOIN table_name2
ON table_name1.column_name=table_name2.column_name

没有匹配的话,就不会返回行了

同理:left join…

union

UNION 操作符用于合并两个或多个 SELECT 语句的结果集。

1
2
3
4
/*列出所有在中国和美国的不同的雇员名*/
SELECT E_Name FROM Employees_China
UNION
SELECT E_Name FROM Employees_USA

union只会选择出不同值,union all会选择出所有值(包括了两个表之间的共同值)

union合并需要UNION 内部的 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型

select into

SELECT INTO 语句从一个表中选取数据,然后把数据插入另一个表中。

SELECT INTO 语句常用于创建表的备份复件或者用于对记录进行存档。

1
2
3
SELECT *
INTO new_table_name [IN externaldatabase]
FROM old_tablename
1
2
3
4
5
/*从 "Persons" 表中提取居住在 "Beijing" 的人的信息,创建了一个带有两个列的名为 "Persons_backup" 的表*/
SELECT LastName,Firstname
INTO Persons_backup
FROM Persons
WHERE City='Beijing'

create

1
2
3
4
5
6
7
8
CREATE DATABASE database_name
CREATE TABLE 表名称
(
列名称1 数据类型,
列名称2 数据类型,
列名称3 数据类型,
....
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
create table train(
TID int,
SDate date,
TName char(20),
SStationID int,
AStationID int,
STime datetime,
ATime datetime,
constraint PK_train primary key (TID),
constraint UK_date_tname unique(SDate,TName),
constraint FK_train_SStationId foreign key(SStationID) REFERENCES station(SID),
constraint FK_train_AStationId foreign key(AStationID) REFERENCES station(SID)
);

constraints

约束用于限制加入表的数据的类型。

可以在创建表时规定约束(通过 CREATE TABLE 语句),或者在表创建之后也可以(通过 ALTER TABLE 语句)。

  • NOT NULL

  • UNIQUE

    • ```sql
      /当表已经被创建时添加约束/
      ALTER TABLE Persons
      ADD UNIQUE (Id_P)
      /添加多个约束/
      ALTER TABLE Persons
      ADD CONSTRAINT uc_PersonID UNIQUE (Id_P,LastName)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12



      - PRIMARY KEY

      - UNIQUEPRIMARY KEY 约束均为列或列集合提供了唯一性的保证。

      - 每个表可以有多个 UNIQUE 约束,但是每个表只能有一个 PRIMARY KEY 约束。

      - ```sql
      ALTER TABLE Persons
      ADD PRIMARY KEY (Id_P)
  • FOREIGN KEY

    • CREATE TABLE Orders
      (
      Id_O int NOT NULL,
      OrderNo int NOT NULL,
      Id_P int,
      PRIMARY KEY (Id_O),
      FOREIGN KEY (Id_P) REFERENCES Persons(Id_P)
      )
      /*在已存在的情况下创建约束*/
      ALTER TABLE Orders
      ADD FOREIGN KEY (Id_P)
      REFERENCES Persons(Id_P)
      /*撤销相关的约束*/
      ALTER TABLE Orders
      DROP FOREIGN KEY fk_PerOrders
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22

      - CHECK

      - CHECK 约束用于限制列中的值的范围。

      如果对单个列定义 CHECK 约束,那么该列只允许特定的值。

      如果对一个表定义 CHECK 约束,那么此约束会在特定的列中对值进行限制。

      ```sql
      CREATE TABLE Persons
      (
      Id_P int NOT NULL,
      LastName varchar(255) NOT NULL,
      FirstName varchar(255),
      Address varchar(255),
      City varchar(255),
      CONSTRAINT chk_Person CHECK (Id_P>0 AND City='Sandnes')
      )
      /*撤销check约束*/
      ALTER TABLE Persons
      DROP CHECK chk_Person
  • DEFAULT

    • DEFAULT 约束用于向列中插入默认值。

    • ```sql
      CREATE TABLE Persons
      (
      Id_P int NOT NULL,
      LastName varchar(255) NOT NULL,
      FirstName varchar(255),
      Address varchar(255),
      City varchar(255) DEFAULT ‘Sandnes’
      )

      SQL View
      SQL Date
      SQL Nulls
      SQL isnull()
      SQL 数据类型
      SQL 服务器
      SQL 函数
      SQL functions
      SQL avg()
      SQL count()
      SQL first()
      SQL last()
      SQL max()
      SQL min()
      SQL sum()
      SQL Group By
      SQL Having
      SQL ucase()
      SQL lcase()
      SQL mid()
      SQL len()
      SQL round()
      SQL now()
      SQL format()
      SQL 总结
      SQL 快速参考
      SQL 教程总结
      SQL 测验
      SQL 测验
      建站手册
      网站构建
      万维网联盟 (W3C)
      浏览器信息
      网站品质
      语义网
      职业规划
      网站主机
      编程
      Python 教程
      关于 W3School
      帮助 W3School
      SQL DEFAULT 约束
      SQL Check
      SQL Create Index
      SQL DEFAULT 约束
      DEFAULT 约束用于向列中插入默认值。

      如果没有规定其他的值,那么会将默认值添加到所有的新记录。

      SQL DEFAULT Constraint on CREATE TABLE
      下面的 SQL 在 “Persons” 表创建时为 “City” 列创建 DEFAULT 约束:

      My SQL / SQL Server / Oracle / MS Access:
      CREATE TABLE Persons
      (
      Id_P int NOT NULL,
      LastName varchar(255) NOT NULL,
      FirstName varchar(255),
      Address varchar(255),
      City varchar(255) DEFAULT ‘Sandnes’
      )
      通过使用类似 GETDATE() 这样的函数,DEFAULT 约束也可以用于插入系统值:

      CREATE TABLE Orders
      (
      Id_O int NOT NULL,
      OrderNo int NOT NULL,
      Id_P int,
      OrderDate date DEFAULT GETDATE()
      )
      SQL DEFAULT Constraint on ALTER TABLE
      /在表已存在的情况下为 “City” 列创建 DEFAULT 约束/
      ALTER TABLE Persons
      ALTER City SET DEFAULT ‘SANDNES’
      /需撤销 DEFAULT 约束/
      ALTER TABLE Persons
      ALTER City DROP DEFAULT

      1
      2
      3
      4
      5
      6
      7
      8
      9

      ### index

      在表中创建索引

      ```sql
      /*允许使用重复的值*/
      CREATE INDEX index_name
      ON table_name (column_name)

View

视图是基于 SQL 语句的结果集的可视化的表。

视图包含行和列,就像一个真实的表。视图中的字段就是来自一个或多个数据库中的真实的表中的字段。我们可以向视图添加 SQL 函数、WHERE 以及 JOIN 语句,我们也可以提交数据,就像这些来自于某个单一的表。

视图的作用:

  1. 简化了操作,把经常使用的数据定义为视图,可以将复杂的SQL查询语句进行封装。
  2. 安全性,用户只能查询和修改能看到的数据。
    • 使用视图,基表中的数据就有了一定的安全性。因为视图是虚拟的,物理上是不存在的,只是存储了数据的集合,我们可以不通过视图将基表中重要的字段信息给用户。视图是动态的数据的集合,数据是随着基表的更新而更新的。同时,用户对视图,不可以随意的更改和删除,可以保证数据的安全性。
  3. 逻辑上的独立性,屏蔽了真实表的结构带来的影响。

一、概览

Java 的 I/O 大概可以分成以下几类:

  • 磁盘操作:File
  • 字节操作:InputStream 和 OutputStream
  • 字符操作:Reader 和 Writer
  • 对象操作:Serializable
  • 网络操作:Socket
  • 新的输入/输出:NIO

一、JVM的基本介绍

原文地址:https://juejin.im/post/5e1505d0f265da5d5d744050#heading-28

JVM其实就类似于一台小电脑运行在windows或者linux这些操作系统环境下即可。它直接和操作系统进行交互,与硬件不直接交互,可操作系统可以帮我们完成和硬件进行交互的工作。


1.1 Java文件是如何运行的?

类加载器

如果 JVM 想要执行这个 .class 文件,我们需要将其装进一个 类加载器 中,它就像一个搬运工一样,会把所有的 .class 文件全部搬进JVM里面来。

img

方法区

方法区 是用于存放类似于元数据信息方面的数据的,比如类信息,常量,静态变量,编译后代码···等

类加载器将 .class 文件搬过来就是先丢到这一块上

主要放了一些存储的数据,比如对象实例,数组···等,它和方法区都同属于 线程共享区域 。也就是说它们都是 线程不安全

这是我们的代码运行空间。我们编写的每一个方法都会放到 里面运行。

我们会听说过 本地方法栈 或者 本地方法接口 这两个名词,不过我们基本不会涉及这两块的内容,它俩底层是使用C来进行工作的,和Java没有太大的关系。

本地方法栈

本地方法栈与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。

本地方法一般是用其它语言(C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。


程序计数器

主要就是完成一个加载工作,类似于一个指针一样的,指向下一行我们需要执行的代码。和栈一样,都是 线程独享 的,就是说每一个线程都会有自己对应的一块区域而不会存在并发和多线程的问题。

img

小总结

  1. Java文件经过编译后变成 .class 字节码文件
  2. 字节码文件通过类加载器被搬运到 JVM 虚拟机中
  3. 虚拟机主要的5大块:方法区,堆都为线程共享区域,有线程安全问题,栈和本地方法栈和计数器都是独享区域,不存在线程安全问题,而 JVM 的调优主要就是围绕堆,栈两大块进行

例子

1
2
3
4
5
6
7
8
9
10
11
public class Student
{
public String name;
public Student(String name)
{
this.name=name;
}
public void sayName(){
System.out.println("student is name is "+name);
}
}
1
2
3
4
5
6
7
public class App{
public static void main(String[] args)
{
Student student = new Student("tellUrDream");
student.sayName();
}
}

执行main方法的步骤如下:

  1. 编译好 App.java 后得到 App.class 后,执行 App.class,系统会启动一个 JVM 进程,从 classpath 路径中找到一个名为 App.class 的二进制文件,将 App 的类信息加载到运行时数据区的方法区内,这个过程叫做 App 类的加载
  2. JVM 找到 App 的主程序入口,执行main方法
  3. 这个main中的第一条语句为 Student student = new Student(“tellUrDream”) ,就是让 JVM 创建一个Student对象,但是这个时候方法区中是没有 Student 类的信息的,所以 JVM 马上加载 Student 类,把 Student 类的信息放到方法区中
  4. 加载完 Student 类后,JVM 在堆中为一个新的 Student 实例分配内存,然后调用构造函数初始化 Student 实例,这个 Student 实例持有指向方法区中的 Student 类的类型信息 的引用
  5. 执行student.sayName();时,JVM 根据 student 的引用找到 student 对象,然后根据 student 对象持有的引用定位到方法区中 student 类的类型信息的方法表,获得 sayName() 的字节码地址。
  6. 执行sayName()

其实也不用管太多,只需要知道对象实例初始化时会去方法区中找类信息,完成后再到栈那里去运行方法。找方法就在方法表中找。

二、类加载器的介绍

之前也提到了它是负责加载.class文件的,它们在文件开头会有特定的文件标示,将class文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时数据结构,并且ClassLoader只负责class文件的加载,而是否能够运行则由 Execution Engine 来决定

类是在运行期间第一次使用时动态加载的,而不是一次性加载所有类。因为如果一次性加载,那么会占用很多的内存。

2.1 类加载器的流程

从类被加载到虚拟机内存中开始,到释放内存总共有7个步骤:加载,验证,准备,解析,初始化,使用,卸载。其中验证,准备,解析三个部分统称为连接


2.1.1 加载

  1. 加载是类加载的一个阶段,注意不要混淆。

    加载过程完成以下三件事:

    • 通过类的完全限定名称获取定义该类的二进制字节流。
    • 将该字节流表示的静态存储结构转换为方法区的运行时存储结构。
    • 在内存中生成一个代表该类的 Class 对象,作为方法区中该类各种数据的访问入口。

    其中二进制字节流可以从以下方式中获取:

    • 从 ZIP 包读取,成为 JAR、EAR、WAR 格式的基础。
    • 从网络中获取,最典型的应用是 Applet。
    • 运行时计算生成,例如动态代理技术,在 java.lang.reflect.Proxy 使用 ProxyGenerator.generateProxyClass 的代理类的二进制字节流。
    • 由其他文件生成,例如由 JSP 文件生成对应的 Class 类。

2.1.2 链接

  1. 验证:确保加载的类符合 JVM 规范和安全,保证被校验类的方法在运行时不会做出危害虚拟机的事件,其实就是一个安全检查
  2. 准备:为static变量方法区中分配内存空间,设置变量的初始值,例如 static int a = 3 (注意:准备阶段只设置类中的==静态变量(方法区中)==,不包括实==例变量(堆内存中)==,实例变量是对象初始化时赋值的)
  3. 解析:虚拟机将常量池内的符号引用替换为直接引用的过程(符号引用比如我现在import java.util.ArrayList这就算符号引用,直接引用就是指针或者对象地址,注意引用对象一定是在内存进行)

2.1.3 初始化

初始化其实就是执行类构造器方法的<clinit>()的过程,而且要保证执行前父类的<clinit>()方法执行完毕。这个方法由编译器收集,顺序执行所有类变量(static修饰的成员变量)显式初始化和静态代码块中语句。此时准备阶段时的那个 static int a 由默认初始化的0变成了显式初始化的3. 由于执行顺序缘故,初始化阶段类变量如果在静态代码块中又进行了更改,会覆盖类变量的显式初始化,最终值会为静态代码块中的赋值。

注意:字节码文件中初始化方法有两种,非静态资源初始化的<init>和静态资源初始化的<clinit>,类构造器方法<clinit>()不同于类的构造器,这些方法都是字节码文件中只能给JVM识别的特殊方法。

2.1.4 卸载

GC将无用对象从内存中卸载

2.2 类加载器分类和加载顺序

从 Java 虚拟机的角度来讲,只存在以下两种不同的类加载器:

  • 启动类加载器(Bootstrap ClassLoader),使用 C++ 实现,是虚拟机自身的一部分;

  • 所有其它类的加载器,使用 Java 实现,独立于虚拟机,继承自抽象类 java.lang.ClassLoader。

从 Java 开发人员的角度看,类加载器可以划分得更细致一些:

  • 启动类加载器(Bootstrap ClassLoader)此类加载器负责将存放在 <JRE_HOME>\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可。

  • 扩展类加载器(Extension ClassLoader)这个类加载器是由 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将 <JAVA_HOME>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中,开发者可以直接使用扩展类加载器。

  • 应用程序类加载器(Application ClassLoader)这个类加载器是由 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,因此一般称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

加载一个Class类的顺序也是有优先级的,类加载器从最底层开始往上的顺序是这样的

  1. BootStrap ClassLoader:rt.jar
  2. Extension ClassLoader: 加载扩展的jar包
  3. App ClassLoader:指定的classpath下面的jar包
  4. Custom ClassLoader:自定义的类加载器

2.3 双亲委派机制(重点)

双亲委派模式要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器,请注意双亲委派模式中的父子关系并非通常所说的类继承关系,而是采用组合关系来复用父类加载器的相关代码,类加载器间的关系如下:


当一个类收到了加载请求时,它是不会先自己去尝试加载的,而是委派给父类去完成,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。

比如我现在要new一个Person,这个Person是我们自定义的类,如果我们要加载它,就会先委派App ClassLoader,只有当父类加载器都反馈自己无法完成这个请求(也就是父类加载器都没有找到加载所需的Class)时,子类加载器才会自行尝试加载

双亲委派模式优势

  • 采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
  • 其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改

三、运行时数据区

3.1 本地方法栈和程序计数器

比如说我们现在点开Thread类的源码,会看到它的start0方法带有一个native关键字修饰,而且不存在方法体,这种用native修饰的方法就是本地方法,这是使用C来实现的,然后一般这些方法都会放到一个叫做本地方法栈的区域。

程序计数器其实就是一个指针,它指向了我们程序中下一句需要执行的指令,它也是内存区域中唯一一个不会出现OutOfMemoryError的区域,而且占用内存空间小到基本可以忽略不计。这个内存仅代表当前线程所执行的字节码的行号指示器,字节码解析器通过改变这个计数器的值选取下一条需要执行的字节码指令。

如果执行的是native方法,那这个指针就不工作了。

3.2 方法区

用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。当它存储的信息过大时,会在无法满足内存分配时报错。

和堆一样不需要连续的内存,并且可以动态扩展,动态扩展失败一样会抛出 OutOfMemoryError 异常。

对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载,但是一般比较难实现。

HotSpot 虚拟机把它当成永久代来进行垃圾回收。但很难确定永久代的大小,因为它受到很多因素影响,并且每次 Full GC 之后永久代的大小都会改变,所以经常会抛出 OutOfMemoryError 异常。为了更容易管理方法区,从 JDK 1.8 开始,移除永久代,并把方法区移至元空间,它位于本地内存中,而不是虚拟机内存中。

方法区是一个 JVM 规范,永久代与元空间都是其一种实现方式。在 JDK 1.8 之后,原来永久代的数据被分到了堆和元空间中。元空间存储类的元信息,静态变量和常量池等放入堆中。

3.2.1 运行时常量池

运行时常量池是方法区的一部分。

Class 文件中的常量池(编译器生成的字面量和符号引用)会在类加载后被放入这个区域。

除了在编译期生成的常量,还允许动态生成,例如 String 类的 intern()。

3.2.2 直接内存

在 JDK 1.4 中新引入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存,然后通过 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在堆内存和堆外内存来回拷贝数据。

3.3 虚拟机栈和虚拟机堆

一句话便是:栈管运行,堆管存储。则虚拟机栈负责运行代码,而虚拟机堆负责存储数据。

3.3.1 虚拟机栈的概念

它是Java方法执行的内存模型。里面会对局部变量,动态链表,方法出口,栈的操作(入栈和出栈)进行存储,且线程独享。同时如果我们听到局部变量表,那也是在说虚拟机栈

1
2
3
4
5
6
7
public class Person{
int a = 1;

public void doSomething(){
int b = 2;
}
}

3.3.2 虚拟机栈存在的异常

如果线程请求的栈的深度大于虚拟机栈的最大深度,就会报 StackOverflowError (这种错误经常出现在递归中)。Java虚拟机也可以动态扩展,但随着扩展会不断地申请内存,当无法申请足够内存时就会报错 OutOfMemoryError

3.3.3 虚拟机栈的生命周期

对于栈来说,不存在垃圾回收。只要程序运行结束,栈的空间自然就会释放了。栈的生命周期和所处的线程是一致的。

这里补充一句:8种基本类型的变量+对象的引用变量+实例方法都是在栈里面分配内存。

3.3.4 虚拟机栈的执行

每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。


3.3.5 局部变量的复用

局部变量表用于存放方法参数和方法内部所定义的局部变量。它的容量是以Slot为最小单位,一个slot可以存放32位以内的数据类型。

虚拟机通过索引定位的方式使用局部变量表,范围为[0,局部变量表的slot的数量]。方法中的参数就会按一定顺序排列在这个局部变量表中,至于怎么排的我们可以先不关心。而为了节省栈帧空间,这些slot是可以复用的,当方法执行位置超过了某个变量,那么这个变量的slot可以被其它变量复用。当然如果需要复用,那我们的垃圾回收自然就不会去动这些内存。

3.3.6 虚拟机堆的概念

JVM内存会划分为堆内存和非堆内存,堆内存中也会划分为年轻代老年代,而非堆内存则为永久代。年轻代又会分为EdenSurvivor区。Survivor也会分为FromPlaceToPlace,toPlace的survivor区域是空的。Eden,FromPlace和ToPlace的默认占比为 8:1:1。该比例可以通过一个 -XX:+UsePSAdaptiveSurvivorSizePolicy 参数来根据生成对象的速率动态调整

image-20200324095521916

堆内存中存放的是对象,**垃圾收集就是收集这些对象然后交给GC算法进行回收。 **
非堆内存==方法区。
在1.8中已经移除永久代,替代品是一个元空间(MetaSpace),最大区别是metaSpace是不存在于JVM中的,它使用的是本地内存。并有两个参数

1
2
MetaspaceSize:初始化元空间大小,控制发生GC
MaxMetaspaceSize:限制元空间大小上限,防止占用过多物理内存。

移除的原因可以大致了解一下:融合HotSpot JVM和JRockit VM而做出的改变,因为JRockit是没有永久代的,不过这也间接性地解决了永久代的OOM问题。

3.3.7 Eden年轻代的介绍

当我们new一个对象后,会先放到Eden划分出来的一块作为存储空间的内存,但是我们知道对堆内存是线程共享的,所以有可能会出现两个对象共用一个内存的情况。这里JVM的处理是每个线程都会预先申请好一块连续的内存空间并规定了对象存放的位置,而如果空间不足会再申请多块内存空间。这个操作我们会称作TLAB。

当Eden空间满了之后,会触发一个叫做Minor GC(就是一个发生在年轻代的GC)的操作,存活下来的对象移动到Survivor0区。Survivor0区满后触发 Minor GC,就会将存活对象移动到Survivor1区,此时还会把from和to两个指针交换,这样保证了一段时间内总有一个survivor区为空且to所指向的survivor区为空。经过多次的 Minor GC后仍然存活的对象(这里的存活判断是15次,对应到虚拟机参数为 -XX:MaxTenuringThreshold 。为什么是15,因为HotSpot会在对象投中的标记字段里记录年龄,分配到的空间仅有4位,所以最多只能记录到15)会移动到老年代。老年代是存储长期存活的对象的,占满时就会触发我们最常听说的Full GC,期间会停止所有线程等待GC的完成。所以对于响应要求高的应用应该尽量去减少发生Full GC从而避免响应超时的问题。

而且当老年区执行了full gc之后仍然无法进行对象保存的操作,就会产生OOM,这时候就是虚拟机中的堆内存不足,原因可能会是堆内存设置的大小过小,这个可以通过参数-Xms、-Xmx来调整。也可能是代码中创建的对象大且多,而且它们一直在被引用从而长时间垃圾收集无法收集它们。

img

四、垃圾收集

4.1 如何判断一个对象需要被干掉

img

图中程序计数器、虚拟机栈、本地方法栈,3个区域随着线程的生存而生存的。内存分配和回收都是确定的。随着线程的结束内存自然就被回收了,因此不需要考虑垃圾回收的问题。而Java堆和方法区则不一样,各线程共享,内存的分配和回收都是动态的。因此垃圾收集器所关注的都是堆和方法这部分内存。

判断方法:

1.引用计数器计算:给对象添加一个引用计数器,每次引用这个对象时计数器加一,引用失效时减一,计数器等于0时就是不会再次使用的。不过这个方法有一种情况就是出现对象的循环引用时,引用计数器永远不为0,GC没法回收。

2.可达性分析计算:以 GC Roots 为起始点进行搜索,可达的对象都是存活的,不可达的对象可被回收。

(了解一下即可)在Java语言汇总能作为GC Roots的对象分为以下几种:

  1. 虚拟机栈(栈帧中的本地方法表)中引用的对象(局部变量)
  2. 方法区中静态变量所引用的对象(静态变量)
  3. 方法区中常量引用的对象
  4. 本地方法栈(即native修饰的方法)中JNI引用的对象(JNI是Java虚拟机调用对应的C函数的方式,通过JNI函数也可以创建新的Java对象。且JNI对于对象的局部引用或者全局引用都会把它们指向的对象都标记为不可回收)
  5. 已启动的且未终止的Java线程

这种方法的优点是能够解决循环引用的问题,可它的实现需要耗费大量资源和时间,也需要GC(它的分析过程引用关系不能发生变化,所以需要停止所有进程)

4.2 如何宣告一个对象的真正死亡

首先必须要提到的是一个名叫 finalize() 的方法

finalize()是Object类的一个方法、一个对象的finalize()方法只会被系统自动调用一次,经过finalize()方法逃脱死亡的对象,第二次不会再调用。

【不建议使用finalize方法】

判断一个对象的死亡至少需要两次标记

  1. 如果对象进行可达性分析之后没发现与GC Roots相连的引用链,那它将会第一次标记并且进行一次筛选。判断的条件是决定这个对象是否有必要执行finalize()方法。如果对象有必要执行finalize()方法,则被放入F-Queue队列中。
  2. GC对F-Queue队列中的对象进行二次标记。如果对象在finalize()方法中重新与引用链上的任何一个对象建立了关联,那么二次标记时则会将它移出“即将回收”集合。如果此时对象还没成功逃脱,那么只能被回收了。

如果确定对象已经死亡,我们又该如何回收这些垃圾呢

4.3 引用类型

无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象是否可达,判定对象是否可被回收都与引用有关。

Java 提供了四种强度不同的引用类型。

1. 强引用

被强引用关联的对象不会被回收。

使用 new 一个新对象的方式来创建强引用。

1
Object obj = new Object();

2. 软引用

被软引用关联的对象只有在内存不够的情况下才会被回收。

使用 SoftReference 类来创建软引用。

1
2
3
Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null; // 使对象只被软引用关联

3. 弱引用

被弱引用关联的对象一定会被回收,也就是说它只能存活到下一次垃圾回收发生之前。

使用 WeakReference 类来创建弱引用。

1
2
3
Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;

4. 虚引用

又称为幽灵引用或者幻影引用,一个对象是否有虚引用的存在,不会对其生存时间造成影响,也无法通过虚引用得到一个对象。

为一个对象设置虚引用的唯一目的是能在这个对象被回收时收到一个系统通知。

使用 PhantomReference 来创建虚引用。

1
2
3
Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj, null);
obj = null;

4.4 垃圾回收算法

4.4.1 标记清除算法

在标记阶段,程序会检查每个对象是否为活动对象,如果是活动对象,则程序会在对象头部打上标记。

在清除阶段,会进行对象回收并取消标志位,另外,还会判断回收后的分块与前一个空闲分块是否连续,若连续,会合并这两个分块。回收对象就是把对象作为分块,连接到被称为 “空闲链表” 的单向链表,之后进行分配时只需要遍历这个空闲链表,就可以找到分块。

在分配时,程序会搜索空闲链表寻找空间大于等于新对象大小 size 的块 block。如果它找到的块等于 size,会直接返回这个分块;如果找到的块大于 size,会将块分割成大小为 size 与 (block - size) 的两部分,返回大小为 size 的分块,并把大小为 (block - size) 的块返回给空闲链表。

不足:

  • 标记和清除过程效率都不高;
  • 会产生大量不连续的内存碎片,导致无法给大对象分配内存。

img

此时可使用的内存块都是零零散散的,导致了刚刚提到的大内存对象问题

4.4.2 复制算法

将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过的内存空间进行一次清理。

主要不足是只使用了内存的一半。

img

现在的商业虚拟机都采用这种收集算法回收新生代,但是并不是划分为大小相等的两块,而是一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 和其中一块 Survivor。在回收时,将 Eden 和 Survivor 中还存活着的对象全部复制到另一块 Survivor 上,最后清理 Eden 和使用过的那一块 Survivor。

HotSpot 虚拟机的 Eden 和 Survivor 大小比例默认为 8:1,保证了内存的利用率达到 90%。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 就不够用了,此时需要依赖于老年代进行空间分配担保,也就是借用老年代的空间存储放不下的对象。

4.4.3 标记整理算法

复制算法在对象存活率高的时候会有一定的效率问题,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存

优点:

  • 不会产生内存碎片

不足:

  • 需要移动大量对象,处理效率比较低。

img

4.4.4 分代收集算法

根据对象存活周期将内存划分为几块,不同块采用适当的收集算法。

一般将堆分为新生代和老年代。

  • 新生代使用:复制算法
    • 每次垃圾收集时都发现有大批对象死去,只有少量存活
  • 老年代使用:标记 - 清除 或者 标记 - 整理 算法
    • 因为对象存活率高、没有额外空间对它进行分配担保

4.5 方法区的回收

因为方法区主要存放永久代对象,而永久代对象的回收率比新生代低很多,所以在方法区上进行回收性价比不高。

主要是对常量池的回收和对类的卸载

为了避免内存溢出,在大量使用反射和动态代理的场景都需要虚拟机具备类卸载功能。

类的卸载条件很多,需要满足以下三个条件,并且满足了条件也不一定会被卸载:

  • 该类所有的实例都已经被回收,此时堆中不存在该类的任何实例。
  • 加载该类的 ClassLoader 已经被回收。
  • 该类对应的 Class 对象没有在任何地方被引用,也就无法在任何地方通过反射访问该类方法。

五、内存分配和回收策略

5.1 Minor GC 和 Full GC

  • Minor GC:回收新生代,因为新生代对象存活时间很短,因此 Minor GC 会频繁执行,执行的速度一般也会比较快。
  • Full GC:回收老年代和新生代,老年代对象其存活时间长,因此 Full GC 很少执行,执行速度会比 Minor GC 慢很多。

5.2 内存分配策略

5.2.1 对象优先在 Eden 分配

大多数情况下,对象在新生代 Eden 上分配,当 Eden 空间不够时,发起 Minor GC。

5.2.2 大对象直接进入老年代

大对象是指需要连续内存空间的对象,最典型的大对象是那种很长的字符串以及数组。

经常出现大对象会提前触发垃圾收集以获取足够的连续空间分配给大对象。

-XX:PretenureSizeThreshold,大于此值的对象直接在老年代分配,避免在 Eden 和 Survivor 之间的大量内存复制。

5.2.3 长期存活的对象进入老年代

为对象定义年龄计数器,对象在 Eden 出生并经过 Minor GC 依然存活,将移动到 Survivor 中,年龄就增加 1 岁,增加到一定年龄则移动到老年代中。

-XX:MaxTenuringThreshold 用来定义年龄的阈值。

5.2.4 动态对象年龄判定

虚拟机并不是永远要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 中相同年龄所有对象大小的总和大于 Survivor 空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无需等到 MaxTenuringThreshold 中要求的年龄。

5.2.5 空间分配担保

在发生 Minor GC 之前,虚拟机先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果条件成立的话,那么 Minor GC 可以确认是安全的。

如果不成立的话虚拟机会查看 HandlePromotionFailure 的值是否允许担保失败,如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC;如果小于,或者 HandlePromotionFailure 的值不允许冒险,那么就要进行一次 Full GC。

5.3 Full GC 的触发条件

对于 Minor GC,其触发条件非常简单,当 Eden 空间满时,就将触发一次 Minor GC。而 Full GC 则相对复杂,有以下条件:

1. 调用 System.gc()

只是建议虚拟机执行 Full GC,但是虚拟机不一定真正去执行。不建议使用这种方式,而是让虚拟机管理内存。

2. 老年代空间不足

老年代空间不足的常见场景为前文所讲的大对象直接进入老年代、长期存活的对象进入老年代等。

为了避免以上原因引起的 Full GC,应当尽量不要创建过大的对象以及数组。除此之外,可以通过 -Xmn 虚拟机参数调大新生代的大小,让对象尽量在新生代被回收掉,不进入老年代。还可以通过 -XX:MaxTenuringThreshold 调大对象进入老年代的年龄,让对象在新生代多存活一段时间。

3. 空间分配担保失败

使用复制算法的 Minor GC 需要老年代的内存空间作担保,如果担保失败会执行一次 Full GC。具体内容请参考上面的第 5 小节。

4. JDK 1.7 及以前的永久代空间不足

在 JDK 1.7 及以前,HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 Class 的信息、常量、静态变量等数据。

当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。如果经过 Full GC 仍然回收不了,那么虚拟机会抛出 java.lang.OutOfMemoryError。

为避免以上原因引起的 Full GC,可采用的方法为增大永久代空间或转为使用 CMS GC。

5. Concurrent Mode Failure

执行 CMS GC 的过程中同时有对象要放入老年代,而此时老年代空间不足(可能是 GC 过程中浮动垃圾过多导致暂时性的空间不足),便会报 Concurrent Mode Failure 错误,并触发 Full GC。

一、基础知识

1.1 相关概念

  1. DBMS:数据库管理系统,是位于用户与操作系统之间的一层数据管理软件,用于科学地组织、存储和管理数据、高效地获取和维护数据。

  2. DBS:数据库系统,指在计算机系统中引入数据库后的系统,一般由数据库、数据库管理系统、应用系统、数据库管理员(DBA)构成。

  3. 数据模型:是用来抽象、表示和处理现实世界中的数据和信息的工具,是对现实世界的模拟,是数据库系统的核心和基础;其组成元素有数据结构、数据操作和完整性约束

  4. 概念模型:也称信息模型,是按用户的观点来对数据和信息建模,主要用于数据库设计。

  5. 逻辑模型:是按计算机系统的观点对数据建模,用于DBMS实现。

  6. 物理模型:是对数据最底层的抽象,描述数据在系统内部的表示方式和存取方法,在磁盘或磁带上的存储方式和存取方法,是面向计算机系统的。

  7. 实体和属性:客观存在并可相互区别的事物称为实体。实体所具有的某一特性称为属性。

  8. E-R图:即实体-关系图,用于描述现实世界的事物及其相互关系,是数据库概念模型设计的主要工具。

  9. 关系模式:从用户观点看,关系模式是由一组关系组成,每个关系的数据结构是一张规范化的二维表。

  10. 型/值:型是对某一类数据的结构和属性的说明;值是型的一个具体赋值,是型的实例。

  11. 数据库模式:是对数据库中全体数据的逻辑结构(数据项的名字、类型、取值范围等)和特征(数据之间的联系以及数据有关的安全性、完整性要求)的描述。

  12. 数据库的三级系统结构:外模式、模式和内模式。

  13. 数据库内模式:又称为存储模式,是对数据库物理结构和存储方式的描述,是数据在数据库内部的表示方式。一个数据库只有一个内模式。

  14. 数据库外模式:又称为子模式或用户模式,它是数据库用户能够看见和使用的局部数据的逻辑结构和特征的描述,是数据库用户的数据视图。通常是模式的子集。一个数据库可有多个外模式

  15. 数据库的二级映像:外模式/模式映像、模式/内模式映像。

    • 外模式/模式映像:同一个模式下可以有任意多个外模式。【逻辑独立性】
    • 模式/内模式映像:唯一的【储存结构】
  16. 候选码(Candidate Key):关系中的一个属性组,其值能唯一标识一个元组

  17. 主键: 能够唯一地标识一个元组的属性或属性组称为关系的键或候选键。 若一个关系有多个候选键则可选其一作为主键(Primary key)。

  18. 外键:如果一个关系的一个或一组属性引用(参照)了另一个关系的主键,则称这个或这组属性为外码或外键(Foreign key)。

  19. 关系数据库:依照关系模型建立的数据库称为关系数据库。 它是在某个应用领域的所有关系的集合。

  20. 关系模式: 简单地说,关系模式就是对关系的型的定义, 包括关系的属性构成、各属性的数据类型、 属性间的依赖、 元组语义及完整性约束等。 记作R(A1 , A2 ,…, An ) 。
    关系是关系模式在某一时刻的状态或内容, 关系模型是型, 关系是值, 关系模型是静态的、 稳定的, 而关系是动态的、随时间不断变化的,因为关系操作在不断地更新着数据库中的数据。

    • 五元组形式化表示为:R(U,D,DOM,F),其中

      ​ R —— 关系名

      ​ U —— 组成该关系的属性名集合

      ​ D —— 属性组 U 中属性所来自的域

      ​ DOM —— 属性向域的映象集合

      ​ F —— 属性间的数据依赖关系集合

  21. 实体完整性:用于标识实体的唯一性。它要求基本关系必须要有一个能够标识元组唯一性的主键,主键不能为空,也不可取重复值

  22. 参照完整性: 用于维护实体之间的引用关系。 它要求一个关系的外键要么为空, 要么取与被参照关系对应的主键值,即外键值必须是主键中已存在的值。
    意义:如果关系R2的某个元组t2参照了关系R1的某个元组t1,则t1必须存在

  23. 用户定义的完整性:就是针对某一具体应用的数据必须满足的语义约束。包括非空、 唯一和布尔条件约束三种情况。

1.2 笛卡尔积

二、事务

事务指的是满足 ACID 特性的一组操作,可以通过 Commit 提交一个事务,也可以使用 Rollback 进行回滚。

2.1 四大特性——ACID

1. 原子性(Atomicity)

事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚。

回滚可以用回滚日志(Undo Log)来实现,回滚日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。

2. 一致性(Consistency)

数据库在事务执行前后都保持一致性状态。在一致性状态下,所有事务对同一个数据的读取结果都是相同的。

3. 隔离性(Isolation)

一个事务所做的修改在最终提交以前,对其它事务是不可见的。

4. 持久性(Durability)

一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。

系统发生崩溃可以用重做日志(Redo Log)进行恢复,从而实现持久性。与回滚日志记录数据的逻辑修改不同,重做日志记录的是数据页的物理修改。

事务的 ACID 特性概念简单,但不是很好理解,主要是因为这几个特性不是一种平级关系:

  • 只有满足一致性,事务的执行结果才是正确的。
  • 在无并发的情况下,事务串行执行,隔离性一定能够满足。此时只要能满足原子性,就一定能满足一致性。
  • 在并发的情况下,多个事务并行执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性。
  • 事务满足持久化是为了能应对系统崩溃的情况。

AUTOCOMMIT

MySQL 默认采用自动提交模式。也就是说,如果不显式使用START TRANSACTION语句来开始一个事务,那么每个查询操作都会被当做一个事务并自动提交。

一、集合介绍

Java的集合类

  集合类主要负责保存、盛装和管理对象,因此集合类也被称为容器类。

  集合类分为Set、List、Map和Queue四大体系。

  • Set 代表无序、不可重复集合;
  • List 代表有序、可重复集合;
  • Map 代表具有映射关系元素的集合;
  • Queue 代表队列,实现元素的先进先出管理。

  数组也是一种集合类,它是能随机存储和访问引用序列的诸多方法中最高效的一种,当追求高效的数据访问时,数组是很不错的选择。

所有集合类都位于java.util包中,集合中只能保存对象的引用。集合类把它所含有的元素看成是Object的实例,这样方便但是也有隐患,即多个类型不同的元素被放入一个集合中,会增加集合访问时类型转换的困难,甚至会产生错误。

  泛型的引入改善了这种情况,使用泛型来限制集合里元素的类型,并让集合记住元素的类型。这样可以允许编译器检查加入集合的元素类型,避免值类型不一致的错误。

二、集合概述

Java的集合类主要由两个接口派生而出——Collection和Map,它们是Java集合框架的根接口。


Collection接口存储一组不唯一(可以重复),无序的对象

img


 标准集合类实现了Collection接口,其中一些是抽象类,实现部分接口,其它是具体类,可以直接在代码中使用。

2.1 Set

  • TreeSet:(有序,唯一): 红黑树(自平衡的排序二叉树)。HashSet 查找的时间复杂度为 O(1),TreeSet 则为 O(logN)。

  • HashSet:无序,唯一): 基于 HashMap 实现的,底层采用HashMap 来保存元素。

  • LinkedHashSet:具有 HashSet 的查找效率,并且内部使用双向链表维护元素的插入顺序。

2.2 List

  • ArrayList:基于动态数组实现,支持随机访问。

  • Vector:和 ArrayList 类似,但它是线程安全的。

    • 1.Stack栈是继承了Vector类,所以底层还是数组

      2.Stack栈是将底层的数组的最后一个元素当做栈顶。第一个元素当做栈底。

  • LinkedList:基于双向链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList 还可以用作栈、队列和双向队列。

2.3 Queue

  • LinkedList:可以用它来实现双向队列。
  • PriorityQueue:基于堆结构实现,可以用它来实现优先队列。

2.4 Map

  • TreeMap:基于红黑树实现。
  • HashMap:DK1.8 之前 HashMap 由数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间
  • key不可重复
  • HashTable: 数组+链表组成的,数组是HashTable的主体,链表则是主要为了解决哈希冲突而存在的。
    线程安全,这意味着同一时刻多个线程同时写入 HashTable 不会导致数据不一致。它是遗留类,不应该去使用它,而是使用 ConcurrentHashMap 来支持线程安全,ConcurrentHashMap 的效率会更高,因为 ConcurrentHashMap 引入了分段锁。
  • LinkedHashMap:LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上使用双向链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序。详细可以查看:《LinkedHashMap 源码详细分析(JDK1.8)》

2.5 如何选用集合

需要根据键值获取到元素值时就选用 Map 接口下的集合:

  • 需要排序时选择 TreeMap
  • 不需要排序时就选择 HashMap
  • 需要保证线程安全就选用 ConcurrentHashMap

只需要存放元素值时,就选择实现Collection 接口的集合:

  • 需要保证元素唯一时选择实现 Set 接口的集合比如 TreeSetHashSet
  • 不需要就选择实现 List 接口的比如 ArrayListLinkedList

2.6 为什么需要使用集合?

数组存储的数据是有序的、可重复的,特点单一。 但是集合提高了数据存储的灵活性,Java 集合不仅可以用来存储不同类型不同数量的对象,还可以保存具有映射关系的数据。

三、Collection的子接口——List

3.1 ArrayList和Vector的区别

  • ArrayListList 的主要实现类,底层使用 Object[ ]存储,适用于频繁的查找工作,线程不安全 ;
  • VectorList 的古老实现类,底层使用 Object[ ] 存储,线程安全的。

3.2 ArrayList和LinkedList的区别

  1. 是否保证线程安全: ArrayListLinkedList 都是不同步的,也就是不保证线程安全;
  2. 底层数据结构: Arraylist 底层使用的是 Object 数组LinkedList 底层使用的是 双向链表 数据结构(JDK1.6 之前为循环链表,JDK1.7 取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!)
  3. 插入和删除是否受元素位置的影响:
    1. ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。
    2. LinkedList 采用链表存储,所以对于add(E e)方法的插入,删除元素时间复杂度不受元素位置的影响,近似 O(1),如果是要在指定位置i插入和删除元素的话((add(int index, E element)) 时间复杂度近似为o(n))因为需要先移动到指定位置再插入。
  4. 是否支持快速随机访问:
    1. LinkedList 不支持高效的随机元素访问
    2. ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。
  5. 内存空间占用: ArrayList 的空间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。

3.3 ArrayList的扩容机制(重点)

  1. 初始化的默认容量是10;
  2. 当添加元素的时候,add()方法会调用ensureCapacityInternal(size+1)方法,用来进行容量检查,决定扩容的想要的最小容量。
  3. 当需要扩容的时候,会调用grow()方法。
    int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次扩容之后容量都会变为原来的 1.5 倍左右(oldCapacity 为偶数就是 1.5 倍,否则是 1.5 倍左右)!
  4. 真正的实现扩容其实是Arrays.copy方法,就是复制数组实现扩容

四、Collection的子接口Set

4.1 comparable 和 Comparator 的区别

  • comparable 接口实际上是出自java.lang包 它有一个 compareTo(Object obj)方法用来排序
  • comparator接口实际上是出自 java.util包它有一个compare(Object obj1, Object obj2)方法用来排序

4.2 比较 HashSet、LinkedHashSet 和 TreeSet 三者的异同

HashSetSet 接口的主要实现类 ,HashSet 的底层是 HashMap,线程不安全的,可以存储 null 值;

LinkedHashSetHashSet 的子类,能够按照添加的顺序遍历;

TreeSet 底层使用红黑树,能够按照添加元素的顺序进行遍历,排序的方式有自然排序和定制排序。

五、Map接口

5.1 HashMap 和 Hashtable 的区别

  1. 线程是否安全: HashMap 是非线程安全的,HashTable 是线程安全的,因为 HashTable 内部的方法基本都经过synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!);
  2. 效率: 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在代码中使用它;
  3. 对 Null key 和 Null value 的支持: HashMap 可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个;HashTable 不允许有 null 键和 null 值,否则会抛出 NullPointerException
  4. 初始容量大小和每次扩充容量大小的不同 : ① 创建时如果不指定容量初始值,Hashtable 默认的初始大小为 11,之后每次扩充,容量变为原来的 2n+1。HashMap 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。② 创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为 2 的幂次方大小(HashMap 中的tableSizeFor()方法保证,下面给出了源代码)。也就是说 HashMap 总是使用 2 的幂作为哈希表的大小,后面会介绍到为什么是 2 的幂次方。
  5. 底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。

5.2 HashMap 和 HashSet 区别

HashSet 底层就是基于 HashMap 实现的。

HashMap HashSet
实现了 Map 接口 实现 Set 接口
存储键值对 仅存储对象
调用 put()向 map 中添加元素 调用 add()方法向 Set 中添加元素
HashMap 使用键(Key)计算 hashcode HashSet 使用成员对象来计算 hashcode 值,对于两个对象来说 hashcode 可能相同,所以 equals()方法用来判断对象的相等性

5.3 HashMap 和 TreeMap 区别

相比于HashMap来说 TreeMap 主要多了对集合中的元素根据键排序的能力以及对集合内元素的搜索的能力。

5.4 HashSet 如何检查重复

先看hashcode;再用equals()

当你把对象加入HashSet时,HashSet 会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的 hashcode 值作比较,如果没有相符的 hashcodeHashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让加入操作成功。

hashCode()equals() 的相关规定:

  1. 如果两个对象相等,则 hashcode 一定也是相同的
  2. 两个对象相等,对两个 equals() 方法返回 true
  3. 两个对象有相同的 hashcode 值,它们也不一定是相等的
  4. 综上,equals() 方法被覆盖过,则 hashCode() 方法也必须被覆盖
  5. hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。

5.5 HashMap 多线程操作导致死循环问题

主要原因在于并发下的 Rehash 会造成元素之间会形成一个循环链表。不过,jdk 1.8 后解决了这个问题,但是还是不建议在多线程下使用 HashMap,因为多线程下使用 HashMap 还是会存在其他问题比如数据丢失。并发环境下推荐使用 ConcurrentHashMap 。

5.6 HashMap 有哪几种常见的遍历方式?

HashMap 的 7 种遍历方式与性能分析!

HashMap 遍历从大的方向来说,可分为以下 4 类

  1. 迭代器(Iterator)方式遍历;
  2. For Each 方式遍历;
  3. Lambda 表达式遍历(JDK 1.8+);
  4. Streams API 遍历(JDK 1.8+)。

5.7 ConcurrentHashMap 和 Hashtable 的区别

ConcurrentHashMapHashtable 的区别主要体现在实现线程安全的方式上不同。

  • 底层数据结构: JDK1.7 的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟 HashMap1.8 的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
  • 实现线程安全的方式(重要):在 JDK1.7 的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 到了 JDK1.8 的时候已经摒弃了 Segment 的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6 以后 对 synchronized 锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在 JDK1.8 中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。

两者的对比图:

HashTable:

HashTable全表锁

http://www.cnblogs.com/chengxiao/p/6842045.html>

JDK1.7 的 ConcurrentHashMap:

JDK1.7的ConcurrentHashMap

http://www.cnblogs.com/chengxiao/p/6842045.html>

JDK1.8 的 ConcurrentHashMap:

Java8 ConcurrentHashMap 存储结构(图片来自 javadoop)

JDK1.8 的 ConcurrentHashMap 不在是 Segment 数组 + HashEntry 数组 + 链表,而是 Node 数组 + 链表 / 红黑树。不过,Node 只能用于链表的情况,红黑树的情况需要使用 **TreeNode**。当冲突链表达到一定长度时,链表会转换成红黑树。

5.8 ConcurrentHashMap 线程安全的具体实现方式/底层具体实现

JDK1.7

首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。

ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成

Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数据。

1
2
static class Segment<K,V> extends ReentrantLock implements Serializable {
}

一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和 HashMap 类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个 HashEntry 数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 的锁。

JDK1.8 (上面有示意图)

ConcurrentHashMap 取消了 Segment 分段锁,采用 CAS 和 synchronized 来保证并发安全。数据结构跟 HashMap1.8 的结构类似,数组+链表/红黑二叉树。Java 8 在链表长度超过一定阈值(8)时将链表(寻址时间复杂度为 O(N))转换为红黑树(寻址时间复杂度为 O(log(N)))

synchronized 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发,效率又提升 N 倍。

六、Collections 工具类

Collections 工具类常用方法:

  1. 排序
  2. 查找,替换操作
  3. 同步控制(不推荐,需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合)

6.1 排序

1
2
3
4
5
6
void reverse(List list)//反转
void shuffle(List list)//随机排序
void sort(List list)//按自然排序的升序排序
void sort(List list, Comparator c)//定制排序,由Comparator控制排序逻辑
void swap(List list, int i , int j)//交换两个索引位置的元素
void rotate(List list, int distance)//旋转。当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 list的前distance个元素整体移到后面

6.2 查找,替换操作

1
2
3
4
5
6
7
int binarySearch(List list, Object key)//对List进行二分查找,返回索引,注意List必须是有序的
int max(Collection coll)//根据元素的自然顺序,返回最大的元素。 类比int min(Collection coll)
int max(Collection coll, Comparator c)//根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c)
void fill(List list, Object obj)//用指定的元素代替指定list中的所有元素。
int frequency(Collection c, Object o)//统计元素出现次数
int indexOfSubList(List list, List target)//统计target在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target).
boolean replaceAll(List list, Object oldVal, Object newVal), 用新元素替换旧元素

6.3 同步控制

Collections 提供了多个synchronizedXxx()方法·,该方法可以将指定集合包装成线程同步的集合,从而解决多线程并发访问集合时的线程安全问题。

我们知道 HashSetTreeSetArrayList,LinkedList,HashMap,TreeMap 都是线程不安全的。Collections 提供了多个静态方法可以把他们包装成线程同步的集合。

最好不要用下面这些方法,效率非常低,需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合。

方法如下:

1
2
3
4
synchronizedCollection(Collection<T>  c) //返回指定 collection 支持的同步(线程安全的)collection。
synchronizedList(List<T> list)//返回指定列表支持的同步(线程安全的)List。
synchronizedMap(Map<K,V> m) //返回由指定映射支持的同步(线程安全的)Map。
synchronizedSet(Set<T> s) //返回指定 set 支持的同步(线程安全的)set。

七、容器中的设计模式

7.1 迭代器模式


Collection 继承了 Iterable 接口,其中的 iterator() 方法能够产生一个 Iterator 对象,通过这个对象就可以迭代遍历 Collection 中的元素。

从 JDK 1.5 之后可以使用 foreach 方法来遍历实现了 Iterable 接口的聚合对象。

1
2
3
4
5
6
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
for (String item : list) {
System.out.println(item);
}

7.2 适配器模式

java.util.Arrays#asList() 可以把数组类型转换为 List 类型。

1
2
@SafeVarargs
public static <T> List<T> asList(T... a)

应该注意的是 asList() 的参数为泛型的变长参数,不能使用基本类型数组作为参数,只能使用相应的包装类型数组。

1
2
Integer[] arr = {1, 2, 3};
List list = Arrays.asList(arr);

也可以使用以下方式调用 asList():

1
List list = Arrays.asList(1, 2, 3);