/*SQLyog Ultimate v12.09 (64 bit)MySQL - 5.5.40 : Database - lplblog**********************************************************************//*!40101 SET NAMES utf8 */;/*!40101 SET SQL_MODE=''*/;/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;CREATE DATABASE /*!32312 IF NOT EXISTS*/`lplblog` /*!40100 DEFAULT CHARACTER SET utf8 */;USE `lplblog`;/*Table structure for table `article` */DROP TABLE IF EXISTS `article`;CREATE TABLE `article` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',`title` varchar(255) NOT NULL COMMENT '文章标题',`content` text NOT NULL COMMENT '文章内容',`cid` int(11) DEFAULT NULL COMMENT '文章类别',`uid` int(11) DEFAULT NULL COMMENT '文章作者',`createtime` timestamp NULL DEFAULT CURRENT_TIMESTAMP,`is_del` int(11) DEFAULT '0' COMMENT '是否删除,默认0,未删除',`adesc` varchar(255) DEFAULT NULL COMMENT '文章摘要',PRIMARY KEY (`id`),KEY `cid` (`cid`),KEY `uid` (`uid`),CONSTRAINT `article_ibfk_1` FOREIGN KEY (`cid`) REFERENCES `article_category` (`id`),CONSTRAINT `article_ibfk_2` FOREIGN KEY (`uid`) REFERENCES `user` (`id`)) ENGINE=InnoDB AUTO_INCREMENT=67 DEFAULT CHARSET=utf8;/*Data for the table `article` */insert into `article`(`id`,`title`,`content`,`cid`,`uid`,`createtime`,`is_del`,`adesc`) values (1,'测试标题1','# 测试测试1222\r\n# 哈哈哈,今天天气不错哦!\r\n+ 11\r\n+ 22\r\n+ 33\r\n> aaaa\r\n',4,14,'2020-06-05 15:23:11',1,'测试简介1'),(3,'测试标题2222','# 测试标题2',NULL,14,'2020-06-05 15:27:22',0,'测试2222222'),(4,'认识HTML5','HTML5是构建Web内容的一种语言描述方式。HTML5是互联网的下一代标准,是构建以及呈现互联网内容的一种语言方式.被认为是互联网的核心技术之一。HTML产生于1990年,1997年HTML4成为互联网标准,并广泛应用于互联网应用的开发。\r\nHTML5是Web中核心语言HTML的规范,用户使用任何手段进行网页浏览时看到的内容原本都是HTML格式的,在浏览器中通过一些技术处理将其转换成为了可识别的信息。HTML5在从前HTML4.01的基础上进行了一定的改进,虽然技术人员在开发过程中可能不会将这些新技术投入应用,但是对于该种技术的新特性,网站开发技术人员是必须要有所了解的。',3,14,'2020-06-05 15:30:07',1,'HTML5是构建Web内容的一种语言描述方式。HTML5是互联网的下一代标准,是构建以及呈现互联网内容的一种语言方式.被认为是互联网的核心技术之一'),(5,'HTML5新特性','新特性编辑\r\nHTML5将Web带入一个成熟的应用平台,在这个平台上,视频、音频、图像、动画以及与设备的交互都进行了规范。 [5] \r\n智能表单\r\n表单是实现用户与页面后台交互主要组成部分,HTML5在表单的设计上功能更加强大。input类型和属性的多样性大大地增强了HTML可表达的表单形式,再加上新增加的一些表单标签,使得原本需要JavaScript来实现的控件,可以直接使用HTML5的表单来实现;一些如内容提示、焦点处理、数据验证等功能,也可以通过HTML5的智能表单属性标签来完成。 [6] \r\n图2 HTML5\r\n图2 HTML5\r\n绘图画布\r\nHTML5的canvas元素可以实现画布功能,该元素通过自带的API结合使用JavaScript脚本语言在网页上绘制图形和处理,拥有实现绘制线条、弧线以及矩形,用样式和颜色填充区域,书写样式化文本,以及添加图像的方法,且使用JavaScript可以控制其每一个像素。HTML5的canvas元素使得浏览器无需Flash或Silverlight等插件就能直接显示图形或动画图像。 [6] \r\n多媒体\r\nHTML5最大特色之一就是支持音频视频,在通过增加了<audio>、<video>两个标签来实现对多媒体中的音频、视频使用的支持,只要在Web网页中嵌入这两个标签,而无需第三方插件(如Flash)就可以实现音视频的播放功能。HTML5对音频、视频文件的支持使得浏览器摆脱了对插件的依赖,加快了页面的加载速度,扩展了互联网多媒体技术的发展空间。 [6] \r\n地理定位\r\n现今移动网络备受青睐,用户对实时定位的应用越来,要求也越来越高。HTML5通过引入Geolocation的API可以通过GPS或网络信息实现用户的定位功能,定位更加准确、灵活。通过HTML5进行定位,除了可以定位自己的位置,还可以在他人对你开放信息的情况下获得他人的定位信息。 [6] \r\n数据存储\r\nHTML5较之传统的数据存储有自已的存储方式,允许在客户端实现较大规模的数据存储。为了满足不同的需求,HTML5支持DOM Storage和Web SQL Database 两种存储机制。其中,DOM Storage 适用于具有key/value对的基本本地存储;而WebSQLDatabase是适用于关系型数据库的存储方式,开发者可以使用SQL语法对这些数据进行查询、插入等操作。 [6] \r\n多线程\r\nHTML5利用Web Worker将Web应用程序从原来的单线程业界中解放出来,通过创建一个Web Worker对象就可以实现多线程操作。JavaScript创建的Web程序处理事务都是在单线程中执行,响应时间较长,而当JavaScript过于复杂时,还有可能出现死锁的局面。HTML5新增加了一个WebWorkerAPI,用户可以创建多个在后台的线程,将耗费较长时间的处理交给后台面不影响用户界面和响应速度,这些处理不会因用户交互而运行中断。使用后台线程不能访问页面和窗口对象,但后台线程可以和贞面之间进行数据交互。子线程与子线程之间的数据交互,大致步骤如下:1先创建发送数据的子线程;2执行子线程任务,把要传递的数据发送给主线程;3在主线程接受到子线程传递回的消息时创建接收数据的子线程,然后把发送数据的子线程中返回的消息传递给接收数据的子线程;4执行接收数据子线程中的代码。 [6] \r\n',NULL,14,'2020-06-05 15:31:26',1,'HTML5将Web带入一个成熟的应用平台,在这个平台上,视频、音频、图像、动画以及与设备的交互都进行了规范'),(6,'HTML5的优缺点','## 优点\r\n新一代网络标准能够让程序通过Web浏览器,消费者从而能够从包括个人电脑、笔记本电脑、智能手机或平板电脑在内的任意终端访问相同的程序和基于云端的信息。HTML5允许程序通过Web浏览器运行,并且将视频等目前需要插件和其它平台才能使用的多媒体内容也纳入其中,这将使浏览器成为一种通用的平台,用户通过浏览器就能完成任务。此外,消费者还可以访问以远程方式存储在"云"中的各种内容,不受位置和设备的限制。 [7] \r\n## 缺点\r\n(1)开放性带来的困扰\r\n在从前网络平台上存在大量的专利产品,想要实现HTML5技术的大量应用首先就需要将这些专利性的产品变为开放式的产品,由于各种原因,当前面对这一问题还存在许多争议。以视频格式为例,两大阵营对于视频格式的设置存在争议,一大阵营以苹果为代表,另一大阵营则以Opera、火狐、谷歌为代表。WPEG阵营是苹果所属阵营,由于其自身全部使用的是这一种格式,所以坚持认为应当将此格式作为标准,而WebM阵营则认为由于WPEG格式的专利依然没有解除,对于HTML5技术要求的开放性没有达标,所以不同意将其作为标准格式。 [1] \r\n(2)发展的速度有待提升\r\n在HTML5中提出了一些从前HTML技术中不具有的新技术,但是有许多主流浏览器在长时间的发展过程中已经完成了此种技术的开发,在自身浏览器中实现了此种功能,就这一情况来说HTML5的发展速度方面存在一定的问题。同时由于HTML5的不成熟,当前关于HTML5的相关技术标准还没有完全确定,所以在短时间想要将其投入大规模应用还比较困难。 [1] \r\n(3)技术手段的不完善\r\n由于HTML5技术中存在较为先进的本地存储技术,所以其能做到降低应用程序的相应时间为用户带来更便捷的体验',3,14,'2020-06-05 15:34:33',0,'新一代网络标准能够让程序通过Web浏览器,消费者从而能够从包括个人电脑、笔记本电脑、智能手机或平板电脑在内的任意终端访问相同的程序和基于云端的信息'),(7,'认识CSS3','CSS3是CSS(层叠样式表)技术的升级版本,于1999年开始制订,2001年5月23日W3C完成了CSS3的工作草案,主要包括盒子模型、列表模块、超链接方式、语言模块、背景和边框、文字特效、多栏布局等模块 [1] 。\r\nCSS演进的一个主要变化就是W3C决定将CSS3分成一系列模块。浏览器厂商按CSS节奏快速创新,因此通过采用模块方法,CSS3规范里的元素能以不同速度向前发展,因为不同的浏览器厂商只支持给定特性。但不同浏览器在不同时间支持不同特性,这也让跨浏览器开发变得复杂 [2] 。',4,14,'2020-06-05 15:35:29',0,'CSS3是CSS(层叠样式表)技术的升级版本'),(8,'CSS3发展进程','早在2001年W3C就完成了CSS3的草案规范。CSS3规范的一个新特点是被分为若干个相互独立的模块。一方面分成若干较小的模块较利于规范及时更新和发布,及时调整模块的内容,这些模块独立实现和发布,也为日后CSS的扩展奠定了基础。另外一方面,由于受支持设备和浏览器厂商的限制,设备或者厂商可以有选择的支持一部分模块,支持CSS3的一个子集,这样有利于CSS3的推广 [3] 。\r\n以下为截至2019年12月,CSS3各模块的规范情况:\r\n时间 名称 最后状态 模块\r\n1999.01.27 - 2019年08月13日 文本修饰模块 候选推荐 css-text-decor-3 [4] \r\n1999.06.22 - 2018年10月18日 \r\n分页媒体模块\r\n工作草案 css-page-3 [5] \r\n1999.06.23 - 2019年10月15日 \r\n多列布局\r\n工作草案 css-multicol-1 [6] \r\n1999.06.22 - 2018年06月19日 颜色模块 推荐 css-color-3 [7] \r\n1999.06.25 - 2014年03月20日 命名空间模块 推荐 css-namespaces-3 [8] \r\n1999.08.03 - 2018年11月06日 \r\n选择器\r\n推荐 selectors-3 [9] \r\n2001.04.04 - 2012年06月19日 \r\n媒体查询\r\n推荐 css3-mediaqueries [10] \r\n2001.05.17 - 2019年11月13日 文本模块 工作草案 css-text-3 [11] \r\n2001.07.13 - 2018年08月28日 级联和继承 候选推荐 css-cascade-3 [12] \r\n2001.07.13 - 2019年06月06日 取值和单位模块 候选推荐 css-values-3 [13] \r\n2001.07.26 - 2018年12月18日 基本盒子模型 工作草案 css-box-3 [14] \r\n2001.07.31- 2018年09月20日 \r\n字体模块\r\n推荐 css-fonts-3 [15] \r\n2001.09.24 - 2017年10月17日 背景和边框模块 候选推荐 css-backgrounds-3 [16] \r\n2002.02.20 - 2019.08-17 列表模块 工作草案 css-lists-3 [17] \r\n2002.05.15 - 2018年08月08日 行内布局模块 工作草案 css-inline-3 [18] \r\n2002.08.02 - 2018年06月21日 基本用户界面模块 推荐 css-ui-3 [19] \r\n2003.05.14 - 2019年08月02日 生成内容模块 工作草案 css-content-3 [20] \r\n2003.08.13 - 2019年07月16日 语法模块 候选推荐 css-syntax-3 [21] \r\n2004.02.24 - 2014年10月14日 超链接显示模块 工作组笔记 css3-hyperlinks [22] \r\n2005.12.15 - 2015年03月26日 模板布局模块 工作组笔记 css-template-3 [23] \r\n2006.06.12 - 2014年05月13日 分页媒体模块生成内容 工作草案 css-gcpm-3 [24] \r\n2008.08.01 - 2014年10月14日 Marquee模块 工作组笔记 css3-marquee [25] \r\n2009.07.23 - 2019年10月10日 图像模块 候选推荐 css-images-3 [26] \r\n2010.12.02 - 2019年12月10日 书写模式 推荐 css-writing-modes-3 [27] \r\n2011.09.01 - 2013年04月04日 条件规则模块 候选推荐 css3-conditionalr [28] \r\n2012.02.07 - 2016年05月17日 定位布局模块 工作草案 css-position-3 [29] \r\n2012.02.28 - 2018年12月04日 片段模块 候选推荐 css-break-3 [30] \r\n2012.09.27 - 2019年05月22日 宽高大小模块 工作草案 css-sizing-3 [31] \r\n2012.10.09 - 2017年12月14日 计数器风格 候选推荐 css-counter-styles-3 [32] \r\n2013.04.18 - 2018年07月31日 溢出模块 工作草案 css-overflow-3 [33] \r\n2014.02.20- 2019年07月11日 显示类型模块 候选推荐 css-display-3 [34] \r\n[35] \r\n编程开发编辑\r\nCSS3编程工具同CSS,任何一种文本编辑工具都可用来编写,如:Windows下的写字本、记事本;或其他专门用于编辑网页文本的工具 [36] ,如:IntelliJ IDEA、Eclipse、MyEclipse、webstorm、sublime text;此外还有专门针对前端开发的插件,如:Emmet。',4,14,'2020-06-05 15:36:22',0,'早在2001年W3C就完成了CSS3的草案规范。CSS3规范的一个新特点是被分为若干个相互独立的模块'),(9,'CSS3工作原理','CSS3原理同CSS,是在网页中自定义样式表的选择符,然后在网页中大量引用这些选择符 [37] 。\r\n新增特性中transform的原理较为特殊,虽然使用位移函数translate()、缩放函数scale()、旋转函数rotate()和倾斜函数skew()可以简单地实现变形,但是变形中的矩阵函数都可以使用matrix()函数来代替,如:\r\n1\r\n2\r\n3\r\n#object{\r\n transform: rotate(15deg) translateX(230px) scale(1.5, 2.6) skew(220deg, -150deg) translateX(230px);\r\n}\r\n使用一个矩阵matrix()规则变成如下\r\n1\r\n2\r\n3\r\n#object{\r\n transform:matrix(1.06, 1.84, 0.54, 2.8, 466, 482);\r\n}\r\nCSS3中的2D矩阵matrix总共提供了六个参数a、b、c、d、e和f,其基本写法为matrix(a,b,c,d,e,f),参数对应的矩阵为:\r\n\r\n×ばつ3矩阵的乘积。matri×ばつ3变换矩阵,对原先的坐标施加变换,就能得到新的坐标了。依据矩阵变换规则即可得到:\r\n\r\n四个函数与matrix的转换原理如下:\r\n1. 位移函数translate\r\n首先看最简单的位移translate()函数。transform: translate(tx, ty)的基本含义是将一个元素的显示位置平移tx和ty。在矩阵变形中,translate的matrix参数为:matrix(1, 0, 0, 1, tx, ty),tx和ty分别对应X和Y轴的增量,对应矩阵如右图。由此公式可知:transform:translate(100px,100px)对应transform: matrix(1, 0, 0, 1, 100, 100)。推算出的结果:X\'=x+tx=x+100,Y\'=y+ty=y+100。\r\n2.缩放函数scale\r\ntransform:scale(sx, sy)将一个元素按指定的倍数进行缩放:matrix(sx*x, 0, 0, sy*y, 0, 0),sx和sy分别对应X轴和Y轴的缩放比率,其对应的矩阵如右图。由此公式可知:transform:scale(1.5, 1.5)对应transform: matrix(1.5, 0, 0, 1.5, 0, 0)。推算出的结果:X\'=x*sx=1.5*x,Y\'=y*sy=1.5*y。\r\n3.旋转函数rotate\r\ntransform:rotate(a)将一个元素按指定的角度旋转:matrix(cos(a), sin(a), -sin(a), cos(a), 0, 0),cos(a)和sin(a)是指旋转度,其对应的矩阵如右图。由此公式可知:transform: rotate(45deg)对应transform:matrix(0.53, 0.85, -0.85, 0.53, 0, 0)【sin(45°)=0.85,cos(45°)=0.53】,推算出来的结果:X\'=cos(a)*x-sin(a)*y=cos(45°)*x-sin(45°)*y,y’=sin(a)*x+cos(a)*y=sin(45°)*x+cos(45°)*y。\r\n(4)倾斜函数skew\r\ntransform:skew(ax, ay)将一个元素按指定的角度在x轴和Y轴倾斜:matrix(1, tan(ay), tan(ax), 1, 0, 0),tan(ax)和tan(ay)是对应的倾斜角度,其对应的矩阵如右图。由此公式可知:transform:skew(45deg)对应transform:matrix(1, 0, 1, 1, 0, 0)。其中tan(45°)=1。推算出来的结果:X\'=x+tan(a)*y,Y\'=tan(a)*x+y [38] 。',4,14,'2020-06-05 15:37:27',0,'CSS3原理同CSS,是在网页中自定义样式表的选择符,然后在网页中大量引用这些选择符 '),(10,'CSS3语言基础','CSS3的语法是建立在CSS原先版本基础上的,它允许使用者在标签中指定特定的HTML元素而不必使用多余的class、ID或JavaScript。CSS选择器中的大部分并不是在CSS3中新添加的,只是在之前的版本中没有得到广泛的应用。如果想尝试实现一个干净的、轻量级的标签以及结构与表现更好的分离,高级选择器是非常有用的,它们可以减少在标签中的class和ID的数量并让设计师更方便地维护样式表 [39] 。\r\n新增的选择器如下表所示:\r\n选择符类型 表达式 描述\r\n子串匹配的属性选择符\r\nE[att^=\"val\"]\r\n匹配具有att属性、且值以val开头的E元素\r\nE[att$=\"val\"]\r\n匹配具有att属性、且值以val结尾的E元素\r\nE[att*=\"val\"]\r\n匹配具有att属性、且值中含有val的E元素\r\n结构性伪类\r\nE:root\r\n匹配文档的根元素。在HTML(标准通用标记语言下的一个应用)中,根元素永远是HTML\r\nE:nth-child(n)\r\n匹配父元素中的第n个子元素E\r\nE:nth-last-child(n)\r\n匹配父元素中的倒数第n个结构子元素E\r\nE:nth-of-type(n)\r\n匹配同类型中的第n个同级兄弟元素E\r\nE:nth-last-of-type(n)\r\n匹配同类型中的倒数第n个同级兄弟元素E\r\nE:last-child\r\n匹配父元素中最后一个E元素\r\nE:first-of-type\r\n匹配同级兄弟元素中的第一个E元素\r\nE:only-child\r\n匹配属于父元素中唯一子元素的E\r\nE:only-of-type\r\n匹配属于同类型中唯一兄弟元素的E\r\nE:empty\r\n匹配没有任何子元素(包括text节点)的元素E\r\n目标伪类\r\nE:target\r\n匹配相关URL指向的E元素\r\nUI元素状态伪类\r\nE:enabled\r\n匹配所有用户界面(form表单)中处于可用状态的E元素\r\nE:disabled\r\n匹配所有用户界面(form表单)中处于不可用状态的E元素\r\nE:checked\r\n匹配所有用户界面(form表单)中处于选中状态的元素E\r\nE::selection\r\n匹配E元素中被用户选中或处于高亮状态的部分\r\n否定伪类\r\nE:not(s)\r\n匹配所有不匹配简单选择符s的元素E\r\n通用兄弟元素选择器\r\nE ~ F\r\n匹配E元素之后的F元素\r\n[40]',4,14,'2020-06-05 15:38:11',0,'CSS3的语法是建立在CSS原先版本基础上的,它允许使用者在标签中指定特定的HTML元素而不必使用多余的class、ID或JavaScript。'),(11,'CSS3新增特性','CSS3的新特征有很多,例如圆角效果、图形化边界、块阴影与文字阴影、使用RGBA实现透明效果、渐变效果、使用@Font-Face实现定制字体、多背景图、文字或图像的变形处理(旋转、缩放、倾斜、移动)、多栏布局、媒体查询等。\r\nCSS3圆角表格\r\nCSS3圆角表格\r\n1、边框特性\r\nCSS3对网页中的边框进行了一些改进,主要包括支持圆角边框、多层边框、边框色彩与图片等。在CSS3中最常用的一个改进就是圆角边框,通过CSS3的属性可以快速实现圆角定义,同时还可以根据实际情况针对特定角进行圆角定义 [39] 。\r\n',4,14,'2020-06-05 15:39:55',0,'CSS3的新特征有很多,例如圆角效果、图形化边界、块阴影与文字阴影、使用RGBA实现透明效果、渐变效果'),(12,'Ajax第一篇:Ajax基础','[TOC]\r\n## 第一章:Ajax概述\r\n\r\n### 1.1-为什么要学习Ajax\r\n\r\n在没有Ajax之前的web,我们称之为**传统web**。\r\n\r\n传统web所面临的一系列**问题**:\r\n\r\n1. 网速慢的情况下,页面加载时间长,用户只能等待\r\n2. 表单提交后,如果一项内容不合格,需要重新填写所有表单内容\r\n3. 页面跳转,重新加载页面,造成资源浪费,增加用户等待时间 \r\n4. ....\r\n\r\n传统web所产生的问题的根本**原因**是:web程序中的交互(请求与响应)是`同步的`。\r\n\r\n要解决这些问题则必须想办法在web程序中使用`异步`交互方式。而实现的技术**解决方案**就是**Ajax**。\r\n\r\n\r\n\r\n### 1.2-同步和异步\r\n\r\n> **同步交互**:\r\n\r\n指发送一个请求,需要等待返回结果,才能发送下一个请求,有一个等待过程。\r\n\r\n\r\n\r\n> **异步交互**:\r\n\r\n指发送一个请求,不需要等待返回结果,随时可以发送下一个请求,既不需要等待。\r\n\r\n\r\n\r\n\r\n\r\n### 1.3-什么是Ajax\r\n\r\nAjax 即"**A**synchronous **J**avascript **A**nd **X**ML"(异步 JavaScript 和 XML[传输数据的方式,现在更多的使用json]),是指一种创建交互式、快速动态网页应用的网页开发技术,无需重新加载整个网页的情况下,能够更新部分网页的技术。\r\n\r\n通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。\r\n\r\nAjax 不是一种新的编程语言,而是一种用于创建更好更快以及交互性更强的Web应用程序的技术。\r\n\r\n使用 JavaScript 向服务器提出请求并处理响应而不阻塞用户核心对象**XMLHttpRequest**\r\n\r\n\r\n\r\n### 1.5-Ajax的应用场景\r\n\r\n1. 页面上拉加载更多数据\r\n2. 列表数据无刷新分页\r\n3. 表单项离开焦点数据验证\r\n4. 搜索框提示文字下拉列表\r\n5. ....\r\n\r\n\r\n\r\n## 第二章:第一个Ajax程序\r\n\r\n需求:点击按钮,向服务端(此处使用的是NodeJS)发送get请求,获取服务端请求,并在不刷新网页的情况下,将请求结果展示在网页中。\r\n\r\n> 服务端程序\r\n\r\n```js\r\nconst express = require(\'express\')\r\nconst path = require(\'path\')\r\n\r\nconst app = express();\r\napp.use(express.static(path.join(__dirname,\'/public\')))\r\n \r\napp.get(\'/firstAjax\', (req, res) => {\r\n res.send(\'Hello Ajax!\')\r\n})\r\n\r\napp.listen(80, \"localhost\")\r\n```\r\n\r\n> 前端程序\r\n\r\n```html\r\n <button>发送请求</button>\r\n <div>请求结果:<strong></strong></div>\r\n <script>\r\n // 获取页面元素\r\n var btn = document.querySelector(\'button\')\r\n var strong = document.querySelector(\'strong\')\r\n // 按钮注册点击事件\r\n btn.onclick = function () {\r\n // 1. 创建XMLHttpRequest对象\r\n var xhr = new XMLHttpRequest()\r\n // 2. 配置请求方式和请求地址\r\n xhr.open(\'get\', \'http://localhost/firstAjax\')\r\n // 3. 发送请求\r\n xhr.send()\r\n // 4. 获取服务端给客户端响应的数据\r\n xhr.onload = function () {\r\n // 5. 获取响应结果\r\n var result = xhr.responseText\r\n // 显示在页面中\r\n strong.innerHTML = result;\r\n }\r\n }\r\n </script>\r\n```\r\n\r\n> 执行结果\r\n\r\n\r\n\r\n\r\n\r\n## 第三章:请求方式及参数设置\r\n\r\n### 3.1-get请求及参数\r\n\r\n> 方式\r\n\r\n请求方式及参数格式:**xhr.open(\'get\', \'url?`key=value`&`key=value`&`key=value`...\')**\r\n\r\n如:xhr.open(\'get\', \'http://www.example.com?name=zhangsan&age=20\');\r\n\r\n> 代码\r\n\r\n服务端程序\r\n\r\n```javascript\r\nconst express = require(\'express\')\r\nconst path = require(\'path\')\r\n\r\nconst app = express();\r\napp.use(express.static(path.join(__dirname,\'/public\')))\r\n \r\napp.get(\'/getAjax\', (req, res) => {\r\n res.send(req.query)\r\n})\r\n\r\napp.listen(80, \"localhost\")\r\n```\r\n\r\n前端程序\r\n\r\n```html\r\n <p>姓名:<input type=\"text\" class=\"username\"/></p>\r\n <p>年龄:<input type=\"text\" class=\"age\"/></p>\r\n <button>提交</button>\r\n <script>\r\n var btn = document.querySelector(\'button\')\r\n var usernameDom = document.querySelector(\'.username\')\r\n var ageDom = document.querySelector(\'.age\')\r\n btn.onclick = function () {\r\n var xhr = new XMLHttpRequest()\r\n // 参数拼接\r\n var params = \'name=\' + usernameDom.value + \'&age=\' + ageDom.value;\r\n // 配置get请求方式和请求地址及参数\r\n xhr.open(\'get\', \'http://localhost/getAjax?\'+params)\r\n xhr.send()\r\n xhr.onload = function () {\r\n var result = xhr.responseText\r\n console.log(result)\r\n }\r\n }\r\n </script>\r\n```\r\n\r\n执行结果:\r\n\r\n\r\n\r\n\r\n\r\n### 3.2-post请求及参数\r\n\r\n> 方式\r\n\r\n请求方式及参数格式:\r\n\r\n+ **xhr.open(\'post\', \'url\')**;\r\n+ **xhr.setRequestHeader(\'Content-Type\', \'application/x-www-form-urlencoded\')**\r\n+ **xhr.send(\'`key=value`&`key=value`&`key=value`...\')**\r\n\r\n如:\r\n\r\n+ xhr.send(\'name=zhangsan&age=20\');\r\n\r\n> 代码\r\n\r\n服务端程序\r\n\r\n```javascript\r\nconst express = require(\'express\')\r\nconst bodyParser = require(\'body-parser\')\r\nconst path = require(\'path\')\r\n\r\nconst app = express();\r\napp.use(express.static(path.join(__dirname,\'/public\')))\r\napp.use(bodyParser.urlencoded({ extend: false }))\r\n \r\napp.post(\'/postAjax\', (req, res) => {\r\n res.send(req.body)\r\n})\r\n\r\napp.listen(80, \"localhost\")\r\n```\r\n\r\n\r\n\r\n前端程序\r\n\r\n```html\r\n <p>姓名:<input type=\"text\" class=\"username\"/></p>\r\n <p>年龄:<input type=\"text\" class=\"age\"/></p>\r\n <button>提交</button>\r\n <script>\r\n var btn = document.querySelector(\'button\')\r\n var usernameDom = document.querySelector(\'.username\')\r\n var ageDom = document.querySelector(\'.age\')\r\n btn.onclick = function () {\r\n var xhr = new XMLHttpRequest()\r\n // 参数拼接\r\n var params = \'name=\' + usernameDom.value + \'&age=\' + ageDom.value;\r\n // 配置get请求方式和请求地址\r\n xhr.open(\'post\', \'http://localhost/postAjax\')\r\n // 配置请求头\r\n xhr.setRequestHeader(\'Content-Type\', \'application/x-www-form-urlencoded\')\r\n // 发送请求并传入请求参数\r\n xhr.send(params)\r\n xhr.onload = function () {\r\n var result = xhr.responseText\r\n console.log(result)\r\n }\r\n }\r\n </script>\r\n```\r\n\r\n执行结果\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n## 第四章:Ajax执行过程\r\n\r\n> 执行过程\r\n\r\n在创建ajax对象,配置ajax对象,发送请求,以及接收完服务器端响应数据,这个过程中的每一个步骤都会对应一个数值,这个数值就是ajax状态码,表示ajax从开始创建到响应的过程。不同的数值表示不同的执行阶段。可以通过`xhr.readyState` 获取Ajax状态码。\r\n\r\n 0:请求未初始化(还没有调用open())\r\n 1:请求已经建立,但是还没有发送(还没有调用send())\r\n 2:请求已经发送\r\n 3:请求正在处理中,通常响应中已经有部分数据可以用了\r\n 4:响应已经完成,可以获取并使用服务器的响应了\r\n\r\n> onreadystatechange事件\r\n\r\n当 Ajax 状态码发生变化时将自动触发该事件。\r\n\r\n在事件处理函数中可以获取 Ajax 状态码并对其进行判断,当状态码为 4 时就可以通过 xhr.responseText 获取服务器端的响应数据了。\r\n\r\n后端代码如下:\r\n\r\n```javascript\r\nconst express = require(\'express\')\r\nconst path = require(\'path\')\r\n\r\nconst app = express();\r\napp.use(express.static(path.join(__dirname,\'/public\')))\r\n \r\napp.get(\'/getAjax\', (req, res) => {\r\n res.send(req.query)\r\n})\r\n\r\napp.listen(80, \"localhost\")\r\n```\r\n\r\n前端代码如下:\r\n\r\n```html\r\n <p>姓名:<input type=\"text\" class=\"username\"/></p>\r\n <p>年龄:<input type=\"text\" class=\"age\"/></p>\r\n <button>提交</button>\r\n <script>\r\n var btn = document.querySelector(\'button\')\r\n var usernameDom = document.querySelector(\'.username\')\r\n var ageDom = document.querySelector(\'.age\')\r\n btn.onclick = function () {\r\n var xhr = new XMLHttpRequest()\r\n console.log(xhr.readyState) // 0\r\n var params = \'name=\' + usernameDom.value + \'&age=\' + ageDom.value;\r\n // 配置get请求方式和请求地址及参数\r\n xhr.open(\'get\', \'http://localhost/getAjax?\'+params)\r\n console.log(xhr.readyState) //1\r\n xhr.send() \r\n xhr.onreadystatechange = function () {\r\n console.log(xhr.readyState) // 2 3 4 \r\n if(xhr.readyState==4){\r\n console.log(xhr.responseText)\r\n }\r\n }\r\n }\r\n </script>\r\n```\r\n\r\n> ajax中的onload和onreadystatechange事件区别\r\n\r\n\r\n\r\n\r\n\r\n## 第五章:Ajax错误处理\r\n\r\n> 网络畅通的情况\r\n\r\n服务器端能接收到请求,服务器端返回的结果不是预期结果。\r\n\r\n可以判断服务器端返回的状态码,分别进行处理。`xhr.status` 获取http状态码。常见的状态码如下:\r\n\r\n+ 200 请求成功\r\n+ 301 重定向\r\n+ 404 请求地址错误\r\n+ 500 服务端内部错误\r\n\r\n> 网络中断\r\n\r\n网络中断,请求无法发送到服务器端。\r\n\r\n会触发xhr对象下面的`onerror`事件,在onerror事件处理函数中对错误进行处理。\r\n\r\n> 代码如下\r\n\r\n后端程序:\r\n\r\n```javascript\r\nconst express = require(\'express\')\r\nconst path = require(\'path\')\r\n\r\nconst app = express();\r\napp.use(express.static(path.join(__dirname,\'/public\')))\r\n \r\napp.get(\'/getAjax\', (req, res) => {\r\n res.send(req.query)\r\n})\r\n\r\napp.listen(80, \"localhost\")\r\n```\r\n\r\n前端程序:\r\n\r\n```html\r\n <p>姓名:<input type=\"text\" class=\"username\"/></p>\r\n <p>年龄:<input type=\"text\" class=\"age\"/></p>\r\n <button>提交</button>\r\n <script>\r\n var btn = document.querySelector(\'button\')\r\n var usernameDom = document.querySelector(\'.username\')\r\n var ageDom = document.querySelector(\'.age\')\r\n btn.onclick = function () {\r\n var xhr = new XMLHttpRequest()\r\n var params = \'name=\' + usernameDom.value + \'&age=\' + ageDom.value;\r\n // 配置get请求方式和请求地址及参数\r\n xhr.open(\'get\', \'http://localhost/getAjax?\'+params)\r\n xhr.send() \r\n xhr.onreadystatechange = function () {\r\n console.log(xhr.status) \r\n if(xhr.readyState==4&&xhr.status==200){\r\n console.log(xhr.responseText)\r\n }\r\n }\r\n xhr.onerror=function(ev){\r\n console.log(\'您的网络异常!\')\r\n }\r\n }\r\n </script>\r\n```\r\n\r\n\r\n\r\n## 第六章:IE 浏览器的缓存问题\r\n\r\n问题:在低版本的 IE 浏览器中,Ajax 请求有严重的缓存问题,即在请求地址不发生变化的情况下,只有第一次请求会真正发送到服务器端,后续的请求都会从浏览器的缓存中获取结果。即使服务器端的数据更新了,客户端依然拿到的是缓存中的旧数据。\r\n\r\n解决方案:在请求地址的后面加请求参数,保证每一次请求中的请求参数的值不相同。 \r\n\r\n```javascript\r\n xhr.open(\'get\', \'http://www.example.com?t=\' + Math.random());\r\n```\r\n',5,14,'2020-06-05 15:42:34',0,'什么是Ajax及实现'),(13,' Ajax第二篇:Ajax简单封装','\r\n## 概述\r\n\r\n问题:发送一次请求代码过多,发送多次请求代码冗余且重复。\r\n\r\n解决方案:将请求代码封装到函数中,发请求时调用函数即可。\r\n\r\n## 封装\r\n\r\n封装代码如下:\r\n\r\nmyAjax.js\r\n\r\n```javascript\r\n(function (window) {\r\n /**\r\n * \r\n * @param {*} option 配置对象\r\n * option.type 请求方式 string\r\n * option.url 请求的地址 string\r\n * option.data 请求的参数 object\r\n * option.success 成功回调函数 function 参数1:响应的数据;参数2:xhr对象\r\n * option.error 失败回调函数 function 参数1:状态码或错误消息;参数2:xhr对象或异常事件对象\r\n */\r\n function ajax(option) {\r\n /*默认配置对象 */\r\n var defaults = {\r\n type: \'get\'\r\n };\r\n /*覆盖默认配置对象 */\r\n Object.assign(defaults, option)\r\n /* ajax */\r\n var xhr = new XMLHttpRequest();\r\n /*检测是否有请求参数 */\r\n if (defaults.data) {\r\n var paramsArr = []\r\n for (var key in defaults.data) {\r\n paramsArr.push(key+\'=\' + defaults.data[key])\r\n }\r\n var params = paramsArr.join(\'&\')\r\n // get\r\n if (defaults.type.toLowerCase() == \'get\') {\r\n xhr.open(\'get\',defaults.url+\'?\' + params)\r\n xhr.send()\r\n }\r\n // post\r\n else if (defaults.type.toLowerCase() == \'post\') {\r\n xhr.open(\'post\',defaults.url)\r\n xhr.setRequestHeader(\'Content-Type\', \'application/x-www-form-urlencoded\')\r\n xhr.send(params)\r\n }\r\n }\r\n // 没有参数\r\n else {\r\n xhr.open(defaults.type.toLowerCase(), defaults.url)\r\n // 检测是否是post请求\r\n if (defaults.type.toLowerCase == \'post\') {\r\n xhr.setRequestHeader(\'Content-Type\', \'application/x-www-form-urlencoded\')\r\n }\r\n xhr.send()\r\n }\r\n // 监听响应\r\n xhr.onload = function () {\r\n if (xhr.status == 200) {\r\n defaults.success && defaults.success(xhr.responseText,xhr);\r\n } else {\r\n defaults.error && defaults.error(xhr.status,xhr)\r\n }\r\n }\r\n // 网络中断\r\n xhr.onerror = function (ev) {\r\n defaults.error && defaults.error(\'网络异常\',ev)\r\n }\r\n \r\n\r\n }\r\n window.ajax = ajax;\r\n})(window)\r\n```\r\n\r\n\r\n\r\n## 使用\r\n\r\n> 后端程序\r\n\r\n```javascript\r\nconst express = require(\'express\')\r\nconst bodyParser = require(\'body-parser\')\r\nconst path = require(\'path\')\r\n\r\nconst app = express();\r\napp.use(express.static(path.join(__dirname,\'/public\')))\r\napp.use(bodyParser.urlencoded({ extend: false }))\r\n \r\napp.get(\'/getAjax\', (req, res) => {\r\n res.send(req.query)\r\n})\r\napp.post(\'/postAjax\', (req, res) => {\r\n console.log(req.body)\r\n res.send(req.body)\r\n})\r\n\r\n\r\napp.listen(80, \"localhost\")\r\n```\r\n\r\n> 前端程序\r\n\r\n```html\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n <meta charset=\"UTF-8\">\r\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n <title>Document</title>\r\n</head>\r\n<body>\r\n <button id=\"btn1\">get请求</button>\r\n <button id=\"btn2\">post请求</button>\r\n <script src=\"./myajax.js\"></script>\r\n <script>\r\n // get \r\n btn1.onclick = function() {\r\n ajax({\r\n url: \'http://localhost/getAjax\',\r\n success:function(data) {\r\n console.log(data)\r\n },\r\n data: {\r\n name: \'admin\',\r\n age: 10\r\n },\r\n error: function(msg) {\r\n console.log(msg)\r\n }\r\n })\r\n }\r\n // post \r\n btn2.onclick = function() {\r\n ajax({\r\n type:\'post\',\r\n url: \'http://localhost/postAjax\',\r\n success:function(data) {\r\n console.log(data)\r\n },\r\n data: {\r\n name: \'admin\',\r\n age: 10\r\n },\r\n error: function(msg) {\r\n console.log(msg)\r\n }\r\n })\r\n }\r\n </script>\r\n</body>\r\n</html>\r\n```\r\n',5,14,'2020-06-05 15:43:57',0,'Ajax简单封装'),(14,'MySQL-环境安装、配置、卸载、系统介绍','\r\n[TOC]\r\n## 第一章:初识数据库\r\n\r\n### 1.1-数据库介绍\r\n\r\n>**数据的存储方式有哪些?**\r\n\r\n| 存储位置 | 优点 | 缺点 |\r\n| -------- | ---------------------------------------------- | ---------------------------- |\r\n| `内存` | 速度快 | 数据是临时状态,不能永久保存 |\r\n| `文件` | 数据可以永久保存 | 数据操作不方便 |\r\n| `数据库` | 1 数据可以永久保存。2查询速度快。3数据便于管理 | 占用资源,需要购买 |\r\n\r\n> **什么是数据库?**\r\n\r\n1. 存储数据的仓库 ,是一个软件。\r\n2. 本质上是一个文件系统,还是以文件的方式存在服务器的电脑上的。 \r\n3. 所有的关系型数据库都可以使用**通用的 SQL 语句进行管理 DBMS DataBase Management System**\r\n\r\n\r\n\r\n### 1.2-常见的数据库排行榜\r\n\r\n> **排行榜**\r\n\r\n\r\n\r\n\r\n+ **MySQL**:开源免费的数据库,小型的数据库,已经被 Oracle 收购了。MySQL6.x 版本也开始收费。后来 Sun公司收购了 MySQL,而 Sun 公司又被 Oracle 收购\r\n+ **Oracle**:收费的大型数据库,Oracle 公司的产品。 \r\n+ **DB2** :IBM 公司的数据库产品,收费的。常应用在银行系统中。 \r\n+ **SQL Server**:MicroSoft 公司收费的中型的数据库。C#、.net 等语言常使用 \r\n+ SQLite: 嵌入式的小型数据库,应用在手机端,如:Android \r\n\r\n> **为什么选择MySQL ?**\r\n\r\n1. 免费 \r\n2. 功能强大 \r\n\r\n\r\n\r\n\r\n\r\n## 第二章:数据库的安装与卸载\r\n\r\n###2.1-下载\r\n\r\n+ **链接**:https://pan.baidu.com/s/1wO_Zpj3EDtKrBdwPvtubOg \r\n+ **提取码**:b9w5 \r\n\r\n###2.2-安装\r\n\r\n1. **打开下载的 mysql 安装文件双击解压缩,运行"mysql-5.5.40-win32.msi"】**\r\n \r\n\r\n \r\n\r\n2. **选择安装类型,有"Typical(默认)"、"Complete(完全)"、"Custom(用户自定义)"三个选项,选择"Custom",按"next"键继续**\r\n \r\n\r\n3. **点选"Browse",手动指定安装目录。**\r\n \r\n\r\n4. **填上安装目录,我的是"d:\\Program Files (x86)\\MySQL\\MySQL Server 5.0",按"OK"继续。**\r\n \r\n\r\n5. **确认一下先前的设置,如果有误,按"Back"返回重做。按"Install"开始安装。**\r\n \r\n\r\n \r\n\r\n \r\n\r\n \r\n\r\n \r\n\r\n6. **正在安装中,请稍候,直到出现下面的界面, 则完成 MYSQL 的安装**\r\n \r\n\r\n\r\n\r\n\r\n###2.3-配置\r\n\r\n数据库安装好了还需要对数据库进行配置才能使用 MYSQL 的配置 。\r\n\r\n1. **【安装完成了,出现如下界面将进入 mysql 配置向导。】**\r\n \r\n\r\n\r\n2. **【选择配置方式,"Detailed Configuration(手动精确配置)"、"Standard Configuration(标准配置)",我们选择"Detailed Configuration",方便熟悉配置过程。】**\r\n \r\n\r\n\r\n3. **【选择服务器类型,"Developer Machine(开发测试类,mysql 占用很少资源)"、"Server Machine(服务器类型,mysql 占用较多资源)"、"Dedicated MySQL Server Machine(专门的数据库服务器,mysql 占用所有可用资源)"】**\r\n \r\n\r\n\r\n4. **【选择mysql数据库的大致用途,"Multifunctional Database(通用多功能型,好)"、"TransactionalDatabase Only(服务器类型,专注于事务处理,一般)"、"Non-Transactional Database Only(非事务处理型,较简单,主要做一些监控、记数用,对 MyISAM 数据类型的支持仅限于 non-transactional),按"Next" 继续。】**\r\n \r\n\r\n \r\n\r\n\r\n5. **【选择网站并发连接数,同时连接的数目,"Decision Support(DSS)/OLAP(20个左右)"Online"、Transaction Processing(OLTP)(500 个左右)"、"Manual Setting(手动设置,自己输一个数)"。】** \r\n \r\n\r\n\r\n6. **【是否启用 TCP/IP 连接,设定端口,如果不启用,就只能在自己的机器上访问 mysql 数据库了,在这个页面上,您还可以选择"启用标准模式"(Enable Strict Mode),这样 MySQL 就不会允许细小的语法错误。如果是新手,建议您取消标准模式以减少麻烦。但熟悉 MySQL 以后,尽量使用标准模式,因为它可以降低有害数据进入数据库的可能性。按"Next"继续】**\r\n \r\n\r\n\r\n7. **【就是对 mysql 默认数据库语言编码进行设置(重要),一般选 UTF-8,按 "Next"继续。】** \r\n \r\n\r\n\r\n8. **【选择是否将 mysql 安装为 windows 服务,还可以指定 Service Name(服务标识名称),是否将 mysql 的 bin 目录加入到 Windows PATH(加入后,就可以直接使用 bin 下的文件,而不用指出目录名,比如连接, "mysql.exe -uusername -ppassword;"就可以了,不用指出 mysql.exe 的完整地址,很方便),我这里全部 打上了勾,Service Name 不变。按"Next"继续。】** \r\n \r\n\r\n\r\n9. **【询问是否要修改默认 root 用户(超级管理)的密码。"Enable root access from remote machines(是否允 许 root 用户在其它的机器上登陆,如果要安全,就不要勾上,如果要方便,就勾上它)"。最后"Create An Anonymous Account(新建一个匿名用户,匿名用户可以连接数据库,不能操作数据,包括查询)", 13 / 35 一般就不用勾了,设置完毕,按"Next"继续。 】**\r\n \r\n\r\n\r\n10. **【确认设置无误,按"Execute"使设置生效,即完成 MYSQL 的安装和配置。】** \r\n \r\n\r\n \r\n\r\n\r\n11. **注意:设置完毕,按"Finish"后有一个比较常见的错误,就是不能"Start service",一般出现在以前有安装 mysql 的服务器上,解决的办法,先保证以前安装的 mysql 服务器彻底卸载掉了;不行的话,检查是否按上面一步所说, 之前的密码是否有修改,照上面的操作;如果依然不行,将 mysql 安装目录下的 data 文件夹备份,然后删除,在 安装完成后,将安装生成的 data 文件夹删除,备份的 data 文件夹移回来,再重启 mysql 服务就可以了,这种情况 下,可能需要将数据库检查一下,然后修复一次,防止数据出错。解决方法是:`卸载 MySQL,重装 MySQL`**\r\n \r\n\r\n\r\n \r\n\r\n### 2.4-卸载\r\n\r\n1. **停止 window 的 MySQL 服务。 找到"控制面板"-> "管理工具"-> "服务",停止 MySQL 后台服务**。\r\n \r\n\r\n2. **卸载 MySQL 安装程序。找到"控制面板"-> \"程序和功能\",卸载 MySQL 程序。**\r\n \r\n\r\n3. **删除 MySQL 安装目录下的所有文件。**\r\n4. **删除 c 盘 ProgramDate 目录中关于 MySQL 的目录。路径为:C:\\ProgramData\\MySQL(是隐藏文件,需要显示 出来)**\r\n \r\n\r\n\r\n\r\n\r\n## 第三章:数据库服务启动与登录\r\n\r\nMySQL 服务器启动方式有两种: \r\n\r\n1. 通过服务的方式自动启动 \r\n2. 手动启动的方式 \r\n\r\n### 3.1-Windows 服务方式启动\r\n\r\n1. **步骤1:**\r\n \r\n\r\n2. **步骤2:**\r\n \r\n\r\n\r\n\r\n\r\n### 3.2-DOS 命令方式启动 \r\n\r\n1. **步骤1:**\r\n \r\n\r\n2. **步骤2:**\r\n \r\n\r\n\r\n\r\n\r\n### 3.3-控制台连接数据库\r\n\r\nMySQL 是一个需要账户名密码登录的数据库,登陆后使用,它提供了一个默认的 root 账号,使用安装时设置 的密码即可登录 \r\n\r\n> **登录方式1**\r\n\r\n命令:`mysql -u 用户名 -p 密码 `\r\n\r\n+ **输入用户名和密码**\r\n \r\n\r\n+ **后输入密码**\r\n \r\n\r\n\r\n> **登录方式2**\r\n\r\n命令:`mysql -hip 地址 -u 用户名 -p 密码 `\r\n\r\n注意:`127.0.0.1`IP地址代表本地。\r\n\r\n\r\n\r\n\r\n> **登录方式3**\r\n\r\n命令:`mysql --host=ip 地址 --user=用户名 --password=密码 `\r\n\r\n\r\n\r\n\r\n> **退出MySQL**\r\n\r\n命令:`quit` 或 `exit`\r\n\r\n\r\n\r\n### 3.4-SQLyog 图形化工具\r\n\r\nSQLyog 是业界著名的 Webyog 公司出品的一款简洁高效、功能强大的图形化 MySQL 数据库管理工具。使用 SQLyog 可以快速直观地让您从世界的任何角落通过网络来维护远端的 MySQL 数据库 。\r\n\r\n\r\n\r\n\r\n> **使用 SQLyog 登录数据库**\r\n\r\n\r\n\r\n\r\n\r\n\r\n### 3.5-MySQL目录结构\r\n\r\n| 目录结构 | 描述 |\r\n| -------------------------------------------- | -------------------------------------- |\r\n| bin目录 | 所有 mysql 的可执行文件,如:mysql.exe |\r\n| MySQLInstanceConfig.exe | 数据库的配置向导,在安装时出现的内容 |\r\n| data目录 | 系统必须的数据库所在的目录 |\r\n| my.ini 文件 | mysql 的配置文件,一般不建议去修改。 |\r\n| c:\\ProgramData\\MySQL\\MySQ L Server 5.5\\data\\ | 我们自己创建的数据库所在的文件夹 |\r\n\r\n## 第四章:数据库管理系统介绍\r\n\r\n> **概述**\r\n\r\n数据库管理系统(DataBase Management System,DBMS):指一种操作和管理数据库的大型软件,用于建 立、使用和维护数据库,对数据库进行统一管理和控制,以保证数据库的安全性和完整性。用户通过数据库管理 系统访问数据库中表内的数据 。\r\n\r\n> **数据库管理系统、数据库和表的关系 **\r\n\r\n数据库管理程序(DBMS)可以管理多个数据库,一般开发人员会针对每一个应用创建一个数据库。为保存应用 中实体的数据,一般会在数据库创建多个表,以保存程序中实体 User 的数据。 数据库管理系统、数据库和表的关系如图所示: \r\n\r\n\r\n\r\n\r\n> **总结**\r\n\r\n1. 一个数据库服务器包含多个库 \r\n2. 一个数据库包含多张表\r\n3. 一张表包含多条记录 ',17,14,'2020-06-05 15:45:01',0,'MySQL-环境安装、配置、卸载、系统介绍'),(15,'MySQL介绍','\r\n\r\n[TOC]\r\n\r\n## 第一章:MySQL介绍\r\n\r\n### 1.1-什么是SQL\r\n\r\n`S`tructured `Q`uery `L`anguage 结构化查询`语言` \r\n\r\n\r\n\r\n### 1.2-SQL的作用\r\n\r\n1. 是一种所有关系型数据库的查询规范,不同的数据库都支持。 \r\n2. 通用的数据库操作语言,可以用在不同的数据库中。 \r\n3. 不同的数据库 SQL 语句有一些区别\r\n\r\n\r\n\r\n\r\n\r\n\r\n### 1.3-SQL语句分类\r\n\r\n1. Data Definition Language (DDL 数据定义语言) 如:建库,建表 \r\n2. `Data Manipulation Language(DML 数据操纵语言)`,如:对表中的记录操作增删改 \r\n3. `Data Query Language(DQL 数据查询语言)`,如:对表中的查询操作 \r\n4. Data Control Language(DCL 数据控制语言),如:对用户权限的设置\r\n\r\n\r\n\r\n###1.4-SQL语法注意事项\r\n\r\n1. 每条语句以分号结尾,如果在 SQLyog 中不是必须加的。 \r\n\r\n2. SQL 中不区分大小写,关键字中认为大写和小写是一样的 。\r\n\r\n3. 3 种注释:\r\n\r\n | 注释语法 | 说明 |\r\n | -------- | ------------------------- |\r\n | `--空格` | 单行注释 |\r\n | `/**/` | 多行注释 |\r\n | `#` | 这是 mysql 特有的注释方式 |\r\n\r\n## 第二章:DDL操作数据库\r\n\r\n### 2.1-创建数据库\r\n\r\n> 创建数据\r\n\r\n`CREATE DATABASE 数据库名`; \r\n\r\n> 判断数据库是否已经存在,不存在则创建数据库 \r\n\r\n`CREATE DATABASE IF NOT EXISTS 数据库名`;\r\n\r\n> 创建数据库并指定字符集 \r\n\r\n`CREATE DATABASE 数据库名 CHARACTER SET 字符集; `\r\n\r\n> 演示:\r\n\r\n```sql\r\n--创建一个名为db1的数据库\r\ncreate database db1;\r\n--若没有存在,则创建一个名为db2的数据库\r\ncreate database if not exists db2;\r\n--创建要给字符集为gbk的数据库db3\r\ncreate database db3 character set gbk;\r\n```\r\n\r\n\r\n\r\n### 2.2-查看数据库\r\n\r\n> 查看所有数据库\r\n\r\n`show databases; `\r\n\r\n> 查看指定的数据库\r\n\r\n`show create database 数据库名`\r\n\r\n> 演示:\r\n\r\n```sql\r\n--查看所有数据库\r\nshow databases;\r\n--查看名为db1的数据库\r\nshow create database db1;\r\n```\r\n\r\n\r\n\r\n### 2.3-修改数据库\r\n\r\n> 修改数据库的字符集\r\n\r\n`ALTER DATABASE 数据库名 DEFAULT CHARACTER SET 字符集; `\r\n\r\n> 演示:\r\n\r\n```sql\r\nALTER DATABASE db1 character set utf8;\r\n```\r\n\r\n\r\n\r\n### 2.4-删除数据库\r\n\r\n> 语法\r\n\r\n`DROP DATABASE 数据库名; `\r\n\r\n> 演示:\r\n\r\n```sql\r\nDROP DATABASE db1;\r\n```\r\n\r\n\r\n\r\n### 2.5-使用数据库\r\n\r\n> 查看使用的数据库\r\n\r\n`SELECT DATABASE(); `\r\n\r\n> 切换使用的数据库\r\n\r\n`use 数据库名;`\r\n\r\n> 演示\r\n\r\n```sql\r\nselect database();\r\n```\r\n\r\n\r\n\r\n## 第三章:DDL操作表结构\r\n\r\n前提先使用某个数据库 。\r\n\r\n### 3.1-创建表\r\n\r\n> 格式\r\n\r\n```sql\r\nCREATE TABLE 表名 (\r\n 字段名 1 字段类型 1,\r\n 字段名 2 字段类型 2\r\n);\r\n```\r\n\r\n> 关键字说明\r\n\r\n+ `CREATE` 创建\r\n+ `TABLE` 表\r\n\r\n\r\n\r\n### 3.2-MySQL数据类型\r\n\r\n> 类型\r\n\r\n\r\n\r\n\r\n日期:`TIMESTAMP` 时间戳\r\n\r\n> 演示\r\n\r\n```sql\r\ncreate table student (\r\n id int, -- 整数\r\n name varchar(20), -- 字符串\r\n birthday date -- 生日,最后没有逗号\r\n);\r\n\r\n```\r\n\r\n\r\n\r\n### 3.3-查看表\r\n\r\n> 查看某个数据库的所有表\r\n\r\n`SHOW TABLES; `\r\n\r\n> 查看表结构\r\n\r\n`DESC 表名`\r\n\r\n> 查看创建表的SQL语句\r\n\r\n`SHOW CREATE TABLE 表名 `\r\n\r\n\r\n\r\n### 3.4-快速创建一个与表结构相同的表\r\n\r\n> 语法\r\n\r\n`CREATE TABLE 新表名 LIKE 旧表名; `\r\n\r\n> 演示\r\n\r\n```sql\r\nCREATE TABLE student LINKE stu;\r\n```\r\n\r\n\r\n\r\n### 3.5-删除表\r\n\r\n> 直接删除表\r\n\r\n`DROP TABLE 表名; `\r\n\r\n> 判断表是否存在,如果存在则删除表 \r\n\r\n`DROP TABLE IF EXISTS 表名`\r\n\r\n> 演示\r\n\r\n```sql\r\nDROP TABLE student;\r\nDROP TABLE IF EXISTS student\r\n```\r\n\r\n\r\n\r\n### 3.6-修改表结构\r\n\r\n> 添加一个列\r\n\r\n`alter table 表名 add 列名 类型`\r\n\r\n> 修改列的类型\r\n\r\n`ALTER TABLE 表名 MODIFY 列名 新的类型 `\r\n\r\n> 修改列名\r\n\r\n`ALTER TABLE 表名 CHANGE 旧列名 新列名 类型; `\r\n\r\n> 删除列\r\n\r\n`ALTER TABLE 表名 DROP 列名; `\r\n\r\n> 修改表名\r\n\r\n`RENAME TABLE 表名 TO 新表名; `\r\n\r\n> 修改表名字符集\r\n\r\n`ALTER TABLE 表名 character set 字符集; `\r\n\r\n\r\n\r\n。因此,满足第三范 式的数据库表应该不存在如下依赖关系:主键列 → 非主键列 x → 非主键列 y \r\n\r\n> 示例\r\n\r\n学生信息表\r\n\r\n\r\n\r\n\r\n存在传递的决定关系: 学号→所在学院 →学院地点 \r\n\r\n拆分成两张表\r\n\r\n\r\n\r\n\r\n### 8.5-三大范式小结\r\n\r\n| 范式 | 特点 |\r\n| ---- | ------------------------------------------------------------ |\r\n| 1NF | 原子性:表中每列不可再拆分。 |\r\n| 2NF | 不产生局部依赖,一张表只描述一件事情 |\r\n| 3NF | 不产生传递依赖,表中每一列都直接依赖于主键。而不是通过其它列间接依赖于主键。 |\r\n\r\n\r\n\r\n##第九章:数据库备份和还原\r\n\r\n### 9.1-备份的应用场景\r\n\r\n在服务器进行数据传输、数据存储和数据交换,就有可能产生数据故障。比如发生意外停机或存储介质损坏。 这时,如果没有采取数据备份和数据恢复手段与措施,就会导致数据的丢失,造成的损失是无法弥补与估量的。\r\n\r\n\r\n\r\n### 9.2-备份与还原的语句\r\n\r\n> 备份格式: DOS 下,未登录的时候。这是一个可执行文件 exe,在 bin 文件夹 \r\n\r\n`mysqldump -u 用户名 -p 密码 数据库 > 文件的路径 `\r\n\r\n导出结果:数据库中的所有表和数据都会导出成 SQL 语句 \r\n\r\n```mysql\r\n-- 备份 day21 数据库中的数据到 d:\\day21.sql 文件中\r\nmysqldump -uroot -proot day21 > d:/day21.sql\r\n```\r\n\r\n> 还原格式:mysql 中的命令,需要登录后才可以操作 \r\n\r\n`USE 数据库;`\r\n\r\n`SOURCE 导入文件的路径; `\r\n\r\n还原 day21 数据库中的数据,注意:还原的时候需要先登录 MySQL,并选中对应的数据库。 \r\n\r\n1. 删除 day21 数据库中的所有表 \r\n2. 登录 MySQL \r\n3. 选中数据库 \r\n4. 使用 SOURCE 命令还原数据 \r\n5. 查看还原结果 \r\n\r\n```mysql\r\nuse day21;\r\nsource d:/day21.sql;\r\n```\r\n\r\n\r\n\r\n### 9.3-图形化界面备份与还原 \r\n\r\n> 备份数据库中的数据 \r\n\r\n1. 选中数据库,右键 "备份/导出" \r\n2. 指定导出路径,保存成.sql 文件即可。 \r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n> 还原数据库中的数据 \r\n\r\n1. 删除数据库 \r\n2. 数据库列表区域右键"执行 SQL 脚本", 指定要执行的 SQL 文件,执行即可 \r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n## 第十章:多表查询\r\n\r\n### 10.1-多表查询介绍\r\n\r\n> **数据准备**\r\n\r\n```mysql\r\n# 创建部门表\r\ncreate table dept(\r\n id int primary key auto_increment,\r\n name varchar(20)\r\n)\r\ninsert into dept (name) values (\'开发部\'),(\'市场部\'),(\'财务部\');\r\n# 创建员工表\r\ncreate table emp (\r\n id int primary key auto_increment,\r\n name varchar(10),\r\n gender char(1), -- 性别\r\n salary double, -- 工资\r\n join_date date, -- 入职日期\r\n dept_id int,\r\n foreign key (dept_id) references dept(id) -- 外键,关联部门表(部门表的主键)\r\n)\r\ninsert into emp(name,gender,salary,join_date,dept_id) values(\'孙悟空\',\'男\r\n\',7200,\'2013-02-24\',1);\r\ninsert into emp(name,gender,salary,join_date,dept_id) values(\'猪八戒\',\'男\r\n\',3600,\'2010-12-02\',2);\r\ninsert into emp(name,gender,salary,join_date,dept_id) values(\'唐僧\',\'男\',9000,\'2008-\r\n08-08\',2);\r\ninsert into emp(name,gender,salary,join_date,dept_id) values(\'白骨精\',\'女\r\n\',5000,\'2015-10-07\',3);\r\ninsert into emp(name,gender,salary,join_date,dept_id) values(\'蜘蛛精\',\'女\r\n\',4500,\'2011-03-14\',1);\r\n```\r\n\r\n> **为什么要多表查询?**\r\n\r\n比如:我们想查询孙悟空的名字和他所在的部门的名字,则需要使用多表查询。 \r\n\r\n如果一条 SQL 语句查询多张表,因为查询结果在多张不同的表中。每张表取 1 列或多列。 \r\n\r\n> **多表查询的分类**\r\n\r\n\r\n\r\n\r\n\r\n\r\n### 10.2-笛卡尔积现象\r\n\r\n> **什么是笛卡尔积现象?**\r\n\r\n```mysql\r\n-- 需求:查询所有的员工和所有的部门\r\nselect * from emp,dept;\r\n```\r\n\r\n**结果分析**\r\n\r\n\r\n\r\n\r\n> **如何清除笛卡尔积无用的数据影响?**\r\n\r\n我们发现不是所有的数据组合都是有用的,只有员工表.dept_id = 部门表.id 的数据才是有用的。所以需要 通过条件过滤掉没用的数据。 \r\n\r\n```mysql\r\n-- 设置过滤条件 Column \'id\' in where clause is ambiguous\r\nselect * from emp,dept where id=5;\r\nselect * from emp,dept where emp.`dept_id` = dept.`id`;\r\n-- 查询员工和部门的名字\r\nselect emp.`name`, dept.`name` from emp,dept where emp.`dept_id` = dept.`id`;\r\n```\r\n\r\n\r\n\r\n### 10.3-内连接\r\n\r\n用左边表的记录去匹配右边表的记录,如果符合条件的则显示。如:从表.外键=主表.主键 \r\n\r\n> **隐式内连接**\r\n\r\n隐式内连接:看不到 JOIN 关键字,条件使用 WHERE 指定 \r\n\r\n`SELECT 字段名 FROM 左表, 右表 WHERE 条件 `\r\n\r\n```mysql\r\nselect * from emp,dept where emp.`dept_id` = dept.`id`;\r\n```\r\n\r\n\r\n\r\n\r\n> **显示内连接**\r\n\r\n显示内连接:使用 INNER JOIN ... ON 语句, 可以省略 INNER \r\n\r\n`SELECT 字段名 FROM 左表 [INNER] JOIN 右表 ON 条件 `\r\n\r\n**需求:** 查询唐僧的信息,显示员工 id,姓名,性别,工资和所在的部门名称,我们发现需要联合 2 张表同时才能 查询出需要的数据,使用内连接 。\r\n\r\n\r\n\r\n\r\n**1.确定查询哪些表**\r\n\r\n```mysql\r\nselect * from emp inner join dept;\r\n```\r\n\r\n\r\n\r\n\r\n\r\n\r\n**2.确定表连接条件,员工表.dept_id = 部门表.id 的数据才是有效的**\r\n\r\n```mysql\r\nselect * from emp e inner join dept d on e.`dept_id` = d.`id`;\r\n```\r\n\r\n\r\n\r\n\r\n\r\n\r\n**3.确定查询条件,我们查询的是唐僧的信息,员工表.name=\'唐僧\'**\r\n\r\n ```mysql\r\nselect * from emp e inner join dept d on e.`dept_id` = d.`id` where e.`name`=\'唐僧\';\r\n ```\r\n\r\n\r\n\r\n\r\n\r\n**4.确定查询字段,查询唐僧的信息,显示员工 id,姓名,性别,工资和所在的部门名称**\r\n\r\n```mysql\r\nselect e.`id`,e.`name`,e.`gender`,e.`salary`,d.`name` from emp e inner join dept d on e.`dept_id` = d.`id` where e.`name`=\'唐僧\';\r\n```\r\n\r\n\r\n\r\n\r\n\r\n\r\n**5.我们发现写表名有点长,可以给表取别名,显示的字段名也使用别名**\r\n\r\n```mysql\r\nselect e.`id` 编号,e.`name` 姓名,e.`gender` 性别,e.`salary` 工资,d.`name` 部门名字 from emp e inner join dept d on e.`dept_id` = d.`id` where e.`name`=\'唐僧\';\r\n```\r\n\r\n\r\n\r\n\r\n\r\n\r\n> **内连接总结**\r\n\r\n1. 确定查询哪些表 \r\n2. 确定表连接的条件\r\n3. 确定查询的条件\r\n4. 确定查询的字段 \r\n\r\n\r\n\r\n### 10.4-左外连接\r\n\r\n 左外连接:使用 `LEFT OUTER JOIN ... ON`,OUTER 可以省略 \r\n\r\n`SELECT 字段名 FROM 左表 LEFT [OUTER] JOIN 右表 ON 条件 `\r\n\r\n用左边表的记录去匹配右边表的记录,如果符合条件的则显示;否则,显示 NULL 可以理解为:在内连接的基础上保证左表的数据全部显示(左表是部门,右表员工) \r\n\r\n```mysql\r\n-- 在部门表中增加一个销售部\r\ninsert into dept (name) values (\'销售部\');\r\nselect * from dept;\r\n-- 使用内连接查询\r\nselect * from dept d inner join emp e on d.`id` = e.`dept_id`;\r\n-- 使用左外连接查询\r\nselect * from dept d left join emp e on d.`id` = e.`dept_id`;\r\n```\r\n\r\n\r\n\r\n\r\n\r\n\r\n### 10.5-右外连接\r\n\r\n右外连接:使用 `RIGHT OUTER JOIN ... ON`,OUTER 可以省略 \r\n\r\n`SELECT 字段名 FROM 左表 RIGHT [OUTER ]JOIN 右表 ON 条件` \r\n\r\n用右边表的记录去匹配左边表的记录,如果符合条件的则显示;否则,显示 NULL 可以理解为:在内连接的基础上保证右表的数据全部显示 \r\n\r\n```mysql\r\n-- 在员工表中增加一个员工\r\ninsert into emp values (null, \'沙僧\',\'男\',6666,\'2013-12-05\',null);\r\nselect * from emp;\r\n-- 使用内连接查询\r\nselect * from dept inner join emp on dept.`id` = emp.`dept_id`;\r\n-- 使用右外连接查询\r\nselect * from dept right join emp on dept.`id` = emp.`dept_id`;\r\n```\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n## 第十一章:子查询\r\n\r\n### 11.1 什么是子查询\r\n\r\n> **子查询的概念**\r\n\r\n1. 一个查询的结果做为另一个查询的条件\r\n2. 有查询的嵌套,内部的查询称为子查询 \r\n3. 子查询要使用括号 \r\n\r\n> **演示**\r\n\r\n```mysql\r\n-- 需求:查询开发部中有哪些员工\r\nselect * from emp;\r\n-- 通过两条语句查询\r\nselect id from dept where name=\'开发部\' ;\r\nselect * from emp where dept_id = 1;\r\n-- 使用子查询\r\nselect * from emp where dept_id = (select id from dept where name=\'市场部\');\r\n```\r\n\r\n\r\n\r\n### 11.2-子查询的三种情况\r\n\r\n> **子查询的结果是单行单列**\r\n\r\n\r\n\r\n\r\n> **子查询的结果是多行单列**\r\n\r\n\r\n\r\n\r\n> **子查询的结果是多行多列 **\r\n\r\n\r\n\r\n\r\n\r\n\r\n### 11.3-子查询结果是一个值\r\n\r\n子查询结果只要是单行单列,肯定在 WHERE 后面作为条件,父查询使用:比较运算符,如:`> 、<、<>、=` 等 \r\n\r\n`SELECT 查询字段 FROM 表 WHERE 字段=(子查询); `\r\n\r\n> **案例:查询工资最高的员工是谁? **\r\n\r\n```mysql\r\n-- 1) 查询最高工资是多少\r\nselect max(salary) from emp;\r\n-- 2) 根据最高工资到员工表查询到对应的员工信息\r\nselect * from emp where salary = (select max(salary) from emp);\r\n\r\n```\r\n\r\n> **查询工资小于平均工资的员工有哪些?** \r\n\r\n```mysql\r\n-- 1) 查询平均工资是多少\r\nselect avg(salary) from emp;\r\n-- 2) 到员工表查询小于平均的员工信息\r\nselect * from emp where salary < (select avg(salary) from emp);\r\n```\r\n\r\n\r\n\r\n### 11.4-子查询结果是多行单列\r\n\r\n子查询结果是单例多行,结果集类似于一个数组,父查询使用 IN 运算符 \r\n\r\n`SELECT 查询字段 FROM 表 WHERE 字段 IN (子查询);` \r\n\r\n> **查询工资大于 5000 的员工,来自于哪些部门的名字 **\r\n\r\n```mysql\r\n-- 先查询大于 5000 的员工所在的部门 id\r\nselect dept_id from emp where salary > 5000;\r\n-- 再查询在这些部门 id 中部门的名字 Subquery returns more than 1 row\r\nselect name from dept where id = (select dept_id from emp where salary > 5000);\r\nselect name from dept where id in (select dept_id from emp where salary > 5000);\r\n```\r\n\r\n> **查询开发部与财务部所有的员工信息** \r\n\r\n```mysql\r\n-- 先查询开发部与财务部的 id\r\nselect id from dept where name in(\'开发部\',\'财务部\');\r\n-- 再查询在这些部门 id 中有哪些员工\r\nselect * from emp where dept_id in (select id from dept where name in(\'开发部\',\'财务部\'));\r\n```\r\n\r\n\r\n\r\n### 11.5-子查询的结果是多行多列 \r\n\r\n子查询结果只要是多列,肯定在 FROM 后面作为表 \r\n\r\n`SELECT 查询字段 FROM (子查询) 表别名 WHERE 条件; `\r\n\r\n子查询作为表需要取别名,否则这张表没有名称则无法访问表中的字段 \r\n\r\n> **查询出 2011 年以后入职的员工信息,包括部门名称** \r\n\r\n```sql\r\n-- 查询出 2011 年以后入职的员工信息,包括部门名称\r\n-- 在员工表中查询 2011年1月1日 以后入职的员工\r\nselect * from emp where join_date >=\'2011-1-1\';\r\n-- 查询所有的部门信息,与上面的虚拟表中的信息组合,找出所有部门 id 等于的 dept_id\r\nselect * from dept d, (select * from emp where join_date >=\'2011-1-1\') e where d.`id`= e.dept_id ;\r\n```\r\n\r\n**也可以使用表连接**\r\n\r\n```mysql\r\nselect * from emp inner join dept on emp.`dept_id` = dept.`id` where join_date >=\'2011-1-1\';\r\nselect * from emp inner join dept on emp.`dept_id` = dept.`id` and join_date >=\'2011-1-1\';\r\n```\r\n\r\n\r\n\r\n### 11.6-总结\r\n\r\n1. 子查询结果只要是单列,则在 WHERE 后面作为条件 \r\n2. 子查询结果只要是多列,则在 FROM 后面作为表进行二次查询 \r\n\r\n\r\n\r\n## 第十二章:事务\r\n\r\n### 12.1-事务概述\r\n\r\n> **事务的应用场景**\r\n\r\n**什么是事务**: 在实际的开发过程中,一个业务操作如:转账,往往是要多次访问数据库才能完成的。转 账是一个用户扣钱,另一个用户加钱。如果其中有一条 SQL 语句出现异常,这条 SQL 就可能执行失败。\r\n\r\n 事务执行是一个整体,所有的 SQL 语句都必须执行成功。如果其中有 1 条 SQL 语句出现异常,则所有的 SQL 语句都要回滚,整个业务执行失败。 \r\n\r\n> **转账操作**\r\n\r\n模拟张三给李四转 500 元钱,一个转账的业务操作最少要执行下面的 2 条语句: 张三账号-500 李四账号+500 \r\n\r\n```mysql\r\n-- 创建数据表\r\nCREATE TABLE account (\r\nid INT PRIMARY KEY AUTO_INCREMENT,\r\nNAME VARCHAR(10),\r\nbalance DOUBLE\r\n);\r\n-- 添加数据\r\nINSERT INTO account (NAME, balance) VALUES (\'张三\', 1000), (\'李四\', 1000);\r\n-- 张三账号-500\r\nupdate account set balance = balance - 500 where name=\'张三\';\r\n-- 李四账号+500\r\nupdate account set balance = balance + 500 where name=\'李四\';\r\n```\r\n\r\n假设当张三账号上-500 元,服务器崩溃了。李四的账号并没有+500 元,数据就出现问题了。我们需要保证其中 一条 SQL 语句出现问题,整个转账就算失败。只有两条 SQL 都成功了转账才算成功。这个时候就需要用到事务。 \r\n\r\n\r\n\r\n### 12.2-手动提交事务\r\n\r\nMYSQL 中可以有两种方式进行事务的操作: \r\n\r\n1. 手动提交事务\r\n2. 自动提交事务 \r\n\r\n> **手动提交事务的SQL**\r\n\r\n| 功能 | SQL语句 |\r\n| -------- | --------------------- |\r\n| 开启事务 | `start transaction; ` |\r\n| 提交事务 | `commit; ` |\r\n| 回滚事务 | `rollback; ` |\r\n\r\n> **手动提交事务的执行过程**\r\n\r\n\r\n\r\n\r\n1. 执行成功的情况: `开启事务 → 执行多条 SQL 语句 → 成功提交事务 `\r\n2. 执行失败的情况: `开启事务 → 执行多条 SQL 语句 → 事务的回滚 `\r\n \r\n\r\n> **案例1:模拟张三给李四转 500 元钱(成功) 目前数据库数据如下**\r\n\r\n\r\n\r\n\r\n1- 使用 DOS 控制台进入 MySQL \r\n\r\n2- 执行以下 SQL 语句: 1.开启事务, 2.张三账号-500, 3.李四账号+500 \r\n\r\n3- 使用 SQLYog 查看数据库:发现数据并没有改变 \r\n\r\n4- 在控制台执行 commit 提交事务: \r\n\r\n5- 使用 SQLYog 查看数据库:发现数据改变 \r\n\r\n\r\n\r\n\r\n> **案例演示 2:事务回滚 **\r\n\r\n模拟张三给李四转 500 元钱(失败) 目前数据库数据如下: \r\n\r\n\r\n\r\n\r\n1- 在控制台执行以下 SQL 语句:1.开启事务, 2.张三账号-500 \r\n\r\n2- 使用 SQLYog 查看数据库:发现数据并没有改变 \r\n\r\n3- 在控制台执行 rollback 回滚事务: \r\n\r\n4- 使用 SQLYog 查看数据库:发现数据没有改变 \r\n\r\n\r\n\r\n\r\n总结: 如果事务中 SQL 语句没有问题,commit 提交事务,会对数据库数据的数据进行改变。 如果事务中 SQL 语句有问题,rollback 回滚事务,会回退到开启事务时的状态。 \r\n\r\n\r\n\r\n### 12.3-自动提交事务\r\n\r\n> **概述**\r\n\r\nMySQL 默认每一条 DML(增删改)语句都是一个单独的事务,每条语句都会自动开启一个事务,语句执行完毕 自动提交事务,MySQL 默认开始自动提交事务 \r\n\r\n\r\n\r\n\r\n> **案例:自动提交事务**\r\n\r\n1. 将金额重置为 1000 \r\n2. 更新其中某一个账户 \r\n3. 使用 SQLYog 查看数据库:发现数据已经改变 \r\n\r\n\r\n\r\n\r\n> **取消自动提交**\r\n\r\n**查看 MySQL 是否开启自动提交事务**\r\n\r\n\r\n\r\n\r\n`@@表示全局变量,1 表示开启,0 表示关闭 `\r\n\r\n**取消自动提交事务 **\r\n\r\n\r\n\r\n\r\n执行更新语句,使用 SQLYog 查看数据库,发现数据并没有改变 ,在控制台执行 commit 提交任务 \r\n\r\n\r\n\r\n\r\n\r\n\r\n### 12.4-事务原理\r\n\r\n事务开启之后, 所有的操作都会临时保存到事务日志中, 事务日志只有在得到 commit 命令才会同步到数据表 中,其他任何情况都会清空事务日志(rollback,断开连接) \r\n\r\n> **原理图**\r\n\r\n\r\n\r\n\r\n> **事务的步骤**\r\n\r\n1. 客户端连接数据库服务器,创建连接时创建此用户临时日志文件 \r\n2. 开启事务以后,所有的操作都会先写入到临时日志文件中 \r\n3. 所有的查询操作从表中查询,但会经过日志文件加工后才返回 \r\n4. 如果事务提交则将日志文件中的数据写到表中,否则清空日志文件 \r\n\r\n\r\n\r\n### 12.5-回滚点\r\n\r\n> **什么是回滚点**\r\n\r\n在某些成功的操作完成之后,后续的操作有可能成功有可能失败,但是不管成功还是失败,前面操作都已经成 功,可以在当前成功的位置设置一个回滚点。可以供后续失败操作返回到该位置,而不是返回所有操作,这个点称 之为回滚点。 \r\n\r\n> **回滚点的操作语句**\r\n\r\n| 操作 | 语句 |\r\n| ---------- | ------------------- |\r\n| 设置回滚点 | `savepoint 名字 ` |\r\n| 回到回滚点 | `rollback to 名字 ` |\r\n\r\n> **具体操作**\r\n\r\n1) 将数据还原到 1000 \r\n\r\n2) 开启事务 \r\n\r\n3) 让张三账号减 3 次钱,每次 10 块 \r\n\r\n4) 设置回滚点:savepoint three_times; \r\n\r\n5) 让张三账号减 4 次钱,每次 10 块 \r\n\r\n6) 回到回滚点:rollback to three_times; \r\n\r\n7) 分析执行过程 \r\n\r\n设置回滚点可以让我们在失败的时候回到回滚点,而不是回到事务开启的时候。 \r\n\r\n\r\n\r\n### 12.6-事务的四大特性\r\n\r\n| 事务特性 | 含义 |\r\n| --------------------- | ------------------------------------------------------------ |\r\n| 原子性(Atomicity) | 每个事务都是一个整体,不可再拆分,事务中所有的 SQL 语句要么都执行成功, 要么都失败。 |\r\n| 一致性(Consistency) | 事务在执行前数据库的状态与执行后数据库的状态保持一致。如:转账前2个人的 总金额是 2000,转账后 2 个人总金额也是 2000 |\r\n| 隔离性(Isolation) | 事务与事务之间不应该相互影响,执行时保持隔离的状态。 |\r\n| 持久性(Durability) | 一旦事务执行成功,对数据库的修改是持久的。就算关机,也是保存下来的。 |\r\n\r\n### 12.7-事务的隔离级别\r\n\r\n#### 隔离级别介绍\r\n\r\n> **概述**\r\n\r\n事务在操作时的理想状态: 所有的事务之间保持隔离,互不影响。因为并发操作,多个用户同时访问同一个 数据。可能引发并发访问的问题: \r\n\r\n| 并发访问的问题 | 含义 |\r\n| -------------- | ------------------------------------------------------------ |\r\n| 脏读 | 一个事务读取到了另一个事务中尚未提交的数据 |\r\n| 不可重复度 | 一个事务中两次读取的数据`内容`不一致,要求的是一个事务中多次读取时数据是一致的,这 是事务 update 时引发的问题 |\r\n| 幻读 | 一个事务中两次读取的数据的`数量`不一致,要求在一个事务多次读取的数据的数量是一致 的,这是 insert 或 delete 时引发的问题 |\r\n\r\n> **MySQL 数据库有四种隔离级别**\r\n\r\n上面的级别最低,下面的级别最高。"是"表示会出现这种问题,"否"表示不会出现这种问题。 \r\n\r\n| 级别 | 名字 | 隔离级别 | 脏读 | 不可重复读 | 幻读 | 数据库默认隔离级别 |\r\n| ---- | -------- | -------------------- | ---- | ---------- | ---- | -------------------- |\r\n| 1 | 读未提交 | ` read uncommitted ` | 是 | 是 | 是 | |\r\n| 2 | 读已提交 | `read committed` | 否 | 是 | 是 | Oracle 和 SQL Server |\r\n| 3 | 可重复读 | `repeatable read` | 否 | 否 | 是 | MySQL |\r\n| 4 | 串行化 | ` serializable` | 否 | 否 | 否 | |\r\n\r\n隔离级别越高,性能越差,安全性越高。 \r\n\r\n> **MySQL 事务隔离级别相关的命令** \r\n\r\n1. 查询隔离级别 `select @@tx_isolation`; \r\n2. 设置隔离级别 `set global transaction isolation level 级别字符串`; \r\n\r\n设置事务隔离级别,需要退出 MySQL 再重新登录才能看到隔离级别的变化 \r\n\r\n\r\n\r\n#### 脏读问题及解决方案演示\r\n\r\n> **演示**\r\n\r\n将数据进行恢复:`UPDATE account SET balance = 1000; `\r\n\r\n**【01-打开 A 窗口登录 MySQL,设置全局的隔离级别为最低】** \r\n\r\n```mysql\r\nmysql -uroot -proot\r\nset global transaction isolation level read uncommitted;\r\n```\r\n\r\n\r\n\r\n\r\n**【02-打开 B 窗口,AB 窗口都开启事务】** \r\n\r\n\r\n\r\n\r\n**【03-A 窗口更新 2 个人的账户数据,未提交】**\r\n\r\n```mysql\r\nupdate account set balance=balance-500 where id=1;\r\nupdate account set balance=balance+500 where id=2;\r\n```\r\n\r\n\r\n\r\n\r\n**【04-B 窗口查询账户】**\r\n\r\n\r\n\r\n\r\n**【05-A 窗口回滚 】**\r\n\r\n\r\n\r\n\r\n**【06-B 窗口查询账户,钱没了 】**\r\n\r\n\r\n\r\n\r\n脏读非常危险的,比如张三向李四购买商品,张三开启事务,向李四账号转入 500 块,然后打电话给李四说钱 已经转了。李四一查询钱到账了,发货给张三。张三收到货后回滚事务,李四的再查看钱没了。 \r\n\r\n> **解决脏读的问题:将全局的隔离级别进行提升** \r\n\r\n将数据进行恢复: \r\n\r\n```mysql\r\nUPDATE account SET balance = 1000;\r\n```\r\n\r\n**【01-在 A 窗口设置全局的隔离级别为 `read committed` 】**\r\n\r\n```mysql\r\nset global transaction isolation level read committed;\r\n```\r\n\r\n\r\n\r\n\r\n\r\n\r\n**【02-B 窗口退出 MySQL,B 窗口再进入 MySQL 】**\r\n\r\n\r\n\r\n\r\n**【03-AB 窗口同时开启事务 】**\r\n\r\n\r\n\r\n\r\n**【04-A 更新 2 个人的账户,未提交 】**\r\n\r\n```mysql\r\nupdate account set balance=balance-500 where id=1;\r\nupdate account set balance=balance+500 where id=2;\r\n```\r\n\r\n\r\n\r\n\r\n**【05-B 窗口查询账户】**\r\n\r\n\r\n\r\n\r\n\r\n\r\n**【06-A 窗口 commit 提交事务 】**\r\n\r\n\r\n\r\n\r\n**【08-B 窗口查看账户 】**\r\n\r\n\r\n\r\n\r\n结论:read committed 的方式可以避免脏读的发生 \r\n\r\n\r\n\r\n#### 不可重复读问题及解决方案\r\n\r\n> **问题演示**\r\n\r\n将数据进行恢复: \r\n\r\n```mysql\r\nUPDATE account SET balance = 1000;\r\n```\r\n\r\n【01-开启 A 窗口 】\r\n\r\n```mysql\r\nset global transaction isolation level read committed;\r\n```\r\n\r\n\r\n\r\n\r\n【02-开启 B 窗口,在 B 窗口开启事务 】\r\n\r\n```mysql\r\nstart transaction;\r\nselect * from account;\r\n```\r\n\r\n\r\n\r\n\r\n【03-在 A 窗口开启事务,并更新数据 】\r\n\r\n```mysql\r\nstart transaction;\r\nupdate account set balance=balance+500 where id=1;\r\ncommit;\r\n```\r\n\r\n\r\n\r\n\r\n【04-B 窗口查询 】\r\n\r\n\r\n\r\n\r\n两次查询输出的结果不同,到底哪次是对的?不知道以哪次为准。 很多人认为这种情况就对了,无须困惑, 当然是后面的为准。我们可以考虑这样一种情况,比如银行程序需要将查询结果分别输出到电脑屏幕和发短信给客 户,结果在一个事务中针对不同的输出目的地进行的两次查询不一致,导致文件和屏幕中的结果不一致,银行工作 人员就不知道以哪个为准了。 \r\n\r\n> **解决不可重复读的问题** \r\n\r\n将全局的隔离级别进行提升为:`repeatable read` 将数据进行恢复: \r\n\r\n```mysql\r\nUPDATE account SET balance = 1000;\r\n```\r\n\r\n【01-A 窗口设置隔离级别为:repeatable read 】\r\n\r\n```mysql\r\nset global transaction isolation level repeatable read;\r\n```\r\n\r\n\r\n\r\n\r\n【02-B 窗口退出 MySQL,B 窗口再进入 MySQL ,开启事务,查询数据】\r\n\r\n```mysql\r\nstart transaction;\r\nselect * from account\r\n```\r\n\r\n\r\n\r\n\r\n【03-A 窗口更新数据 】\r\n\r\n```mysql\r\nstart transaction;\r\nupdate account set balance=balance+500 where id=1;\r\ncommit;\r\n\r\n```\r\n\r\n\r\n\r\n\r\n【04-B 窗口查询 】\r\n\r\n\r\n\r\n\r\n\r\n\r\n结论:同一个事务中为了保证多次查询数据一致,必须使用 repeatable read 隔离级别 。\r\n\r\n\r\n\r\n#### 幻读的演示\r\n\r\n在 MySQL 中无法看到幻读的效果。 \r\n\r\n但我们可以将事务隔离级别设置到最高,以挡住幻读的发生 将数据进行恢复: \r\n\r\n```mysql\r\nUPDATE account SET balance = 1000;\r\n```\r\n\r\n【01-开启 A 窗口 】\r\n\r\n```mysql\r\nset global transaction isolation level serializable; -- 设置隔离级别为最高\r\n```\r\n\r\n\r\n\r\n\r\n【02-A 窗口退出 MySQL,A 窗口重新登录 MySQL 】\r\n\r\n```mysql\r\nstart transaction;\r\nselect count(*) from account;\r\n```\r\n\r\n\r\n\r\n\r\n【03-再开启 B 窗口,登录 MySQL 】\r\n\r\n【04-在 B 窗口中开启事务,添加一条记录 】\r\n\r\n```mysql\r\nstart transaction; -- 开启事务\r\ninsert into account (name,balance) values (\'LaoWang\', 500);\r\n```\r\n\r\n\r\n\r\n\r\n【05-在 A 窗口中 commit 提交事务,B 窗口中 insert 语句会在 A 窗口事务提交后立马运行 】\r\n\r\n\r\n\r\n\r\n【06-在 A 窗口中接着查询,发现数据不变 】\r\n\r\n\r\n\r\n\r\n【07-B 窗口中 commit 提交当前事务 】\r\n\r\n\r\n\r\n\r\n【08-A 窗口就能看到最新的数据 】\r\n\r\n\r\n\r\n\r\n结论:使用 serializable 隔离级别,一个事务没有执行完,其他事务的 SQL 执行不了,可以挡住幻读 \r\n\r\n\r\n\r\n## 第十三章:DCL\r\n\r\n我们现在默认使用的都是 root 用户,超级管理员,拥有全部的权限。但是,一个公司里面的数据库服务器上面 可能同时运行着很多个项目的数据库。所以,我们应该可以根据不同的项目建立不同的用户,分配不同的权限来管 理和维护数据库。 注:mysqld 是 MySQL 的主程序,服务器端。mysql 是 MySQL 的命令行工具,客户端。 \r\n\r\n\r\n\r\n\r\n### 13.1-创建用户\r\n\r\n> **语法**\r\n\r\n`CREATE USER \'用户名\'@\'主机名\' IDENTIFIED BY \'密码\'; `\r\n\r\n> **关键字说明**\r\n\r\n| 关键字 | 说明 |\r\n| ------ | ------------------------------------------------------------ |\r\n| 用户名 | 将创建的用户名 |\r\n| 主机名 | 指定该用户在哪个主机上可以登陆,如果是本地用户可用 localhost,如果想让该用户可以 从任意远程主机登陆,可以使用通配符% |\r\n| 密码 | 该用户的登陆密码,密码可以为空,如果为空则该用户可以不需要密码登陆服务器 |\r\n\r\n> **具体操作**\r\n\r\n```mysql\r\n-- 创建 user1 用户,只能在 localhost 这个服务器登录 mysql 服务器,密码为 123 \r\ncreate user \'user1\'@\'localhost\' identified by \'123\';\r\n-- 创建 user2 用户可以在任何电脑上登录 mysql 服务器,密码为 123\r\ncreate user \'user2\'@\'%\' identified by \'123\';\r\n```\r\n\r\n 注:创建的用户名都在 mysql 数据库中的 user 表中可以查看到,密码经过了加密。 \r\n\r\n\r\n\r\n\r\n\r\n\r\n### 13.2-给用户授权\r\n\r\n用户创建之后,没什么权限!需要给用户授权 \r\n\r\n> **语法**\r\n\r\n`GRANT 权限 1, 权限 2... ON 数据库名.表名 TO \'用户名\'@\'主机名\'`; \r\n\r\n> **关键字说明**\r\n\r\n| 关键字 | 说明 |\r\n| -------------------- | ------------------------------------------------------------ |\r\n| ` GRANT...ON...TO ` | 授权关键字 |\r\n| ` 权限 ` | 授予用户的权限,如 `CREATE、ALTER、SELECT、INSERT、UPDATE` 等。如果要授 予所有的权限则使用 `ALL` |\r\n| ` 数据库名.表名` | 该用户可以操作哪个数据库的哪些表。如果要授予该用户对所有数据库和表的相应操作 权限则可用`*`表示,如 ` *`.`*` |\r\n| ` \'用户名\'@\'主机名\'` | 给哪个用户授权,注:有 2 对单引号 |\r\n\r\n> **具体操作**\r\n\r\n```mysql\r\n-- 给 user1 用户分配对 test 这个数据库操作的权限:创建表,修改表,插入记录,更新记录,查询\r\ngrant create,alter,insert,update,select on test.* to \'user1\'@\'localhost\';\r\n-- 给 user2 用户分配所有权限,对所有数据库的所有表\r\ngrant all on *.* to \'user2\'@\'%\';\r\n-- 注:用户名和主机名要与上面创建的相同,要加单引号。\r\n```\r\n\r\n\r\n\r\n### 13.3-撤销授权\r\n\r\n> **语法**\r\n\r\n`REVOKE 权限 1, 权限 2... ON 数据库.表名 revoke all on test.* from \'user1\'@\'localhost\'; \'用户名\'@\'主机 名\'; `\r\n\r\n| 关键字 | 说明 |\r\n| ----------------- | ------------------------------------------------------------ |\r\n| REVOKE...ON...FROM | 撤销授权的关键字 |\r\n| 权限 | 用户的权限,如 CREATE、ALTER、SELECT、INSERT、UPDATE 等,所有的权 限则使用 ALL |\r\n| 数据库名.表名 | 对哪些数据库的哪些表,如果要取消该用户对所有数据库和表的操作权限则可用 `*`表 示,如`*`.`*` |\r\n| \'用户名\'@\'主机名\' | 给哪个用户撤销 |\r\n\r\n> **具体操作**\r\n\r\n```mysql\r\n-- 撤销 user1 用户对 test 数据库所有表的操作的权限\r\nrevoke all on test.* from \'user1\'@\'localhost\';\r\n```\r\n\r\n\r\n\r\n### 13.4-查看权限\r\n\r\n> **语法**\r\n\r\n`SHOW GRANTS FOR \'用户名\'@\'主机名\'; `\r\n\r\n> **具体操作**\r\n\r\n\r\n\r\n\r\n注:usage 是指连接(登陆)权限,建立一个用户,就会自动授予其 usage 权限(默认授予)。 \r\n\r\n\r\n\r\n### 13.5-删除用户\r\n\r\n> **语法**\r\n\r\n`DROP USER \'用户名\'@\'主机名\'; `\r\n\r\n> **具体操作**\r\n\r\n```mysql\r\n-- 删除 user2\r\ndrop user \'user2\'@\'%\';\r\n```\r\n\r\n\r\n\r\n\r\n\r\n\r\n### 13.6-修改管理员密码\r\n\r\n> **语法**\r\n\r\n`mysqladmin -uroot -p password 新密码` \r\n\r\n注意:需要在未登陆 MySQL 的情况下操作,新密码不需要加上引号。 \r\n\r\n> **具体操作**\r\n\r\n1) 将 root 管理员的新密码改成 123456 \r\n\r\n2) 要求输入旧密码 \r\n\r\n3) 使用新密码登录 \r\n\r\n\r\n\r\n\r\n\r\n\r\n### 13.7-修改普通用户密码\r\n\r\n> **语法**\r\n\r\n`set password for \'用户名\'@\'主机名\' = password(\'新密码\'); `\r\n\r\n注意:需要在登陆 MySQL 的情况下操作,新密码要加单引号。 \r\n\r\n> **具体操作**\r\n\r\n1) 将\'user1\'@\'localhost\'的密码改成\'666666\' \r\n\r\n2) 使用新密码登录,老密码登录不了 \r\n\r\n',17,14,'2020-06-05 15:50:50',0,'MySQL介绍'),(16,'JDBC操作MySQL','\r\n[TOC]\r\n## 第一章:JDBC快速入门\r\n\r\n### 1.1-客户端操作MySQL的方式\r\n\r\n01-使用第三方客户端来访问 MySQL:SQLyog、Navicat、SQLWave、MyDB Studio、EMS SQL Manager for MySQL 。\r\n\r\n\r\n\r\n02-使用 MySQL 自带的命令行方式 。\r\n\r\n03-**通过 Java 来访问 MySQL 数据库**,本篇要讲解的内容\r\n\r\n\r\n\r\n### 1.2-JDBC概述\r\n\r\n> **什么是JDBC**\r\n\r\nJDBC (Java Database Connectivity,简称*JDBC* )是 **Java 访问数据库的标准规范**,真正怎么操作数据库还需要具体的实现类,也就是数据库驱动。每个 数据库厂商根据自家数据库的通信格式编写好自己数据库的驱动。所以我们只需要会调用 JDBC 接口中的方法即可,数据库驱动由数据库厂商提供。 \r\n\r\n> **JDBC的好处**\r\n\r\n01-程序员如果要开发访问数据库的程序,只需要会调用 JDBC 接口中的方法即可,不用关注类是如何实现的。 \r\n\r\n02-使用同一套 Java 代码,进行少量的修改就可以访问其他 JDBC 支持的数据库 。\r\n\r\n\r\n\r\n\r\n> **使用 JDBC 开发使用到的包**\r\n\r\n| 会使用到的包 | 说明 |\r\n| -------------- | ------------------------------------------------------------ |\r\n| ` java.sql` | 所有与 JDBC 访问数据库相关的接口和类 |\r\n| `javax.sql` | 数据库扩展包,提供数据库额外的功能。如:连接池 |\r\n| `数据库的驱动` | 由各大数据库厂商提供,需要额外去下载,是对 JDBC 接口实现的类 |\r\n\r\n\r\n\r\n### 1.3-JDBC核心API\r\n\r\n| 接口或类 | 作用 |\r\n| ----------------------- | ------------------------------------------------------------ |\r\n| `DriverManager 类` | 1) 管理和注册数据库驱动<br> 2) 得到数据库连接对象 |\r\n| `Connection 接口` | 一个连接对象,可用于创建 Statement 和 PreparedStatement 对象 |\r\n| `Statement 接口` | 一个 SQL 语句对象,用于将 SQL 语句发送给数据库服务器。 |\r\n| `PreparedStatemen 接口` | 一个 SQL 语句对象,是 Statement 的子接口 |\r\n| `ResultSet 接口` | 用于封装数据库查询的结果集,返回给客户端 Java 程序 |\r\n\r\n\r\n\r\n### 1.4-导入驱动jar包\r\n\r\n> **下载**\r\n\r\n链接:https://pan.baidu.com/s/1igjB1mTP7GBLBAZGP_bsGA \r\n提取码:v72g\r\n\r\n> **导入步骤**\r\n\r\n\r\n\r\n\r\n\r\n\r\n### 1.5-加载和注册驱动\r\n\r\n> **加载方法**\r\n\r\n| 加载和注册驱动的方法 | 描述 |\r\n| --------------------------------- | ------------------------------------------------------------ |\r\n| `Class.forName(数据库驱动实现类)` | 加载和注册数据库驱动,数据库驱动由 mysql 厂商\"com.mysql.jdbc.Driver\" |\r\n\r\n> **具体实现**\r\n\r\n```java\r\npublic class Demo1 {\r\n public static void main(String[] args) throws ClassNotFoundException {\r\n //抛出类找不到的异常,注册数据库驱动\r\n Class.forName(\"com.mysql.jdbc.Driver\");\r\n }\r\n}\r\n\r\n```\r\n\r\n疑问:为什么这样可以注册驱动? \r\n\r\n```java\r\npublic class Driver implements java.sql.Driver {\r\n public Driver() throws SQLException {\r\n }\r\n static {\r\n try {\r\n DriverManager.registerDriver(new Driver()); //注册数据库驱动\r\n } catch (SQLException var1) {\r\n throw new RuntimeException(\"Can\'t register driver!\");\r\n }\r\n }\r\n}\r\n\r\n```\r\n\r\ncom.mysql.jdbc.Driver 源代码: \r\n\r\nDriver 接口,所有数据库厂商必须实现的接口,表示这是一个驱动类。 \r\n\r\n> **注意事项**\r\n\r\n从 JDBC3 开始,目前已经普遍使用的版本。可以不用注册驱动而直接使用。Class.forName 这句话可以省略。 \r\n\r\n为了向下兼容,建议不要省略。\r\n\r\n\r\n\r\n## 第二章:DriverManager类\r\n\r\n### 2.1-DriverManager作用\r\n\r\n1. 管理和注册驱动 \r\n2. 创建数据库的连接 \r\n\r\n\r\n\r\n### 2.2-类中的方法\r\n\r\n| DriverManager 类中的静态方法 | 描述 |\r\n| ------------------------------------------------------------ | -------------------------------------------------- |\r\n| `Connection getConnection (String url, String user, String password)` | 通过连接字符串,用户名,密码来得到数据库的连接对象 |\r\n| `Connection getConnection (String url, Properties info)` | 通过连接字符串,属性对象来得到连接对象 |\r\n\r\n### 2.3-连接数据库的四个参数\r\n\r\n| 参数 | 说明 |\r\n| ---------------- | ------------------------------------------------------------ |\r\n| 用户名 | 登录的用户名 |\r\n| 密码 | 登录的密码 |\r\n| 连接字符串 URL | 不同的数据库 URL 是不同的<br>mysql 的写法` jdbc:mysql://localhost:3306/数据库[?参数名=参数值] ` |\r\n| 驱动类的字符串名 | `com.mysql.jdbc.Driver ` |\r\n\r\n### 2.4-连接数据库URL\r\n\r\n> **格式**\r\n\r\n`协议名:子协议://服务器名或 IP 地址:端口号/数据库名?参数=参数值` \r\n\r\n> **MySQL格式**\r\n\r\n\r\n\r\n\r\n> **MySQL简写格式**\r\n\r\n前提:必须是本地服务器,端口号是 3306 。\r\n\r\n简写格式:`jdbc:mysql:///数据库名`\r\n\r\n> **乱码的处理**\r\n\r\n如果数据库出现乱码,可以指定参数: `?characterEncoding=utf8`,表示让数据库以 UTF-8 编码来处理数据。 \r\n\r\n```\r\njdbc:mysql://localhost:3306/数据库?characterEncoding=utf8\r\n```\r\n\r\n\r\n\r\n### 2.5-代码演示\r\n\r\n> **方式1:使用用户名、密码、URL 得到连接对象** \r\n\r\n```java\r\nimport java.sql.Connection;\r\nimport java.sql.DriverManager;\r\nimport java.sql.SQLException;\r\n/**\r\n* 得到连接对象\r\n*/\r\npublic class Demo2 {\r\n public static void main(String[] args) throws SQLException {\r\n String url = \"jdbc:mysql://localhost:3306/db3\";\r\n //1) 使用用户名、密码、URL 得到连接对象\r\n Connection connection = DriverManager.getConnection(url, \"root\", \"root\");\r\n //com.mysql.jdbc.JDBC4Connection@68de145\r\n System.out.println(connection);\r\n }\r\n}\r\n\r\n```\r\n\r\n> **方式2:使用属性文件和 url 得到连接对象** \r\n\r\n```java\r\nimport java.sql.Connection;\r\nimport java.sql.DriverManager;\r\nimport java.sql.SQLException;\r\nimport java.util.Properties;\r\npublic class Demo3 {\r\n public static void main(String[] args) throws SQLException {\r\n //url 连接字符串\r\n String url = \"jdbc:mysql://localhost:3306/db3\";\r\n //属性对象\r\n Properties info = new Properties();\r\n //把用户名和密码放在 info 对象中\r\n info.setProperty(\"user\",\"root\");\r\n info.setProperty(\"password\",\"root\");\r\n Connection connection = DriverManager.getConnection(url, info);\r\n //com.mysql.jdbc.JDBC4Connection@68de145\r\n System.out.println(connection);\r\n }\r\n}\r\n\r\n```\r\n\r\n\r\n\r\n## 第三章:Connection接口\r\n\r\n> **作用**\r\n\r\nConnection 接口,具体的实现类由数据库的厂商实现,代表一个连接对象。 \r\n\r\n> **方法**\r\n\r\n| 方法 | 描述 |\r\n| ----------------------------- | --------------------- |\r\n| `Statement createStatement()` | 创建一条 SQL 语句对象 |\r\n\r\n\r\n\r\n## 第四章:Statement 接口 \r\n\r\n### 4.1-JDBC访问数据库的步骤\r\n\r\n\r\n\r\n\r\n1. 注册和加载驱动(可以省略) \r\n2. 获取连接 \r\n3. Connection 获取 Statement 对象 \r\n4. 使用 Statement 对象执行 SQL 语句 \r\n5. 返回结果集 \r\n6. 释放资源 \r\n\r\n\r\n\r\n### 4.2-Statement 作用 \r\n\r\n代表一条语句对象,用于发送 SQL 语句给服务器,用于执行静态 SQL 语句并返回它所生成结果的对象。 \r\n\r\n\r\n\r\n### 4.3-Statement 中的方法\r\n\r\n| Statement 接口中的方法 | 描述 |\r\n| ------------------------------------ | ------------------------------------------------------------ |\r\n| `int executeUpdate(String sql)` | 用于发送 DML 语句,增删改的操作(insert、update、delete) <br>参数:SQL 语句 <br>返回值:返回对数据库影响的行数 |\r\n| `ResultSet executeQuery(String sql)` | 用于发送 DQL 语句,执行查询的操作。<br>select 参数:SQL 语句 <br>返回值:查询的结果集 |\r\n\r\n### 4.4-释放资源\r\n\r\n1. 需要释放的对象:ResultSet 结果集,Statement 语句,Connection 连接 \r\n2. 释放原则:先开的后关,后开的先关。ResultSet →Statement →Connection \r\n3. 放在哪个代码块中:finally 块 \r\n\r\n\r\n\r\n### 4.5-执行DDL\r\n\r\n需求:使用 JDBC 在 MySQL 的数据库中创建一张学生表 \r\n\r\n\r\n\r\n\r\n```java\r\nimport java.sql.Connection;\r\nimport java.sql.DriverManager;\r\nimport java.sql.SQLException;\r\nimport java.sql.Statement;\r\n/**\r\n* 创建一张学生表\r\n*/\r\npublic class Demo4DDL {\r\n public static void main(String[] args) {\r\n //1. 创建连接\r\n Connection conn = null;\r\n Statement statement = null;\r\n try {\r\n conn = DriverManager.getConnection(\"jdbc:mysql:///db3\", \"root\", \"root\");\r\n //2. 通过连接对象得到语句对象\r\n statement = conn.createStatement();\r\n //3. 通过语句对象发送 SQL 语句给服务器\r\n //4. 执行 SQL\r\n statement.executeUpdate(\"create table student (id int PRIMARY key auto_increment, \" +\r\n \"name varchar(20) not null, gender boolean, birthday date)\");\r\n //5. 返回影响行数(DDL 没有返回值)\r\n System.out.println(\"创建表成功\");\r\n } catch (SQLException e) {\r\n e.printStackTrace();\r\n }\r\n //6. 释放资源\r\n finally {\r\n //关闭之前要先判断\r\n if (statement != null) {\r\n try {\r\n statement.close();\r\n } catch (SQLException e) {\r\n e.printStackTrace();\r\n }\r\n }\r\n if (conn != null) {\r\n try {\r\n conn.close();\r\n } catch (SQLException e) {\r\n e.printStackTrace();\r\n }\r\n }\r\n }\r\n }\r\n}\r\n```\r\n\r\n\r\n\r\n### 4.6-执行DML\r\n\r\n> **需求**\r\n\r\n向学生表中添加 4 条记录,主键是自动增长 \r\n\r\n\r\n\r\n\r\n> **步骤**\r\n\r\n1. 创建连接对象\r\n2. 创建 Statement 语句对象\r\n3. 执行 SQL 语句:executeUpdate(sql)\r\n4. 返回影响的行数\r\n5. 释放资源 \r\n\r\n> **代码**\r\n\r\n```java\r\nimport java.sql.Connection;\r\nimport java.sql.DriverManager;\r\nimport java.sql.SQLException;\r\nimport java.sql.Statement;\r\n/**\r\n* 向学生表中添加 4 条记录,主键是自动增长\r\n*/\r\npublic class Demo5DML {\r\n public static void main(String[] args) throws SQLException {\r\n // 1) 创建连接对象\r\n Connection connection = DriverManager.getConnection(\"jdbc:mysql:///day24\", \"root\",\r\n \"root\");\r\n // 2) 创建 Statement 语句对象\r\n Statement statement = connection.createStatement();\r\n // 3) 执行 SQL 语句:executeUpdate(sql)\r\n int count = 0;\r\n // 4) 返回影响的行数\r\n count += statement.executeUpdate(\"insert into student values(null, \'孙悟空\', 1, \'1993-03-\r\n 24\')\");\r\n count += statement.executeUpdate(\"insert into student values(null, \'白骨精\', 0, \'1995-03-\r\n 24\')\");\r\n count += statement.executeUpdate(\"insert into student values(null, \'猪八戒\', 1, \'1903-03-\r\n 24\')\");\r\n count += statement.executeUpdate(\"insert into student values(null, \'嫦娥\', 0, \'1993-03-\r\n 11\')\");\r\n System.out.println(\"插入了\" + count + \"条记录\");\r\n // 5) 释放资源\r\n statement.close();\r\n connection.close();\r\n }\r\n}\r\n\r\n```\r\n\r\n\r\n\r\n### 4.7-执行DQL\r\n\r\n> **ResultSet 接口**\r\n\r\n作用:封装数据库查询的结果集,对结果集进行遍历,取出每一条记录。 \r\n\r\n\r\n\r\n\r\n**接口中的方法**:\r\n\r\n\r\n\r\n\r\n**其他方法**\r\n\r\n\r\n\r\n\r\n> **常用的数据类型转换表**\r\n\r\n\r\n\r\n\r\njava.sql.Date、Time、Timestamp(时间戳),三个共同父类是:java.util.Date \r\n\r\n> **需求**\r\n\r\n确保数据库中有 3 条以上的记录,查询所有的学员信息 \r\n\r\n> **步骤**\r\n\r\n1. 得到连接对象 \r\n2. 得到语句对象 \r\n3. 执行 SQL 语句得到结果集 ResultSet 对象 \r\n4. 循环遍历取出每一条记录 \r\n5. 输出的控制台上 \r\n6. 释放资源 \r\n\r\n> **代码**\r\n\r\n```java\r\nimport java.sql.*;\r\n/**\r\n* 查询所有的学生信息\r\n*/\r\npublic class Demo6DQL {\r\n public static void main(String[] args) throws SQLException {\r\n //1) 得到连接对象\r\n Connection connection =\r\n DriverManager.getConnection(\"jdbc:mysql://localhost:3306/day24\",\"root\",\"root\");\r\n //2) 得到语句对象\r\n Statement statement = connection.createStatement();\r\n //3) 执行 SQL 语句得到结果集 ResultSet 对象\r\n ResultSet rs = statement.executeQuery(\"select * from student\");\r\n //4) 循环遍历取出每一条记录\r\n while(rs.next()) {\r\n int id = rs.getInt(\"id\");\r\n String name = rs.getString(\"name\");\r\n boolean gender = rs.getBoolean(\"gender\");\r\n Date birthday = rs.getDate(\"birthday\");\r\n //5) 输出的控制台上\r\n System.out.println(\"编号:\" + id + \", 姓名:\" + name + \", 性别:\" + gender + \", 生日:\" +\r\n birthday);\r\n }\r\n //6) 释放资源\r\n rs.close();\r\n statement.close();\r\n connection.close();\r\n }\r\n}\r\n\r\n```\r\n\r\n> **结果**\r\n\r\n\r\n\r\n\r\n> **ResultSet使用注意事项**\r\n\r\n1. 如果光标在第一行之前,使用 rs.getXX()获取列值,报错:Before start of result set \r\n2. 如果光标在最后一行之后,使用 rs.getXX()获取列值,报错:After end of result set \r\n3. 使用完毕以后要关闭结果集 ResultSet,再关闭 Statement,再关闭 Connection \r\n\r\n\r\n\r\n## 第五章:数据库工具类 JdbcUtils\r\n\r\n如果一个功能经常要用到,我们建议把这个功能做成一个工具类,可以在不同的地方重用。 \r\n\r\n> **需求**\r\n\r\n上面写的代码中出现了很多重复的代码,可以把这些公共代码抽取出来 \r\n\r\n> **代码实现**\r\n\r\n1) 可以把几个字符串定义成常量:用户名,密码,URL,驱动类 \r\n\r\n2) 得到数据库的连接:getConnection() \r\n\r\n3) 关闭所有打开的资源: `close(Connection conn, Statement stmt)`,`close(Connection conn, Statement stmt, ResultSet rs)` \r\n\r\n```java\r\nimport java.sql.*;\r\n/**\r\n* 访问数据库的工具类\r\n*/\r\npublic class JdbcUtils {\r\n //可以把几个字符串定义成常量:用户名,密码,URL,驱动类\r\n private static final String USER = \"root\";\r\n private static final String PWD = \"root\";\r\n private static final String URL = \"jdbc:mysql://localhost:3306/day24\";\r\n private static final String DRIVER= \"com.mysql.jdbc.Driver\";\r\n /**\r\n * 注册驱动\r\n */\r\n static {\r\n try {\r\n Class.forName(DRIVER);\r\n } catch (ClassNotFoundException e) {\r\n e.printStackTrace();\r\n }\r\n }\r\n /**\r\n * 得到数据库的连接\r\n */\r\n public static Connection getConnection() throws SQLException {\r\n return DriverManager.getConnection(URL,USER,PWD);\r\n }\r\n /**\r\n * 关闭所有打开的资源\r\n */\r\n public static void close(Connection conn, Statement stmt) {\r\n if (stmt!=null) {\r\n try {\r\n stmt.close();\r\n } catch (SQLException e) {\r\n e.printStackTrace();\r\n }\r\n }\r\n if (conn!=null) {\r\n try {\r\n conn.close();\r\n } catch (SQLException e) {\r\n e.printStackTrace();\r\n }\r\n }\r\n }\r\n /**\r\n * 关闭所有打开的资源\r\n */\r\n public static void close(Connection conn, Statement stmt, ResultSet rs) {\r\n if (rs!=null) {\r\n try {\r\n rs.close();\r\n } catch (SQLException e) {\r\n e.printStackTrace();\r\n }\r\n }\r\n close(conn, stmt);\r\n }\r\n}\r\n\r\n```\r\n\r\n\r\n\r\n## 第六章:用户登录案例\r\n\r\n### 6.1-需求\r\n\r\n1) 有一张用户表 \r\n\r\n2) 添加几条用户记录\r\n\r\n3) 使用 Statement 字符串拼接的方式实现用户的登录, 用户在控制台上输入用户名和密码。 \r\n\r\n```mysql\r\ncreate table user (\r\n id int primary key auto_increment,\r\n name varchar(20),\r\n password varchar(20)\r\n)\r\ninsert into user values (null,\'jack\',\'123\'),(null,\'rose\',\'456\');\r\n-- 登录, SQL 中大小写不敏感\r\nselect * from user where name=\'JACK\' and password=\'123\';\r\n-- 登录失败\r\nselect * from user where name=\'JACK\' and password=\'333\';\r\n```\r\n\r\n\r\n\r\n### 6.2-步骤\r\n\r\n1. 得到用户从控制台上输入的用户名和密码来查询数据库 \r\n2. 写一个登录的方法 \r\n + 通过工具类得到连接 \r\n + 创建语句对象,使用拼接字符串的方式生成 SQL 语句 \r\n + 查询数据库,如果有记录则表示登录成功,否则登录失败 \r\n + 释放资源 \r\n\r\n```java\r\nimport com.it.utils.JdbcUtils;\r\nimport javax.xml.transform.Result;\r\nimport java.sql.Connection;\r\nimport java.sql.ResultSet;\r\nimport java.sql.SQLException;\r\nimport java.sql.Statement;\r\nimport java.util.Scanner;\r\npublic class Demo7Login {\r\n //从控制台上输入的用户名和密码\r\n public static void main(String[] args) {\r\n Scanner sc = new Scanner(System.in);\r\n System.out.println(\"请输入用户名:\");\r\n String name = sc.nextLine();\r\n System.out.println(\"请输入密码:\");\r\n String password = sc.nextLine();\r\n login(name, password);\r\n }\r\n /**\r\n * 登录的方法\r\n */\r\n public static void login(String name, String password) {\r\n //a) 通过工具类得到连接\r\n Connection connection = null;\r\n Statement statement = null;\r\n ResultSet rs = null;\r\n try {\r\n connection = JdbcUtils.getConnection();\r\n //b) 创建语句对象,使用拼接字符串的方式生成 SQL 语句\r\n statement = connection.createStatement();\r\n //c) 查询数据库,如果有记录则表示登录成功,否则登录失败\r\n String sql = \"select * from user where name=\'\" + name + \"\' and password=\'\" + password\r\n + \"\'\";\r\n System.out.println(sql);\r\n rs = statement.executeQuery(sql);\r\n if (rs.next()) {\r\n System.out.println(\"登录成功,欢迎您:\" + name);\r\n } else {\r\n System.out.println(\"登录失败\");\r\n }\r\n } catch (SQLException e) {\r\n e.printStackTrace();\r\n } finally {\r\n //d) 释放资源\r\n JdbcUtils.close(connection, statement, rs);\r\n }\r\n }\r\n}\r\n```\r\n\r\n\r\n\r\n### 6.3-SQL 注入问题 \r\n\r\n> **现象**\r\n\r\n当我们输入以下密码,我们发现我们账号和密码都不对竟然登录成功了 \r\n\r\n```\r\n请输入用户名:\r\nnewboy\r\n请输入密码:\r\na\' or \'1\'=\'1\r\nselect * from user where name=\'newboy\' and password=\'a\' or \'1\'=\'1\'\r\n登录成功,欢迎您:newboy\r\n```\r\n\r\n> **问题分析**\r\n\r\n```mysql\r\nselect * from user where name=\'newboy\' and password=\'a\' or \'1\'=\'1\'\r\n-- name=\'newboy\' and password=\'a\' 为假\r\n-- \'1\'=\'1\' 真\r\n-- 相当于\r\nselect * from user where true; -- 查询了所有记录\r\n\r\n```\r\n\r\n我们让用户输入的密码和 SQL 语句进行字符串拼接。用户输入的内容作为了 SQL 语句语法的一部分,改变了 原有 SQL 真正的意义,以上问题称为 SQL 注入。要解决 SQL 注入就不能让用户输入的密码和我们的 SQL 语句进 行简单的字符串拼接。 \r\n\r\n\r\n\r\n## 第七章:PreparedStatement 接口 \r\n\r\n### 7.1-继承结构与作用 \r\n\r\n\r\n\r\n\r\nPreparedStatement 是 Statement 接口的子接口,继承于父接口中所有的方法。它是一个预编译的 SQL 语句 \r\n\r\n\r\n\r\n### 7.2-PreparedSatement 的执行原理 \r\n\r\n\r\n\r\n\r\n1) 因为有预先编译的功能,提高 SQL 的执行效率。 \r\n\r\n2) 可以有效的防止 SQL 注入的问题,安全性更高。 \r\n\r\n\r\n\r\n### 7.3-PreparedStatement 对象\r\n\r\n> **创建**\r\n\r\n\r\n\r\n\r\n> **常用方法**\r\n\r\n\r\n\r\n\r\n> **作用**\r\n\r\n1. prepareStatement()会先将 SQL 语句发送给数据库预编译。PreparedStatement 会引用着预编译后的结果。 可以多次传入不同的参数给 PreparedStatement 对象并执行。减少 SQL 编译次数,提高效率。 \r\n2. 安全性更高,没有 SQL 注入的隐患。 \r\n3. 提高了程序的可读性 \r\n\r\n\r\n\r\n### 7.4-使用 PreparedStatement 的步骤 \r\n\r\n1) 编写 SQL 语句,未知内容使用?占位:`\"SELECT * FROM user WHERE name=? AND password=?\"`; \r\n\r\n2) 获得 PreparedStatement 对象 \r\n\r\n3) 设置实际参数:setXxx(占位符的位置, 真实的值) \r\n\r\n4) 执行参数化 SQL 语句 \r\n\r\n5) 关闭资源 \r\n\r\n\r\n\r\n\r\n使用 PreparedStatement 改写上面的登录程序,看有没有 SQL 注入的情况 \r\n\r\n```java\r\nimport com.it.utils.JdbcUtils;\r\nimport java.sql.Connection;\r\nimport java.sql.PreparedStatement;\r\nimport java.sql.ResultSet;\r\nimport java.sql.SQLException;\r\nimport java.util.Scanner;\r\n/**\r\n* 使用 PreparedStatement\r\n*/\r\npublic class Demo8Login {\r\n //从控制台上输入的用户名和密码\r\n public static void main(String[] args) throws SQLException {\r\n Scanner sc = new Scanner(System.in);\r\n System.out.println(\"请输入用户名:\");\r\n String name = sc.nextLine();\r\n System.out.println(\"请输入密码:\");\r\n String password = sc.nextLine();\r\n login(name, password);\r\n }\r\n /**\r\n * 登录的方法\r\n * @param name\r\n * @param password\r\n */\r\n private static void login(String name, String password) throws SQLException {\r\n Connection connection = JdbcUtils.getConnection();\r\n //写成登录 SQL 语句,没有单引号\r\n String sql = \"select * from user where name=? and password=?\";\r\n //得到语句对象\r\n PreparedStatement ps = connection.prepareStatement(sql);\r\n //设置参数\r\n ps.setString(1, name);\r\n ps.setString(2,password);\r\n ResultSet resultSet = ps.executeQuery();\r\n if (resultSet.next()) {\r\n System.out.println(\"登录成功:\" + name);\r\n }\r\n else {\r\n System.out.println(\"登录失败\");\r\n }\r\n //释放资源,子接口直接给父接口\r\n JdbcUtils.close(connection,ps,resultSet);\r\n }\r\n}\r\n\r\n```\r\n\r\n\r\n\r\n### 7.5-表与类的关系\r\n\r\n\r\n\r\n\r\n> **案例:使用 PreparedStatement 查询一条数据,封装成一个学生 Student 对象** \r\n\r\n```java\r\nimport com.it.entity.Student;\r\nimport com.it.utils.JdbcUtils;\r\nimport java.sql.Connection;\r\nimport java.sql.PreparedStatement;\r\nimport java.sql.ResultSet;\r\nimport java.sql.SQLException;\r\npublic class Demo9Student {\r\n public static void main(String[] args) throws SQLException {\r\n //创建学生对象\r\n Student student = new Student();\r\n Connection connection = JdbcUtils.getConnection();\r\n PreparedStatement ps = connection.prepareStatement(\"select * from student where id=?\");\r\n //设置参数\r\n ps.setInt(1,2);\r\n ResultSet resultSet = ps.executeQuery();\r\n if (resultSet.next()) {\r\n //封装成一个学生对象\r\n student.setId(resultSet.getInt(\"id\"));\r\n student.setName(resultSet.getString(\"name\"));\r\n student.setGender(resultSet.getBoolean(\"gender\"));\r\n student.setBirthday(resultSet.getDate(\"birthday\"));\r\n }\r\n //释放资源\r\n JdbcUtils.close(connection,ps,resultSet);\r\n //可以数据\r\n System.out.println(student);\r\n }\r\n}\r\n\r\n```\r\n\r\n> **案例:将多条记录封装成集合 List,集合中每个元素是一个 JavaBean 实体类** \r\n\r\n需求: 查询所有的学生类,封装成 `List<student>`返回 \r\n\r\n```java\r\nimport com.it.entity.Student;\r\nimport com.it.utils.JdbcUtils;\r\nimport java.sql.Connection;\r\nimport java.sql.PreparedStatement;\r\nimport java.sql.ResultSet;\r\nimport java.sql.SQLException;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\npublic class Demo10List {\r\n public static void main(String[] args) throws SQLException {\r\n //创建一个集合\r\n List<Student> students = new ArrayList<>();\r\n Connection connection = JdbcUtils.getConnection();\r\n PreparedStatement ps = connection.prepareStatement(\"select * from student\");\r\n //没有参数替换\r\n ResultSet resultSet = ps.executeQuery();\r\n while(resultSet.next()) {\r\n //每次循环是一个学生对象\r\n Student student = new Student();\r\n //封装成一个学生对象\r\n student.setId(resultSet.getInt(\"id\"));\r\n student.setName(resultSet.getString(\"name\"));\r\n student.setGender(resultSet.getBoolean(\"gender\"));\r\n student.setBirthday(resultSet.getDate(\"birthday\"));\r\n //把数据放到集合中\r\n students.add(student);\r\n }\r\n //关闭连接\r\n JdbcUtils.close(connection,ps,resultSet);\r\n //使用数据\r\n for (Student stu: students) {\r\n System.out.println(stu);\r\n }\r\n }\r\n}\r\n\r\n```\r\n\r\n\r\n\r\n### 7.6-PreparedStatement 执行 DML 操作 \r\n\r\n```java\r\nimport com.it.utils.JdbcUtils;\r\nimport java.sql.Connection;\r\nimport java.sql.PreparedStatement;\r\nimport java.sql.SQLException;\r\npublic class Demo11DML {\r\n public static void main(String[] args) throws SQLException {\r\n //insert();\r\n //update();\r\n delete();\r\n }\r\n //插入记录\r\n private static void insert() throws SQLException {\r\n Connection connection = JdbcUtils.getConnection();\r\n PreparedStatement ps = connection.prepareStatement(\"insert into student\r\n values(null,?,?,?)\");\r\n ps.setString(1,\"小白龙\");\r\n ps.setBoolean(2, true);\r\n ps.setDate(3,java.sql.Date.valueOf(\"1999-11-11\"));\r\n int row = ps.executeUpdate();\r\n System.out.println(\"插入了\" + row + \"条记录\");\r\n JdbcUtils.close(connection,ps);\r\n }\r\n //更新记录: 换名字和生日\r\n private static void update() throws SQLException {\r\n Connection connection = JdbcUtils.getConnection();\r\n PreparedStatement ps = connection.prepareStatement(\"update student set name=?, birthday=?\r\n where id=?\");\r\n ps.setString(1,\"黑熊怪\");\r\n ps.setDate(2,java.sql.Date.valueOf(\"1999-03-23\"));\r\n ps.setInt(3,5);\r\n int row = ps.executeUpdate();\r\n System.out.println(\"更新\" + row + \"条记录\");\r\n JdbcUtils.close(connection,ps);\r\n }\r\n //删除记录: 删除第 5 条记录\r\n private static void delete() throws SQLException {\r\n Connection connection = JdbcUtils.getConnection();\r\n PreparedStatement ps = connection.prepareStatement(\"delete from student where id=?\");\r\n ps.setInt(1,5);\r\n int row = ps.executeUpdate();\r\n System.out.println(\"删除了\" + row + \"条记录\");\r\n JdbcUtils.close(connection,ps);\r\n }\r\n}\r\n\r\n```\r\n\r\n\r\n\r\n## 第八章:JDBC处理事务\r\n\r\n之前我们是使用 MySQL 的命令来操作事务。接下来我们使用 JDBC 来操作银行转账的事务 \r\n\r\n### 7.1-准备数据\r\n\r\n```mysql\r\nCREATE TABLE account (\r\n id INT PRIMARY KEY AUTO_INCREMENT,\r\n NAME VARCHAR(10),\r\n balance DOUBLE\r\n);\r\n-- 添加数据\r\nINSERT INTO account (NAME, balance) VALUES (\'Jack\', 1000), (\'Rose\', 1000);\r\n```\r\n\r\n\r\n\r\n###7.2-API介绍\r\n\r\n\r\n\r\n\r\n\r\n\r\n### 7.3-开发步骤\r\n\r\n1. 获取连接 \r\n2. 开启事务\r\n3. 获取到 PreparedStatement \r\n4. 使用 PreparedStatement 执行两次更新操作 \r\n5. 正常情况下提交事务 \r\n6. 出现异常回滚事务 \r\n7. 最后关闭资源 \r\n\r\n```java\r\nimport com.it.utils.JdbcUtils;\r\nimport java.sql.Connection;\r\nimport java.sql.PreparedStatement;\r\nimport java.sql.SQLException;\r\npublic class Demo12Transaction {\r\n //没有异常,提交事务,出现异常回滚事务\r\n public static void main(String[] args) {\r\n //1) 注册驱动\r\n Connection connection = null;\r\n PreparedStatement ps = null;\r\n try {\r\n //2) 获取连接\r\n connection = JdbcUtils.getConnection();\r\n //3) 开启事务\r\n connection.setAutoCommit(false);\r\n //4) 获取到 PreparedStatement\r\n //从 jack 扣钱\r\n ps = connection.prepareStatement(\"update account set balance = balance - ? where\r\n name=?\");\r\n ps.setInt(1, 500);\r\n ps.setString(2,\"Jack\");\r\n ps.executeUpdate();\r\n //出现异常\r\n System.out.println(100 / 0);\r\n //给 rose 加钱\r\n ps = connection.prepareStatement(\"update account set balance = balance + ? where\r\n name=?\");\r\n ps.setInt(1, 500);\r\n ps.setString(2,\"Rose\");\r\n ps.executeUpdate();\r\n //提交事务\r\n connection.commit();\r\n System.out.println(\"转账成功\");\r\n } catch (Exception e) {\r\n e.printStackTrace();\r\n try {\r\n //事务的回滚\r\n connection.rollback();\r\n } catch (SQLException e1) {\r\n e1.printStackTrace();\r\n }\r\n System.out.println(\"转账失败\");\r\n }\r\n finally {\r\n //7) 关闭资源\r\n JdbcUtils.close(connection,ps);\r\n }\r\n }\r\n}\r\n```\r\n\r\n\r\n\r\n## 第九章:数据库连接池\r\n\r\n### 9.1-为什么要学习数据库连接池\r\n\r\n 数据库连接是一种关键的、有限的、昂贵的资源,这一点在多用户的网页应用程序中体现得尤为突出。 \r\n **一个数据库连接对象均对应一个物理数据库连接,每次操作都打开一个物理连接,使用完都关闭连接,这样造成系统的 性能低下**。 \r\n 数据库连接池的解决方案是在应用程序启动时建立足够的数据库连接,并将这些连接组成一个连接池(简单说:在一个"池"里放了好多数据库连接对象),由应用程序动态地对池中的连接进行申请、使用和释放。\r\n 对于多于连接池中连接数的并发请求,应该在请求队列中排队等待。并且应用程序可以根据池中连接的使用率,动态增加或减少池中的连接数。 \r\n 连接池技术尽可能多地重用了消耗内存地资源,大大节省了内存,提高了服务器地服务效率,能够支持更多的客户服务。通过使用连接池,将大大提高程序运行效率,同时,我们可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。 \r\n\r\n\r\n\r\n### 9.2-什么是数据库连接池\r\n\r\n数据库连接池负责分配、管理和释放数据库连接,它允许应用程序**重复使用一个现有的数据库连接**,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。这项技术能明显提高对数据库操作的性能。 \r\n\r\n\r\n\r\n\r\n\r\n\r\n### 9.3-如何使用数据库连接池\r\n\r\nJava提供了数据库连接池接口:`DataSource` javax.sql包下的。\r\n\r\n数据库厂商实现接口,常用的有以下两种:\r\n\r\n1. `C3P0`:数据库连接池技术\r\n2. `Druid`:数据库连接池实现技术,由阿里巴巴提供的\r\n\r\n\r\n\r\n### 9.4-C3P0\r\n\r\n> **准备资料**\r\n\r\n\r\n\r\n\r\n> **下载**\r\n\r\n链接:https://pan.baidu.com/s/1XAstwgWObgRM2G1J0iLTLA \r\n提取码:450q\r\n\r\n> **使用步骤**\r\n\r\n01-导入jar包 (两个) `c3p0-0.9.5.2.jar` 和 `mchange-commons-java-0.2.12.jar` 。不要忘记导入数据库驱动jar包\r\n\r\n02-定义配置文件。\r\n\r\n+ 名称:` c3p0.properties` 或者 `c3p0-config.xml`\r\n+ 路径:直接将文件放在src目录下即可。\r\n\r\n03-创建核心对象 数据库连接池对象 ComboPooledDataSource。\r\n\r\n04-获取数据库连接对象。\r\n\r\n> **c3p0-config.xml配置文件**\r\n\r\n```xml\r\n<c3p0-config>\r\n <!-- 使用默认的配置读取连接池对象 -->\r\n <default-config>\r\n <!-- 连接参数 -->\r\n <!--驱动-->\r\n <property name=\"driverClass\">com.mysql.jdbc.Driver</property>\r\n <!--数据库地址-->\r\n <property name=\"jdbcUrl\">jdbc:mysql://localhost:3306/db2</property>\r\n <!--用户名-->\r\n <property name=\"user\">root</property>\r\n <!--密码-->\r\n <property name=\"password\">root</property>\r\n \r\n <!-- 连接池参数 -->\r\n <!--初始化对象连接个数-->\r\n <property name=\"initialPoolSize\">5</property>\r\n <!--最大个数-->\r\n <property name=\"maxPoolSize\">10</property>\r\n <!--超时时长-->\r\n <property name=\"checkoutTimeout\">3000</property>\r\n </default-config>\r\n <!--指定名称读取连接池对象-->\r\n <named-config name=\"otherc3p0\"> \r\n <!-- 连接参数 -->\r\n <property name=\"driverClass\">com.mysql.jdbc.Driver</property>\r\n <property name=\"jdbcUrl\">jdbc:mysql://localhost:3306/day25</property>\r\n <property name=\"user\">root</property>\r\n <property name=\"password\">root</property>\r\n \r\n <!-- 连接池参数 -->\r\n <property name=\"initialPoolSize\">5</property>\r\n <property name=\"maxPoolSize\">8</property>\r\n <property name=\"checkoutTimeout\">1000</property>\r\n </named-config>\r\n</c3p0-config>\r\n```\r\n\r\n> **代码演示**\r\n\r\n```java\r\npublic class Test01 {\r\n public static void main(String[] args) throws SQLException {\r\n // 创建数据库连接池对象\r\n DataSource ds = new ComboPooledDataSource();\r\n // 获取数据库连接对象\r\n Connection con = ds.getConnection();\r\n System.out.println(con);\r\n }\r\n}\r\n```\r\n\r\n\r\n\r\n### 9.5-Druid\r\n\r\n> **准备资料**\r\n\r\n\r\n\r\n\r\n> **下载**\r\n\r\n链接:https://pan.baidu.com/s/1nWIFTscCI-UODwAEaAy8NQ \r\n提取码:f43e \r\n\r\n> **使用步骤**\r\n\r\n 1. 导入jar包 druid-1.0.9.jar\r\n 2. 定义配置文件:\r\n * 是properties形式的\r\n * 可以叫任意名称,可以放在任意目录下\r\n 3. 加载配置文件。Properties\r\n 4. 获取数据库连接池对象:通过工厂来来获取 DruidDataSourceFactory\r\n 5. 获取连接:getConnection\r\n> **druid.properties配置文件**\r\n\r\n```properties\r\n# 数据库驱动\r\ndriverClassName=com.mysql.jdbc.Driver\r\n# 数据库连接地址\r\nurl=jdbc:mysql://127.0.0.1:3306/db2\r\n# 用户名\r\nusername=root\r\n# 密码\r\npassword=root\r\n# 初始化连接对象个数\r\ninitialSize=5\r\n# 最大个数\r\nmaxActive=10\r\n# 超时等待时长\r\nmaxWait=3000\r\n```\r\n\r\n> **代码**\r\n\r\n```java\r\npublic class Test01 {\r\n public static void main(String[] args) throws Exception {\r\n Properties pro = new Properties();\r\n InputStream is = Test01.class.getClassLoader().getResourceAsStream(\"druid.properties\");\r\n pro.load(is);\r\n DataSource ds = DruidDataSourceFactory.createDataSource(pro);\r\n Connection con = ds.getConnection();\r\n System.out.println(con);\r\n }\r\n}\r\n```\r\n\r\n\r\n\r\n### 9.6-DruidUtils\r\n\r\n> **DruidUtils**\r\n\r\n```java\r\npackage it.leilei.util;\r\nimport com.alibaba.druid.pool.DruidDataSourceFactory;\r\nimport javax.sql.DataSource;\r\nimport java.io.IOException;\r\nimport java.io.InputStream;\r\nimport java.sql.Connection;\r\nimport java.sql.ResultSet;\r\nimport java.sql.SQLException;\r\nimport java.sql.Statement;\r\nimport java.util.Properties;\r\npublic class DruidUtils {\r\n private static DataSource ds;\r\n private static InputStream is;\r\n static {\r\n Properties pro = new Properties();\r\n try {\r\n // 读取配置文件\r\n is = DruidUtils.class.getClassLoader().getResourceAsStream(\"druid.properties\");\r\n pro.load(is);\r\n // 创建数据库连接池对象\r\n ds = DruidDataSourceFactory.createDataSource(pro);\r\n } catch (IOException e) {\r\n e.printStackTrace();\r\n } catch (Exception e) {\r\n e.printStackTrace();\r\n }finally {\r\n if(is!=null){\r\n try {\r\n is.close();\r\n } catch (IOException e) {\r\n e.printStackTrace();\r\n }\r\n }\r\n }\r\n\r\n }\r\n // 获取Connection对象\r\n public static Connection getConnection() throws SQLException {\r\n return ds.getConnection();\r\n }\r\n // 释放资源\r\n public static void close(Statement sta, Connection con, ResultSet rs) {\r\n if(sta!=null){\r\n try {\r\n sta.close();\r\n } catch (SQLException e) {\r\n e.printStackTrace();\r\n }\r\n\r\n }\r\n if(con!=null){\r\n try {\r\n con.close();\r\n } catch (SQLException e) {\r\n e.printStackTrace();\r\n }\r\n }\r\n if(rs!=null){\r\n try {\r\n rs.close();\r\n } catch (SQLException e) {\r\n e.printStackTrace();\r\n }\r\n }\r\n }\r\n public static void close(Statement sta, Connection con){\r\n close(sta,con,null);\r\n }\r\n // 获取连接池对象\r\n public static DataSource getDataSource(){\r\n return ds;\r\n }\r\n}\r\n\r\n```\r\n\r\n> **测试**:向数据库db2中的表account中添加一条新的数据\r\n\r\n```java\r\npackage it.leilei.DruidDemo;\r\n\r\nimport it.leilei.util.DruidUtils;\r\nimport java.sql.Connection;\r\nimport java.sql.PreparedStatement;\r\nimport java.sql.SQLException;\r\npublic class Test02 {\r\n public static void main(String[] args) {\r\n Connection con = null;\r\n PreparedStatement ps = null;\r\n try {\r\n // 获取数据库连接对象\r\n con = DruidUtils.getConnection();\r\n // 定义SQL\r\n String sql = \"INSERT INTO account VALUES(NULL,?,?)\";\r\n // 创建执行SQL语句对象\r\n ps = con.prepareStatement(sql);\r\n // 给参数设置值\r\n ps.setString(1,\"小龙女\");\r\n ps.setDouble(2,8000);\r\n // 执行sql\r\n int count = ps.executeUpdate();\r\n if(count>0){\r\n System.out.println(\"添加成功\");\r\n }else {\r\n System.out.println(\"添加失败\");\r\n }\r\n } catch (SQLException e) {\r\n e.printStackTrace();\r\n }finally {\r\n DruidUtils.close(ps,con);\r\n }\r\n }\r\n}\r\n\r\n```\r\n\r\n\r\n\r\n## 第十章:Spring JDBC\r\n\r\n### 10.1-概述\r\n\r\n以上数据库连接池确实优化并简化了Java对SQL的操作,但是在针对增删改查操作时相对还是有些繁琐,在实际开发中,我们通常会使用框架中封装好的JDBC来操作数据库,可以更加简化Java对SQL的操作,提高开发效率。\r\n\r\nSpring框架对JDBC的简单封装。提供了一个JDBCTemplate对象简化JDBC的开发。\r\n\r\n\r\n\r\n### 10.2-使用Spring JDBC\r\n\r\n> **准备资料**\r\n\r\n\r\n\r\n\r\n> **下载**\r\n\r\n链接:https://pan.baidu.com/s/1JLru45Sb3Xog35U_xv7nNA \r\n提取码:73vt \r\n\r\n> **使用步骤**\r\n\r\n1. 导入jar包(lib目录下所有jar包)\r\n + commons-logging-1.2.jar\r\n + spring-beans-5.0.0.RELEASE.jar\r\n + spring-core-5.0.0.RELEASE.jar\r\n + spring-jdbc-5.0.0.RELEASE.jar\r\n + spring-tx-5.0.0.RELEASE.jar\r\n2. 创建`JdbcTemplate`对象。依赖于数据源DataSource。\r\n3. 调用JdbcTemplate的方法来完成CRUD的操作\r\n + `update()`:执行DML语句。增、删、改语句\r\n + `queryForMap()`:查询结果将结果集封装为map集合,将列名作为key,将值作为value 将这条记录封装为一个map集合\r\n + 注意:这个方法查询的结果集长度只能是1\r\n + `queryForList()`:查询结果将结果集封装为list集合\r\n + 注意:将每一条记录封装为一个Map集合,再将Map集合装载到List集合中\r\n + `query()`:查询结果,将结果封装为JavaBean对象\r\n + query的参数:RowMapper\r\n 1. 一般我们使用BeanPropertyRowMapper实现类。可以完成数据到JavaBean的自动封装\r\n 2. new BeanPropertyRowMapper`<`类型`>`(类型.class)\r\n + queryForObject:查询结果,将结果封装为对象\r\n + 一般用于聚合函数的查询\r\n\r\n\r\n\r\n> **代码演示**\r\n\r\n```java\r\npackage it.leilei.JDBCTemplateDemo;\r\nimport it.leilei.domain.Emp;\r\nimport it.leilei.util.DruidUtils;\r\nimport org.junit.Test;\r\nimport org.springframework.dao.DataAccessException;\r\nimport org.springframework.jdbc.core.BeanPropertyRowMapper;\r\nimport org.springframework.jdbc.core.JdbcTemplate;\r\nimport org.springframework.jdbc.core.ResultSetExtractor;\r\n\r\nimport java.sql.ResultSet;\r\nimport java.sql.SQLException;\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\npublic class Test01 {\r\n // 创建JdbcTemplate对象\r\n private JdbcTemplate jt = new JdbcTemplate(DruidUtils.getDataSource());\r\n /*\r\n 添加一条新的数据\r\n */\r\n @Test\r\n public void test01(){\r\n // 定义SQL\r\n String sql = \"INSERT INTO emp(NAME,gender,salary)VALUES(?,?,?)\";\r\n // 执行\r\n int i = jt.update(sql,\"张三丰\",\"男\",90000);\r\n System.out.println(i);\r\n }\r\n /*\r\n 更新一条数据\r\n */\r\n @Test\r\n public void test02(){\r\n String sql = \"update emp set salary=? where id=1\";\r\n int i = jt.update(sql,10000);\r\n System.out.println(i);\r\n }\r\n /*\r\n 删除一条数据\r\n */\r\n @Test\r\n public void test03(){\r\n String sql = \"delete from emp where id=?\";\r\n int i = jt.update(sql,8);\r\n System.out.println(i);\r\n }\r\n /*\r\n * 查询一条数据返回map集合\r\n * */\r\n @Test\r\n public void test04(){\r\n String sql = \"select * from emp where id=?\";\r\n Map<String,Object> map = jt.queryForMap(sql,1);\r\n System.out.println(map);\r\n }\r\n /*\r\n * 查询多条数据返回List集合\r\n * */\r\n @Test\r\n public void test05(){\r\n String sql = \"select * from emp where id=? or id=?\";\r\n List<Map<String, Object>> list = jt.queryForList(sql,1,2);\r\n System.out.println(list);\r\n }\r\n /*\r\n * 查询多条数据返回List<Emp>集合\r\n * */\r\n @Test\r\n public void test06(){\r\n String sql = \"select * from emp where id=? or id=?\";\r\n List<Emp> list = jt.query(sql,new BeanPropertyRowMapper<Emp>(Emp.class),1,2);\r\n System.out.println(list);\r\n }\r\n\r\n /*\r\n * 聚合函数\r\n * */\r\n @Test\r\n public void test07(){\r\n String sql = \"select count(id) from emp\";\r\n long l = jt.queryForObject(sql,Long.class);\r\n System.out.println(l);\r\n }\r\n\r\n}\r\n\r\n```',17,14,'2020-06-05 15:51:49',0,'JDBC操作MySQL'),(17,'Linux安装MySql','[TOC]\r\n\r\n## 01-卸载Linux自带MySql\r\n\r\n> **查看**\r\n\r\n查看命令:rpm -qa | grep mysql \r\n\r\n> **卸载**\r\n\r\n卸载命令:rpm -e --nodeps mysql-libs-5.1.71-1.el6.x86_64\r\n\r\n\r\n\r\n## 02-下载Linux版MySql\r\n\r\n链接:https://pan.baidu.com/s/1sgEUqQf17nbtMPVMU2llnA \r\n提取码:8vmr\r\n\r\n\r\n\r\n## 03-上传到Linux中\r\n\r\n上传工具下载即使用参照博客:[上传工具](https://www.cnblogs.com/lpl666/p/12253335.html#下载上传工具)\r\n\r\n\r\n\r\n## 04-创建MySql安装路径\r\n\r\n创建命令:mkdir /usr/local/mysql\r\n\r\n\r\n\r\n## 05-解压安装包到指定目录\r\n\r\n解压命令:tar -xvf MySQL-5.6.22-1.el6.i686.rpm-bundle.tar -C /usr/local/mysql\r\n\r\n\r\n\r\n## 06-安装依赖\r\n\r\n+ yum -y install libaio.so.1 libgcc_s.so.1 libstdc++.so.6\r\n+ yum update libstdc++-4.4.7-4.el6.x86_64\r\n\r\n\r\n\r\n## 07-安装服务端\r\n\r\n\r\n\r\n进入mysql目录,安装服务端\r\n\r\n安装命令:rpm -ivh MySQL-server-5.6.22-1.el6.i686.rpm\r\n\r\n\r\n\r\n## 08-安装客户端\r\n\r\n安装命令:rpm -ivh MySQL-client-5.6.22-1.el6.i686.rpm\r\n\r\n\r\n\r\n## 09-启动MySql服务\r\n\r\n查看状态命令:service mysql status\r\n\r\n启动服务命令:service mysql start\r\n\r\n\r\n\r\n## 10-登录MySql\r\n\r\n在安装过程中,会随机产生一个登录密码存放在/root/.mysql_secret\r\n\r\n\r\n\r\n\r\n\r\n查看密码并进行登录:msyql –u root -p\r\n\r\n修改密码:set password = password(\'123456\');\r\n\r\n\r\n\r\n## 11-设置开机自动启动mysql\r\n\r\n加入到系统服务命令:chkconfig --add mysql\r\n\r\n自动启动命令:chkconfig mysql on\r\n\r\n\r\n\r\n## 12-开启远程服务\r\n\r\n先登录mysql:\r\n\r\n执行命令:grant all privileges on *.* to \'root\' @\'%\' identified by \'123456\';\r\n\r\n 执行命令:flush privileges;\r\n\r\n\r\n\r\n## 13-设置Linux的防火墙\r\n\r\n执行命令:/sbin/iptables -I INPUT -p tcp --dport 3306 -j ACCEPT\r\n\r\n执行命令:/etc/rc.d/init.d/iptables save\r\n使用SQLyog远程连接Linux上安装的MySql\r\n\r\n',17,14,'2020-06-05 15:52:39',0,'Linux安装MySql'),(18,'JavaSE基础语法','\r\n[TOC]\r\n## 第一章:变量\r\n\r\n### 1.1-为什么需要变量?\r\n\r\n 在计算机中,程序运行需要对**数据**进行处理,而程序中的数据在运行时会**存储在内存**中,编程中需要频繁**操作**内存中的数据,为了方便操作,程序中需要**通过变量名作为标识符**来操作内存中相关的数据。\r\n\r\n\r\n\r\n\r\n\r\n\r\n### 1.2-什么是变量?\r\n\r\n 变量就是在**内存中所分配给数据的一块空间**。同时内存会为该空间指定一个内存地址。\r\n\r\n 程序中可以通过**变量名**操作内存中的数据。\r\n\r\n\r\n\r\n\r\n\r\n\r\n### 1.3-如何定义和使用变量?\r\n\r\n> **定义变量**\r\n\r\n+ 基本格式:`数据类型 变量名 = 数据;`\r\n\r\n+ 代码演示:\r\n\r\n ```java\r\n public static void main(String[] args) {\r\n /* 基本格式:数据类型 变量名 = 数据 */;\r\n int age = 10;\r\n /* 其他格式: 先定义,后赋值 */\r\n int num1;\r\n num1 = 10;\r\n /* 其他格式:批量定义赋值 */\r\n int a = 10,b = 20,c = 30;\r\n }\r\n ```\r\n\r\n> **使用变量**\r\n\r\n+ 使用方式:在程序中,直接调用变量名即可。\r\n\r\n+ 代码演示:\r\n\r\n ```java\r\n public static void main(String[] args) {\r\n /* 定义变量 */;\r\n int age = 10;\r\n /* 调用变量 */\r\n System.out.println(age); // 输出结果:10\r\n }\r\n ```\r\n\r\n+ 注意事项:\r\n\r\n + 在java方法中,若定义变量且没有赋值时,在调用变量时,编译无法通过。\r\n\r\n + 代码演示:\r\n\r\n ```java\r\n public static void main(String[] args) {\r\n /* 在方法中,定义变量,但没有赋值*/\r\n int age;\r\n System.out.println(age); // 结果:编译不通过\r\n }\r\n ```\r\n\r\n \r\n\r\n### 1.4-变量名的命名规则和规范是什么?\r\n\r\n+ 规则:必需按照此方式定义变量名,否则编译不通过\r\n 1. 变量名必须由数字、字母、下划线、$组合,但不能以数字开头。\r\n 2. 变量名不能使用关键字(如:class、int等)\r\n+ 规范:在开发中,为了便于后期维护,定义变量名时,要遵循以下规范。若不遵守,编译也可以通过,但会影响代码的阅读性\r\n 1. 变量名要按照小驼峰格式命名。变量名若有多个单词组合时,首个单词字母小写,后续单词首字母大写。如:userName\r\n 2. 变量名命名要顾名思义。\r\n 3. 变量名区分大小写。\r\n\r\n\r\n\r\n## 第二章:数据类型\r\n\r\n###2.1-数据为什么要区分类型?\r\n\r\n> **不同种类的数据可以表示不同的事物**\r\n\r\n 计算机中存储的所有内容(文本文件、音乐文件、电影文件等等),都可称为数据。\r\n\r\n 数据主要用来模拟现实事物。比如,如何描述商品的价格、人的身高呢?那就可以用数字类型的数据表示。如何描述一段文字呢(如:床前明月光)?此时就无法用数字类型的数据表示了,那就得用字符串表示。\r\n\r\n 所以,根据模拟的现实事物不同,需要用不同类型的数据表示。\r\n\r\n 另外需要注意的是,无论操作什么类型的数据,在计算机中最终都是以二进制存储的。\r\n\r\n> **数据类型的划分**\r\n\r\n在Java中,数据分为两大类:\r\n\r\n+ 基本数据类型\r\n + 数字\r\n + 布尔\r\n + 字符\r\n+ 引用数据类型\r\n + 类\r\n + 接口\r\n\r\n本篇先学习基本数据类型,后续再学习引用类型的数据。\r\n\r\n\r\n\r\n### 2.2-基本数据类型有哪些?\r\n\r\n八种基本数据类型:\r\n\r\n| 数据类型 | 关键字 | 内存占用 | 取值范围 |\r\n| :----------: | :-----: | :------: | :--------------------: |\r\n| 字节型 | byte | 1个字节 | -128~127 |\r\n| 短整型 | short | 2个字节 | -32768~32767 |\r\n| 整型 | int | 4个字节 | -2的31次方~2的31次方-1 |\r\n| 长整型 | long | 8个字节 | -2的63次方~2的63次方-1 |\r\n| 单精度浮点型 | float | 4个字节 | 1.4013E-45~3.4028E+38 |\r\n| 双精度浮点型 | double | 8个字节 | 4.9E-324~1.7977E+308 |\r\n| 字符型 | char | 2个字节 | 0-65535 |\r\n| 布尔型 | boolean | 1个字节 | true, false |\r\n\r\n+ 四类八种\r\n 1. 整数:byte/short/int/long\r\n 2. 浮点数:float/double\r\n 3. 字符:char\r\n 4. 布尔:boolean\r\n+ 注意\r\n 1. 整数默认类型是int\r\n 2. 浮点数默认类型是double\r\n\r\n\r\n\r\n### 2.3-不同类型的数据如何转换?\r\n\r\n> **自动转换(隐式)**\r\n\r\n+ 特点:不需要特殊的代码处理,会自动转换。\r\n\r\n+ 规则:针对数字和字符,范围小的类型会自动转换为范围大的类型。\r\n\r\n + 代码:\r\n\r\n ```java\r\n public static void main(String[] args) {\r\n /* 字节类型 */\r\n byte a = 10;\r\n /* 整型 */\r\n int b = a;\r\n // 结果:字节类型10会自动转换为int类型\r\n }\r\n ```\r\n\r\n> **强制转换(显式)**\r\n\r\n+ 特点:需要特殊代码处理\r\n\r\n+ 格式:(要转换的数据类型)数据\r\n\r\n+ 代码:\r\n\r\n ```java\r\n public static void main(String[] args) {\r\n /* 大范围数据无法自动转换为小范围数据 */\r\n // int a = 20;\r\n // byte b = a; \r\n // 结果:编译错误\r\n \r\n /* 强制转换 */\r\n int a = 20;\r\n byte b = (byte)a;\r\n // 结果:转换成功\r\n }\r\n ```\r\n\r\n+ 注意事项:\r\n\r\n + 事项:\r\n\r\n 1. 强制转换可能会造成数据溢出。\r\n 2. 强制转换可能会丢失精度。\r\n\r\n + 代码:\r\n\r\n ```java\r\n public static void main(String[] args) {\r\n /* 数据溢出 */\r\n int a = 130;\r\n byte b = (byte)a;\r\n System.out.println(b);\r\n // 运行结果:-126\r\n // 原因:130超出了byte表示范围\r\n \r\n /* 丢失精度 */\r\n double pai = 3.14;\r\n int c = (int)pai;\r\n System.out.println(c);\r\n // 运行结果:3\r\n // 丢失了0.14\r\n }\r\n ```\r\n\r\n\r\n\r\n## 第三章:运算符\r\n\r\n### 3.1-为什么需要运算符?\r\n\r\n 在程序中,需要频繁的运算数据,比如我们在网上购物时,需要计算总价格。\r\n\r\n 如何命令计算机计算数据呢?这时就需要运算符号表示运算。\r\n\r\n\r\n\r\n### 3.2-如何使用不同的运算符?\r\n\r\n> **算术运算符**\r\n\r\n+ 运算符号:\r\n\r\n 1. 表示加法运算:`+`\r\n 2. 表示减法运算:`-`\r\n 3. 表示乘法运算:`*`\r\n 4. 表示除法运算:`/`\r\n 5. 表示取模运算:`%`\r\n\r\n+ 代码:\r\n\r\n ```java\r\n public static void main(String[] args) {\r\n int a = 10;\r\n int b = 3;\r\n /* 加法运算 */\r\n System.out.println(a + b);\r\n // 结果:13\r\n /* 减法运算 */\r\n System.out.println(a - b);\r\n // 结果:7\r\n /* 乘法运算 */\r\n System.out.println(a * b);\r\n // 结果:30\r\n /* 除法运算 */\r\n System.out.println(a / b);\r\n // 结果:3\r\n /* 取模运算-求余数 */\r\n System.out.println(a % b);\r\n // 结果:1\r\n /* 注意:在java中,任何整数不能除以0, 在运行时会发生错误! */\r\n System.out.println(200/0);\r\n // 结果:Exception in thread \"main\" java.lang.ArithmeticException: / by zero\r\n }\r\n ```\r\n\r\n> **比较运算符**\r\n\r\n+ 运算符:\r\n\r\n + 大于:`>`\r\n + 小于:`<`\r\n + 等于:`==`\r\n + 大于等于:`>=`\r\n + 小于等于:`<=`\r\n + 不等于:`!=`\r\n\r\n+ 代码:\r\n\r\n ```java\r\n public static void main(String[] args) {\r\n int a = 10;\r\n int b = 12;\r\n /* 大于 */\r\n System.out.println(a > b);\r\n // 结果:false\r\n /* 小于 */\r\n System.out.println(a < b);\r\n // 结果:true\r\n /* 等于 */\r\n System.out.println(a == b);\r\n // 结果:false\r\n /* 大于等于 */\r\n System.out.println(a >= b);\r\n // 结果:false\r\n /* 小于等于 */\r\n System.out.println(a <= b);\r\n // 结果:true\r\n /* 不等于 */\r\n System.out.println(a != b);\r\n // 结果:true\r\n }\r\n ```\r\n\r\n+ 注意:比较运算符的运算结果是布尔值(true或false)\r\n\r\n> **逻辑运算符**\r\n\r\n+ 运算符及运算规则:运算结果始终是布尔值\r\n\r\n + 与运算:`&&`\r\n + 使用格式:`左侧条件表达式 && 右侧条件表达式`\r\n + 规则:当左右两边的表达式结果都是true时,与运算结果才是true,其他结果都是false\r\n + 或运算:`||`\r\n + 使用格式:`左侧条件表达式 || 右侧条件表达式`\r\n + 规则:当左右两边的表达式有任意一边结果是true时,或运算结果才是true,其他情况结果都是false\r\n + 非运算:`!`\r\n + 使用格式:`!条件表达式`\r\n + 规则:取反,若条件表达式结果是true时,取反结果就是false,反之就是true。\r\n\r\n+ 代码:\r\n\r\n ```java\r\n public static void main(String[] args) {\r\n int age = 18;\r\n char gender = \'女\';\r\n /* 逻辑运算与- && */\r\n System.out.println(age>=18 && gender==\'男\');\r\n // 运行结果:false。原因:右侧表达式不满足\r\n \r\n /* 逻辑运算或- || */\r\n System.out.println(age>=18 || gender==\'男\');\r\n // 运行结果:true。原因:左侧表达式满足\r\n \r\n /* 逻辑运算非- ! */\r\n System.out.println(!(age>=18));\r\n // 运行结果:false\r\n }\r\n ```\r\n\r\n> **其他运算符**\r\n\r\n+ **赋值运算符**\r\n\r\n + 运算符:`=`\r\n\r\n + 意义:可以把右侧的数据赋值给指定的变量。\r\n\r\n + 代码:\r\n\r\n ```java\r\n int age = 10; \r\n ```\r\n\r\n+ **复合赋值运算符**\r\n\r\n + 运算符:`+=`、`-=`、`/=`、`*=`、`%=`\r\n\r\n + 意义:针对变量,是算术运算符和赋值运算符的组合使用。\r\n\r\n + 代码:\r\n\r\n ```java\r\n int age = 10;\r\n age+=3; \r\n System.out.print(age); \r\n // 运行结果:13。 age+=3 拆解 age = aeg + 3\r\n ```\r\n\r\n+ **小括号**\r\n\r\n + 运算符:`(表达式)`\r\n\r\n + 意义:可以提高运算的优先级。\r\n\r\n + 代码:\r\n\r\n ```java\r\n int result = (10 + 8) * 2\r\n ```\r\n\r\n+ **自增和自减**\r\n\r\n + 运算符:`++` 、 `--`\r\n\r\n + 意义:针对变量自增1或自减1\r\n\r\n + 前置自增或自减:`++变量`或`--变量`,先让变量自身加1或减1,再参与表达式运算。\r\n + 后置自增或自减`变量++`或`变量--`,先让变量自身参与表达式运算,再自身加1或减1\r\n\r\n + 代码:\r\n\r\n ```java\r\n /* 前置自增 */\r\n int a = 10;\r\n System.out.print(++a); // 结果:11\r\n System.out.print(a); // 结果:11\r\n /* 后置自增 */\r\n int b = 10;\r\n System.out.print(b++); // 结果:10\r\n System.out.print(b); // 结果:11\r\n ```\r\n\r\n+ **三元运算符**\r\n\r\n + 格式:`条件表达式?表达式1:表达式2`\r\n\r\n + 意义:若条件表达式成立,则执行并返回表达式1的结果,反之返回表达式2的结果。\r\n\r\n + 代码:\r\n\r\n ```java\r\n int a = 10>8?10:8;\r\n System.out.print(a); // 结果:10\r\n ```\r\n\r\n \r\n\r\n## 第四章:流程控制\r\n\r\n### 4.1-为什么需要流程控制?\r\n\r\n 流程,指的是程序的执行顺序。程序默认的执行顺序是自上而下执行的。\r\n\r\n 但是,在程序中有些程序是选择执行的或是重复执行的,不同的选择会有不同的功能。比如我们玩游戏时王者荣耀时,我们在手机上选择不同英雄时,就是程序在执行不同的代码。\r\n\r\n 所以,若要控制程序执行的流程,就必须学习流程控制语句。\r\n\r\n 流程控制语句分为两大类:**分支语句** 和 **循环语句**\r\n\r\n### 4.2-如何使用流程控制?\r\n\r\n> **分支语句**\r\n\r\n1. **if语句**\r\n\r\n + 格式:\r\n\r\n ```java\r\n if(条件表达式){\r\n // 代码块\r\n }\r\n /*\r\n 若条件表达式成立(结果是true),则执行代码块中的程序。\r\n */\r\n ```\r\n\r\n + 图解执行流程:\r\n \r\n\r\n\r\n + 代码:\r\n\r\n ```java\r\n /* if语句 */\r\n int age = 19;\r\n if(age >=18 ){\r\n System.out.println(\"欢迎进入\");\r\n }\r\n // 执行结果:欢迎进入\r\n ```\r\n\r\n2. **if-else语句**\r\n\r\n + 格式:\r\n\r\n ```java\r\n if(条件表达式){\r\n // 代码块\r\n }else {\r\n // 代码块\r\n }\r\n /*\r\n 若条件表达式成立(结果是true),则执行if代码块中的程序,反之执行else代码块中的程序。\r\n */\r\n ```\r\n\r\n + 图解执行流程:\r\n\r\n \r\n\r\n\r\n + 代码:\r\n\r\n ```java\r\n /* if语句 */\r\n int age = 10;\r\n if(age >=18 ){\r\n System.out.println(\"欢迎进入\");\r\n }else {\r\n System.out.println(\"对不起,年龄不合法\");\r\n }\r\n // 执行结果:对不起,年龄不合法\r\n ```\r\n\r\n3. **if-else if...else语句**\r\n\r\n + 格式:\r\n\r\n ```java\r\n if(条件表达式){\r\n // 代码块\r\n }else if(条件表达式){\r\n // 代码块\r\n }\r\n else if(条件表达式){\r\n // 代码块\r\n }\r\n ...\r\n else {\r\n // 代码块\r\n }\r\n /*\r\n 代码从上而下依次判断。\r\n 若其中有满足条件的,则进入对于的代码块执行,后续分支不再执行。\r\n 若都不满足,则执行else中的语句。\r\n */\r\n ```\r\n\r\n + 图解执行流程:\r\n \r\n\r\n\r\n + 代码:\r\n\r\n ```java\r\n /* if-else if -...-else 语句 */\r\n int score = 80;\r\n if(score>=90){\r\n System.out.println(\"A\");\r\n }else if(score>=80){\r\n System.out.println(\"B\");\r\n }else if(score>=70){\r\n System.out.println(\"C\");\r\n }else if(score>=60){\r\n System.out.println(\"D\");\r\n }else {\r\n System.out.println(\"E\");\r\n }\r\n // 执行结果:B\r\n ```\r\n\r\n4. **switch语句**\r\n\r\n + 格式:\r\n\r\n ```java\r\n switch(表达式) { \r\n case 常量值1: \r\n 语句体1;\r\n break;\r\n case 常量值2:\r\n 语句体2;\r\n break; \r\n ...\r\n default: \r\n 语句体n+1; \r\n break;\r\n }\r\n // 首先计算出表达式的值 \r\n // 其次,和case依次比较,一旦有对应的值,就会执行相应的语句,在执行的过程中,遇到break就会结 束。\r\n // 最后,如果所有的case都和表达式的值不匹配,就会执行default语句体部分,然后程序结束掉。\r\n ```\r\n\r\n + 图解执行流程:\r\n \r\n\r\n\r\n + 代码:\r\n\r\n ```java\r\n /* switch 语句*/\r\n int num = 2;\r\n switch (num){\r\n case 1:\r\n System.out.println(\"星期一\");\r\n break;\r\n case 2:\r\n System.out.println(\"星期二\");\r\n break;\r\n case 3:\r\n System.out.println(\"星期三\");\r\n break;\r\n case 4:\r\n System.out.println(\"星期四\");\r\n break;\r\n case 5:\r\n System.out.println(\"星期五\");\r\n break;\r\n case 6:\r\n System.out.println(\"星期六\");\r\n break;\r\n case 7:\r\n System.out.println(\"星期日\");\r\n break;\r\n default:\r\n System.out.println(\"不合法\");\r\n break;\r\n }\r\n ```\r\n\r\n> **循环语句**\r\n\r\n1. **for循环**\r\n\r\n + 格式:\r\n\r\n ```java\r\n for(初始化表达式1; 布尔表达式2; 步进表达式4){ \r\n // 循环体3\r\n }\r\n /*\r\n 执行顺序:1234>234>234...2不满足为止。 \r\n 1负责完成循环变量初始化 \r\n 2负责判断是否满足循环条件,不满足则跳出循环 \r\n 3具体执行的语句 \r\n 4循环后,循环条件所涉及变量的变化情况\r\n */\r\n ```\r\n\r\n + 图解:\r\n \r\n\r\n + 代码:\r\n\r\n ```java\r\n /* for循环 */\r\n for (int i = 0; i <5 ; i++) {\r\n System.out.println(\"hello\" + i);\r\n }\r\n /*\r\n 执行结果:\r\n hello0\r\n hello1\r\n hello2\r\n hello3\r\n hello4\r\n */\r\n ```\r\n\r\n2. **while循环**\r\n\r\n + 格式:\r\n\r\n ```java\r\n 初始化表达式1 \r\n while(布尔表达式2){\r\n 循环体3\r\n 步进表达式4 \r\n }\r\n /*\r\n 执行顺序:1234>234>234...2不满足为止。\r\n 1负责完成循环变量初始化。\r\n 2负责判断是否满足循环条件,不满足则跳出循环。 \r\n 3具体执行的语句。 \r\n 4循环后,循环变量的变化情况\r\n */\r\n ```\r\n\r\n + 图解:\r\n\r\n \r\n\r\n\r\n + 代码:\r\n\r\n ```java\r\n /* while循环 */\r\n int count = 0;\r\n while(count<5){\r\n System.out.println(count);\r\n count++;\r\n }\r\n /*\r\n 执行结果:\r\n hello0\r\n hello1\r\n hello2\r\n hello3\r\n hello4\r\n */\r\n ```\r\n\r\n3. **do-while循环**\r\n\r\n + 格式:\r\n\r\n ```java\r\n 初始化表达式1 do{\r\n 循环体3\r\n 步进表达式4 \r\n }while(布尔表达式2);\r\n /*\r\n \r\n 执行顺序:134>234>234...2不满足为止。 \r\n 1负责完成循环变量初始化。 \r\n 2负责判断是否满足循环条件,不满足则跳出循环。 \r\n 3具体执行的语句\r\n 4循环后,循环变量的变化情况\r\n */\r\n ```\r\n\r\n + 图解:\r\n\r\n \r\n\r\n\r\n + 代码:\r\n\r\n ```java\r\n /* do-while循环 */\r\n int j = 0;\r\n do {\r\n System.out.println(j);\r\n j++;\r\n }while (j<5);\r\n /*\r\n 执行结果:\r\n hello0\r\n hello1\r\n hello2\r\n hello3\r\n hello4\r\n */\r\n ```\r\n\r\n4. **break和continue关键字**\r\n\r\n + **break:**终止整个循环。\r\n\r\n + **continue:**结束循环中本次执行,并继续循环下次执行。\r\n\r\n + 代码:\r\n\r\n ```java\r\n /* break 关键字 */\r\n // 如:连续吃5个包子,吃到第3个,吃饱了,后面第包子就不吃了\r\n for(int i = 1; i <=5; i++){\r\n if(i==3){\r\n System.out.println(\"吃饱了\");\r\n break;\r\n }\r\n System.out.println(\"吃第\" + i + \"个包子\");\r\n }\r\n \r\n /* continue关键字 */\r\n // 如:连续吃5个包子,吃到第3个时掉地上了,则第3个包子就不吃了,继续吃后面的包子\r\n for (int i = 1; i <=5 ; i++) {\r\n if(i==3){\r\n System.out.println(\"第\" + i+ \"包子掉地上了\");\r\n continue;\r\n }\r\n System.out.println(\"吃第\" + i + \"个包子\");\r\n \r\n }\r\n ```\r\n\r\n \r\n\r\n## 第五章:数组\r\n\r\n### 5.1-为什么要学习数组?\r\n\r\n 在程序中,有时需要一个变量来管理一组数据,此时用数组结构比较合适。\r\n\r\n### 5.2-什么是数组?\r\n\r\n 数组,就是在内存中开辟一组连续的长度固定的且有序的存储一组同类型的数据。\r\n\r\n 数组,是一种引用数据类型。\r\n\r\n+ 数组是有序的,索引或是下标是有序的,从0开始\r\n+ 同类型,数组中的数据也称为数组的元素,必须是同类型的。\r\n\r\n\r\n\r\n\r\n### 5.3-如何定义数组?\r\n\r\n+ 方式:\r\n\r\n 1. `数据类型[]变量名 = new 数据类型[数组长度]`\r\n 2. `数据类型[]变量名 = new 数据类型[]{数据1,数据2...数据n}`\r\n 3. `数据类型[]变量名 = {数据1,数据2...数据n}`\r\n\r\n+ 代码:\r\n\r\n ```java\r\n /* 方式1-定义一个长度为5的数组,默认每个位置的元素值是0 */\r\n int[]nums = new int[5];\r\n /* 方式2-定义一个长度为3,且指定位置的值 */\r\n int[]nums2 = new int[]{100,200,300};\r\n /* 方式3-方式2的简写 */\r\n int[]nums3 = {100,200,300};\r\n ```\r\n\r\n \r\n\r\n### 5.4-如何操作数组?\r\n\r\n> **操作数组内部元素**\r\n\r\n+ 读取数组内部元素:`数组名[索引]`\r\n\r\n+ 设置数组内部元素:`数组名[索引]= 值`\r\n\r\n+ 代码:\r\n\r\n ```java\r\n int[]nums3 = {100,200,300};\r\n System.out.println(nums3[1]); // 执行结果:200\r\n nums3[1] = 20;\r\n System.out.println(nums3[1]); // 执行结果:20\r\n ```\r\n\r\n> **遍历数组**\r\n\r\n+ 数组的长度:`数组名.length`\r\n\r\n+ 遍历方式:`循环`\r\n\r\n+ 代码:\r\n\r\n ```java\r\n /* 遍历数组 */\r\n for (int i = 0; i < numbers.length; i++) {\r\n System.out.println(numbers[i]); \r\n }\r\n ```\r\n\r\n \r\n\r\n### 5.5-基本数据类型和引用数据类型的区别?\r\n\r\n> **数据创建时,内存分配的区别**\r\n\r\n+ 基本数据类型:\r\n\r\n + 会在栈内存开辟一块空间存放数据。\r\n\r\n+ 引用数据类型:\r\n\r\n + 会在堆内存开辟一块空间存放数据具体信息。\r\n + 同时也会在栈内存开辟一块空间存放堆数据的引用(堆区的地址)\r\n\r\n+ 图解:\r\n\r\n \r\n\r\n\r\n> **数据传递时的区别**\r\n\r\n+ 基本数据类型是值传递(数据在栈中会克隆一份新的,两个数据互不影响)。\r\n\r\n+ 引用数据类型是引用传递(数据的引用在栈中会克隆一份新的,但两个引用指向堆区中的同一个具体数据)\r\n\r\n+ 代码:\r\n\r\n ```java\r\n public static void main(String[] args) {\r\n int a = 10;\r\n int b = a;\r\n b = 20;\r\n System.out.println(a);\r\n // 结果:10。改变b时,a不会受到影响。\r\n \r\n int[]numbers1 = {100,200,300};\r\n int[]numbers2 = numbers1;\r\n numbers2[0] = 10;\r\n System.out.println(numbers1[0]);\r\n // 结果:10。改变numbers2时,numbers1受到影响。\r\n }\r\n ```\r\n\r\n+ 图解:\r\n\r\n + 图解1:基本数据类型传值\r\n\r\n \r\n\r\n\r\n + 图解2: 引用数据类型传递引用\r\n\r\n \r\n\r\n## 第六章:方法\r\n\r\n### 6.1-什么是方法\r\n\r\n方法,在编程中也叫**函数**。\r\n\r\n方法,可以将一段代码封装在一个代码块中,代表一个功能。\r\n\r\n### 6.2 方法的定义和使用\r\n\r\n> **定义方法的两个关键**\r\n\r\n1. 返回值\r\n2. 参数列表\r\n\r\n> **定义方式**\r\n\r\n```java\r\n权限修饰符 返回值类型 方法名(参数列表){\r\n // 封装的程序\r\n // return 返回对应类型的数据;\r\n}\r\n```\r\n\r\n注意事项:\r\n\r\n1. 若没有返回值,则返回值类型定义为`void`\r\n2. 参数列表可有可无,根据需求定义。\r\n3. 方法名和变量名命名规则一样。\r\n\r\n```java\r\n// 定义无参无返回值方法\r\npublic void fn1(){\r\n System.out.print(\"你好");\r\n}\r\n// 定义有参无返回值方法\r\npublic void fn2(int num1,int num2){\r\n System.out.print(num1 + num2); \r\n}\r\n// 定义有参有返回值方法\r\npublic int fn3(int num1,int num2){\r\n return num1 + num2; \r\n}\r\n \r\n```\r\n\r\n> **调用方法**\r\n\r\n调用方式:`方法名(实参列表)`\r\n\r\n注意事项:\r\n\r\n1. 若定义时没有形参标识,不用传入实参\r\n2. 若方法有返回值,则需要定义与返回值同类型的变量接收。\r\n\r\n如调用上述定义的方法:\r\n\r\n```java\r\nfn1();\r\nfn2(100,200);\r\nint sum = fn3(100,300);\r\n```\r\n\r\n\r\n\r\n### 6.3 方法的重载\r\n\r\n在Java程序中,可以定义的多个·`重名`的`参数列表不同`(参列表的个数或类型不同)的方法。这种现象叫做方法的重载。\r\n\r\n代码如下:\r\n\r\n```java\r\npublic void fn(int num1,int num2){\r\n System.out.print(num1 + num2); \r\n}\r\n// 定义有参有返回值方法\r\npublic int fn(int num1,int num2,int num3){\r\n return num1 + num2; \r\n}\r\n```\r\n\r\n注意事项:\r\n\r\n1. 重载与方法的权限修饰符和返回值类型无关\r\n\r\n\r\n ',NULL,14,'2020-06-05 15:53:31',0,'JavaSE基础语法'),(19,'JavaSE常用API','\r\n\r\n[TOC]\r\n## 第一章:API概述\r\n\r\n> **什么是API?**\r\n\r\n API(Application Programming Interface),**应用程序编程接口**。Java API是一本程序员的 字典 ,是JDK中提供给 \r\n\r\n我们使用的类的说明文档。这些类将底层的代码实现封装了起来,我们不需要关心这些类是如何实现的,只需要学 \r\n\r\n习这些类如何使用即可。所以我们可以通过查询API的方式,来学习Java提供的类,并得知如何使用它们。 \r\n\r\n> **API使用步骤**\r\n\r\n1. 打开帮助文档。 \r\n2. 点击显示,找到索引,看到输入框。 \r\n3. 你要找谁?在输入框里输入,然后回车。 \r\n4. 看包。java.lang下的类不需要导包,其他需要。 \r\n5. 看类的解释和说明。 \r\n6. 学习构造方法。\r\n7. 使用成员方法\r\n\r\n\r\n\r\n## 第二章:Scanner类\r\n\r\n> **Scanner类介绍**\r\n\r\n一个可以解析基本类型和字符串的简单文本扫描器 \r\n\r\n> **使用步骤**\r\n\r\n+ 导包:`import java.util.Scanner; `\r\n\r\n+ 构造函数:`public Scanner(InputStream source)`\r\n\r\n+ 常用方法:\r\n\r\n 1. `public String next()` 查找并返回来自此扫描器的下一个完整标记\r\n 2. `public int nextInt()`将输入信息的下一个标记扫描为一个 int\r\n\r\n+ 代码\r\n\r\n ```java\r\n public static void main(String[] args) {\r\n System.out.println(\"请输入您的姓名:\");\r\n String name = new Scanner(System.in).next();\r\n System.out.println(\"请输入您的年龄:\");\r\n int age = new Scanner(System.in).nextInt();\r\n System.out.println(\"您的姓名:\" + name);\r\n System.out.println(\"您的年龄:\" + age);\r\n }\r\n ```\r\n\r\n## 第三章:Random类\r\n\r\n> **Random类介绍**\r\n\r\n此类的实例用于生成伪随机数流。\r\n\r\n> **Random类使用步骤**\r\n\r\n+ 导包:`import java.util.Random;`\r\n\r\n+ 构造方法:`public Random() `\r\n\r\n+ 常用方法:\r\n\r\n 1. `public int nextInt(int n)`返回一个伪随机数,它是取自此随机数生成器序列的、在 0(包括)和指定值(不包括)之间均匀分布的 int 值\r\n\r\n+ 代码:\r\n\r\n ```java\r\n public static void main(String[] args) {\r\n Random random = new Random();\r\n // 获取 0-1之间的随机数\r\n double num1 = random.nextDouble();\r\n System.out.println(num1);\r\n // 获取 0-3之间的随机整数数\r\n int num2 = random.nextInt(4);\r\n System.out.println(num2);\r\n // 获取 1-3之间的随机整数\r\n int num3 = random.nextInt(3) + 1;\r\n System.out.println( num3 );\r\n }\r\n ```\r\n\r\n \r\n\r\n## 第四章:ArrayList类\r\n\r\n> **ArrayList介绍**\r\n\r\njava.util.ArrayList 是大小可变的数组的实现,存储在内的数据称为元素。此类提供一些方法来操作内部存储 \r\n\r\n的元素。 ArrayList 中可不断添加元素,其大小也自动增长。\r\n\r\n> **ArrayList的使用步骤**\r\n\r\n+ 导包:`java.util.ArrayList <E> `\r\n\r\n+ 构造方法:`public ArrayList()`\r\n\r\n+ 常用方法:\r\n\r\n 1. `public boolean add(E e) `:将指定的元素添加到此集合的尾部。\r\n 2. `public E remove(int index) `:移除此集合中指定位置上的元素。返回被删除的元素。 \r\n 3. `public E get(int index) `:返回此集合中指定位置上的元素。返回获取的元素。 \r\n 4. `public int size() `:返回此集合中的元素数。遍历集合时,可以控制索引范围,防止越界。 \r\n\r\n+ 代码:\r\n\r\n ```java\r\n public static void main(String[] args) {\r\n ArrayList<String> list = new ArrayList<>();\r\n // add\r\n list.add(\"张三\");\r\n list.add(\"李四\");\r\n list.add(\"王五\");\r\n // remove\r\n list.remove(1);\r\n // size 和 get\r\n for (int i = 0; i < list.size(); i++) {\r\n System.out.println(list.get(i));\r\n }\r\n // contains\r\n System.out.println(list.contains(\"李四\"));\r\n }\r\n ```\r\n\r\n \r\n\r\n## 第五章:String类\r\n\r\n> **概述**\r\n\r\n`java.lang.String `类代表字符串。\r\n\r\nJava程序中所有的字符串文字(例如 \"abc\" )都可以被看作是实现此类的实例。 \r\n\r\n类 String 中包括用于检查各个字符串的方法,比如用于比较字符串,搜索字符串,提取子字符串以及创建具有翻 译为大写或小写的所有字符的字符串的副本。 \r\n\r\n> **特点**\r\n\r\n1. 字符串不变:字符串的值在创建后不能被更改。\r\n\r\n ```java\r\n String s1 = \"abc\";\r\n s1 += \"d\";\r\n System.out.println(s1); // \"abcd\"\r\n // 内存中有\"abc\",\"abcd\"两个对象,s1从指向\"abc\",改变指向,指向了\"abcd\"。\r\n ```\r\n\r\n2. 因为String对象是不可变的,所以它们可以被共享。 \r\n\r\n ```java\r\n String s1 = \"abc\";\r\n String s2 = \"abc\";\r\n // 内存中只有一个\"abc\"对象被创建,同时被s1和s2共享。\r\n ```\r\n\r\n3. \"abc\" 等效于 char[] data={ \'a\' , \'b\' , \'c\' } 。 \r\n\r\n ```java\r\n 例如:\r\n String str = \"abc\";\r\n 相当于:\r\n char data[] = {\'a\', \'b\', \'c\'};\r\n String str = new String(data);\r\n // String底层是靠字符数组实现的。\r\n ```\r\n\r\n> **导包和构造方法**\r\n\r\n+ 导包:`import.lang.String`\r\n\r\n+ 构造方法:\r\n\r\n 1. `public String() `:初始化新创建的 String对象,以使其表示空字符序列。 \r\n 2. `public String(char[] value)` :通过当前参数中的字符数组来构造新的String。\r\n 3. ` public String(byte[] bytes)` :通过使用平台的默认字符集解码当前参数中的字节数组来构造新的 String。 \r\n\r\n+ 代码:\r\n\r\n ```java\r\n // 无参构造\r\n String str = new String();\r\n // 通过字符数组构造\r\n char chars[] = {\'a\', \'b\', \'c\'};\r\n String str2 = new String(chars);\r\n // 通过字节数组构造\r\n byte bytes[] = { 97, 98, 99 };\r\n String str3 = new String(bytes);\r\n // 常用方式\r\n String str4 = \"字符串\";\r\n ```\r\n\r\n \r\n\r\n> **判断功能的方法**\r\n\r\n+ 方法:\r\n\r\n 1. `public boolean equals (Object anObject)` :将此字符串与指定对象进行比较。\r\n 2. `public boolean equalsIgnoreCase (String anotherString)` :将此字符串与指定对象进行比较,忽略大小 写。 \r\n\r\n+ 代码:\r\n\r\n ```java\r\n public static void main(String[] args) {\r\n String str1 = \"hello\";\r\n String str2 = \"Hello\";\r\n System.out.println(str1.equals(str2)); // false\r\n System.out.println(str1.equalsIgnoreCase(str2)); // true\r\n }\r\n ```\r\n\r\n> **获取功能的方法**\r\n\r\n+ 方法:\r\n\r\n 1. `public int length ()` :返回此字符串的长度。 \r\n 2. `public String concat (String str)` :将指定的字符串连接到该字符串的末尾。 \r\n 3. `public char charAt (int index)` :返回指定索引处的 char值。 \r\n 4. `public int indexOf (String str)` :返回指定子字符串第一次出现在该字符串内的索引。 \r\n 5. `public String substring (int beginIndex)` :返回一个子字符串,从beginIndex开始截取字符串到字符 串结尾。 \r\n 6. `public String substring (int beginIndex, int endIndex)` :返回一个子字符串,从beginIndex到 endIndex截取字符串。含beginIndex,不含endIndex \r\n 7. `7. public boolean contains(CharSequence s)` 检测指定的子字符串是否存在。\r\n\r\n+ 代码:\r\n\r\n ```java\r\n public static void main(String[] args) {\r\n String str1 = \"abc\";\r\n String str2 = \"defg\";\r\n //1. `public int length ()` :返回此字符串的长度。\r\n System.out.println(str1.length());// 3\r\n //2. `public String concat (String str)` :将指定的字符串连接到该字符串的末尾。\r\n System.out.println(str1.concat(str2)); // abcdefg\r\n //3. `public char charAt (int index)` :返回指定索引处的 char值。\r\n System.out.println(str1.charAt(1)); // b\r\n //4. `public int indexOf (String str)` :返回指定子字符串第一次出现在该字符串内的索引。\r\n System.out.println(str1.indexOf(\"b\")); // 1\r\n System.out.println(str1.indexOf(\"j\")); // -1 不存在\r\n //5. `public String substring (int beginIndex)` :返回一个子字符串,从beginIndex开始截取字符串到字符 串结尾。\r\n System.out.println(str1.substring(1)); // bc\r\n //6. `public String substring (int beginIndex, int endIndex)` :返回一个子字符串,从beginIndex到 endIndex截取字符串。含beginIndex,不含endIndex\r\n System.out.println(str2.substring(1,3)); // ef\r\n // 7. public boolean contains(CharSequence s) 检测指定的子字符串是否存在\r\n System.out.println(str2.contains(\"ef\")); // true\r\n System.out.println(str2.contains(\"efgh\")); // false\r\n }\r\n ```\r\n\r\n> **转换功能的方法**\r\n\r\n+ 方法:\r\n\r\n 1. `public char[] toCharArray ()` :将此字符串转换为新的字符数组。 \r\n 2. `public byte[] getBytes () `:使用平台的默认字符集将该 String编码转换为新的字节数组。 \r\n 3. `public String replace (CharSequence target, CharSequence replacement`) :将与target匹配的字符串使 用replacement字符串替换。 \r\n\r\n+ 代码:\r\n\r\n ```java\r\n public static void main(String[] args) {\r\n String str1 = \"abac\";\r\n // 1. `public char[] toCharArray ()` :将此字符串转换为新的字符数组。\r\n char[]cs = str1.toCharArray();\r\n // 2. `public byte[] getBytes () `:使用平台的默认字符集将该 String编码转换为新的字节数组。\r\n byte[]bs = str1.getBytes();\r\n // 3. `public String replace (CharSequence target, CharSequence replacement`) :将与target匹配的字符串使 用replacement字符串替换。\r\n System.out.println(str1.replace(\"a\",\"A\")); // AbAc\r\n }\r\n ```\r\n\r\n> **分割功能方法**\r\n\r\n+ 方法:\r\n\r\n 1. `public String[] split(String regex)` :将此字符串按照给定的regex(规则)拆分为字符串数组。 \r\n\r\n+ 代码:\r\n\r\n ```java\r\n public static void main(String[] args) {\r\n //`public String[] split(String regex)` :将此字符串按照给定的regex(规则)拆分为字符串数组。\r\n //创建字符串对象\r\n String s = \"aa-bb-cc\";\r\n String[] strArray = s.split(\"-\"); // [\"aa\",\"bb\",\"cc\"]\r\n for(int x = 0; x < strArray.length; x++) {\r\n System.out.println(strArray[x]); // aa bb cc\r\n }\r\n \r\n }\r\n ```\r\n\r\n\r\n\r\n## 第六章:Arrays类\r\n\r\n> **概述**\r\n\r\n`java.util.Arrays` 此类包含用来操作数组的各种方法,比如排序和搜索等。其所有方法均为静态方法,调用起来 非常简单。 \r\n\r\n\r\n\r\n> **常用方法**\r\n\r\n+ 方法:\r\n\r\n 1. `public static String toString(int[] a) `:返回指定数组内容的字符串表示形式。 \r\n 2. `public static void sort(int[] a)` :对指定的 int 型数组按数字升序进行排序。 \r\n\r\n+ 代码:\r\n\r\n ```java\r\n public static void main(String[] args) {\r\n int[]nums = {100,400,200,300,600,500};\r\n // 1. `public static String toString(int[] a) `:返回指定数组内容的字符串表示形式。\r\n System.out.println(Arrays.toString(nums)); // [100, 400, 200, 300, 600, 500]\r\n // 2. `public static void sort(int[] a)` :对指定的 int 型数组按数字升序进行排序。\r\n Arrays.sort(nums);\r\n System.out.println(Arrays.toString(nums)); // [100, 200, 300, 400, 500, 600]\r\n }\r\n ```\r\n\r\n\r\n\r\n## 第七章:Math类\r\n\r\n> 概述\r\n\r\n java.lang.Math 类包含用于执行基本数学运算的方法,如初等指数、对数、平方根和三角函数。类似这样的工具 类,其所有方法均为静态方法,并且不会创建对象,调用起来非常简单。 \r\n\r\n> 方法\r\n\r\n+ 方法:\r\n\r\n 1. `public static double abs(double a)` :返回 double 值的绝对值。\r\n 2. `public static double ceil(double a)`返回大于等于参数的最小的整数。 \r\n\r\n+ 代码:\r\n\r\n ```java\r\n public static void main(String[] args) {\r\n System.out.println(Math.abs(-100)); // 100\r\n System.out.println(Math.ceil(10.1)); // 11.0\r\n System.out.println(Math.floor(10.1)); // 10.0\r\n }\r\n ```\r\n\r\n## 第八章:Object类\r\n\r\n> 概述\r\n\r\n`java.lang.Object`类是Java语言中的根类,即所有类的父类。它中描述的所有方法子类都可以使用。在对象实例化的时候,最终找的父类就是Object。\r\n\r\n如果一个类没有特别指定父类, 那么默认则继承自Object类\r\n\r\n根据JDK源代码及Object类的API文档,Object类当中包含的方法有11个。今天我们主要学习其中的2个:\r\n\r\n- `public String toString()`:返回该对象的字符串表示。\r\n- `public boolean equals(Object obj)`:指示其他某个对象是否与此对象"相等"。\r\n\r\n> toString方法\r\n\r\n+ `public String toString()`:返回该对象的字符串表示。toString方法返回该对象的字符串表示,其实该字符串内容就是对象的类型+@+内存地址值。由于toString方法返回的结果是内存地址,而在开发中,经常需要按照对象的属性得到相应的字符串表现形式,因此也需要重写它。\r\n\r\n+ 覆盖方法:\r\n\r\n ```java\r\n public class Person {\r\n private String name;\r\n private int age;\r\n private String gender;\r\n \r\n @Override\r\n public String toString() {\r\n return \"Person{\" +\r\n \"name=\'\" + name + \'\\\'\' +\r\n \", age=\" + age +\r\n \", gender=\'\" + gender + \'\\\'\' +\r\n \'}\';\r\n }\r\n }\r\n ```\r\n\r\n> equals方法\r\n\r\n+ `public boolean equals(Object obj)`:指示其他某个对象是否与此对象"相等"。调用成员方法equals并指定参数为另一个对象,则可以判断这两个对象是否是相同的。这里的"相同"有默认和自定义两种方式。\r\n\r\n+ **默认地址比较**,如果没有覆盖重写equals方法,那么Object类中默认进行`==`运算符的对象地址比较,只要不是同一个对象,结果必然为false。\r\n\r\n+ **对象内容比较**,如果希望进行对象的内容比较,即所有或指定的部分成员变量相同就判定两个对象相同,则可以覆盖重写equals方法。\r\n\r\n ```java\r\n public class Person {\r\n private String name;\r\n private int age;\r\n private String gender;\r\n \r\n @Override\r\n public String toString() {\r\n return \"Person{\" +\r\n \"name=\'\" + name + \'\\\'\' +\r\n \", age=\" + age +\r\n \", gender=\'\" + gender + \'\\\'\' +\r\n \'}\';\r\n }\r\n \r\n @Override\r\n public boolean equals(Object o) {\r\n if (this == o) return true;\r\n if (!(o instanceof Person)) return false;\r\n Person person = (Person) o;\r\n return age == person.age &&\r\n Objects.equals(name, person.name) &&\r\n Objects.equals(gender, person.gender);\r\n }\r\n \r\n @Override\r\n public int hashCode() {\r\n return Objects.hash(name, age, gender);\r\n }\r\n }\r\n \r\n ```\r\n\r\n> Objects类\r\n\r\n在刚才IDEA自动重写equals代码中,使用到了`java.util.Objects`类,那么这个类是什么呢?\r\n\r\n在**JDK7**添加了一个Objects工具类,它提供了一些方法来操作对象,它由一些静态的实用方法组成,这些方法是null-save(空指针安全的)或null-tolerant(容忍空指针的),用于计算对象的hashcode、返回对象的字符串表示形式、比较两个对象。\r\n\r\n在比较两个对象的时候,Object的equals方法容易抛出空指针异常,而Objects类中的equals方法就优化了这个问题。方法如下:\r\n\r\n- `public static boolean equals(Object a, Object b)`:判断两个对象是否相等。\r\n\r\n我们可以查看一下源码,学习一下:\r\n\r\n```java\r\npublic static boolean equals(Object a, Object b) { \r\n return (a == b) || (a != null && a.equals(b)); \r\n}\r\n```\r\n\r\n## 第九章:Date类\r\n\r\n> **Date基本使用**\r\n\r\n` java.util.Date`类 表示特定的瞬间,精确到毫秒。\r\n\r\n继续查阅Date类的描述,发现Date拥有多个构造函数,只是部分已经过时,但是其中有未过时的构造函数可以把毫秒值转成日期对象。\r\n\r\n- `public Date()`:分配Date对象并初始化此对象,以表示分配它的时间(精确到毫秒)。\r\n- `public Date(long date)`:分配Date对象并初始化此对象,以表示自从标准基准时间(称为"历元(epoch)",即1970年1月1日00:00:00 GMT)以来的指定毫秒数。\r\n- 常用方法:\r\n + `public long getTime()` 把日期对象转换成对应的时间毫秒值。\r\n\r\n代码:\r\n\r\n```java\r\n public static void main(String[] args) {\r\n // 创建当前日期对象\r\n Date date = new Date();\r\n System.out.println(date); // Sat Dec 07 17:29:13 CST 2019\r\n // 根据时间戳创建指定日期对象\r\n Date date2 = new Date(0);\r\n System.out.println(date2); // Thu Jan 01 08:00:00 CST 1970\r\n // 返回当前日期的时间戳\r\n System.out.println(date.getTime()); // 1575710983951\r\n\r\n }\r\n```\r\n\r\n\r\n\r\n> **DateFormat日期格式类**\r\n\r\n+ **概述:**`java.text.DateFormat` 是日期/时间格式化子类的抽象类,我们通过这个类可以帮我们完成日期和文本之间的转换,也就是可以在Date对象与String对象之间进行来回转换。\r\n\r\n - **格式化**:按照指定的格式,从Date对象转换为String对象。\r\n - **解析**:按照指定的格式,从String对象转换为Date对象。\r\n\r\n+ **构造方法:**由于DateFormat为抽象类,不能直接使用,所以需要常用的子类`java.text.SimpleDateFormat`。这个类需要一个模式(格式)来指定格式化或解析的标准。构造方法为:\r\n\r\n - `public SimpleDateFormat(String pattern)`:用给定的模式和默认语言环境的日期格式符号构造SimpleDateFormat。参数pattern是一个字符串,代表日期时间的自定义格式。\r\n\r\n+ **格式规则**常用的格式规则为:\r\n\r\n | 标识字母(区分大小写) | 含义 |\r\n | ---------------------- | ---- |\r\n | y | 年 |\r\n | M | 月 |\r\n | d | 日 |\r\n | h | 时 |\r\n | m | 分 |\r\n | s | 秒 |\r\n\r\n+ **常用方法**\r\n\r\n - `public String format(Date date)`:将Date对象格式化为字符串。\r\n - `public Date parse(String source)`:将字符串解析为Date对象。\r\n\r\n+ **代码**:\r\n\r\n ```java\r\n public static void main(String[] args) throws ParseException {\r\n // 创建当前日期对象\r\n Date date = new Date();\r\n // 创建格式化对象\r\n DateFormat format = new SimpleDateFormat(\"yyyy-MM-dd hh:mm:ss\");\r\n String dateStr = format.format(date);\r\n System.out.println(dateStr); // 2019年12月07日 05:37:53\r\n // 把格式化日期转换为日期对象\r\n Date date2 = format.parse(\"2100-12-12 12:12:12\");\r\n System.out.println(date2); // Sun Dec 12 00:12:12 CST 2100\r\n }\r\n ```\r\n\r\n> **Calendar类**\r\n\r\n+ 概述:`java.util.Calendar`是日历类,在Date后出现,替换掉了许多Date的方法。该类将所有可能用到的时间信息封装为静态成员变量,方便获取。日历类就是方便获取各个时间属性的。\r\n\r\n+ 获取Calendar对象:Calendar为抽象类,由于语言敏感性,Calendar类在创建对象时并非直接创建,而是通过静态方法创建,返回子类对象\r\n\r\n + `public static Calendar getInstance()`:使用默认时区和语言环境获得一个日历\r\n\r\n+ 常用方法:\r\n\r\n - `public int get(int field)`:返回给定日历字段的值。\r\n - `public void set(int field, int value)`:将给定的日历字段设置为给定值。\r\n - `public abstract void add(int field, int amount)`:根据日历的规则,为给定的日历字段添加或减去指定的时间量。\r\n - `public Date getTime()`:返回一个表示此Calendar时间值(从历元到现在的毫秒偏移量)的Date对象。\r\n\r\n+ field:Calendar类中提供很多成员常量,代表给定的日历字段:\r\n\r\n | 字段值 | 含义 |\r\n | ------------ | ------------------------------------- |\r\n | YEAR | 年 |\r\n | MONTH | 月(从0开始,可以+1使用) |\r\n | DAY_OF_MONTH | 月中的天(几号) |\r\n | HOUR | 时(12小时制) |\r\n | HOUR_OF_DAY | 时(24小时制) |\r\n | MINUTE | 分 |\r\n | SECOND | 秒 |\r\n | DAY_OF_WEEK | 周中的天(周几,周日为1,可以-1使用) |\r\n\r\n+ 代码:\r\n\r\n ```java\r\n public static void main(String[] args) {\r\n // 获取日历对象\r\n Calendar calendar = Calendar.getInstance();\r\n // 获取当前日期的部分数据\r\n System.out.println(calendar.get(Calendar.YEAR));\r\n System.out.println(calendar.get(Calendar.MONTH));\r\n System.out.println(calendar.get(Calendar.DAY_OF_MONTH));\r\n System.out.println(calendar.get(Calendar.HOUR_OF_DAY));\r\n System.out.println(calendar.get(Calendar.MINUTE));\r\n System.out.println(calendar.get(Calendar.SECOND));\r\n System.out.println(calendar.get(Calendar.DAY_OF_WEEK));\r\n \r\n }\r\n ```\r\n\r\n## 第十章:System类\r\n\r\n> 概述\r\n\r\n`java.lang.System`类中提供了大量的静态方法,可以获取与系统相关的信息或系统级操作,在System类的API文档中,常用的方法有:\r\n\r\n- `public static long currentTimeMillis()`:返回以毫秒为单位的当前时间。\r\n\r\n- `public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)`:将数组中指定的数据拷贝到另一个数组中。\r\n\r\n + 数组的拷贝动作是系统级的,性能很高。System.arraycopy方法具有5个参数,含义分别为:\r\n\r\n | 参数序号 | 参数名称 | 参数类型 | 参数含义 |\r\n | -------- | -------- | -------- | -------------------- |\r\n | 1 | src | Object | 源数组 |\r\n | 2 | srcPos | int | 源数组索引起始位置 |\r\n | 3 | dest | Object | 目标数组 |\r\n | 4 | destPos | int | 目标数组索引起始位置 |\r\n | 5 | length | int | 复制元素个数 |\r\n\r\n> 代码\r\n\r\n```java\r\n public static void main(String[] args) {\r\n long startTime = System.currentTimeMillis();\r\n int[]nums1 = {1,2,3,4,5};\r\n int[]nums2={88,99,188};\r\n System.arraycopy(nums1,1,nums2,0,2);\r\n System.out.println(Arrays.toString(nums2)); // [2, 3, 188]\r\n long endTime = System.currentTimeMillis();\r\n System.out.println(\"共耗时:\" + (endTime-startTime) + \"毫秒\");\r\n }\r\n```\r\n\r\n\r\n\r\n## 第十一章:StringBuilder类\r\n\r\n> 字符串拼接问题\r\n\r\n由于String类的对象内容不可改变,所以每当进行字符串拼接时,总是会在内存中创建一个新的对象,既耗时,又浪费空间。为了解决这一问题,可以使用`java.lang.StringBuilder`类。\r\n\r\n> 概述\r\n\r\n查阅`java.lang.StringBuilder`的API,StringBuilder又称为可变字符序列,它是一个类似于 String 的字符串缓冲区,通过某些方法调用可以改变该序列的长度和内容。\r\n\r\n原来StringBuilder是个字符串的缓冲区,即它是一个容器,容器中可以装很多字符串。并且能够对其中的字符串进行各种操作。\r\n\r\n它的内部拥有一个数组用来存放字符串内容,进行字符串拼接时,直接在数组中加入新内容。StringBuilder会自动维护数组的扩容。\r\n\r\n> 构造方法\r\n\r\n- `public StringBuilder()`:构造一个空的StringBuilder容器。\r\n- `public StringBuilder(String str)`:构造一个StringBuilder容器,并将字符串添加进去。\r\n\r\n> 常用方法\r\n\r\n- `public StringBuilder append(...)`:添加任意类型数据的字符串形式,并返回当前对象自身。\r\n- `public String toString()`:将当前StringBuilder对象转换为String对象。\r\n\r\n> 代码:\r\n\r\n```java\r\npublic static void main(String[] args) {\r\n StringBuilder builder = new StringBuilder();\r\n for (int i = 0; i < 10000; i++) {\r\n builder.append(i);\r\n }\r\n System.out.println(builder.toString());\r\n }\r\n```\r\n\r\n\r\n\r\n## 第十二章:包装类\r\n\r\n> **概述**\r\n\r\nJava提供了两个类型系统,基本类型与引用类型,使用基本类型在于效率,然而很多情况,会创建对象使用,因为对象可以做更多的功能,如果想要我们的基本类型像对象一样操作,就可以使用基本类型对应的包装类\r\n\r\n如下:\r\n\r\n| 基本类型 | 对应的包装类(位于java.lang包中) |\r\n| -------- | --------------------------------- |\r\n| byte | Byte |\r\n| short | Short |\r\n| int | **Integer** |\r\n| long | Long |\r\n| float | Float |\r\n| double | Double |\r\n| char | **Character** |\r\n| boolean | Boolean |\r\n\r\n> **装箱与拆箱**\r\n\r\n基本类型与对应的包装类对象之间,来回转换的过程称为"装箱"与"拆箱":\r\n\r\n- **装箱**:从基本类型转换为对应的包装类对象。\r\n- **拆箱**:从包装类对象转换为对应的基本类型。\r\n\r\n用Integer与 int为例:(看懂代码即可)\r\n\r\n基本数值---->包装对象\r\n\r\n```java\r\nInteger i = new Integer(4);//使用构造函数函数\r\nInteger iii = Integer.valueOf(4);//使用包装类中的valueOf方法\r\n```\r\n\r\n包装对象---->基本数值\r\n\r\n```java\r\nint num = i.intValue();\r\n```\r\n\r\n> **自动装箱与自动拆箱**\r\n\r\n由于我们经常要做基本类型与包装类之间的转换,从Java 5(JDK 1.5)开始,基本类型与包装类的装箱、拆箱动作可以自动完成。例如:\r\n\r\n```java\r\nInteger i = 4;//自动装箱。相当于Integer i = Integer.valueOf(4);\r\ni = i + 5;//等号右边:将i对象转成基本数值(自动拆箱) i.intValue() + 5;\r\n//加法运算完成后,再次装箱,把基本数值转成对象。\r\n```\r\n\r\n> **基本数据类型和字符串互相转换**\r\n\r\n+ 基本数据类型转换字符串:`基本类型的数据 + \"\"`\r\n\r\n+ 字符串转基本数据类型:除了Character类之外,其他所有包装类都具有parseXxx静态方法可以将字符串参数转换为对应的基本类型:\r\n\r\n - `public static byte parseByte(String s)`:将字符串参数转换为对应的byte基本类型。\r\n - `public static short parseShort(String s)`:将字符串参数转换为对应的short基本类型。\r\n - `public static int parseInt(String s)`:将字符串参数转换为对应的int基本类型。\r\n - `public static long parseLong(String s)`:将字符串参数转换为对应的long基本类型。\r\n - `public static float parseFloat(String s)`:将字符串参数转换为对应的float基本类型。\r\n - `public static double parseDouble(String s)`:将字符串参数转换为对应的double基本类型。\r\n - `public static boolean parseBoolean(String s)`:将字符串参数转换为对应的boolean基本类型。\r\n\r\n 代码使用(仅以Integer类的静态方法parseXxx为例)如:\r\n\r\n ```java\r\n public class Demo18WrapperParse {\r\n public static void main(String[] args) {\r\n int num = Integer.parseInt(\"100\");\r\n }\r\n }\r\n ```\r\n\r\n > 注意:如果字符串参数的内容无法正确转换为对应的基本类型,则会抛出`java.lang.NumberFormatException`异常。',NULL,14,'2020-06-05 15:54:51',0,'JavaSE常用API'),(20,'JavaSE面向对象','\r\n[TOC]\r\n\r\n## 第一章:面向对象-概述\r\n\r\n### 1.1-什么是面向对象?\r\n\r\n> **概述**\r\n\r\n+ 面向对象是一种程序设计思想。\r\n+ 程序设计的目的是为了用编程解决现实问题,而面向对象编程的关键在于强调**对象**,用**什么对象来完成什么功能**。\r\n+ 在面向对象程序设计思想中,任何的事物都可以看作对象,也就是我们经常所说的"万物皆对象"(一部手机、一个人、一条狗等等都可以看做是对象)。\r\n+ 对象是由**属性**和**方法**组成的。\r\n + 属性:对象的状态。\r\n + 方法:对象的行为或功能。\r\n + 图解对象(比如:一个人)\r\n \r\n\r\n\r\n> **面向对象编程思想**\r\n\r\n 面向对象编程思想就是,将真实世界的复杂关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟。(总而言之,就是找对象做事儿)\r\n\r\n+ 真实世界的复杂关系:`明确需求`\r\n+ 抽象为一个个对象:`划分对象`\r\n+ 对象之间的分工与合作:\r\n + 分工:`对象的功能 `\r\n + 合作: `对象之间的关系`\r\n\r\n\r\n\r\n\r\n> **面向对象编程的特点**\r\n\r\n+ `封装`:封装(encapsulation)就是隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读取和修改的访问级别 。\r\n+ `继承`: 继承( inheritance)指的是 子类与父类的关系,子类可以继承父类中的属性和方法\r\n+ `多态`: 多态(Polymorphism)按字面的意思就是"多种状态"。在面向对象语言中,接口的多种不同的实现方式即为多态。 \r\n\r\n关于对象的三大特征,后续会逐渐展开讲解。\r\n\r\n\r\n\r\n### 1.2-如何创建和使用对象?\r\n\r\n上述讲到,要面向对象编程,就得找对象做事儿。\r\n\r\n找对象的前提,得先有对象。\r\n\r\n现在没有对象怎么办?\r\n\r\n那就得先创建对象,创建对象的规则,需要先知道如何定义**类**\r\n\r\n> **类**\r\n\r\n+ 类class,是**对现实生活中一类具有共同特征的事物的抽象** 。\r\n\r\n比如,"狗","狗"是类,不是一个具体的对象,而是一个抽象的名称或概念,泛指世界上"会汪汪叫"、"对主人忠诚"、\"会看家护院\"、"爱啃骨头"、"有四条腿、两只眼睛、一个鼻子、两只耳朵等"具有这一系列特征的物种的称呼。\r\n\r\n所以,类,简而言之就是一个**抽象的概念**;\r\n\r\n> **对象**\r\n\r\n+ 对象,具有**属性**和**方法**的一个具体的实例。\r\n\r\n> **类与对象的关系**\r\n\r\n+ 类是对象的模板,可以用来描述对象的属性和方法。\r\n+ 对象是类的一个具体实例。对象属于类。\r\n\r\n\r\n\r\n\r\n> **定义类**\r\n\r\n+ 关键字:`class`\r\n\r\n+ 定义格式:\r\n\r\n ```java\r\n public class 类名 {\r\n // 成员变量-描述对象的属性\r\n // 成员方法-描述对象的方法\r\n }\r\n ```\r\n\r\n+ 代码演示:\r\n\r\n ```java\r\n /* 定义类 */\r\n public class Dog {\r\n // 成员变量\r\n String name;\r\n String type;\r\n String color;\r\n // 成员方法\r\n public void eat(){\r\n System.out.println(\"我喜欢啃骨头\");\r\n }\r\n public void sound(){\r\n System.out.println(\"汪汪~~~\");\r\n }\r\n }\r\n ```\r\n\r\n注意:类名单词的首字母要大写(比如:`Dog`)\r\n\r\n> **创建和使用对象**\r\n\r\n+ 关键字:`new`\r\n\r\n+ 创建格式:`类名 对象名 = new 类名()`\r\n\r\n+ 使用对象:\r\n\r\n + 操作成员变量:\r\n + 获取:`对象名.成员变量`\r\n + 设置:`对象名.成员变量=值`\r\n + 操作成员方法:\r\n + 调用方法:`对象名.成员方法()`\r\n\r\n+ 代码:\r\n\r\n ```java\r\n public class DogTest {\r\n public static void main(String[] args) {\r\n // 创建对象\r\n Dog wangCai = new Dog();\r\n // 设置对象的属性-成员变量\r\n wangCai.name = \"旺财\";\r\n wangCai.color = \"黄色\";\r\n wangCai.type = \"中华田园犬\";\r\n // 读取对象的属性\r\n System.out.println(\"这一条狗的名字:\" + wangCai.name);\r\n System.out.println(\"这一条狗狗的毛色:\" + wangCai.color);\r\n System.out.println(\"这一条狗狗的种族:\" + wangCai.type);\r\n // 调用对象的方法\r\n wangCai.eat();\r\n wangCai.sound();\r\n }\r\n }\r\n ```\r\n\r\n+ 注意:\r\n\r\n + 类是引用数据类型,而对象就是具体的数据。\r\n + 对象的命名和变量命名规则一样。\r\n\r\n### 1.3-成员变量的默认值是什么?\r\n\r\n+ 默认值:\r\n \r\n\r\n\r\n+ 代码:\r\n\r\n ```java\r\n public class DogTest02 {\r\n public static void main(String[] args) {\r\n Dog wangCai = new Dog();\r\n // 读取对象的属性\r\n System.out.println(\"这一条狗的名字:\" + wangCai.name); // null\r\n System.out.println(\"这一条狗狗的毛色:\" + wangCai.color); // null\r\n System.out.println(\"这一条狗狗的种族:\" + wangCai.type); // null\r\n }\r\n }\r\n // 字符串是引用数据类型,索引默认值是null\r\n ```\r\n\r\n \r\n\r\n### 1.4-成员变量和局部变量的区别?\r\n\r\n+ 成员变量:定义在类中的方法`外`的变量。\r\n+ 局部变量:定义在类中的方法`内`的变量。\r\n+ 区别:\r\n 1. 位置不同\r\n + 成员变量,类中,方法外。\r\n + 局部变量,类中,方法内\r\n 2. 作用范围不同\r\n + 成员变量,作用范围-类中。\r\n + 局部变量,作用方法-方法中\r\n 3. 默认值不同\r\n + 成员变量,有默认值。\r\n + 局部变量,没有默认值。并且,使用局部变量时,局部变量必须赋值,否则编译不通过。\r\n 4. 内存位置不同\r\n + 成员变量,堆区\r\n + 局部变量,栈区\r\n 5. 生命周期不同\r\n + 成员变量,随着对象的创建而创建,随着对象的销毁而销毁。\r\n + 局部变量,随着方法的调用而创建,随着方法调用完毕而销毁。\r\n\r\n\r\n\r\n### 1.5-图解内存中的对象\r\n\r\n> **一个对象创建并为属性赋值的内存图**\r\n\r\n+ **Person类代码**\r\n\r\n ```java\r\n public class Person {\r\n // 成员变量\r\n String name;\r\n int age;\r\n // 成员方法\r\n public void eat(){\r\n System.out.println(\"吃饭\");\r\n }\r\n }\r\n ```\r\n\r\n+ **PersonTest类代码**\r\n\r\n ```java\r\n public class PersonTest {\r\n public static void main(String[] args) {\r\n Person zs = new Person();\r\n zs.name = \"张三\";\r\n zs.age = 10;\r\n }\r\n }\r\n ```\r\n\r\n+ **内存图解**\r\n \r\n\r\n\r\n+ **总结**:\r\n\r\n 1. 对象是引用数据类型的数据\r\n\r\n> **一个对象调用方法的内存图**\r\n\r\n+ **Person类代码-同上**\r\n\r\n+ **PersonTest类代码**\r\n\r\n ```java\r\n public class PersonTest {\r\n public static void main(String[] args) {\r\n Person zs = new Person();\r\n zs.name = \"张三\";\r\n zs.age = 10;\r\n // 调用方法\r\n zs.eat();\r\n }\r\n }\r\n ```\r\n\r\n+ **内存图解**\r\n \r\n\r\n\r\n+ **总结**\r\n\r\n 1. 调用方法时,方法会入栈执行,执行完毕后会出栈。\r\n\r\n> **两个对象调用方法的内存图**\r\n\r\n+ **Person类代码-同上**\r\n\r\n+ **PersonTest类代码**\r\n\r\n ```java\r\n public class PersonTest {\r\n public static void main(String[] args) {\r\n Person zs = new Person();\r\n zs.name = \"张三\";\r\n zs.age = 10;\r\n zs.eat();\r\n Person ls = new Person();\r\n ls.name = \"李四\";\r\n ls.age = 11;\r\n ls.eat();\r\n }\r\n }\r\n ```\r\n\r\n+ **内存图解**\r\n \r\n\r\n\r\n+ **总结**\r\n\r\n 1. new关键字创建对象时,都会独立开辟空间存放对象的信息。\r\n 2. 所有对象的成员方法都是共享方法区中的class文件中的方法(节省内存)。\r\n\r\n \r\n\r\n## 第二章:面向对象-封装\r\n\r\n### 2.1-什么是封装 ?\r\n\r\n 封装(encapsulation)就是 `隐藏对象的属性和实现细节,仅对外公开接口`,控制在程序中属性的读取和修改的访问级别 。\r\n\r\n 封装的可以让代码更加健壮且易于维护,提高程序的安全性。\r\n\r\n### 2.2-如何封装?\r\n\r\n+ private关键字修饰成员变量\r\n\r\n + private修饰的成员变量,仅能够在本类中使用。\r\n + 格式:`private 数据类型 成员变量名;`\r\n\r\n+ 对外提供公开的getXXX/setXXX方法操作对象的属性。\r\n\r\n + this关键字,在方法中表示调用方法的对象。\r\n\r\n+ 代码:\r\n\r\n ```java\r\n public class Person {\r\n private String name;\r\n private int age;\r\n \r\n public String getName() {\r\n return name;\r\n }\r\n public void setName(String name) {\r\n this.name = name;\r\n }\r\n public int getAge() {\r\n return age;\r\n }\r\n public void setAge(int age) {\r\n this.age = age;\r\n }\r\n }\r\n \r\n ```\r\n\r\n### 2.3-封装优化-构造方法\r\n\r\n> **什么是构造方法-construction**\r\n\r\n+ 与类同名的方法\r\n\r\n+ 方法修饰没有返回值类型,没有void。\r\n\r\n ```java\r\n public class Person{\r\n // 无参构造方法\r\n public Person(){\r\n \r\n }\r\n }\r\n ```\r\n\r\n注意:类默认有一个无参的构造方法。构造方法可重载\r\n\r\n> **构造方法的作用**\r\n\r\n1. 创建对象。\r\n + 创建对象时,会默认调用类的无参构造方法。\r\n2. 初始化对象的属性值。\r\n + 可以定义带参数构造方法初始化对象属性\r\n\r\n+ 代码:\r\n\r\n + **Person类**\r\n\r\n ```java\r\n public class Person {\r\n private String name;\r\n private int age;\r\n // 无参数构造方法\r\n public Person() {\r\n }\r\n // 有参数构造方法\r\n public Person(String name, int age) {\r\n this.name = name;\r\n this.age = age;\r\n }\r\n public void show(){\r\n System.out.println(\"姓名:\" + this.name);\r\n System.out.println(\"年龄:\" + this.age);\r\n }\r\n }\r\n \r\n ```\r\n\r\n + **测试类**\r\n\r\n ```java\r\n public class PersonTest {\r\n public static void main(String[] args) {\r\n // 【调用无参构造方法】\r\n Person zs = new Person();\r\n zs.name = \"张三\";\r\n zs.age = 10;\r\n zs.show(); // 打印结果:姓名:张三 年龄:10\r\n \r\n // 【调用有参构造方法】\r\n Person ls = new Person(\"李四\",12);\r\n ls.show(); // 打印结果:姓名:李四 年龄:12\r\n }\r\n }\r\n ```\r\n\r\n### 2.4-JavaBean\r\n\r\n JavaBean 是 Java语言编写类的一种标准规范。符合 JavaBean 的类,要求类必须是具体的和公共的,并且具有无参数的构造方法,提供用来操作成员变量的 set 和 get 方法。\r\n\r\n```java\r\n public class 类名 {\r\n // 成员变量\r\n // 无参构造方法【必须】\r\n // 有参构造方法\r\n // get/set方法\r\n // 成员方法\r\n }\r\n```\r\n\r\n\r\n\r\n## 第三章:面向对象-继承\r\n\r\n### 3.1-继承概述?\r\n\r\n> **什么是继承?**\r\n\r\n继承是**子类**和**父类**的关系,子类可以使用父类中的成员。\r\n\r\n+ 父类:也叫基类或超类。\r\n+ 子类:也叫派生类\r\n\r\n例如:现实生活中的继承\r\n\r\n\r\n\r\n注意:\r\n\r\n+ Java的继承是单继承,但支持多层继承。\r\n \r\n\r\n\r\n+ 所有的类都直接或间接的继承了**Object类**\r\n\r\n + Object是祖宗类。\r\n\r\n+ `对象 instanceof类` 检测一个对象是否属于某个类。返回布尔值,true表示属于,false表示不属于。\r\n\r\n + **Person类**\r\n\r\n ```java\r\n public class Person {\r\n }\r\n ```\r\n\r\n + **PersonTest类**\r\n\r\n ```java\r\n public class PersonTest {\r\n public static void main(String[] args) {\r\n Person p1 = new Person();\r\n // p1属于Person类\r\n System.out.println(p1 instanceof Person); // 结果:true\r\n // p1也属于Object类\r\n System.out.println(p1 instanceof Object); // 结果:true\r\n }\r\n }\r\n ```\r\n\r\n> **继承的作用**\r\n\r\n+ 实现代码的复用性。\r\n+ 提高代码的可扩展性。\r\n\r\n\r\n\r\n### 3.2-如何实现继承?\r\n\r\n关键字:`extends`\r\n\r\n+ 格式\r\n\r\n ```java\r\n public class 子类 extends 父类 { \r\n }\r\n ```\r\n\r\n+ 代码:\r\n\r\n + 父类-手机类\r\n\r\n ```java\r\n public class Phone {\r\n private String name;\r\n private int year;\r\n public Phone() {\r\n }\r\n public Phone(String name, int year) {\r\n this.name = name;\r\n this.year = year;\r\n }\r\n public String getName() {\r\n return name;\r\n }\r\n public void setName(String name) {\r\n this.name = name;\r\n }\r\n public int getYear() {\r\n return year;\r\n }\r\n public void setYear(int year) {\r\n this.year = year;\r\n }\r\n // 打电话功能\r\n public void call(){\r\n System.out.println(\"打电话\");\r\n }\r\n // 发短信\r\n public void sendMessage(){\r\n System.out.println(\"发送文字\");\r\n }\r\n // 手机信息\r\n public void showInfo(){\r\n System.out.println(\"手机名称:\" + this.name);\r\n System.out.println(\"生产年份:\" + this.year);\r\n }\r\n }\r\n \r\n ```\r\n\r\n + 子类-智能手机类\r\n\r\n ```java\r\n /*智能手机类*/\r\n public class IntelligentPhone extends Phone {\r\n public IntelligentPhone() {\r\n }\r\n public IntelligentPhone(String name, int year) {\r\n super(name, year);\r\n }\r\n public void playMusic(){\r\n System.out.println(\"播放音乐\");\r\n }\r\n }\r\n ```\r\n\r\n + 测试类\r\n\r\n ```java\r\n public class Test {\r\n public static void main(String[] args) {\r\n IntelligentPhone iphone = new IntelligentPhone(\"iphone100\",2100);\r\n iphone.call(); // 打电话\r\n iphone.sendMessage(); // 发送文字\r\n iphone.playMusic(); // 播放音乐\r\n iphone.showInfo(); // 手机名称:iphone100 生产年份:2100\r\n }\r\n }\r\n ```\r\n\r\n+ 关键字:`super`,在子类中表示父类的引用。\r\n\r\n + 在子类中调用父类构造方法:`super(参数列表)`\r\n + 在子类中调用父类的非私有的成员变量:`super.成员变量名`\r\n + 在子类中调用父类的非私有的成员方法:`super.成员方法()`\r\n\r\n### 3.3-方法的重写-override\r\n\r\n+ 子类中的方法和父类中的方法重名时(返回值和参数列表一致),叫做方法的重载\r\n\r\n+ 代码:\r\n\r\n + 父类-同上手机类\r\n\r\n + 子类\r\n\r\n ```java\r\n public class IntelligentPhone extends Phone {\r\n public IntelligentPhone() {\r\n }\r\n public IntelligentPhone(String name, int year) {\r\n super(name, year);\r\n }\r\n public void playMusic(){\r\n System.out.println(\"播放音乐\");\r\n }\r\n \r\n @Override\r\n // 重写父类的方法sendMessage\r\n public void sendMessage() {\r\n // 调用父类的发送文字的功能\r\n super.sendMessage();\r\n // 扩展发送图片\r\n System.out.println(\"发送图片\");\r\n }\r\n }\r\n ```\r\n\r\n + 测试类\r\n\r\n ```java\r\n public class Test {\r\n public static void main(String[] args) {\r\n IntelligentPhone iphone = new IntelligentPhone(\"iphone100\",2100);\r\n iphone.sendMessage();\r\n }\r\n }\r\n // 结果:\r\n // 发送文字\r\n // 发送图片\r\n ```\r\n\r\n### 3.7-抽象类、抽象方法\r\n\r\n> **抽象类和抽象方法的定义**\r\n\r\n+ 关键字:`abstract`\r\n+ 抽象方法:只有声明没有方法体的方法。\r\n + 格式:`public abstract 返回值类型 方法名(参数列表);`\r\n + 一个抽象方法所在的类,该类一定是抽象类。\r\n + 抽象方法在子类中必须重写,除非子类也是抽象类。\r\n+ 抽象类:被abstruct关键字修饰的类。\r\n + 格式:`public abstract class 类名{}`\r\n + 抽象类不能被实例化(不能通过new关键字调用)。\r\n\r\n> **什么时候适合使用抽象方法**\r\n\r\n+ 当不同子类有相同的行为,但又有不同的具体实现时,此时父类中定义方法时仅定义声明即可。\r\n\r\n+ 代码:需求-创建一条狗对象和一只猫对象,它们有不同的叫声\r\n\r\n + 父类-Animal\r\n\r\n ```java\r\n public abstract class Animal {\r\n private String name;\r\n private int age;\r\n public Animal() {};\r\n public Animal(String name, int age) {\r\n this.name = name;\r\n this.age = age;\r\n }\r\n public void sleep(){\r\n System.out.println(\"睡觉~~~\");\r\n }\r\n // 【抽象方法】不同的动物,叫声,所以让子类重写具体的方法体\r\n public abstract void shout();\r\n public String getName() {\r\n return name;\r\n }\r\n public void setName(String name) {\r\n this.name = name;\r\n }\r\n public int getAge() {\r\n return age;\r\n }\r\n public void setAge(int age) {\r\n this.age = age;\r\n }\r\n }\r\n \r\n ```\r\n\r\n + 子类-Cat\r\n\r\n ```java\r\n public class Cat extends Animal {\r\n public Cat(){}\r\n public Cat(String name,int age){\r\n super(name,age);\r\n }\r\n @Override\r\n public void shout() {\r\n System.out.println(this.getName() + \":喵喵~~~\");\r\n }\r\n }\r\n ```\r\n\r\n + 子类-Dog\r\n\r\n ```java\r\n public class Dog extends Animal {\r\n public Dog(){};\r\n public Dog(String name,int age){\r\n super(name,age);\r\n }\r\n @Override\r\n public void shout() {\r\n System.out.println(this.getName() + \":汪汪~~~\");\r\n }\r\n }\r\n ```\r\n\r\n + 测试类-Test\r\n\r\n ```java\r\n public class Test {\r\n public static void main(String[] args) {\r\n Cat cat = new Cat(\"汤姆\",2);\r\n cat.shout(); //结果: 汤姆:喵喵~~\r\n Dog dog = new Dog(\"旺财\",3);\r\n dog.shout(); // 结果: 旺财:汪汪~~\r\n }\r\n }\r\n ```\r\n\r\n \r\n\r\n### 3.7-接口\r\n\r\n> **概述**\r\n\r\n+ 接口,是一系列方法的集合。若类封装了成员变量、成员方法、构造方法,而接口主要封装了方法。\r\n+ 接口中可以定义**抽象方法**(jdk7之前)、默认方法和静态方法(jdk8)、私有方法(jdk9)以及常量。\r\n+ 接口可以被实现(类似于被继承),实现接口的类叫做**实现类**(类似于子类)。\r\n+ 接口中的抽象方法,实现接口的类必须重写。否则,实现类必须是抽象类。\r\n+ 类可以实现多个接口。\r\n+ 接口之间可以实现多继承。\r\n\r\n> **接口的定义和实现**\r\n\r\n+ **格式**:\r\n\r\n + 定义接口\r\n\r\n ```java\r\n public interface 接口名称{\r\n // 【抽象方法】\r\n public abstract 返回数据类型 抽象方法名(参数列表);\r\n // 可简写为: 返回数据类型 抽象方法名(参数列表);\r\n // 【默认方法】\r\n public default 返回数据类型 方法名(){\r\n // 方法体 \r\n }\r\n // 【静态方法】\r\n public static 返回数据类型 方法名(){\r\n // 方法体 \r\n }\r\n // 【私有方法】\r\n private 返回数据类型 方法名(){\r\n // 方法体 \r\n }\r\n }\r\n ```\r\n\r\n + 实现接口:\r\n\r\n ```java\r\n public class 实现类名 implements 接口1,接口2...接口n {\r\n // 重写接口中的抽象方法【必须】\r\n // 重写接口中的默认方法【可选】\r\n }\r\n ```\r\n\r\n + 接口可以继承其他接口\r\n\r\n ```java\r\n public interface 接口1 extends 接口2,接口3...接口n{\r\n \r\n }\r\n ```\r\n\r\n + 注意:\r\n\r\n 1. 抽象方法,实现类必须重写\r\n 2. 默认方法,实现类可以继承,也可以重写(可选)\r\n 3. 静态方法,实现类和实现类的实例无法调用。接口名可以调用。\r\n 4. 常量,实现类可以继承。\r\n 5. 私有方法,实现类无法继承。\r\n\r\n+ **代码:**\r\n\r\n + 接口\r\n\r\n ```java\r\n public interface CommonInterface {\r\n // 常量\r\n public final double VERSION = 1.0;\r\n // 抽象方法\r\n public abstract void fly();\r\n // 默认方法\r\n public default void fn(){\r\n fn1();\r\n System.out.println(\"默认方法\");\r\n }\r\n // 私有方法\r\n private void fn1(){\r\n System.out.println(\"私有方法\");\r\n }\r\n // 静态方法\r\n public static void show(){\r\n System.out.println(\"静态方法\");\r\n }\r\n \r\n }\r\n ```\r\n\r\n + 实现类\r\n\r\n ```java\r\n public class Bird implements CommonInterface {\r\n @Override\r\n public void fly() {\r\n System.out.println(\"展开翅膀飞行\");\r\n }\r\n }\r\n ```\r\n\r\n + 测试类\r\n\r\n ```java\r\n public class Test {\r\n public static void main(String[] args) {\r\n Bird xn = new Bird();\r\n xn.fly();\r\n xn.fn();\r\n }\r\n }\r\n ```\r\n\r\n> **接口和抽象类的区别?**\r\n\r\n+ **相同点**:\r\n 1. 都可以定义抽象方法。\r\n 2. 都可以实现多态(第四章)。\r\n 3. 都不可以被实例化。\r\n+ **不同点:**\r\n 1. 接口中不可定义成员变量、构造方法、成员方法,抽象类可以。\r\n 2. 接口可以多被多实现(类似多继承)。抽象类仅支持单继承。\r\n 3. 接口可以多继承其他接口。抽象类仅支持单继承。\r\n\r\n> **什么场景下用继承类?什么场景下用实现接口?**\r\n\r\n+ 若强调多个类**是什么**时,可以使用继承。\r\n \r\n\r\n+ 若强调多个类**能做什么**时,可以使用实现接口。\r\n \r\n\r\n \r\n\r\n\r\n\r\n## 第四章:面向对象-多态\r\n\r\n### 4.1-什么是多态?\r\n\r\n多态Polymorphism,按字面意思就是"多种状态"。\r\n\r\n在[面向对象语言](https://baike.baidu.com/item/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E8%AF%AD%E8%A8%80/215742)中,接口的多种不同的实现方式即为多态 。\r\n\r\n多态性是允许你将父对象设置成为一个或更多的他的[子对象](https://baike.baidu.com/item/%E5%AD%90%E5%AF%B9%E8%B1%A1/11001276)相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作(摘自"Delphi4 [编程技术](https://baike.baidu.com/item/%E7%BC%96%E7%A8%8B%E6%8A%80%E6%9C%AF/2646621)内幕")。\r\n\r\n简单的说,就是一句话:允许将子类类型的[指针](https://baike.baidu.com/item/%E6%8C%87%E9%92%88/2878304)赋值给父类类型的指针。 \r\n\r\n\r\n\r\n### 4.2-多态的好处?\r\n\r\n把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化。 \r\n\r\n\r\n\r\n### 4.3-如何实现多态?\r\n\r\n> **实现多态的前提条件**\r\n\r\n1. 继承或者实现【二选一】 \r\n2. 方法的重写【意义体现:不重写,无意义】 \r\n3. 父类引用指向子类对象【格式体现】\r\n\r\n> **实现方式**\r\n\r\n+ 格式:`父类或被实现的接口 变量名 = new 子类或实现类()`\r\n\r\n+ 代码:\r\n\r\n + 父类-Animal\r\n\r\n ```java\r\n public abstract class Animal {\r\n private String name;\r\n private String type;\r\n public Animal(){}\r\n public Animal(String name,String type){\r\n this.name = name;\r\n this.type = type;\r\n }\r\n public abstract void sound();\r\n public String getName() {\r\n return name;\r\n }\r\n public void setName(String name) {\r\n this.name = name;\r\n }\r\n public String getType() {\r\n return type;\r\n }\r\n public void setType(String type) {\r\n this.type = type;\r\n }\r\n }\r\n \r\n ```\r\n\r\n + 子类-Bird\r\n\r\n ```java\r\n public class Bird extends Animal {\r\n public Bird(){}\r\n public Bird(String name,String type) {\r\n super(name,type);\r\n }\r\n @Override\r\n public void sound() {\r\n System.out.println(this.getName() + \":叽叽喳喳~~\");\r\n }\r\n }\r\n ```\r\n\r\n + 子类-Dog\r\n\r\n ```java\r\n public class Dog extends Animal {\r\n public Dog(){}\r\n public Dog(String name,String type) {\r\n super(name,type);\r\n }\r\n @Override\r\n public void sound() {\r\n System.out.println(this.getName() + \":汪汪~~~\");\r\n }\r\n public void play(){\r\n System.out.println(\"狗狗跳起来接飞盘!\");\r\n }\r\n }\r\n ```\r\n\r\n + 测试类-Test\r\n\r\n ```java\r\n public class Test {\r\n public static void main(String[] args) {\r\n AnimalSound(new Dog(\"旺财\",\"中华田园犬\")); // 旺财:汪汪~~~\r\n AnimalSound(new Bird(\"小咕\",\"麻雀\")); // 小咕:叽叽喳喳~~\r\n }\r\n // 动物的叫声\r\n public static void AnimalSound(Animal animal){\r\n animal.sound();\r\n }\r\n }\r\n ```\r\n\r\n### 4.4-向上向下转型\r\n\r\n还是上述代码,若让一个Dog对象指向父类的引用,再调用Dog的play方法时,会编译出错!\r\n\r\n+ 代码\r\n\r\n ```java\r\n public class Test2 {\r\n public static void main(String[] args) {\r\n Animal animal = new Dog(\"旺财\",\"中华田园犬\");\r\n // animal.play(); // 编译不通过\r\n }\r\n }\r\n ```\r\n\r\n此时我们可以通过向下转型为Dog对象,实现对象Dog对象独有的方法调用。\r\n\r\n+ 向上转型:`父类/接口 父类引用 = new 子类/实现类()`\r\n+ 向下转型:`(子类或实现类)父类引用`\r\n\r\n代码如下:\r\n\r\n```java\r\npublic class Test2 {\r\n public static void main(String[] args) {\r\n Animal animal = new Dog(\"旺财\",\"中华田园犬\"); // 向上转型\r\n ((Dog) animal).play(); // 狗狗跳起来接飞盘! // 向下转型\r\n }\r\n}\r\n```\r\n\r\n+ 注意:\r\n\r\n 1. 向下转型时,若子类不对应,在运行时,会出现ClassCastException异常!\r\n\r\n 2. 可以通过instanceOf方法检测。\r\n\r\n ```java\r\n public class Test2 {\r\n public static void main(String[] args) {\r\n Animal animal = new Bird(\"小咕\",\"麻雀\");\r\n // ((Dog) animal).play(); // ClassCastException 因为向上转型的子类对象不是Dog\r\n //【正确方式,先验证类型,再调用】\r\n if(animal instanceof Dog){\r\n ((Dog) animal).play();\r\n }\r\n }\r\n }\r\n ```\r\n\r\n \r\n\r\n## 第五章:面向对象-其他\r\n\r\n### 5.1-final关键字\r\n\r\n若定义在父类中的某些成员变量或成员方法,不希望被子类修改,如何操作呢?\r\n\r\n可以使用关键字`final`修饰\r\n\r\n+ 成员变量\r\n + 被final修饰的成员变量必须初始化,否则编译不通过。\r\n + 被final修饰的成员变量不能重复赋值。\r\n+ 局部变量\r\n + 被final修饰的局部变量,若不赋值而使用会编译不通过。\r\n+ 成员方法\r\n + 被final修饰的成员方法,不能被重写。\r\n+ 成员类\r\n + 被final修饰的类,不能被继承。\r\n\r\n\r\n\r\n### 5.2-权限修饰符\r\n\r\n> **概述**\r\n\r\n在Java中提供了四种访问权限,使用不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限, \r\n\r\n1. public:公共的。 \r\n2. protected:受保护的 \r\n3. default:默认的 \r\n4. private:私有的 \r\n\r\n> **不同权限的访问能力** \r\n\r\n\r\n\r\n\r\n可见,public具有最大权限。private则是最小权限。 \r\n\r\n编写代码时,如果没有特殊的考虑,建议这样使用权限: \r\n\r\n+ 成员变量使用 private ,隐藏细节。 \r\n+ 构造方法使用 public ,方便创建对象。 \r\n+ 成员方法使用 public ,方便调用方法。 \r\n\r\n### 5.3-内部类\r\n\r\n> **概述**\r\n\r\n`内部类和外部类`:将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类。 \r\n\r\n> **内部类定义格式**\r\n\r\n```java\r\nclass 外部类 {\r\n class 内部类{ }\r\n}\r\n```\r\n\r\n在描述事物时,若一个事物内部还包含其他事物,就可以使用内部类这种结构。比如,汽车类 Car 中包含发动机 \r\n\r\n类 Engine ,这时, Engine 就可以使用内部类来描述,定义在成员位置。 \r\n\r\n```java\r\nclass Car { \r\n //外部类 \r\n class Engine {\r\n //内部类 \r\n } \r\n}\r\n```\r\n\r\n> **访问特点**\r\n\r\n+ 内部类可以直接访问外部类的成员,包括私有成员。 \r\n+ 外部类要访问内部类的成员,必须要建立内部类的对象。 \r\n\r\n> **创建内部类对象**\r\n\r\n格式:`外部类名.内部类名 对象名 = new 外部类型().new 内部类型()`; \r\n\r\n+ 人类\r\n\r\n ```java\r\n public class Person { \r\n private boolean live = true; \r\n class Heart { \r\n public void jump() { \r\n // 直接访问外部类成员\r\n if (live) { \r\n System.out.println(\"心脏在跳动\"); \r\n } else { \r\n System.out.println(\"心脏不跳了\"); \r\n } \r\n } \r\n }\r\n public boolean isLive() { \r\n return live; \r\n }\r\n public void setLive(boolean live) { \r\n this.live = live; \r\n } \r\n }\r\n ```\r\n\r\n+ 测试类\r\n\r\n ```java\r\n public class Test03 {\r\n public static void main(String[] args) { \r\n // 创建外部类对象 \r\n Person p = new Person(); \r\n // 创建内部类对象 \r\n Heart heart = p.new Heart();\r\n // 调用内部类方法 \r\n heart.jump(); \r\n // 调用外部类方法 \r\n p.setLive(false); \r\n // 调用内部类方法 \r\n heart.jump(); \r\n } \r\n }\r\n // 输出结果: 心脏在跳动 心脏不跳了\r\n ```\r\n\r\n内部类仍然是一个独立的类,在编译之后会内部类会被编译成独立的.class文件,但是前面冠以外部类的类名 \r\n\r\n和`$`符号 。 比如,`Person$Heart.class`\r\n\r\n> **匿名内部类介绍**\r\n\r\n匿名内部类 :是内部类的简化写法。它的本质是一个 带具体实现的 父类或者父接口的 匿名的 子类对象。 \r\n\r\n开发中,最常用到的内部类就是匿名内部类了。以接口举例,当你使用一个接口时,似乎得做如下几步操作, \r\n\r\n1. 定义子类 \r\n2. 重写接口中的方法 \r\n3. 创建子类对象 \r\n4. 调用重写后的方法 \r\n\r\n我们的目的,最终只是为了调用方法,那么能不能简化一下,把以上四步合成一步呢?匿名内部类就是做这样的快 \r\n\r\n捷方式。 \r\n\r\n> **匿名内部类的使用**\r\n\r\n+ 前提:匿名内部类必须继承一个父类或者实现一个父接口。\r\n\r\n+ 格式\r\n\r\n ```java\r\n new 父类名或者接口名(){ \r\n // 方法重写 \r\n @Override \r\n public void method() { \r\n // 执行语句 \r\n } \r\n };\r\n ```\r\n\r\n+ **代码**\r\n\r\n + Fly接口\r\n\r\n ```java\r\n public interface Fly {\r\n void fly();\r\n }\r\n ```\r\n\r\n + 测试类\r\n\r\n ```java\r\n public class Test {\r\n public static void main(String[] args) {\r\n /*\r\n 1.等号右边:是匿名内部类,定义并创建该接口的子类对象 \r\n 2.等号左边:是多态赋值,接口类型引用指向子类对象 \r\n */\r\n Fly bird = new Fly() {\r\n @Override\r\n public void fly() {\r\n System.out.println(\"小鸟飞...\");\r\n }\r\n };\r\n // 调用匿名内部类重写后的方法。\r\n bird.fly();\r\n \r\n }\r\n }\r\n ```\r\n\r\n> **匿名内部类的作用**\r\n\r\n+ 是实现**函数式编程**的基础\r\n\r\n\r\n\r\n### 5.4-static关键字\r\n\r\n> **概述**\r\n\r\n关于 static 关键字的使用,它可以用来修饰的成员变量和成员方法,被修饰的成员是属于类的,而不是单单是属 \r\n\r\n于某个对象的。也就是说,既然属于类,就可以不靠创建对象来调用了。 \r\n\r\n> **类变量**\r\n\r\n当 static 修饰成员变量时,该变量称为类变量。该类的每个对象都共享同一个类变量的值。任何对象都可以更改 \r\n\r\n该类变量的值,但也可以在不创建该类的对象的情况下对类变量进行操作。 \r\n\r\n+ 定义格式:`修饰符 static 数据类型 变量名; `\r\n+ 举例:`修饰符 static int age`\r\n\r\n> **静态方法**\r\n\r\n当 static 修饰成员方法时,该方法称为类方法 。静态方法在声明中有 static ,建议使用类名来调用,而不需要 \r\n\r\n创建类的对象。调用方式非常简单。 \r\n\r\n+ 定义格式:`static 返回值类型 方法名(参数列表){}`\r\n\r\n+ 代码:\r\n\r\n ```java\r\n public static void show(){\r\n System.out.println(\"hello\");\r\n }\r\n ```\r\n\r\n注意事项:\r\n\r\n1. 静态方法可以直接访问类变量和静态方法。 \r\n2. 静态方法不能直接访问普通成员变量或成员方法。反之,成员方法可以直接访问类变量或静态方法。 \r\n3. 静态方法中,不能使用this关键字。 \r\n\r\n> **静态代码块**\r\n\r\n+ 位置:类中方法外\r\n+ 格式:`static{}`\r\n+ 执行:随着类的加载而执行且执行一次,优先于main方法和构造方法的执行。\r\n+ 作用:给类变量进行初始化赋值 \r\n\r\n```java\r\npublic class ClassName{\r\n static { \r\n // 执行语句 \r\n } \r\n}\r\n```\r\n\r\n> **总结**\r\n\r\nstatic 关键字,可以修饰变量、方法和代码块。在使用的过程中,其主要目的还是想在不创建对象的情况 \r\n\r\n下,去调用方法。下',36,14,'2020-06-05 15:55:54',0,'JavaSE面向对象'),(21,'JavaSE集合','\r\n\r\n\r\n## 目录\r\n\r\n[TOC]\r\n\r\n## 第一章:数据结构基础\r\n\r\n### 1.1-什么是数据结构\r\n\r\n\r\n\r\n\r\n 数据结构就是**计算机存储、组织数据的方式** 。\r\n\r\n 指的是**相互之间存在着特定关系的一种或多种的数据元素集合。**\r\n\r\n\r\n\r\n### 1.2-为什么要学习数据结构\r\n\r\n通常情况下,精心选择**合适的数据结构可以带来更高的运行或存储的效率**。\r\n\r\n数据结构往往同高效的**检索算法**和**索引技术**有关。\r\n\r\n\r\n\r\n### 1.3-数据结构-栈\r\n\r\n> **栈结构介绍**\r\n\r\n\r\n\r\n\r\n- **栈**:栈(stack)又名堆栈,是一种运算受限的**线性表**。\r\n- **受限:**限定**仅在表尾进行插入和删除**操作的线性表(这一端被称为**栈顶**,另一端称为**栈底**)\r\n\r\n> **特性**\r\n\r\n特性:**先进后出**\r\n\r\n\r\n\r\n\r\n\r\n\r\n### 1.4-数据结构-队列\r\n\r\n> **队列介绍**\r\n\r\n- **队列:**是一种受限的特殊线性表。\r\n- **受限:**只允许在表的前端(队头)进行删除操作,后端(队尾)进行插入操作。\r\n\r\n> **队列特性**\r\n\r\n特性:**先进先出**\r\n\r\n\r\n\r\n\r\n\r\n\r\n### 1.5-数据结构-数组\r\n\r\n> **数组介绍**\r\n\r\n**数组:**一组有序的(索引有序并且从0开始)类型相同的长度固定的元素集合。\r\n\r\n> **特性**\r\n\r\n+ 元素有序\r\n+ 元素同类型\r\n+ 长度固定\r\n\r\n> **应用效果**\r\n\r\n- `查询快`。\r\n - 从数组索引0开始查找,根据指定位置的偏移量可快速获取数据。\r\n- `增删慢`。\r\n - 数组的长度是固定的,若删除或增加一格元素,则会先创建一个新的数组,再把原数组的数据根据操作复制到新数组中。\r\n\r\n### 1.6-数据结构-链表\r\n\r\n> **链表介绍**\r\n\r\n+ 链表:链表是一种物理[存储单元](https://baike.baidu.com/item/%E5%AD%98%E5%82%A8%E5%8D%95%E5%85%83/8727749)上非连续、非顺序的[存储结构](https://baike.baidu.com/item/%E5%AD%98%E5%82%A8%E7%BB%93%E6%9E%84/350782),[数据元素](https://baike.baidu.com/item/%E6%95%B0%E6%8D%AE%E5%85%83%E7%B4%A0/715313)的逻辑顺序是通过链表中的[指针](https://baike.baidu.com/item/%E6%8C%87%E9%92%88/2878304)链接次序实现的 \r\n\r\n+ 单向链表:单链表是一种链式存取的数据结构,用一组地址任意的[存储单元](https://baike.baidu.com/item/%E5%AD%98%E5%82%A8%E5%8D%95%E5%85%83/8727749)存放线性表中的[数据元素](https://baike.baidu.com/item/%E6%95%B0%E6%8D%AE%E5%85%83%E7%B4%A0/715313)。链表中的数据是以结点来表示的,每个结点的构成:元素([数据元素](https://baike.baidu.com/item/%E6%95%B0%E6%8D%AE%E5%85%83%E7%B4%A0/715313)的映象) + [指针](https://baike.baidu.com/item/%E6%8C%87%E9%92%88/2878304)(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。\r\n \r\n\r\n \r\n\r\n\r\n+ 双向链表:双向链表也叫[双链表](https://baike.baidu.com/item/%E5%8F%8C%E9%93%BE%E8%A1%A8/5827945),是链表的一种,它的每个数据结点中都有两个[指针](https://baike.baidu.com/item/%E6%8C%87%E9%92%88/2878304),分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向[循环链表](https://baike.baidu.com/item/%E5%BE%AA%E7%8E%AF%E9%93%BE%E8%A1%A8/3228465)。 \r\n\r\n> **链表特性**\r\n\r\n- 由一系列结点(链表中的每一个元素称为结点)组成。\r\n- 结点可以在运行时动态生成。\r\n- 每个结点包括两个部分(单链表)\r\n - 一个是存储数据元素的数据域\r\n - 另一个是存储下一个结点地址的指针域。\r\n\r\n> **应用效果**\r\n\r\n- `查询慢`:链表的地址不是连续的,每次查找都得从头开始查找。\r\n- `增删快`:增删操作不会影响链表的整体结构。\r\n\r\n\r\n\r\n### 1.7-数据结构-红黑树\r\n\r\n> 介绍\r\n\r\n红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在[计算机](https://baike.baidu.com/item/%E8%AE%A1%E7%AE%97%E6%9C%BA)科学中用到的一种[数据结构](https://baike.baidu.com/item/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/1450),典型的用途是实现[关联数组](https://baike.baidu.com/item/%E5%85%B3%E8%81%94%E6%95%B0%E7%BB%84/3317025)。 \r\n\r\n\r\n\r\n\r\n> 特性\r\n\r\n+ `查询速度快`\r\n + 在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。 \r\n\r\n\r\n\r\n## 第二章:泛型介绍\r\n\r\n### 2.1-什么是泛型\r\n\r\n泛型,未知的类型,可以在类或方法中预支地使用未知的类型。\r\n\r\n### 2.2-泛型的好处\r\n\r\n可以避免类型转换的麻烦。\r\n\r\n### 2.3-定义和使用含有泛型的类\r\n\r\n> 定义\r\n\r\n`修饰符 class 类名<代表泛型的变量> { }`\r\n\r\n> 使用\r\n\r\n在创建对象的使用。也就是在创建对象时确定泛型的类型。\r\n\r\n```java\r\npublic class ArrayList<E> {\r\n public boolean add(E e){ }\r\n ...\r\n}\r\n\r\npublic class MainDemo{\r\n public static void main(String[] args) {\r\n ArrayList<String> list = new ArrayList<>();\r\n list.add(\"张三\");\r\n }\r\n}\r\n```\r\n\r\n\r\n\r\n### 2.4-定义和使用含有泛型的方法\r\n\r\n> 定义\r\n\r\n`修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }`\r\n\r\n> 使用\r\n\r\n```java\r\npublic static void main(String[] args) {\r\n methon1(\"张三\");\r\n}\r\npublic static <M> void methon1(M m){\r\n System.out.println(m);\r\n}\r\n```\r\n\r\n\r\n\r\n### 2.5-定义和使用含有泛型的接口\r\n\r\n> 定义\r\n\r\n`修饰符 interface接口名<代表泛型的变量>`\r\n\r\n> 使用\r\n\r\n```java\r\npublic interface TestInterface<T> {\r\n public abstract void show(T t);\r\n}\r\n\r\npublic class<T> TestImpl implements TestInterface<T> {\r\n @Override\r\n public T void show(T o) {\r\n System.out.println(o);\r\n }\r\n}\r\n\r\npublic class Main01 {\r\n public static void main(String[] args) {\r\n TestImpl<String> t = new TestImpl<>();\r\n t.show(\"我的信息\");\r\n\r\n }\r\n \r\n}\r\n```\r\n\r\n\r\n\r\n## 第三章:单列集合-Collection系列\r\n\r\n### 3.1-集合介绍\r\n\r\n- 定义:在Java中,集合是一种可以**存储多个数据**的容器。\r\n\r\n- 代码:\r\n\r\n ```java\r\n ArrayList<String> list = new ArrayList<>();\r\n list.add(\"张三\");\r\n list.add(\"王五\");\r\n list.add(\"赵六\");\r\n System.out.println(list); // [张三, 王五, 赵六]\r\n ```\r\n\r\n\r\n\r\n### 3.2-集合和数组的区别\r\n\r\n- 区别:数组的长度是**固定的**,集合的长度是**可变的**。\r\n\r\n- 代码:\r\n\r\n ```java\r\n String[]strs = new String[2];\r\n strs[0] = \"张三\";\r\n strs[1] = \"李四\";\r\n // strs[2] = \"赵六\"; // 运行时报错java.lang.ArrayIndexOutOfBoundsException\r\n \r\n ArrayList<String> list = new ArrayList<>();\r\n System.out.println(list.size()); // 0\r\n list.add(\"张三\");\r\n list.add(\"王五\");\r\n list.add(\"赵六\");\r\n System.out.println(list.size()); // 3\r\n ```\r\n\r\n \r\n\r\n### 3.3-集合框架Collection\r\n\r\n> 概述\r\n\r\n\r\n\r\n\r\n- 单列集合类的根接口\r\n- 有两个子接口\r\n - java.util.**List**;特点是:内容可重复,有序\r\n - java.util.**Set**; 特点是:内容不可重复,无序\r\n- 定义了单例集合最共性的方法。\r\n\r\n> **通用方法**\r\n\r\n**方法:**\r\n\r\n- `public boolean add(E e)`: 把给定的对象添加到当前集合中 。\r\n- `public void clear()` :清空集合中所有的元素。\r\n- `public boolean remove(E e)`: 把给定的对象在当前集合中删除。\r\n- `public boolean contains(E e)`: 判断当前集合中是否包含给定的对象。\r\n- `public boolean isEmpty()`: 判断当前集合是否为空。\r\n- `public int size()`: 返回集合中元素的个数。\r\n- `public Object[] toArray()`: 把集合中的元素,存储到数组中。\r\n\r\n**代码:**\r\n\r\n```java\r\n List<String> list = new ArrayList<>();\r\n // 添加元素\r\n list.add(\"张三\");\r\n list.add(\"李四\");\r\n System.out.println(list);\r\n // 移除元素\r\n list.remove(\"张三\");\r\n System.out.println(list);\r\n // 判断集合中是否包含某个元素\r\n boolean isHas = list.contains(\"张三\");\r\n System.out.println(isHas); // false\r\n // 判断当前集合是否为空\r\n boolean isEmpty = list.isEmpty();\r\n System.out.println(isEmpty);\r\n // 清空元素\r\n list.clear();\r\n System.out.println(list);\r\n // 集合的长度\r\n System.out.println(list.size());\r\n // 集合中的元素存储到一个数组中\r\n Object[]s = list.toArray();\r\n```\r\n\r\n> **遍历集合**\r\n\r\n- **Iterator方式**\r\n\r\n - **介绍**:Iterator,是一个迭代器接口。Collection中的成员方法**iterator()**被调用后,会返回一个Iterator对象。利用这个对象可以实现遍历集合。如何遍历呢?在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。\r\n\r\n - **Iterator对象的成员方法:**\r\n\r\n - hasNext(); 检测集合中是否存在下一个元素\r\n - next(); 找到并获取下一个元素\r\n\r\n - **迭代器的原理**:在调用Iterator的next方法之前,迭代器的索引位于第一个元素之前,不指向任何元素,当第一次调用迭代器的next方法后,迭代器的索引会向后移动一位,指向第一个元素并将该元素返回,当再次调用next方法时,迭代器的索引会指向第二个元素并将该元素返回,依此类推,直到hasNext方法返回false,表示到达了集合的末尾,终止对元素的遍历。\r\n\r\n - **代码:**\r\n\r\n ```java\r\n List<String> list = new ArrayList<>();\r\n list.add(\"张三\");\r\n list.add(\"李四\");\r\n list.add(\"王五\");\r\n // 得到一个迭代器对象\r\n Iterator<String> it = list.iterator();\r\n while (it.hasNext()) {\r\n String str = it.next();\r\n System.out.println(str);\r\n }\r\n ```\r\n\r\n- **增强for方式**\r\n\r\n - **介绍**:增强for循环(也称for each循环)是**JDK1.5**以后出来的一个高级for循环,专门用来遍历数组和集合的。它的内部原理其实是个Iterator迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作。\r\n\r\n - **使用格式**\r\n\r\n ```java\r\n for(元素的数据类型 变量 : Collection集合or数组){ \r\n //写操作代码\r\n }\r\n ```\r\n\r\n - **代码:**\r\n\r\n ```java\r\n for (String name:list) {\r\n System.out.println(name);\r\n }\r\n ```\r\n\r\n### 3.4-List集合\r\n\r\n> 介绍\r\n\r\n List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操 作集合的特有方法,如下:\r\n\r\n**方法**:\r\n\r\n1. `public void add(int index, E element)` : 将指定的元素,添加到该集合中的指定位置上。 \r\n2. `public E get(int index)` :返回集合中指定位置的元素。 \r\n3. `public E remove(int index)` : 移除列表中指定位置的元素, 返回的是被移除的元素。 \r\n4. `public E set(int index, E element)` :用指定元素替换集合中指定位置的元素,返回值的更新前的元素\r\n\r\n**特点**\r\n\r\n- 存取顺序一致,并且有索引。\r\n- 元素内容可重复。\r\n\r\n**代码**\r\n\r\n```java\r\n List list = new ArrayList();\r\n list.add(\"a\");\r\n list.add(\"b\");\r\n list.add(\"c\");\r\n // public void add(int index, E element) : 将指定的元素,添加到该集合中的指定位置上。\r\n list.add(1,\"d\");\r\n System.out.println(list); // [a, d, b, c]\r\n // public E get(int index) :返回集合中指定位置的元素。\r\n System.out.println(list.get(2)); // b\r\n // public E remove(int index) : 移除列表中指定位置的元素, 返回的是被移除的元素。\r\n list.remove(1);\r\n System.out.println(list); // [a, b, c]\r\n // public E set(int index, E element) :用指定元素替换集合中指定位置的元素,返回值的更新前的元素\r\n list.set(1,\"B\");\r\n System.out.println(list); // [a, B, c]\r\n```\r\n\r\n\r\n\r\n> **List集合实现类-ArrayList**\r\n\r\njava.util.ArrayList 集合数据存储的结构是数组结构。**元素增删慢,查找快**,用于日常开发中使用最多的功能为 **查询数据、遍历数据**,所以 ArrayList 是最常用的集合。 \r\n\r\n许多程序员开发时非常随意地使用ArrayList完成任何需求,并不严谨,这种用法是不提倡的。 \r\n\r\n> **List集合实现类-LinkedList**\r\n\r\njava.util.LinkedList 集合数据存储的结构是**链表结构**。方便元素添加、删除的集合。\r\n\r\n**常用方法:**\r\n\r\n1. `public void addFirst(E e)` :将指定元素插入此列表的开头。 \r\n2. `public void addLast(E e)` :将指定元素添加到此列表的结尾。 \r\n3. `public E getFirst()` :返回此列表的第一个元素。 \r\n4. `public E getLast() `:返回此列表的最后一个元素。 \r\n5. `public E removeFirst()` :移除并返回此列表的第一个元素。 \r\n6. `public E removeLast()` :移除并返回此列表的最后一个元素。 \r\n7. `public E pop()` :从此列表所表示的堆栈处弹出一个元素。 \r\n8. `public void push(E e) `:将元素推入此列表所表示的堆栈。 \r\n9. `public boolean isEmpty() `:如果列表不包含元素,则返回true。\r\n\r\n**代码:**\r\n\r\n```java\r\n LinkedList list = new LinkedList();\r\n list.add(\"a\");\r\n list.add(\"b\");\r\n // public void addFirst(E e) :将指定元素插入此列表的开头。\r\n list.addFirst(\"A\");\r\n // public void addLast(E e) :将指定元素添加到此列表的结尾。\r\n list.addLast(\"B\");\r\n System.out.println(list); // [A, a, b, B]\r\n // public E getFirst() :返回此列表的第一个元素。\r\n System.out.println(list.getFirst()); // A\r\n // public E getLast() :返回此列表的最后一个元素。\r\n System.out.println(list.getLast()); // B\r\n // public E removeFirst() :移除并返回此列表的第一个元素。\r\n list.removeFirst();\r\n // public E removeLast() :移除并返回此列表的最后一个元素。\r\n list.removeLast();\r\n System.out.println(list); //[a, b]\r\n // public E pop() :从此列表所表示的堆栈处弹出一个元素。\r\n list.pop();\r\n System.out.println(list); // [b]\r\n // public void push(E e) :将元素推入此列表所表示的堆栈。\r\n list.push(\"a\");\r\n System.out.println(list); // [a, b]\r\n // public boolean isEmpty() :如果列表不包含元素,则返回true。\r\n System.out.println(list.isEmpty()); // false\r\n```\r\n\r\n\r\n\r\n###3.5-Set集合\r\n\r\n> **概述**\r\n\r\n- 继承了Collectin集合\r\n- 没有扩充方法\r\n- 与List集合不同的是会`保证集合中元素的唯一性`。\r\n\r\n> **HashSet**\r\n\r\n**介绍:**\r\n\r\n- 集合中的元素`存取是无序的`\r\n\r\n- 集合中的元素是`不重复的` \r\n\r\n- HashSet 是`根据对象的哈希值来确定元素在集合中的存储位置`,因此具有良好的存取和查找性能。保证元素`唯一性`的方式依赖于: `hashCode `与 `equals` 方法。 \r\n\r\n- 代码:\r\n\r\n ```java\r\n Set set = new HashSet();\r\n set.add(\"张三\");\r\n set.add(\"李四\");\r\n set.add(\"李四\");\r\n set.add(\"王五\");\r\n set.add(\"赵六\");\r\n System.out.println(set); // [李四, 张三, 王五, 赵六]\r\n ```\r\n\r\n**哈希值:**\r\n\r\n- 一个十进制的逻辑地址。\r\n\r\n- 所有的对象都继承里Object中的HashCode方法\r\n\r\n- 代码:\r\n\r\n ```java\r\n System.out.println(\"a\".hashCode()); // 97\r\n System.out.println(\"b\".hashCode()); // 98\r\n System.out.println(\"张三\".hashCode()); // 774889\r\n System.out.println(\"李四\".hashCode()); // 842061\r\n int[]arr1={1,2,3};\r\n System.out.println(arr1.hashCode()); //1355531311\r\n ```\r\n\r\n**存储结构:**`数组+链表/红黑树`,在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。 但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找 时间。\r\n\r\n\r\n\r\n\r\n\r\n总而言之,JDK1.8引入红黑树大程度优化了HashMap的性能,那么对于我们来讲保证HashSet集合元素的唯一, 其实就是根据对象的hashCode和equals方法来决定的。`如果我们往集合中存放自定义的对象,那么保证其唯一, 就必须复写hashCode和equals方法建立属于当前对象的比较方式。`\r\n\r\n**代码:**要求自定义人物类型(含有姓名、年龄),用HashSet集合存储,若对象的姓名和年龄一致则在集合中不允许重复\r\n\r\n```java\r\n/*人物类*/\r\npublic class People {\r\n private String name;\r\n private int age;\r\n public People(String name, int age) {\r\n this.name = name;\r\n this.age = age;\r\n }\r\n @Override\r\n public boolean equals(Object o) {\r\n if (this == o) return true;\r\n if (o == null || getClass() != o.getClass()) return false;\r\n People people = (People) o;\r\n return age == people.age &&\r\n name.equals(people.name);\r\n }\r\n @Override\r\n public int hashCode() {\r\n return Objects.hash(name, age);\r\n }\r\n public String getName() {\r\n return name;\r\n }\r\n public void setName(String name) {\r\n this.name = name;\r\n }\r\n public int getAge() {\r\n return age;\r\n }\r\n public void setAge(int age) {\r\n this.age = age;\r\n }\r\n}\r\n\r\n// 入口类\r\npublic class Main {\r\n public static void main(String[] args) {\r\n People p1 = new People(\"张三\",10);\r\n People p2 = new People(\"李四\",12);\r\n People p4 = new People(\"李四\",12);\r\n People p3 = new People(\"王五\",10);\r\n\r\n HashSet set = new HashSet();\r\n set.add(p1);\r\n set.add(p2);\r\n set.add(p3);\r\n set.add(p4);\r\n System.out.println(set.size()); // 3\r\n }\r\n}\r\n\r\n```\r\n\r\n> **LinkedHashSet**\r\n\r\n- 组织结构:哈希表(数组+链表/红黑树) + 链表(记录存取顺序)\r\n\r\n- 特点:\r\n\r\n - 元素唯一性\r\n - 元素存取有序。\r\n\r\n- 代码:\r\n\r\n ```java\r\n LinkedHashSet set = new LinkedHashSet();\r\n set.add(\"张三\");\r\n set.add(\"李四\");\r\n set.add(\"李四\");\r\n set.add(\"王五\");\r\n set.add(\"赵六\");\r\n System.out.println(set); // [张三, 李四, 王五, 赵六]\r\n ```\r\n\r\n> **其他扩展-可变参数**\r\n\r\n- 格式:\r\n\r\n ```java\r\n 修饰符 返回值类型 方法名(参数类型... 形参名){ }\r\n 等价于\r\n 修饰符 返回值类型 方法名(参数类型[]形参名){ }\r\n ```\r\n\r\n- 代码:\r\n\r\n ```java\r\n public static void main(String[] args) {\r\n System.out.println(add(1,2,3)); // 6\r\n System.out.println(add(1,200,300,400)); // 901\r\n }\r\n \r\n public static int add(int...num) {\r\n int sum = 0;\r\n for (int i = 0; i < num.length; i++) {\r\n sum += num[i];\r\n }\r\n return sum;\r\n }\r\n ```\r\n\r\n- 原理:在编译成class文件时,源代码中的可变参数会自动变成数组。\r\n\r\n- 注意事项:\r\n\r\n - 可变参数类型要一致\r\n - 可变参数要放在参数列表最后\r\n\r\n### 3.6 Collections\r\n\r\n> **介绍**\r\n\r\njava.utils.Collections 是**集合工具类**,用来对集合进行操作 。常用的方法如下:\r\n\r\n1. `public static <T> boolean addAll(Collection<T> c, T... elements)` :往集合中添加一些元素。 \r\n2. `public static void shuffle(List<?> list)` 打乱顺序 :打乱集合顺序。 \r\n3. `public static <T> void sort(List<T> list)` :将集合中元素按照默认规则排序。\r\n4. `public static <T> void sort(List<T> list,Comparator<? super T> ) `:将集合中元素按照指定规则排序。\r\n\r\n> **代码**\r\n\r\n```java\r\n ArrayList<Integer> list = new ArrayList<>();\r\n //public static <T> boolean addAll(Collection<T> c, T... elements) :往集合中添加一些元素。\r\n Collections.addAll(list, 1, 3, 4, 5, 6, 8, 7);\r\n System.out.println(list); // [1, 3, 4, 5, 6, 8, 7]\r\n //public static void shuffle(List<?> list) 打乱顺序 :打乱集合顺序。\r\n Collections.shuffle(list);\r\n System.out.println(list); // 随机顺序[5, 4, 6, 3, 7, 1, 8]\r\n //public static <T> void sort(List<T> list) :将集合中元素按照默认规则排序。\r\n Collections.sort(list);\r\n System.out.println(list); // 默认升序[1, 3, 4, 5, 6, 7, 8]\r\n //public static <T> void sort(List<T> list,Comparator<? super T> ) :将集合中元素按照指定规则排序。\r\n Collections.sort(list, new Comparator<Integer>() {\r\n @Override\r\n public int compare(Integer o1, Integer o2) {\r\n return o2-o1;\r\n }\r\n });\r\n System.out.println(list);\r\n```\r\n\r\n> **注意事项**\r\n\r\n若要对自定义对象进行排序时,使用 `sort(List<T> list)`时,自定义类型需要实现Comparable接口,并且要重新CompareTo方法\r\n\r\n```java\r\npublic class People implements Comparable<People> {\r\n private String name;\r\n private int age;\r\n\r\n public People(String name, int age) {\r\n this.name = name;\r\n this.age = age;\r\n }\r\n\r\n public String getName() {\r\n return name;\r\n }\r\n\r\n public void setName(String name) {\r\n this.name = name;\r\n }\r\n\r\n public int getAge() {\r\n return age;\r\n }\r\n\r\n public void setAge(int age) {\r\n this.age = age;\r\n }\r\n\r\n @Override\r\n public int compareTo(People o) {\r\n return this.age - o.age; // 升序\r\n // return o.age-this.age; 降序\r\n\r\n }\r\n}\r\n\r\n```\r\n\r\n\r\n\r\n## 第四章:双列集合-Map系列\r\n\r\n### 4.1-Map集合介绍\r\n\r\n- Map集合是一个存储数据的容器。\r\n- Map集合存储数据的方式是**键值对(key/value)**。\r\n - key键不可以重复(若重复设置,则会覆盖原有key对应的值)。\r\n - value值可以重复。\r\n- Map集合的底层数据结构是**哈希表(数组+链表/红黑树)。**\r\n\r\n\r\n\r\n### 4.2-Map集合常用子类\r\n\r\n> **HashMap**\r\n\r\n- 是**Map**集合的一个子类\r\n- 底层数据结构是哈希表\r\n- 键不可以重复(键对应的类型需要重写了hashCode()和equals方法()),值可以重复。\r\n- 存取是无序的(存取顺序可能不一致)\r\n\r\n> **LinkedHashMap**\r\n\r\n- 是**HashMap**集合的一个子类\r\n- 底层数据结构是哈希表+链表(记录存取顺序)\r\n- 键不可以重复(键对应的类型需要重写了hashCode()和equals方法()),值可以重复。\r\n- 存取是有序的(存取的顺序一定是一致的)\r\n\r\n> **注意事项**\r\n\r\nMap接口中的集合都有两个泛型变量,在使用时,要为两个泛型变量赋予数据类型。两个泛型变量的数 据类型可以相同,也可以不同\r\n\r\n`public interface Map<K, V>{}`\r\n\r\n\r\n\r\n### 4.3-Map集合常用方法\r\n\r\n> **方法**\r\n\r\n1. `public V put(K key, V value)` : 把指定的键与指定的值添加到Map集合中。 \r\n2. `public V remove(Object key)` : 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。\r\n3. `public V get(Object key)` 根据指定的键,在Map集合中获取对应的值。 \r\n4. `public Set<K> keySet() `: 获取Map集合中所有的键,存储到Set集合中。 \r\n5. `public Set<Map.Entry<K,V>> entrySet()` : 获取到Map集合中所有的键值对对象的集合(Set集合)。\r\n\r\n> **代码**\r\n\r\n```java\r\n // 创建HashMap对象\r\n HashMap<String,String> hash = new HashMap<>();\r\n // 【添加数据-public V put(K key, V value)】\r\n hash.put(\"郭靖\",\"华筝\");\r\n hash.put(\"郭靖\",\"黄蓉\"); // 会覆盖之前重复的键值对\r\n hash.put(\"杨过\",\"小龙女\");\r\n hash.put(\"张无忌\",\"赵敏\");\r\n hash.put(\"宋青书\",\"周芷若\");\r\n System.out.println(hash); // {杨过=小龙女, 宋青书=周芷若, 郭靖=黄蓉, 张无忌=赵敏}\r\n // 【移除数据-public V remove(Object key)】\r\n hash.remove(\"宋青书\");\r\n System.out.println(hash); // {杨过=小龙女, 郭靖=黄蓉, 张无忌=赵敏}\r\n // 【根据指定的键获取对应的值-public V get(Object key)】\r\n String value = hash.get(\"郭靖\");\r\n System.out.println(value); // 黄蓉\r\n // 【获取Map集合中所有的键存储到Set集合中-public Set<K> keySet()】\r\n Set<String> set = hash.keySet();\r\n Iterator<String> iterator = set.iterator();\r\n while ((iterator.hasNext())){\r\n String key = iterator.next();\r\n System.out.println(key + \"-\" + hash.get(key));\r\n }\r\n // 【获取Map集合中的Entry对象-public Set<Map.Entry<K,V>> entrySet()】\r\n // Map集合中存储了一组Entry对象,entry对象包装了每一对key/value\r\n // Entry对象可以通过getKey()方法获取键,通过getValue方法获取对应的值\r\n Set<Map.Entry<String,String>> set2 = hash.entrySet();\r\n for (Map.Entry<String,String> entry:set2) {\r\n System.out.println(entry.getKey() + \'|\'+entry.getValue());\r\n }\r\n```\r\n\r\n\r\n\r\n### 4.4-Map集合中定义自定义类型的键值对\r\n\r\n对应自定义类型的键的类型中需要重新hashCode和equals方法\r\n\r\n```java\r\n//【执行类】\r\npublic class Main {\r\n public static void main(String[] args) {\r\n // 存储一组学生信息,要求集合中不允许出现同名同年龄的键\r\n HashMap<Student,Integer> hash = new HashMap<>();\r\n hash.put(new Student(\"张三\",18),10010);\r\n hash.put(new Student(\"张三\",18),10086);\r\n hash.put(new Student(\"李四\",17),10010);\r\n hash.put(new Student(\"王五\",17),10010);\r\n System.out.println(hash.size()); // 3个\r\n }\r\n}\r\n//【学生类】\r\npublic class Student {\r\n private String name;\r\n private int age;\r\n public Student() {}\r\n public Student(String name, int age) {\r\n this.name = name;\r\n this.age = age;\r\n }\r\n public String getName() {\r\n return name;\r\n }\r\n public void setName(String name) {\r\n this.name = name;\r\n }\r\n public int getAge() {\r\n return age;\r\n }\r\n public void setAge(int age) {\r\n this.age = age;\r\n }\r\n @Override\r\n public boolean equals(Object o) {\r\n if (this == o) return true;\r\n if (o == null || getClass() != o.getClass()) return false;\r\n Student student = (Student) o;\r\n return age == student.age &&\r\n name.equals(student.name);\r\n }\r\n @Override\r\n public int hashCode() {\r\n return Objects.hash(name, age);\r\n }\r\n}\r\n```\r\n\r\n\r\n\r\n### 4.5-LinkedHashMap\r\n\r\n- **介绍:**是HashMap的子类,存取数据是有序的。\r\n\r\n- **代码:**\r\n\r\n ```java\r\n LinkedHashMap<String,String> hash = new LinkedHashMap<>();\r\n hash.put(\"郭靖\",\"黄蓉\");\r\n hash.put(\"杨过\",\"小龙女\");\r\n hash.put(\"张无忌\",\"赵敏\");\r\n hash.put(\"宋青书\",\"周芷若\");\r\n System.out.println(hash);//{郭靖=黄蓉, 杨过=小龙女, 张无忌=赵敏, 宋青书=周芷若}\r\n ```\r\n\r\n \r\n\r\n### 4.6-HashTable\r\n\r\n- HashTable集合\r\n\r\n - 底层是线程是安全的,单线程,执行速度慢\r\n - 底层数据结构是哈希表\r\n - 之前学习的集合可以存储null键和null值,但是HashTable不可以\r\n\r\n- 代码:\r\n\r\n ```java\r\n HashMap<String,Integer> map = new HashMap<>();\r\n map.put(null,1);\r\n map.put(\"a\",null);\r\n System.out.println(map);\r\n Hashtable<String,Integer> table = new Hashtable<>();\r\n table.put(null,1); // 报错NullPointerException\r\n table.put(\"a\",null);\r\n System.out.println(table);\r\n ```\r\n\r\n \r\n\r\n### 4.7-JDK9对集合添加的优化\r\n\r\n- 在创建少量元素的集合时,使用JDK9中提供的静态方法of添加更加合适\r\n\r\n- 注意事项:\r\n\r\n - of方法只能被接口List、Set、Map接口调用,不能用子类或实现类调用。\r\n - of方法初始化后的集合不能更改。\r\n\r\n- 代码:\r\n\r\n ```java\r\n List<String> list = List.of(\"张三\",\"李四\",\"王五\",\"赵六\");\r\n Set<String> set = Set.of(\"张三\",\"李四\",\"王五\",\"赵六\");\r\n Map<String,Integer> map = Map.of(\"张三\",10,\"李四\",12);\r\n System.out.println(list); // [张三, 李四, 王五, 赵六]\r\n System.out.println(set); // [李四, 赵六, 张三, 王五]\r\n System.out.println(map); // {张三=10, 李四=12}\r\n ```\r\n\r\n',36,14,'2020-06-05 15:59:23',0,'JavaSE集合'),(22,'JavaSE异常','\r\n\r\n\r\n## 目录\r\n\r\n[TOC]\r\n\r\n##第一章:认识异常\r\n\r\n### 1.1-什么是异常?\r\n\r\n 异常,在Java程序中指的是:因**程序问题**而中断程序执行的现象。\r\n\r\n\r\n\r\n### 1.2-异常分类\r\n\r\n\r\n\r\n\r\n 在Java中,为了维护程序正常执行,Java提供了处理异常的异常机制(异常类)。\r\n\r\n 在Java提供的异常机制中,其中**java.lang.Throwable**是根类,而根类的派生类有**java.lang.Error**和**java.lang.Excepiton**两个子类。\r\n\r\n **Error**,错误(绝症),该类型异常在程序中无法处理,只能尽量避免。\r\n\r\n **Excepiton**,编译期异常(写源代码时)(小毛病,比如:类似感冒),该类型异常在程序中是可处理的。Excepiton类型还有一个子类**RunTimeException**,表示运行期异常(程序运行的过程中),该类型异常在程序中也是可处理的。\r\n\r\n 为了更好的区分以上描述的异常分类,我们看以下程序。\r\n\r\n```java\r\n// 【Error异常】\r\n// Exception in thread \"main\" java.lang.OutOfMemoryError: Java heap space\r\n// 内存溢出。超出了分配给JVM内存大小。\r\n// 该程序只能修改源代码解决问题。\r\nint[]nums = new int[1024*1024*1024];\r\n\r\n// 【Exception异常】\r\nSimpleDateFormat format = new SimpleDateFormat(\"yyyy-MM-dd\");\r\n// Unhandled exception: java.text.ParseException\r\n// 此处在编写源代码时就发生异常,该异常后续可以通过相关的处理机制处理和避免\r\nDate date = format.parse(\"2083-10-10\"); \r\n\r\n// 【RunTimeException异常】\r\nint[] nums = {1,2,3};\r\n// Exception in thread \"main\" java.lang.ArrayIndexOutOfBoundsException\r\n// 在程序运行时出现的异常,数组下标超出。该异常可以通过相关异常机制处理\r\nSystem.out.println(nums[4]);\r\n\r\n```\r\n\r\n\r\n\r\n###1.3-异常产生的过程\r\n\r\n在编程中,为了更好的处理异常,我们首先要了解异常产生的过程。下面通过一段异常代码来分析。\r\n\r\n+ **代码**:\r\n\r\n ```java\r\n // 需求:定义一个方法,用来获取指定数组的指定位置的元素\r\n public static void main(String[] args) {\r\n int[]nums = {1,2,3};\r\n System.out.println(getElement(nums,4));\r\n }\r\n public static int getElement(int[]arr,int index){\r\n return arr[index];\r\n }\r\n ```\r\n\r\n+ **分析**:\r\n\r\n 1. 在执行getElement方法的过程中,JVM检测出数组索引越界异常,此时JVM会做两件事:\r\n + 创建一个**ArrayIndexOutOfBoundsException**异常对象,该对象中包含了异常的信息(内容、原因、位置)\r\n + 因为getElment方法中没有异常处理,JVM会把异常对象抛给getElment方法的调用处-main方法。\r\n 2. main方法接收到了**ArrayIndexOutOfBoundsException**异常对象,也没有处理异常,则会把异常对象抛给JVM\r\n 3. JVM接收到了main方法后,会做两件事:\r\n + 在控制台打印异常信息(内容、原因、位置)\r\n + 终止程序的执行。\r\n\r\n+ **图解**:\r\n \r\n\r\n\r\n\r\n\r\n## 第二章:处理异常\r\n\r\n### 2.1 关键字\r\n\r\n异常相关的关键字有:\r\n\r\n+ `try`\r\n+ `catch`\r\n+ `finally`\r\n+ `throw`\r\n+ `throws`\r\n\r\n\r\n\r\n### 2.2 抛出异常-throw\r\n\r\n+ **作用**:在指定的方法内部抛出异常。\r\n\r\n+ **格式**:`throw new xxxException(参数)` \r\n\r\n + 参数是字符串格式,用来描述异常。\r\n\r\n+ **注意事项:**\r\n\r\n 1. throw关键字必须写在方法内部。\r\n 2. throw关键字后创建的对象必须是Exception或Exception的子类对象。\r\n 3. throw关键字抛出的异常对象,我们必须处理异常对象。\r\n + throw关键字抛出的时RunTimeException或RunTimeException子类异常对象时,我们可以不处理,因为JVM会默认处理(打印异常信息、中断程序)。\r\n + throw关键字抛出的时编译期异常时(编写源代码时),我们必须处理(可以通过throws或try-catch的方式处理)\r\n\r\n+ **代码**:\r\n\r\n ```java\r\n public static void main(String[] args) {\r\n int[]nums = {1,2,3};\r\n // getElement(nums,4); // 异常:java.lang.ArrayIndexOutOfBoundsException: 索引越界:4\r\n int[]nums2=null;\r\n getElement(nums2,0); // 异常java.lang.NullPointerException: 传入的数组对象为null\r\n }\r\n public static int getElement(int[]arr,int index){\r\n if(arr==null){\r\n throw new NullPointerException(\"传入的数组对象为null\");\r\n }\r\n if(index<0||index>=arr.length){\r\n throw new ArrayIndexOutOfBoundsException(\"索引越界:\" + index);\r\n }\r\n return arr[index];\r\n }\r\n ```\r\n\r\n\r\n\r\n### 2.3 Objects非空判断\r\n\r\n Objects中提供了一个方法,可以检测一个对象为null并抛出异常,我们来应用一下,并查看其源代码更好的理解throw关键字的应用。\r\n\r\n+ 方法名称:`public static <T> T requireNonNull(T obj) `:查看指定引用对象不是null。\r\n\r\n+ 源代码:\r\n\r\n ```java\r\n public static <T> T requireNonNull(T obj) {\r\n if (obj == null)\r\n throw new NullPointerException();\r\n return obj;\r\n }\r\n ```\r\n\r\n+ 应用:\r\n\r\n ```java\r\n public static void main(String[] args) {\r\n int[]nums = {1,2,3};\r\n // getElement(nums,4); // 异常:java.lang.ArrayIndexOutOfBoundsException: 索引越界:4\r\n int[]nums2=null;\r\n getElement(nums2,0); // 异常java.lang.NullPointerException\r\n }\r\n public static int getElement(int[]arr,int index){\r\n Objects.requireNonNull(arr); // 应用\r\n if(index<0||index>=arr.length){\r\n throw new ArrayIndexOutOfBoundsException(\"索引越界:\" + index);\r\n }\r\n return arr[index];\r\n }\r\n ```\r\n\r\n### 2.4 声明异常-throws\r\n\r\n当程序抛出一个异常对象给调用者时,调用者必须要处理异常,此时可以选择**throws**关键字来处理。\r\n\r\nthrows关键字的作用就是把异常对象继续抛给其他调用者,若其他调用者没有处理异常,则最终会抛给JVM来处理。\r\n\r\n+ **格式**:\r\n\r\n ```java\r\n /*\r\n 修饰符 返回值类型 方法名() throws XXX1Exception,XXX2Exception... {\r\n throw new XXX1Exception(\"产生的原因\");\r\n throw new XXX2Exception(\"产生的原因\");\r\n }\r\n */\r\n ```\r\n\r\n+ **注意事项**:\r\n\r\n 1. throws关键字必须写在方法声明出。\r\n 2. throws关键字后声明的异常必须是Exception或Exception子类异常。\r\n 3. 方法内部抛出多个异常时,那么throws关键字后必须声明多个异常。\r\n + 如果抛出的多个异常之间存在继承关系,那么直接抛出父类即可。\r\n 4. 调用了一个声明异常的方法,调用者必须处理该异常,处理方式有:\r\n + 调用者继续声明throws抛给其他调用者。最终抛给JVM。\r\n + 在调用者内部通过try-catch处理异常。\r\n\r\n+ **代码**\r\n\r\n ```java\r\n // 需求:定义一个读取文件的方法,检测文件的路径和后缀名\r\n public static void main(String[] args) throws Exception {\r\n retFile(\"a.txt\");\r\n }\r\n // 读取文件的方法\r\n // private static void retFile(String path) throws FileNotFoundException,IOException {\r\n private static void retFile(String path) throws IOException {\r\n // 如果后缀名不是.txt则抛出\r\n if(!path.contains(\".txt\")){\r\n throw new FileNotFoundException(\"文件后缀名不是.txt\");\r\n }\r\n if(path.contains(\"c:\")) {\r\n throw new IOException(\"文件路径错误\");\r\n }\r\n }\r\n ```\r\n\r\n\r\n\r\n### 2.5 捕获处理异常-(try-catch)\r\n\r\nthrows声明异常的弊端是:异常后续代码无法执行(因为交给了JVM,JVM会终止程序)。\r\n\r\n**try-catch可以让调用者处理异常,并会继续执行后续程序。**\r\n\r\n+ **格式**\r\n\r\n ```java\r\n try {\r\n // 可能会发生异常的程序\r\n // 若发生异常,try会检测到异常对象\r\n }catch(异常类型 变量) {\r\n // catch会接收到异常对象,可以在此代码块中进行处理\r\n }catch(异常类型 变量) {\r\n // catch会接收到异常对象,可以在此代码块中进行处理\r\n }...\r\n ```\r\n\r\n+ **throwable中常用的异常方法**\r\n\r\n + public String getMessage() :获取异常的描述信息,原因(提示给用户的时候,就提示错误原因。 \r\n + public String toString() :获取异常的类型和异常描述信息(不用)。 \r\n + public void printStackTrace() :打印异常的跟踪栈信息并输出到控制台。 \r\n + 包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace。 \r\n\r\n+ **代码**\r\n\r\n ```java\r\n // 需求:定义一个读取文件的方法,检测文件的路径和后缀名\r\n public static void main(String[] args) {\r\n try{\r\n retFile(\"d.txt\");\r\n }catch (Exception e) {\r\n e.printStackTrace();\r\n }\r\n System.out.println(\"后续代码\");\r\n /*\r\n 执行结果:\r\n java.io.IOException: 文件路径错误\r\n at it.leilei.cn.demo01.Main01.retFile(Main01.java:27)\r\n at it.leilei.cn.demo01.Main01.main(Main01.java:12)\r\n 后续代码\r\n */\r\n \r\n \r\n }\r\n // 读取文件的方法\r\n private static void retFile(String path) throws IOException {\r\n // 如果后缀名不是.txt则抛出\r\n if(!path.contains(\".txt\")){\r\n throw new FileNotFoundException(\"文件后缀名不是.txt\");\r\n }\r\n if(!path.contains(\"c:\")) {\r\n throw new IOException(\"文件路径错误\");\r\n }\r\n }\r\n \r\n \r\n ```\r\n\r\n\r\n\r\n### 2.6 finally代码块\r\n\r\n+ **finally关键字的作用**:有一些特定的代码无论异常是否发生,都需要执行。另外,因为**异常会引发程序跳转**,导致有些语句执行 不到。而finally就是解决这个问题的,在finally代码块中存放的代码都是一定会被执行的。\r\n\r\n+ **finally使用格式**:**try...catch...finally**自身需要处理异常,最终还得关闭资源。 \r\n\r\n+ **finally关键字的应用场景**:当我们在try语句块中打开了一些物理资源(磁盘文件/网络连接/数据库连接等),我们都得在使用完之后,最终关闭打开的资源。 \r\n\r\n+ **代码:**\r\n\r\n ```java\r\n // 需求:定义一个读取文件的方法,检测文件的路径和后缀名\r\n public static void main(String[] args) {\r\n try{\r\n retFile(\"d.txt\");\r\n }catch (Exception e) {\r\n e.printStackTrace();\r\n }finally {\r\n System.out.println(\"释放资源\");\r\n }\r\n System.out.println(\"后续代码\");\r\n \r\n \r\n }\r\n // 读取文件的方法\r\n private static void retFile(String path) throws IOException {\r\n // 如果后缀名不是.txt则抛出\r\n if(!path.contains(\".txt\")){\r\n throw new FileNotFoundException(\"文件后缀名不是.txt\");\r\n }\r\n if(!path.contains(\"c:\")) {\r\n throw new IOException(\"文件路径错误\");\r\n }\r\n }\r\n ```\r\n\r\n \r\n\r\n### 2.7 异常处理注意事项\r\n\r\n+ 多个异常使用捕获又该如何处理呢? \r\n\r\n 1. 多个异常分别处理。\r\n 2. 多个异常一次捕获,多次处理。\r\n 3. 多个异常一次捕获一次处理 \r\n\r\n+ 一般我们是使用一次捕获多次处理方式,格式如下: \r\n\r\n + 格式:\r\n\r\n ```java\r\n try{ \r\n 编写可能会出现异常的代码 \r\n }catch(异常类型A e){ \r\n 当try中出现A类型异常,就用该catch来捕获. \r\n 处理异常的代码 \r\n //记录日志/打印异常信息/继续抛出异常 \r\n }catch(异常类型B e){ \r\n 当try中出现B类型异常,就用该catch来捕获. \r\n 处理异常的代码 \r\n //记录日志/打印异常信息/继续抛出异常 \r\n }\r\n ```\r\n\r\n + 注意:这种异常处理方式,要求多个catch中的异常不能相同,并且若catch中的多个异常之间有子父类异 \r\n\r\n 常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的catch处理。 \r\n\r\n+ 运行时异常被抛出可以不处理。即不捕获也不声明抛出(JVM默认会进行处理)。 \r\n\r\n+ 如果finally有return语句,永远返回finally中的结果,避免该情况.\r\n\r\n ```java\r\n public static void main(String[] args) {\r\n int num = getNum();\r\n System.out.println(num); // 100\r\n }\r\n // 读取文件的方法\r\n public static int getNum() {\r\n int a = 10;\r\n try {\r\n return a;\r\n }catch (Exception e){}\r\n finally {\r\n a = 100;\r\n return a;\r\n }\r\n }\r\n ```\r\n\r\n+ 如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异 常。\r\n\r\n ```java\r\n class Fu{\r\n // 如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异 常。\r\n public void show1() throws IndexOutOfBoundsException{};\r\n }\r\n class Zi extends Fu{\r\n // 子类重写父类方法时,抛出和父类相同的异常\r\n // public void show1() throws IndexOutOfBoundsException{};\r\n // 或者是父类异常的子类ArrayIndexOutOfBoundsException 继承 IndexOutOfBoundsException\r\n // public void show1() throws ArrayIndexOutOfBoundsException{};\r\n // 不抛出异常\r\n public void show1() {};\r\n }\r\n ```\r\n\r\n\r\n\r\n## 第三章:自定义异常类\r\n\r\n### 3.1 为什么要自定义异常类?\r\n\r\n 我们说了Java中不同的异常类,分别表示着某一种具体的异常情况,那么在开发中总是有些异常情况是SUN没有定义好的,此时我们根据自己业务的异常情况来定义异常类。例如年龄负数问题,考试成绩负数问题等等。 \r\n\r\n### 3.2 什么是自定义异常类?\r\n\r\n 在开发中根据自己业务的异常情况来定义异常类. \r\n\r\n 自定义一个业务逻辑异常: RegisterException。一个注册异常类。 \r\n\r\n### 3.3 如何定义异常类\r\n\r\n1. 自定义一个编译期异常: 自定义类 并继承于 java.lang.Exception 。 \r\n2. 自定义一个运行时期的异常类:自定义类 并继承于 java.lang.RuntimeException 。 \r\n\r\n### 3.4 代码\r\n\r\n要求:我们模拟注册操作,如果用户名已存在,则抛出异常并提示:亲,该用户名已经被注册。 \r\n\r\n```java\r\n public static void main(String[] args) {\r\n // 接收用户注册的用户名\r\n String name = new Scanner(System.in).next();\r\n try{\r\n check(name);\r\n }catch (RegisterException e){\r\n e.printStackTrace();\r\n return;\r\n }\r\n System.out.println(\"注册成功\");\r\n }\r\n\r\n public static void check(String n) throws RegisterException{\r\n String[]users={\"张三\",\"李四\",\"王五\"};\r\n for (int i = 0; i < users.length; i++) {\r\n if (n.equals( users[i])) {\r\n throw new RegisterException(\"亲,该用户名已经注册\");\r\n }\r\n }\r\n }\r\n \r\n\r\n```\r\n\r\n```java\r\npublic class RegisterException extends Exception{\r\n public RegisterException(){\r\n super();\r\n }\r\n public RegisterException(String message){\r\n super(message);\r\n }\r\n\r\n}\r\n\r\n```\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n \r\n\r\n',36,14,'2020-06-05 15:59:58',0,'JavaSE异常'),(23,'JavaSE多线程','\r\n[TOC]\r\n## 第一章:多线程基础\r\n\r\n想要设计一个程序,边打游戏边听歌,怎么设计? \r\n\r\n要解决上述问题,得使用多进程或者多线程来解决. \r\n\r\n### 1.1 并发和并行\r\n\r\n\r\n\r\n\r\n+ **并发**:指两个或多个事件在同一个时间段内发生 (交替执行)。\r\n + 在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每 一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。 \r\n\r\n+ **并行**:指两个或多个事件在同一时刻发生(同时发生)。 \r\n + 而在多个 CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行执行,即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核 CPU,便是多核处理器,核 越多,并行处理的程序越多,能大大的提高电脑运行的效率。 \r\n\r\n> 注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同 \r\n>\r\n> 理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个 \r\n>\r\n> 线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为 \r\n>\r\n> 线程调度。 \r\n\r\n\r\n\r\n### 1.2 线程与进程\r\n\r\n**进程**:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多 \r\n\r\n个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创 \r\n\r\n建、运行到消亡的过程。 \r\n\r\n\r\n\r\n**线程**:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程 \r\n\r\n中是可以有多个线程的,这个应用程序也可以称之为多线程程序。 \r\n\r\n\r\n\r\n\r\n**简而言之**:一个程序运行后至少有一个进程,一个进程中可以包含多个线程 \r\n\r\n\r\n\r\n### 1.3 线程调度\r\n\r\n+ **分时调度** \r\n\r\n 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。 \r\n\r\n+ **抢占式调度 **\r\n\r\n + 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),**Java使用的为抢占式调度。** \r\n \r\n\r\n\r\n + 多线程可以提高cpu的利用率\r\n\r\n > 大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。在同时运行的程序,"感觉这些软件好像在同一时刻运行着"。 \r\n >\r\n > 实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。\r\n\r\n\r\n\r\n## 第二章: Java中使用线程\r\n\r\n### 2.1 创建线程-方式1\r\n\r\n Java使用 java.lang.Thread 类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是 完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。 Java中通过继承Thread类来创建并启动多线程的步骤如下: \r\n\r\n+ 步骤:\r\n\r\n 1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把 run()方法称为线程执行体。 \r\n 2. 创建Thread子类的实例,即创建了线程对象 。\r\n 3. 调用线程对象的start()方法来启动该线程 。\r\n\r\n+ 代码:\r\n\r\n ```java\r\n /*测试类中的代码*/\r\n public class DemoThread {\r\n public static void main(String[] args) {\r\n MyThread mt = new MyThread(\"线程1\");\r\n mt.start(); // 启动线程1的任务\r\n MyThread mt2 = new MyThread(\"线程2\");\r\n mt2.start(); // 启动线程1的任务\r\n \r\n }\r\n }\r\n /*定义的线程类代码*/\r\n public class MyThread extends Thread {\r\n public MyThread(String name) {\r\n super(name);\r\n }\r\n \r\n @Override\r\n public void run() {\r\n for (int i = 0; i < 20; i++) {\r\n System.out.println(getName() + \"线程执行\" + i);\r\n }\r\n }\r\n }\r\n ```\r\n\r\n\r\n\r\n### 2.2 多线程原理\r\n\r\n 多个线程之间的程序不会影响彼此(比如一个线程崩溃了并不会影响另一个线程)。\r\n\r\n在Java中,main方法是程序执行的入口,也是Java程序的主线程。当在程序中开辟新的线程时,执行过程是这样的。\r\n\r\n+ **执行过程**\r\n\r\n + 首先main方法作为主程序先压栈执行。\r\n + 在主程序的执行过程中,若创建了新的线程,则内存中会另开辟一个新的栈来执行新的线程。\r\n + 每一个新的线程都会有一个新的栈来存放新的线程任务。\r\n + 栈与栈之间的任务不会互相影响。\r\n + CPU会随机切换执行不同栈中的任务。\r\n\r\n+ **图解执行过程:(上述代码为例)**\r\n \r\n\r\n\r\n \r\n\r\n### 2.3 Thread类常用方法\r\n\r\n+ **构造方法**\r\n\r\n + public Thread() :分配一个新的线程对象。 \r\n + public Thread(String name) :分配一个指定名字的新的线程对象。 \r\n + public Thread(Runnable target) :分配一个带有指定目标新的线程对象。 \r\n + public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。 \r\n\r\n+ **常用方法**\r\n\r\n + public String getName() :获取当前线程名称。 \r\n + public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。 \r\n + public void run() :此线程要执行的任务在此处定义代码。 \r\n + public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。 \r\n + public static Thread currentThread() :返回对当前正在执行的线程对象的引用。\r\n\r\n+ **代码:**\r\n\r\n ```java\r\n //【代码测试类】\r\n public class Main01 {\r\n public static void main(String[] args) {\r\n MyThread mt = new MyThread(\"线程1\");\r\n mt.start();\r\n // 打印线程名称\r\n System.out.println(mt.getName());\r\n System.out.println(\"当前线程是\" + Thread.currentThread().getName());\r\n // 每间隔一秒钟打印一个数字\r\n for (int i = 0; i < 60; i++) {\r\n System.out.println(i);\r\n try {\r\n // sleep抛出了异常,需要处理异常\r\n Thread.sleep(1000);\r\n } catch (InterruptedException e) {\r\n e.printStackTrace();\r\n }\r\n }\r\n }\r\n }\r\n //【MyThread类】\r\n public class MyThread extends Thread{\r\n public MyThread(){\r\n super();\r\n }\r\n // 构造函数中调用父类构造函数传入线程名称\r\n public MyThread(String name) {\r\n super(name);\r\n }\r\n @Override\r\n public void run() {\r\n // 打印线程名称\r\n System.out.println(this.getName());\r\n System.out.println(\"当前线程是\" + Thread.currentThread().getName());\r\n }\r\n }\r\n \r\n ```\r\n\r\n\r\n\r\n### 2.4 创建线程-方式2\r\n\r\n 翻阅API后得知创建线程的方式总共有两种,一种是继承Thread类方式,一种是实现Runnable接口方式 。\r\n\r\n+ **Runnable使用步骤**\r\n\r\n 1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。 \r\n 2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。\r\n 3. 调用线程对象的start()方法来启动线程。 \r\n\r\n+ **代码**\r\n\r\n ```java\r\n // 测试类\r\n public class Main01 {\r\n public static void main(String[] args) {\r\n // 创建Runnable对象\r\n RunnableImpl ra = new RunnableImpl();\r\n // 创建线程对象并传入Runnable对象\r\n Thread th = new Thread(ra);\r\n // 启动并执行线程任务\r\n th.start();\r\n }\r\n }\r\n // 【Runnable实现类】\r\n public class RunnableImpl implements Runnable {\r\n @Override\r\n public void run() {\r\n System.out.println(\"线程任务1\");\r\n }\r\n }\r\n ```\r\n\r\n+ **总结**\r\n\r\n + 通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个执行目标。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。 \r\n\r\n + 在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。\r\n\r\n + 实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是继承Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。 \r\n\r\n + Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。 \r\n\r\n 而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。 \r\n\r\n### 2.5 Runnable和Thread的关系\r\n\r\n 创建线程方式2好像比创建线程方式1操作要麻烦一些,为何要多此一举呢?\r\n\r\n 因为如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。 \r\n\r\n+ **总结:**实现Runnable接口比继承Thread类所具有的优势: \r\n\r\n + 适合多个相同的程序代码的线程去共享同一个资源。 \r\n + 可以避免java中的单继承的局限性。\r\n + 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。 \r\n + 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。 \r\n\r\n+ **扩展:**\r\n\r\n > 在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用 \r\n >\r\n > java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进程。 \r\n\r\n### 2.6 匿名内部类实现线程的创建\r\n\r\n使用线程的内匿名内部类方式,可以方便的实现每个线程执行不同的线程任务操作。\r\n\r\n简而言之,使用匿名内部类可以简化代码。\r\n\r\n+ **格式**:`new 类名/接口名(){ //重写父类/接口中的方法 }`\r\n\r\n+ **代码**\r\n\r\n ```java\r\n // 匿名内部类创建线程方式1\r\n new Thread(){\r\n @Override\r\n public void run() {\r\n System.out.println(Thread.currentThread().getName());\r\n }\r\n }.start();\r\n // 匿名内部类创建线程方式2\r\n new Thread(new Runnable() {\r\n @Override\r\n public void run() {\r\n System.out.println(Thread.currentThread().getName());\r\n }\r\n }).start();\r\n ```\r\n\r\n\r\n\r\n## 第三章: 线程安全问题\r\n\r\n### 3.1 线程安全概述\r\n\r\n 多个线程执行同一个任务并操作同一个数据时,就会造成数据的安全问题。我们通过以下案例来看线程安全问题。\r\n\r\n+ 案例需求:\r\n\r\n > 电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 "皮卡丘大战葫芦娃",本次电影的座位共100个 (本场电影只能卖100张票)。 \r\n >\r\n > 我们来模拟电影院的售票窗口,实现多个窗口同时卖 "皮卡丘大战葫芦娃"这场电影票(多个窗口一起卖这100张票) 需要窗口,采用线程对象来模拟;需要票,Runnable接口子类来模拟 。\r\n\r\n+ 案例代码实现:\r\n\r\n ```java\r\n //【操作票的任务代码类】\r\n public class RunnableImpl implements Runnable {\r\n // 线程任务要操作的数据\r\n private int ticket = 100;\r\n @Override\r\n public void run() {\r\n while (true){\r\n if(ticket>0){\r\n System.out.println(Thread.currentThread().getName() + \"正在卖第\" + ticket+\"张票\");\r\n ticket--;\r\n }\r\n }\r\n }\r\n }\r\n //【测试类】\r\n public class Main01 {\r\n public static void main(String[] args) {\r\n // 创建线程任务\r\n RunnableImpl ra = new RunnableImpl();\r\n // 创建第一个线程执行线程任务\r\n new Thread(ra).start();\r\n // 创建第二线程执行线程任务\r\n new Thread(ra).start();\r\n // 创建第三个线程执行线程任务\r\n new Thread(ra).start();\r\n \r\n }\r\n }\r\n ```\r\n\r\n+ **执行结果及问题**\r\n \r\n\r\n\r\n+ **原因** \r\n\r\n + **抢夺cpu执行权和线程执行时间是不确定的**,比如线程0抢到了cpu执行权并执行到了打印代码处,此时cpu又被线程1抢夺,其他线程处于等待线程1页执行到了打印代码处,没等ticket--,两个线程都打印了售票信息。\r\n\r\n+ **总结**:这种问题,几个窗口(线程)票数**不同步**了,这种问题称为线程不安全。 \r\n\r\n > 线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写 操作,一般来说,这个全局变量是线程安全的;**若有多个线程同时执行写操作,一般都需要考虑线程同步**, 否则的话就可能影响线程安全。 \r\n\r\n\r\n\r\n### 3.2 线程安全解决方案\r\n\r\n 上述我们知道,线程安全问题是因为线程在操作数据时不同步造成的,所以只要能够实现操作数据同步,就可以解决线程安全问题。\r\n\r\n 同步指的就是,当一个线程执行指定同步的代码任务时,其他线程必须等该线程操作完毕后再执行。\r\n\r\n 根据案例描述:窗口1线程进入操作的时候,窗口2和窗口3线程只能在外等着,窗口1操作结束,窗口1和窗口2和窗口3才有机会进入代码 去执行。也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU 资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。 \r\n\r\n 为了保证每个线程都能正常执行原子操作,**Java引入了线程同步机制(synchronize)。** \r\n\r\n 那么怎么去使用呢?有三种方式完成同步操作:\r\n\r\n1. 同步代码块\r\n2. 同步方法\r\n3. 同步锁\r\n\r\n\r\n\r\n### 3.3 同步代码块\r\n\r\n+ **同步代码块:**synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。 \r\n\r\n+ **格式:**`synchronized(同步锁){ 需要同步操作的代码 }`\r\n\r\n + 同步锁:对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁。\r\n + 锁对象可以是任意类型。 \r\n + 多个线程对象 要使用同一把锁。 \r\n + 注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着 。\r\n\r\n+ **代码:**\r\n\r\n ```java\r\n //【测试类】\r\n public class Main01 {\r\n public static void main(String[] args) {\r\n // 创建线程任务\r\n RunnableImpl ra = new RunnableImpl();\r\n // 创建第一个线程执行线程任务\r\n new Thread(ra).start();\r\n // 创建第二线程执行线程任务\r\n new Thread(ra).start();\r\n // 创建第三个线程执行线程任务\r\n new Thread(ra).start();\r\n \r\n }\r\n }\r\n //【线程任务类】\r\n public class RunnableImpl implements Runnable {\r\n // 线程任务要操作的数据\r\n private int ticket = 100;\r\n // 定义线程锁对象(任意对象)\r\n Object obj = new Object();\r\n @Override\r\n public void run() {\r\n while (true){\r\n synchronized (obj){\r\n if(ticket>0){\r\n try {\r\n Thread.sleep(10);\r\n System.out.println(Thread.currentThread().getName()+\"在售卖第\" + ticket + \"张票\");\r\n ticket--;\r\n } catch (InterruptedException e) {\r\n e.printStackTrace();\r\n }\r\n \r\n }\r\n }\r\n \r\n }\r\n }\r\n }\r\n ```\r\n\r\n \r\n\r\n### 3.4 同步方法\r\n\r\n+ **同步方法:**使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着\r\n\r\n+ **格式:**`public synchronized void method(){ // 可能会产生线程安全问题的代码 }`\r\n\r\n + 同步锁是谁?\r\n + 对于非static方法,同步锁就是this \r\n + 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。 \r\n\r\n+ **代码:**\r\n\r\n ```java\r\n //【测试类】\r\n public class Main01 {\r\n public static void main(String[] args) {\r\n // 创建线程任务\r\n RunnableImpl ra = new RunnableImpl();\r\n // 创建第一个线程执行线程任务\r\n new Thread(ra).start();\r\n // 创建第二线程执行线程任务\r\n new Thread(ra).start();\r\n // 创建第三个线程执行线程任务\r\n new Thread(ra).start();\r\n }\r\n }\r\n //【线程任务类】\r\n public class RunnableImpl implements Runnable {\r\n // 线程任务要操作的数据\r\n private int ticket = 100;\r\n @Override\r\n public void run() {\r\n while (true) {\r\n func();\r\n }\r\n \r\n }\r\n public synchronized void func() {\r\n if (ticket > 0) {\r\n try {\r\n Thread.sleep(10);\r\n } catch (InterruptedException e) {\r\n e.printStackTrace();\r\n }\r\n System.out.println(Thread.currentThread().getName() + \"在售卖第\" + ticket + \"张票\");\r\n ticket--;\r\n }\r\n }\r\n }\r\n ```\r\n\r\n \r\n\r\n### 3.5 同步锁\r\n\r\n+ **Lock:**java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作, 同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。 \r\n\r\n + 实现类:`ReentrantLock`\r\n\r\n+ **方法**:Lock锁也称同步锁,加锁与释放锁方法化了\r\n\r\n + `public void lock()` :加同步锁。 \r\n + `public void unlock()` :释放同步锁。 \r\n\r\n+ **代码:**\r\n\r\n ```java\r\n //【测试类】\r\n public class Main01 {\r\n public static void main(String[] args) {\r\n // 创建线程任务\r\n RunnableImpl ra = new RunnableImpl();\r\n // 创建第一个线程执行线程任务\r\n new Thread(ra).start();\r\n // 创建第二线程执行线程任务\r\n new Thread(ra).start();\r\n // 创建第三个线程执行线程任务\r\n new Thread(ra).start();\r\n }\r\n }\r\n //【线程任务类】\r\n public class RunnableImpl implements Runnable {\r\n // 线程任务要操作的数据\r\n private int ticket = 100;\r\n // 创建锁对象\r\n Lock lock = new ReentrantLock();\r\n @Override\r\n public void run() {\r\n while (true) {\r\n // 开启同步锁\r\n lock.lock();\r\n if (ticket > 0) {\r\n try {\r\n Thread.sleep(10);\r\n System.out.println(Thread.currentThread().getName() + \"在售卖第\" + ticket + \"张票\");\r\n ticket--;\r\n } catch (InterruptedException e) {\r\n e.printStackTrace();\r\n }finally {\r\n // 释放同步锁\r\n lock.unlock();\r\n }\r\n \r\n }\r\n }\r\n \r\n }\r\n }\r\n \r\n ```\r\n\r\n \r\n\r\n## 第四章: 线程状态\r\n\r\n\r\n\r\n\r\n### 4.1 线程状态介绍\r\n\r\n 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中, 有几种状态呢?在API中 java.lang.Thread.State 这个枚举中给出了**六种**线程状态:\r\n\r\n\r\n\r\n\r\n 我们不需要去研究这几种状态的实现原理,我们只需知道在做线程操作中存在这样的状态。那我们怎么去理解这几 个状态呢,新建与被终止还是很容易理解的,我们就研究一下线程从Runnable(可运行)状态与非运行状态之间 的转换问题。 \r\n\r\n\r\n\r\n### 4.2 TimedWaiting计时等待\r\n\r\n Timed Waiting在API中的描述为:一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态。\r\n\r\n 单独 的去理解这句话,真是玄之又玄,其实我们在之前的操作中已经接触过这个状态了,在哪里呢? 在我们写卖票的案例中,为了减少线程执行太快,现象不明显等问题,我们在run方法中添加了sleep语句,这样就 强制当前正在执行的线程休眠(暂停执行),以"减慢线程"。 \r\n\r\n 其实当我们调用了sleep方法之后,当前执行的线程就进入到"休眠状态",其实就是所谓的Timed Waiting(计时等 待),那么我们通过一个案例加深对该状态的一个理解。 \r\n\r\n+ **案例**:实现一个计数器,计数到100,在每个数字之间暂停1秒,每隔10个数字输出一个字符串 。\r\n\r\n+ **代码:**\r\n\r\n ```java\r\n public class MyThread extends Thread {\r\n public void run() {\r\n for (int i = 0; i < 100; i++) {\r\n if ((i) % 10 == 0) {\r\n System.out.println(\"‐‐‐‐‐‐‐\" + i);\r\n }\r\n System.out.print(i);\r\n try {\r\n Thread.sleep(1000);\r\n System.out.print(\" 线程睡眠1秒!\\n\");\r\n } catch (InterruptedException e) {\r\n e.printStackTrace();\r\n }\r\n }\r\n }\r\n public static void main(String[] args) {\r\n new MyThread().start();\r\n }\r\n }\r\n ```\r\n\r\n+ **总结:**通过案例可以发现,sleep方法的使用还是很简单的。我们需要记住下面几点: \r\n\r\n 1. 进入 TIMED_WAITING 状态的一种常见情形是调用的 sleep 方法,单独的线程也可以调用,不一定非要有协 作关系。 \r\n 2. 为了让其他线程有机会执行,可以将Thread.sleep()的调用放线程run()之内。这样才能保证该线程执行过程 中会睡眠 。\r\n 3. sleep与锁无关,线程睡眠到期自动苏醒,并返回到Runnable(可运行)状态 。\r\n\r\n+ **注意:**sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就 开始立刻执行。 \r\n\r\n+ **图解:**\r\n \r\n\r\n\r\n\r\n\r\n### 4.3 Blocked锁阻塞\r\n\r\n Blocked状态在API中的介绍为:一个正在阻塞等待一个监视器锁(锁对象)的线程处于这一状态 。\r\n\r\n 我们已经学完同步机制,那么这个状态是非常好理解的了。比如,线程A与线程B代码中使用同一锁,如果线程A获 取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态。 \r\n\r\n 这是由Runnable状态进入Blocked状态。除此Waiting以及Time Waiting状态也会在某种情况下进入阻塞状态。\r\n\r\n\r\n\r\n\r\n\r\n\r\n### 4.4 Waiting 无限等待\r\n\r\n Wating状态在API中介绍为:一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。 \r\n\r\n 我们通过一段代码来 学习一下: \r\n\r\n+ **代码**:\r\n\r\n ```java\r\n // 锁对象\r\n public static Object obj = new Object();\r\n \r\n public static void main(String[] args) {\r\n // 消费者线程\r\n new Thread(new Runnable() {\r\n @Override\r\n public void run() {\r\n while (true) {\r\n synchronized (obj) {\r\n System.out.println(Thread.currentThread().getName() + \"-顾客1:老板包子好了吗?\");\r\n try {\r\n // 等待\r\n obj.wait();\r\n } catch (InterruptedException e) {\r\n e.printStackTrace();\r\n }\r\n // 唤醒之后要执行的代码\r\n System.out.println(Thread.currentThread().getName() + \"顾客1:可以吃包子了。\");\r\n System.out.println(\"--------------------------------------\");\r\n }\r\n }\r\n }\r\n }).start();\r\n // 生产者线程\r\n new Thread(new Runnable() {\r\n @Override\r\n public void run() {\r\n while (true) {\r\n try {\r\n Thread.sleep(3000);\r\n } catch (InterruptedException e) {\r\n e.printStackTrace();\r\n }\r\n synchronized (obj) {\r\n System.out.println(\"等待3秒后...\");\r\n System.out.println(Thread.currentThread().getName() + \"老板说:包子好了!\");\r\n // 唤醒\r\n obj.notify();\r\n }\r\n }\r\n }\r\n }).start();\r\n }\r\n ```\r\n\r\n+ **分析:**\r\n\r\n > 通过上述案例我们会发现,一个调用了某个对象的 Object.wait 方法的线程会等待另一个线程调用此对象的 Object.notify()方法 或 Object.notifyAll()方法 。\r\n >\r\n > 其实waiting状态并不是一个线程的操作,它体现的是多个线程间的通信,可以理解为多个线程之间的协作关系, 多个线程会争取锁,同时相互之间又存在协作关系。就好比在公司里你和你的同事们,你们可能存在晋升时的竞 争,但更多时候你们更多是一起合作以完成某些任务。 \r\n >\r\n > 当多个线程协作时,比如A,B线程,如果A线程在Runnable(可运行)状态中调用了wait()方法那么A线程就进入 了Waiting(无限等待)状态,同时失去了同步锁。假如这个时候B线程获取到了同步锁,在运行状态中调用了 notify()方法,那么就会将无限等待的A线程唤醒。注意是唤醒,如果获取到锁对象,那么A线程唤醒后就进入 Runnable(可运行)状态;如果没有获取锁对象,那么就进入到Blocked(锁阻塞状态)。 \r\n\r\n+ **图解:**\r\n \r\n\r\n\r\n\r\n\r\n## 第五章: 等待唤醒机制\r\n\r\n### 5.1 线程间的通信\r\n\r\n+ **概念**:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。\r\n\r\n > 比如:线程A用来生成包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,那么线程A与线程B\r\n >\r\n >\r\n\r\n\r\n+ **为什么要处理线程间的通信**\r\n\r\n > 多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们 希望他们有规律的执行, 那么多线程之间需要一些**协调通信**,以此来帮我们达到多线程共同操作一份数据。 \r\n\r\n+ **如何保证线程间通信有效利用资源**\r\n\r\n > 多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。 \r\n\r\n### 5.2 什么是等待唤醒机制\r\n\r\n 这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。就好比在公司里你和你的同事们,你们可能存在在晋升时的竞争,但更多时候你们更多是一起合作以完成某些任务。 \r\n\r\n 就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。 \r\n\r\n **wait/notify 就是线程间的一种协作机制。**\r\n\r\n### 5.3 等待唤醒中的方法 \r\n\r\n 等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下: \r\n\r\n1. **wait**\r\n\r\n > 线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是"通知(notify)"在这个对象上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中。\r\n\r\n2. **notify**\r\n\r\n > 则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先 \r\n >\r\n > 入座。 \r\n\r\n3. **notifyAll**\r\n\r\n > 则释放所通知对象的 wait set 上的全部线程。 \r\n\r\n+ **注意事项**\r\n\r\n 1. 注意1:\r\n\r\n > 哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。 \r\n >\r\n > 总结如下: \r\n >\r\n > 如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态; \r\n >\r\n > 否则,从 wait set 出来,又进入 entry set,线程就从 WAITING 状态又变成 BLOCKED 状态 \r\n\r\n 2. 注意2:\r\n\r\n > 1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。 \r\n >\r\n > 2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。 \r\n >\r\n > 3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方 法。 \r\n\r\n\r\n\r\n### 5.4 代码演示\r\n\r\n等待唤醒机制其实就是经典的"生产者与消费者"的问题。 \r\n\r\n就拿生产包子消费包子来说等待唤醒机制如何有效利用资源。\r\n\r\n> 包子铺线程生产包子,吃货线程消费包子。当包子没有时(包子状态为false),吃货线程等待,包子铺线程生产包子(即包子状态为true),并通知吃货线程(解除吃货的等待状态),因为已经有包子了,那么包子铺线程进入等待状态。接下来,吃货线程能否进一步执行则取决于锁的获取情况。如果吃货获取到锁,那么就执行吃包子动作,包子吃完(包子状态为false),并通知包子铺线程(解除包子铺的等待状态),吃货线程进入等待。包子铺线程能否进一步执行则取决于锁的获取情况。 \r\n\r\n+ **包子类代码**\r\n\r\n ```java\r\n public class BaoZi {\r\n // 包子皮\r\n String pi;\r\n // 包子馅\r\n String xian;\r\n // 包子的状态 true有包子,false没有包子,默认是false\r\n boolean flag = false;\r\n }\r\n ```\r\n\r\n+ **生产者代码**\r\n\r\n ```java\r\n public class BaoZiPu extends Thread {\r\n // 包子\r\n BaoZi bz;\r\n public BaoZiPu(BaoZi bz){\r\n this.bz = bz;\r\n }\r\n // 线程任务\r\n @Override\r\n public void run() {\r\n int i = 0; // 用来交替生产不同的包子\r\n while (true){\r\n synchronized (bz){\r\n // 判断是否有包子\r\n if(bz.flag){\r\n // 有包子,则进入wait状态\r\n try {\r\n bz.wait();\r\n } catch (InterruptedException e) {\r\n e.printStackTrace();\r\n }\r\n }\r\n // 没有有包子,则等待3秒钟\r\n try {\r\n Thread.sleep(3000);\r\n } catch (InterruptedException e) {\r\n e.printStackTrace();\r\n }\r\n // 生产包子\r\n if(i%2==0){\r\n bz.pi=\"蔬菜面皮\";\r\n bz.xian=\"三鲜\";\r\n }else if(i%2==1) {\r\n bz.pi = \"米面皮\";\r\n bz.xian = \"牛肉\";\r\n }\r\n bz.flag = true;\r\n i++;\r\n System.out.println(\"包子铺线程\" + Thread.currentThread().getName() + \":生产好了\" + bz.pi+bz.xian + \"包子\" );\r\n // 唤醒相同锁的其他线程\r\n bz.notify();\r\n }\r\n }\r\n }\r\n }\r\n ```\r\n\r\n+ **消费者代码**\r\n\r\n ```java\r\n public class ChiHuo extends Thread{\r\n // 包子\r\n BaoZi bz;\r\n public ChiHuo(BaoZi bz) {\r\n this.bz = bz;\r\n }\r\n // 线程任务\r\n @Override\r\n public void run() {\r\n while (true){\r\n synchronized (bz){\r\n if(!bz.flag){\r\n // 没有包子,则等待\r\n try {\r\n bz.wait();\r\n } catch (InterruptedException e) {\r\n e.printStackTrace();\r\n }\r\n \r\n }\r\n // 有包子,则吃包子\r\n System.out.println(\"吃货线程\" + Thread.currentThread().getName() + \":正在吃\" + bz.pi + bz.xian + \"包子\");\r\n try {\r\n Thread.sleep(3000);\r\n } catch (InterruptedException e) {\r\n e.printStackTrace();\r\n }\r\n // 三秒钟后,吃完包子,并唤醒其他同步锁线程\r\n System.out.println(\"吃货线程\" + Thread.currentThread().getName() +\"吃完了包子\");\r\n bz.flag = false; // 没有包子了\r\n System.out.println(\"-----------------------------------------------------\");\r\n bz.notify();\r\n }\r\n }\r\n }\r\n }\r\n ```\r\n\r\n+ **测试代码**\r\n\r\n ```java\r\n public class Test {\r\n public static void main(String[] args) {\r\n // 创建包子对象\r\n BaoZi bz = new BaoZi();\r\n // 创建生产者\r\n new BaoZiPu(bz).start();\r\n // 创建消费者\r\n new ChiHuo(bz).start();\r\n }\r\n }\r\n ```\r\n\r\n\r\n\r\n##第六章:线程池\r\n\r\n### 6.1 线程池思想介绍\r\n\r\n 我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: \r\n\r\n 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样**频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间**。 \r\n\r\n 那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务? \r\n\r\n 在Java中可以通过线程池来达到这样的效果。\r\n\r\n### 6.2 什么是线程池\r\n\r\n 其实就是一个**容纳多个线程的容器**,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。 \r\n\r\n\r\n\r\n\r\n+ **合理使用线程池的好处:**\r\n 1. 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。 \r\n 2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。 \r\n 3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。 \r\n\r\n### 6.3 使用线程\r\n\r\n Java里面线程池的顶级接口是 `java.util.concurrent.Executor` ,但是严格意义上讲 Executor 并不是一个线程 池,而只是一个执行线程的工具。真正的线程池接口是 `java.util.concurrent.ExecutorService` 。 \r\n\r\n 要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在 **java.util.concurrent.Executors** 线程工厂类里面提供了一些静态工厂,生成一些常用的线程池。官方建议使用Executors工程类来创建线程池对象。 \r\n\r\n+ **Executors类中有个创建线程池的方法如下**:\r\n\r\n + **public static ExecutorService newFixedThreadPool(int nThreads)** :返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量) \r\n\r\n+ **获取到了一个线程池ExecutorService 对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下:**\r\n\r\n + public Future<?> submit(Runnable task) :获取线程池中的某一个线程对象,并执行 \r\n + Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用。\r\n\r\n+ **使用线程池中线程对象的步骤:** \r\n\r\n 1. 创建线程池对象。 \r\n 2. 创建Runnable接口子类对象。(task) \r\n 3. 提交Runnable接口子类对象。(take task) \r\n 4. 关闭线程池(一般不做)。 \r\n\r\n+ **代码**\r\n\r\n ```java\r\n public class Test {\r\n public static void main(String[] args) {\r\n // 线程任务对象1\r\n Runnable task = new Runnable(){\r\n @Override\r\n public void run() {\r\n System.out.println(Thread.currentThread().getName() + \":执行了任务1\");\r\n }\r\n };\r\n // 线程任务对象2\r\n Runnable task2 = new Runnable(){\r\n @Override\r\n public void run() {\r\n System.out.println(Thread.currentThread().getName() + \":执行了任务2\");\r\n }\r\n };\r\n // 创建线程池\r\n ExecutorService pool = Executors.newFixedThreadPool(2);\r\n // 提交任务\r\n pool.submit(task);\r\n pool.submit(task2);\r\n pool.submit(task);\r\n pool.submit(task2);\r\n pool.submit(task);\r\n pool.submit(task2);\r\n // 关闭线程池,不建议关闭\r\n // pool.shutdown();\r\n }\r\n }\r\n ```\r\n\r\n ',36,14,'2020-06-05 16:00:54',0,'JavaSE多线程'),(24,'JavaSE-Lambda表达式','\r\n## 目录\r\n[TOC]\r\n## 第一章:函数式编程介绍\r\n\r\n\r\n\r\n\r\n在数学中,函数就是有输入量、输出量的一套计算方案,也就是"拿什么东西做什么事情"。相对而言,面向对象过 \r\n\r\n分强调"必须通过对象的形式来做事情",而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以 \r\n\r\n什么形式做。 \r\n\r\n面向对象的思想: 做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情。\r\n\r\n函数式编程思想: 只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程 。\r\n\r\n\r\n\r\n## 第二章: 冗余的Runnable代码\r\n\r\n+ **传统写法:**\r\n\r\n ```java\r\n // 当需要启动一个线程去完成任务时,通常会通过 java.lang.Runnable 接口来定义任务内容,并使用 java.lang.Thread 类来启动该线程。代码如下:\r\n public class Demo01Runnable { \r\n public static void main(String[] args) { \r\n // 匿名内部类 \r\n Runnable task = new Runnable() { \r\n @Override \r\n public void run() {\r\n // 覆盖重写抽象方法 \r\n System.out.println(\"多线程任务执行!\");\r\n } \r\n };\r\n new Thread(task).start(); // 启动线程 \r\n }\r\n }\r\n // 本着"一切皆对象"的思想,这种做法是无可厚非的:首先创建一个 Runnable 接口的匿名内部类对象来指定任务内 容,再将其交给一个线程来启动。\r\n ```\r\n\r\n+ **代码分析:**\r\n\r\n 1. Thread 类需要 Runnable 接口作为参数,其中的抽象 run 方法是用来指定线程任务内容的核心; \r\n 2. 为了指定 run 的方法体,不得不需要 Runnable 接口的实现类; \r\n 3. 为了省去定义一个 RunnableImpl 实现类的麻烦,不得不使用匿名内部类; \r\n 4. 必须覆盖重写抽象 run 方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错; \r\n 5. 而实际上,似乎只有方法体才是关键所在。 \r\n\r\n \r\n\r\n## 第三章: 编程思想转换\r\n\r\n+ **做什么,而不是怎么做**\r\n\r\n > 我们真的希望创建一个匿名内部类对象吗?不。我们只是为了做这件事情而不得不创建一个对象。我们真正希望做的事情是:将 run 方法体内的代码传递给 Thread 类知晓。 \r\n >\r\n > 传递一段代码——这才是我们真正的目的。而创建对象只是受限于面向对象语法而不得不采取的一种手段方式。 \r\n >\r\n > 那有没有更加简单的办法?如果我们将关注点从"怎么做"回归到"做什么"的本质上,就会发现只要能够更好地达 到目的,过程与形式其实并不重要\r\n\r\n+ **生活举例**\r\n\r\n > \r\n\r\n >\r\n > 当我们需要从北京到上海时,可以选择高铁、汽车、骑行或是徒步。我们的真正目的是到达上海,而如何才能到达上海的形式并不重要,所以我们一直在探索有没有比高铁更好的方式——搭乘飞机。 \r\n >\r\n > \r\n\r\n >\r\n > 而现在这种飞机(甚至是飞船)已经诞生:**2014年3月Oracle所发布的Java 8(JDK 1.8)中,加入了Lambda表达式的重量级新特性,为我们打开了新世界的大门。 **\r\n >\r\n > \r\n\r\n## 第四章:体验Lambda的更优写法\r\n\r\n 借助Java 8的全新语法,上述 Runnable 接口的匿名内部类写法可以通过更简单的Lambda表达式达到等效: \r\n\r\n```java\r\npublic class Test {\r\n public static void main(String[] args) {\r\n new Thread(()->System.out.println(\"多线程任务执行!\")).start();\r\n }\r\n}\r\n```\r\n\r\n 这段代码和刚才的执行效果是完全一样的,可以在1.8或更高的编译级别下通过。从代码的语义中可以看出:我们启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定。 \r\n\r\n 不再有"不得不创建接口对象"的束缚,不再有"抽象方法覆盖重写"的负担,就是这么简单!\r\n\r\n## 第五章:Lambda标准格式\r\n\r\n+ **Lambda省去面向对象的条条框框,格式由3个部分组成:** \r\n 1. 一些参数 \r\n 2. 一个箭头 \r\n 3. 一段代码 \r\n+ **标准格式:** `(参数类型 参数名称) ‐> { 代码语句 } `\r\n+ **格式说明:**\r\n 1. 小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。 \r\n 2. `->` 是新引入的语法格式,代表指向动作。 \r\n 3. 大括号内的语法与传统方法体要求基本一致。 \r\n\r\n## 第六章:无参数无返回值的代码\r\n\r\n+ **题目**\r\n\r\n + 给定一个厨子 Cook 接口,内含唯一的抽象方法 makeFood ,且无参数、无返回值。如下: \r\n\r\n + 代码:\r\n\r\n ```java\r\n public interface Cook { void makeFood(); }\r\n // 在下面的代码中,请使用Lambda的标准格式调用 invokeCook 方法,打印输出"吃饭啦!"字样:\r\n public class Demo05InvokeCook { \r\n public static void main(String[] args) { \r\n // TODO 请在此使用Lambda【标准格式】调用invokeCook方法 \r\n }\r\n private static void invokeCook(Cook cook) { \r\n cook.makeFood(); \r\n } \r\n }\r\n ```\r\n\r\n+ **解答:**\r\n\r\n ```java\r\n invokeCook(() ‐> { System.out.println(\"吃饭啦!\"); });\r\n ```\r\n\r\n \r\n\r\n## 第七章:有参数有返回值的代码\r\n\r\n+ **题目:**使用数组存储多个Person对象对数组中的Person对象使用Arrays的sort方法通过年龄进行升序排序 \r\n\r\n > 下面举例演示 `java.util.Comparator<T> `接口的使用场景代码,其中的抽象方法定义为: \r\n >\r\n > + `public abstract int compare(T o1, T o2); `\r\n >\r\n > 当需要对一个对象数组进行排序时, Arrays.sort 方法需要一个 `Comparator `接口实例来指定排序的规则。假设有一个 Person 类,含有 String name 和 int age 两个成员变量:\r\n >\r\n > ```java\r\n > public class Person { \r\n > private String name; \r\n > private int age; \r\n > // 省略构造器、toString方法与Getter Setter \r\n > }\r\n > ```\r\n >\r\n > \r\n\r\n+ **传统方式:**\r\n\r\n ```java\r\n public class Test {\r\n public static void main(String[] args) {\r\n // 本来年龄乱序的对象数组\r\n Person[] array = {\r\n new Person(\"古力娜扎\", 19),\r\n new Person(\"迪丽热巴\", 18),\r\n new Person(\"马尔扎哈\", 20)\r\n };\r\n // 排序\r\n Arrays.sort(array, new Comparator<Person>() {\r\n @Override\r\n public int compare(Person o1, Person o2) {\r\n return o1.getAge() - o2.getAge();\r\n }\r\n });\r\n // 打印\r\n for (int i = 0; i < array.length; i++) {\r\n System.out.println(array[i].getName() + \"---\" + array[i].getAge());\r\n }\r\n }\r\n }\r\n \r\n // 这种做法在面向对象的思想中,似乎也是"理所当然"的。其中 Comparator 接口的实例(使用了匿名内部类)代表 了"按照年龄从小到大"的排序规则。\r\n ```\r\n\r\n+ **代码分析:**下面我们来搞清楚上述代码真正要做什么事情。 \r\n\r\n 1. 为了排序, Arrays.sort 方法需要排序规则,即 Comparator 接口的实例,抽象方法 compare 是关键; \r\n 2. 为了指定 compare 的方法体,不得不需要 Comparator 接口的实现类; \r\n 3. 为了省去定义一个 ComparatorImpl 实现类的麻烦,不得不使用匿名内部类; \r\n 4. 必须覆盖重写抽象 compare 方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错; \r\n 5. 实际上,只有参数和方法体才是关键。 \r\n\r\n+ **Lambda方式:**\r\n\r\n ```java\r\n public class Test {\r\n public static void main(String[] args) {\r\n // 本来年龄乱序的对象数组\r\n Person[] array = {\r\n new Person(\"古力娜扎\", 19),\r\n new Person(\"迪丽热巴\", 18),\r\n new Person(\"马尔扎哈\", 20)\r\n };\r\n // 排序\r\n Arrays.sort(array,(Person o1, Person o2) ->{\r\n return o1.getAge()-o2.getAge();\r\n });\r\n // 打印\r\n for (int i = 0; i < array.length; i++) {\r\n System.out.println(array[i].getName() + \"---\" + array[i].getAge());\r\n }\r\n }\r\n }\r\n ```\r\n\r\n \r\n\r\n## 第八章:Lambda省略格式\r\n\r\n可推导即可省略。\r\n\r\n+ **省略规则:**\r\n\r\n 1. 小括号内参数的类型可以省略; \r\n 2. 如果小括号内有且仅有一个参,则小括号可以省略; \r\n 3. 如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。\r\n\r\n+ **代码:**\r\n\r\n ```java\r\n // 【练习2代码:】\r\n // 省略前\r\n Arrays.sort(array,(Person o1, Person o2) ->{\r\n return o1.getAge()-o2.getAge();\r\n });\r\n // 省略后\r\n Arrays.sort(array,(o1, o2) ->return o1.getAge()-o2.getAge());\r\n ```\r\n\r\n## 第九章:Lambda使用前提\r\n\r\nLambda的语法非常简洁,完全没有面向对象复杂的束缚。但是使用时有几个问题需要特别注意: \r\n\r\n1. **使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。** \r\n\r\n > 无论是JDK内置的 Runnable 、 Comparator 接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda。 \r\n\r\n2. **使用Lambda必须具有上下文推断。** \r\n\r\n > 也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。 \r\n\r\n备注:`有且仅有一个抽象方法的接口,称为"函数式接口"`。',36,14,'2020-06-05 16:01:56',0,'JavaSE-Lambda表达式'),(25,'JavaSE-IO操作','\r\n[TOC]\r\n\r\n## 第一章:IO介绍\r\n\r\n### 1.1 什么是IO\r\n\r\n 生活中,你肯定经历过这样的场景。当你编辑一个文本文件,忘记了 ctrl+s ,可能文件就白白编辑了。当你电脑上插入一个U盘,可以把一个视频,拷贝到你的电脑硬盘里。那么数据都是在哪些设备上的呢?键盘、内存、硬盘、外接设备等等。 \r\n\r\n 我们把这种数据的传输,可以看做是一种数据的流动,**按照流动的方向,以内存为基准,分为 输入input 和 输出output ,即流向内存是输入流,流出内存的输出流**。 \r\n\r\n Java中I/O操作主要是指使用 java.io 包下的内容,进行输入、输出操作。输入也叫做读取数据,输出也叫做作写出数据。 \r\n\r\n\r\n\r\n### 1.2 IO的分类\r\n\r\n\r\n\r\n\r\n根据数据的流向分为:输入流和输出流。 \r\n\r\n+ **输入流** :把数据从 其他设备 上读取到 内存 中的流。 \r\n+ **输出流** :把数据从 内存 中写出到 其他设备 上的流。 \r\n\r\n根据数据的类型分为:字节流和字符流。 \r\n\r\n+ **字节流** :以字节为单位,读写数据的流。 \r\n+ **字符流** :以字符为单位,读写数据的流。\r\n\r\n\r\n\r\n### 1.3 IO顶级的父类\r\n\r\n\r\n\r\n\r\n\r\n\r\n## 第二章:字节流\r\n\r\n### 2.1 一切皆为字节流\r\n\r\n 一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都是一个一个的字节,那么传输时一 样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。 \r\n\r\n\r\n\r\n### 2.2 字节输出流-OutputStream\r\n\r\n java.io.OutputStream 抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。\r\n\r\n+ **方法**\r\n + `public void close() `:关闭此输出流并释放与此流相关联的任何系统资源。 \r\n + `public void flush() `:刷新此输出流并强制任何缓冲的输出字节被写出。 \r\n + `public void write(byte[] b) `:将 b.length字节从指定的字节数组写入此输出流。 \r\n + `public void write(byte[] b, int off, int len)` :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。 \r\n + `public abstract void write(int b)` :将指定的字节输出流。 \r\n+ **注意事项**: \r\n + close方法,当完成流的操作时,必须调用此方法,释放系统资源。\r\n\r\n\r\n\r\n### 2.3 FileOutputStream类\r\n\r\n OutputStream 有很多子类,我们从最简单的一个子类开始。 \r\n\r\n `java.io.FileOutputStream` 类是文件输出流,用于将数据写出到文件。\r\n\r\n+ **构造方法**\r\n\r\n 1. `public FileOutputStream(File file) `:创建文件输出流以写入由指定的 File对象表示的文件。 \r\n 2. `public FileOutputStream(String name)` : 创建文件输出流以指定的名称写入文件。 \r\n 3. 注意:\r\n + 当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据。 \r\n\r\n+ **写出字节数据**\r\n\r\n ```java\r\n FileOutputStream fos = new FileOutputStream(\"a.txt\");\r\n // 【write(int b)】\r\n // 写入的十进制会转换成二进制存储到a.txt\r\n // 读取文件时,文件会按照指定的编码格式转换为对应的内容\r\n fos.write(97); // 写入→97→110 0001→内存→读取→110 0001→ASCII→a\r\n fos.write(98);\r\n // 【 write(byte[] b)】\r\n byte[]bs = {97,98,99,101,102};\r\n fos.write(bs);\r\n // 【字符串转换字节数组getBytes()】\r\n fos.write(\"你好\".getBytes());\r\n // 【write(byte[] b, int off, int len)指定长度的字节数组】\r\n fos.write(\"xyz\".getBytes(),0,2);\r\n fos.close();\r\n \r\n ```\r\n\r\n+ **数据追加续写**\r\n\r\n + **问题**:\r\n\r\n + 经过以上的演示,每次程序运行,创建输出流对象,都会清空目标文件中的数据。如何保留目标文件中数据,还能 继续添加新数据呢? \r\n\r\n + **解决方案**\r\n\r\n + `public FileOutputStream(File file, boolean append)` : 创建文件输出流以写入由指定的 File对象表示的文件。 \r\n + `public FileOutputStream(String name, boolean append)` : 创建文件输出流以指定的名称写入文件。\r\n + 参数: true 表示追加数据, false 表示清空原有数据。 \r\n\r\n + **代码**\r\n\r\n ```java\r\n FileOutputStream fos = new FileOutputStream(\"a.txt\",true);\r\n fos.write(\"我是新追加的数据\".getBytes());\r\n fos.close();\r\n ```\r\n\r\n \r\n\r\n+ **写出换行**\r\n\r\n + **系统中的换行**\r\n\r\n + Windows系统里,每行结尾是 回车+换行 ,即 `\\r\\n` ; \r\n + Unix系统里,每行结尾只有 换行 ,即 `\\n` ; \r\n + Mac系统里,每行结尾是 回车 ,即 `\\r `。从 Mac OS X开始与Linux统一。 \r\n\r\n + **代码**\r\n\r\n ```java\r\n FileOutputStream fos = new FileOutputStream(\"b.txt\",true);\r\n for (int i = 0; i < 10; i++) {\r\n fos.write((\"\\r\\n第\" + i +\"行数据:\" + i*100).getBytes() );\r\n }\r\n fos.close();\r\n ```\r\n\r\n \r\n\r\n### 2.4 字节输入流\r\n\r\n `java.io.InputStream` 抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入 流的基本共性功能方法。\r\n\r\n+ `public void close()` :关闭此输入流并释放与此流相关联的任何系统资源。 \r\n\r\n+ `public abstract int read()` : 从输入流读取数据的下一个字节。 \r\n\r\n+ `public int read(byte[] b) `: 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。\r\n\r\n + 使用数组读取,每次读取多个字节,减少了系统间的IO操作次数,从而提高了读写的效率,建议开发中使 \r\n\r\n 用。 \r\n\r\nclose方法,当完成流的操作时,必须调用此方法,释放系统资源。 \r\n\r\n\r\n\r\n### 2.5 FileInputStream类\r\n\r\njava.io.FileInputStream 类是文件输入流,从文件中读取字节。 \r\n\r\n+ **构造方法**\r\n\r\n 1. `FileInputStream(File file)` : 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系 统中的 File对象 fifile命名。 \r\n 2. `FileInputStream(String name) `: 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件 系统中的路径名 name命名。 \r\n\r\n+ **读取字节数据代码**\r\n\r\n ```java\r\n // 创建字节输入流对象\r\n FileInputStream fis = new FileInputStream(\"day07_IO//a.txt\");\r\n //【一次读取一个字节】\r\n // 循环读取文件中的字节\r\n int len = 0;\r\n while ((len=fis.read())!=-1){\r\n System.out.print((char)len);\r\n }\r\n // 结果:ABC\r\n // 【一次读取多个字节】\r\n // 将字节转换为字符串new Strig(bytes[]bts,int offset,int length);\r\n byte[]bts = new byte[10];\r\n int len2 = 0;\r\n while ((len2=fis.read(bts))!=-1){\r\n System.out.println(new String(bts,0,len2));\r\n }\r\n fis.close();\r\n ```\r\n\r\n \r\n\r\n### 2.6 复制图片文件\r\n\r\n+ **原理**\r\n \r\n\r\n\r\n+ **代码**\r\n\r\n ```java\r\n public static void main(String[] args) throws IOException {\r\n long s = System.currentTimeMillis();\r\n // 创建输入流对象-用来读取本地文件\r\n FileInputStream fis = new FileInputStream(\"D:\\\\test.jpg\");\r\n // 创建输出流对象-用来写入本地文件\r\n FileOutputStream fos = new FileOutputStream(\"day07_IO\\\\test_copy.jpg\");\r\n // 创建字节数组,一次从本地读取多个字节\r\n byte[]bts = new byte[1024];\r\n int len = 0; // 表示读取的有效字节个数\r\n // 循环读取本地数据\r\n while ((len=fis.read(bts))!=-1){\r\n // 把实际读取的字节写入本地文件\r\n fos.write(bts,0,len);\r\n }\r\n // 关闭输出流资源\r\n fos.close();\r\n // 关闭输入流资源\r\n fis.close();\r\n long e = System.currentTimeMillis();\r\n System.out.println(\"复制成功!\");\r\n System.out.println(\"共耗时\" + (e-s)+\"毫秒\");\r\n }\r\n ```\r\n\r\n \r\n\r\n## 第三章:字符流\r\n\r\n### 3.1 为什么要学习字符流\r\n\r\n当使用字节流读取文本文件时,可能会有一个小问题。就是遇到中文字符时,可能不会显示完整的字符,那是因为 \r\n\r\n一个中文字符可能占用多个字节存储。所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文 \r\n\r\n件。\r\n\r\n### 3.2 字符输入流-Reader\r\n\r\n1. `public void close()` :关闭此流并释放与此流相关联的任何系统资源。 \r\n2. `public int read()` : 从输入流读取一个字符。 \r\n3. `public int read(char[] cbuf)` : 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。\r\n\r\n\r\n\r\n### 3.3 FileReader类\r\n\r\n`java.io.FileReader` 类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区\r\n\r\n+ **注意事项**\r\n\r\n 1. 字符编码:字节与字符的对应规则。Windows系统的中文编码默认是GBK编码表。 idea中UTF-8 \r\n\r\n 2. 字节缓冲区:一个字节数组,用来临时存储字节数据。\r\n\r\n+ **构造函数**\r\n\r\n 1. FileReader(File file) : 创建一个新的 FileReader ,给定要读取的File对象。 \r\n 2. FileReader(String fileName) : 创建一个新的 FileReader ,给定要读取的文件的名称。\r\n\r\n+ **代码**\r\n\r\n ```java\r\n public static void main(String[] args) throws IOException {\r\n FileReader reader = new FileReader(\"day07_IO//a.txt\");\r\n // 【一次读取一个字符】\r\n int len = 0;\r\n while ((len = reader.read())!=-1) {\r\n System.out.print((char)len);\r\n }\r\n // 【一次读取多个字符】\r\n char[]chs = new char[1024];\r\n int len = 0;\r\n while ((len = reader.read(chs))!=-1) {\r\n System.out.print(new String(chs,0,len));\r\n // new String(char[]chars,int offset,int count);\r\n }\r\n reader.close();\r\n \r\n }\r\n ```\r\n\r\n\r\n\r\n### 3.4 字符输出流-Writer\r\n\r\njava.io.Writer 抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节 \r\n\r\n输出流的基本共性功能方法。 \r\n\r\n1. `void write(int c)` 写入单个字符。 \r\n2. `void write(char[] cbuf) `写入字符数组。 \r\n3. `abstract void write(char[] cbuf, int off, int len)` 写入字符数组的某一部分,offff数组的开始索引,len 写的字符个数。 \r\n4. `void write(String str) `写入字符串。 \r\n5. `void write(String str, int off, int len) `写入字符串的某一部分,offff字符串的开始索引,len写的字符个 数。\r\n6. `void flush() `刷新该流的缓冲。 \r\n7. `void close() `关闭此流,但要先刷新它。 \r\n\r\n\r\n\r\n### 3.5 FileWriter类 \r\n\r\n`java.io.FileWriter` 类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。 \r\n\r\n+ **构造函数:**\r\n\r\n 1. `FileWriter(File file)` : 创建一个新的 FileWriter,给定要读取的File对象。 \r\n 2. `FileWriter(String fileName)` : 创建一个新的 FileWriter,给定要读取的文件的名称。 \r\n\r\n \r\n\r\n+ **代码:**\r\n\r\n ```java\r\n public static void main(String[] args) throws IOException {\r\n // 创建输出字符流对象\r\n FileWriter fw = new FileWriter(\"day07_IO\\\\b.txt\");\r\n fw.write(\"你好\");\r\n fw.flush(); // flush将缓冲区的数据刷新到文件中,可继续写入\r\n fw.write(\"我好\");\r\n fw.flush();\r\n fw.write(\"大家好\");\r\n fw.close();// close 将缓冲区中的数据刷新到文件中,并且会释放输出流对象,后继无法继续输出\r\n fw.write(\"真好\"); // 异常\r\n \r\n // 创建输出流对象,向指定的文件中追加写入数据\r\n FileWriter fw2 = new FileWriter(\"day07_IO\\\\b.txt\",true);\r\n for (int i = 0; i < 10; i++) {\r\n fw2.write(\"你好,新的世界!\" + i + \"\\r\\n\");\r\n }\r\n fw2.close();\r\n }\r\n ```\r\n\r\n+ **注意事项:**\r\n\r\n + 因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要 flush 方法了。 \r\n + flush :刷新缓冲区,流对象可以继续使用。 \r\n + close :先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。 \r\n\r\n\r\n\r\n## 第四章:IO异常处理\r\n\r\n### 4.1 JDK7之前的处理方式\r\n\r\n+ **处理方式:**`try-catch-finally`\r\n\r\n + 在finally中释放资源\r\n\r\n+ **代码:**\r\n\r\n ```java\r\n public static void main(String[] args) {\r\n // 创建输出流对象,向指定的文件中追加写入数据\r\n FileWriter fw2 = null;\r\n try{\r\n fw2 = new FileWriter(\"day07_IO\\\\b.txt\",true);\r\n for (int i = 0; i < 10; i++) {\r\n fw2.write(\"你好,新的世界!\" + i + \"\\r\\n\");\r\n }\r\n }catch (IOException e) {\r\n e.printStackTrace();\r\n }finally {\r\n if(fw2!=null){\r\n try {\r\n fw2.close();\r\n } catch (IOException e) {\r\n e.printStackTrace();\r\n }\r\n }\r\n }\r\n \r\n }\r\n ```\r\n\r\n\r\n\r\n### 4.2 JDK7中的新特性\r\n\r\n+ **处理方式**:JDK7优化后的 try-with-resource 语句,该语句确保了每个资源在语句结束时关闭。所谓的资源 (resource)是指在程序完成后,必须关闭的对象。\r\n\r\n ```java\r\n try (创建流对象语句,如果多个,使用\';\'隔开) { \r\n // 读写数据 \r\n } catch (IOException e) { \r\n e.printStackTrace(); \r\n }\r\n ```\r\n\r\n+ **代码:**\r\n\r\n ```java\r\n public static void main(String[] args) {\r\n // 创建输出流对象,向指定的文件中追加写入数据\r\n try(FileWriter fw2 = new FileWriter(\"day07_IO\\\\b.txt\",true)){\r\n for (int i = 0; i < 100; i++) {\r\n fw2.write(\"你好,新的世界!\" + i + \"\\r\\n\");\r\n }\r\n }catch (IOException e) {\r\n e.printStackTrace();\r\n }\r\n }\r\n ```\r\n\r\n\r\n\r\n### 4.3 JDK9中的新特性\r\n\r\n JDK9中 try-with-resource 的改进,对于引入对象的方式,支持的更加简洁。被引入的对象,同样可以自动关闭, 无需手动close\r\n\r\n+ **格式**\r\n\r\n ```java\r\n // 被final修饰的对象 \r\n final Resource resource1 = new Resource(\"resource1\"); \r\n // 普通对象 \r\n Resource resource2 = new Resource(\"resource2\"); \r\n // 引入方式:直接引入 \r\n try (resource1; resource2) { \r\n // 使用对象 \r\n } catch (IOException e) { \r\n e.printStackTrace(); \r\n }\r\n ```\r\n\r\n+ **代码**\r\n\r\n ```java\r\n public static void main(String[] args) throws IOException {\r\n // 创建输出流对象,向指定的文件中追加写入数据\r\n FileWriter fw2 = new FileWriter(\"day07_IO\\\\b.txt\",true);\r\n try(fw2){\r\n for (int i = 0; i < 100; i++) {\r\n fw2.write(\"你好,新的世界!\" + i + \"\\r\\n\");\r\n }\r\n }catch (IOException e) {\r\n e.printStackTrace();\r\n }\r\n }\r\n ```\r\n\r\n\r\n\r\n## 第五章:属性集\r\n\r\n### 5.1 属性集合介绍\r\n\r\n`java.util.Properties` 继承于 Hashtable ,来表示一个持久的属性集。它使用键值结构存储数据,每个键及其 \r\n\r\n对应值都是一个字符串。该类也被许多Java类使用,比如获取系统属性时, System.getProperties 方法就是返回 \r\n\r\n一个 Properties 对象。\r\n\r\n\r\n\r\n### 5.2 Properties 类\r\n\r\n+ **构造方法**:`public Properties()` :创建一个空的属性列表。 \r\n\r\n+ **基本的存储方法**\r\n\r\n 1. `public Object setProperty(String key, String value) `: 保存一对属性。 \r\n 2. `public String getProperty(String key)` :使用此属性列表中指定的键搜索属性值。 \r\n 3. `public Set<String> stringPropertyNames()` :所有键的名称的集合。 \r\n\r\n+ **与流相关的方法**\r\n\r\n 1. `public void load(InputStream inStream)` : 从字节输入流中读取键值对。 \r\n + 注意:文本中的数据,必须是键值对形式,可以使用空格、等号、冒号等符号分隔。 \r\n\r\n+ **代码**\r\n\r\n ```java\r\n public static void main(String[] args) throws IOException {\r\n // 获取系统相关的信息\r\n System.out.println(System.getProperties());\r\n // 创建一个属性集合\r\n Properties pro = new Properties();\r\n pro.setProperty(\"张三\",\"180\");\r\n pro.setProperty(\"李四\",\"189\");\r\n pro.setProperty(\"王五\",\"178\");\r\n pro.setProperty(\"赵六\",\"188\");\r\n // 读取属性集合中的数据\r\n Set set = pro.keySet();\r\n for (Object o : set) {\r\n System.out.println(pro.get(o));\r\n }\r\n // 把集合中临时的数据输出到硬盘中\r\n // pro.store(new FileWriter(\"day07_IO\\\\data.txt\"),\"save data\");\r\n \r\n // 读取本地文件中的数据(键值对的方式)到属性集合中\r\n Properties pro2 = new Properties();\r\n pro2.load(new FileReader(\"day07_IO\\\\data.txt\"));\r\n System.out.println(pro2);\r\n }\r\n ```\r\n\r\n \r\n\r\n## 第六章:缓冲流\r\n\r\n### 6.1 缓冲流介绍\r\n\r\n缓冲流,也叫高效流,是对4个基本的 FileXxx 流的增强,所以也是4个流,按照数据类型分类: \r\n\r\n+ **字节缓冲流**: BufferedInputStream , BufferedOutputStream \r\n+ **字符缓冲流**: BufferedReader , BufferedWriter \r\n\r\n缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。 \r\n\r\n\r\n\r\n### 6.2 字节缓冲流 \r\n\r\n+ **构造方法**\r\n\r\n 1. `public BufferedInputStream(InputStream in) `:创建一个 新的缓冲输入流。 \r\n 2. `public BufferedOutputStream(OutputStream out)` : 创建一个新的缓冲输出流。\r\n\r\n+ **代码测试**\r\n\r\n 1. **基本方式一次读写一个字节**\r\n\r\n ```java\r\n public static void main(String[] args) throws IOException {\r\n long s = System.currentTimeMillis();\r\n // 创建字节输入流,读取本地文件数据\r\n FileInputStream fis = new FileInputStream(\"D:\\\\test.jpg\");\r\n // 创建字节输出流,向本地文件写入数据\r\n FileOutputStream fos = new FileOutputStream(\"day08_IO\\\\test01.jpg\");\r\n // 定义变量表示读取的字节\r\n int len = 0;\r\n // 循环遍历读取本地文件\r\n while ((len=fis.read())!=-1) {\r\n // 把读取的字节写入本地\r\n fos.write(len);\r\n }\r\n // 关闭输出流\r\n fos.close();\r\n // 关闭输入流\r\n fis.close();\r\n long e = System.currentTimeMillis();\r\n System.out.println(\"共耗时:\" + (e-s) + \"毫秒\"); // 161毫秒\r\n \r\n }\r\n ```\r\n\r\n 2. **基本方式一次读写多个字节**\r\n\r\n ```java\r\n public static void main(String[] args) throws IOException {\r\n long s = System.currentTimeMillis();\r\n // 创建字节输入流,读取本地文件数据\r\n FileInputStream fis = new FileInputStream(\"D:\\\\test.jpg\");\r\n // 创建字节输出流,向本地文件写入数据\r\n FileOutputStream fos = new FileOutputStream(\"day08_IO\\\\test02.jpg\");\r\n // 定义变量表示读取的有序字节个数\r\n int len = 0;\r\n // 定义字节数组,表示一次要读取的字节数\r\n byte[]bts = new byte[1024];\r\n // 循环遍历读取本地文件\r\n while ((len=fis.read(bts))!=-1) {\r\n // 把读取的字节写入本地\r\n fos.write(bts,0,len);\r\n }\r\n // 关闭输出流\r\n fos.close();\r\n // 关闭输入流\r\n fis.close();\r\n long e = System.currentTimeMillis();\r\n System.out.println(\"共耗时:\" + (e-s) + \"毫秒\"); // 共耗时:7毫秒\r\n \r\n }\r\n ```\r\n\r\n 3. **缓冲流方式默认读写方式**\r\n\r\n ```java\r\n public static void main(String[] args) throws IOException {\r\n long s = System.currentTimeMillis();\r\n // 创建字节缓冲流输入流\r\n BufferedInputStream bis = new BufferedInputStream(new FileInputStream(\"D:\\\\test.jpg\"));\r\n // 创建字节缓冲流输出流f\r\n BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(\"day08_IO\\\\test03.jpg\"));\r\n // 循环读取本地数据\r\n int len = 0;\r\n while ((len = bis.read())!=-1){\r\n bos.write(len);\r\n }\r\n bos.close();\r\n bis.close();\r\n long e = System.currentTimeMillis();\r\n System.out.println(\"共耗时:\" + (e-s) + \"毫秒\"); // 共耗时:8毫秒\r\n }\r\n ```\r\n\r\n 4. **缓冲流方式指定字节读取**\r\n\r\n ```java\r\n public static void main(String[] args) throws IOException {\r\n long s = System.currentTimeMillis();\r\n // 创建字节缓冲流输入流\r\n BufferedInputStream bis = new BufferedInputStream(new FileInputStream(\"D:\\\\test.jpg\"));\r\n // 创建字节缓冲流输出流f\r\n BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(\"day08_IO\\\\test04.jpg\"));\r\n // 循环读取本地数据\r\n int len = 0;\r\n byte[]bts = new byte[1024];\r\n while ((len = bis.read(bts))!=-1){\r\n bos.write(bts,0,len);\r\n }\r\n bos.close();\r\n bis.close();\r\n long e = System.currentTimeMillis();\r\n System.out.println(\"共耗时:\" + (e-s) + \"毫秒\"); // 共耗时:6毫秒\r\n }\r\n ```\r\n\r\n\r\n\r\n### 6.3 字符缓冲流\r\n\r\n+ **构造方法**\r\n\r\n 1. `public BufferedReader(Reader in)` :创建一个 新的缓冲输入流。 \r\n 2. `public BufferedWriter(Writer out)` : 创建一个新的缓冲输出流。\r\n\r\n+ **特有方法:**字符缓冲流的基本方法与普通字符流调用方式一致,不再阐述,我们来看它们具备的特有方法。 \r\n\r\n 1. BufffferedReader: `public String readLine()` : 读一行文字。 \r\n 2. BufffferedWriter: `public void newLine() `: 写一行行分隔符,由系统属性定义符号。\r\n\r\n+ **代码:**\r\n\r\n ```java\r\n public static void main(String[] args) throws IOException {\r\n // 创建字符缓冲输出流\r\n BufferedWriter bw = new BufferedWriter( new FileWriter(\"day08_IO\\\01円.txt\"));\r\n for (int i = 0; i < 100; i++) {\r\n bw.write(\"贵在坚持!不只是说说!\" + i);\r\n bw.newLine();\r\n }\r\n bw.close();\r\n // 创建字符缓冲输入流\r\n BufferedReader br = new BufferedReader(new FileReader(\"day08_IO\\\01円.txt\"));\r\n String line;\r\n while ((line=br.readLine())!=null){\r\n System.out.println(line);\r\n }\r\n br.close();\r\n }\r\n ```\r\n\r\n \r\n\r\n### 6.4 文本排序\r\n\r\n+ **需求**:读取本地文件中的文本,并排序后重新写入本地新的文件中\r\n\r\n ```\r\n 2.JavaScript\r\n 1.Java\r\n 4.PHP\r\n 6.C#\r\n 7.C\r\n 3.C++\r\n 5.VB\r\n ```\r\n\r\n+ **代码:**\r\n\r\n ```java\r\n public static void main(String[] args) throws IOException {\r\n // 创建HashMap对象存放读取的本地数据\r\n HashMap<String,String > map = new HashMap<>();\r\n // 创建字符缓冲区输入流,逐行读取本地数据\r\n BufferedReader br = new BufferedReader(new FileReader(\"day08_IO\\\02円.txt\"));\r\n String line;\r\n while ((line=br.readLine())!=null) {\r\n // 分割\r\n String[]strs = line.split(\"\\\\.\");\r\n map.put(strs[0],strs[1]);\r\n }\r\n br.close();\r\n // 创建字符输出流\r\n BufferedWriter bw = new BufferedWriter(new FileWriter(\"day08_IO\\\03円.txt\"));\r\n for (int i = 1; i <= map.size(); i++) {\r\n bw.write(i +\".\"+ map.get(String.valueOf(i)));\r\n bw.newLine();\r\n }\r\n bw.close();\r\n \r\n }\r\n ```\r\n\r\n\r\n\r\n## 第七章:转换流\r\n\r\n### 7.1 字符编码和字符集\r\n\r\n\r\n\r\n\r\n 计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。按照某种规则,将字符存储到计算机中,称为编码 。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。比如说,按照A规则存储,同样按照A规则解析,那么就能显示正确的文本符号。反之,按照A规则存储,再按照B规则解析,就会导致乱码现象。 \r\n\r\n+ **字符编码Character Encoding **: 就是一套自然语言的字符与二进制数之间的对应规则。 \r\n\r\n+ **字符集charset:**也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。 \r\n\r\n + 计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码常见字符集有ASCII字符集、GBK字符集、Unicode字符集等。可见,当指定了编码,它所对应的字符集自然就指定了,所以编码才是我们最终要关心的。 \r\n\r\n + **ASCII字符集 **\r\n\r\n + ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)。 \r\n + 基本的ASCII字符集,使用7位(bits)表示一个字符,共128字符。ASCII的扩展字符集使用8位(bits表示一个字符,共256字符,方便支持欧洲常用字符。 \r\n\r\n + **ISO-8859-1字符集**\r\n\r\n + 拉丁码表,别名Latin-1,用于显示欧洲使用的语言,包括荷兰、丹麦、德语、意大利语、西班牙语等。 \r\n + ISO-5559-1使用单字节编码,兼容ASCII编码。 \r\n\r\n + **GBxxx字符集**\r\n\r\n + GB就是国标的意思,是为了显示中文而设计的一套字符集。 \r\n + GB2312:简体中文码表。一个小于127的字符的意义与原来相同。但两个大于127的字符连在一起时就表示一个汉字,这样大约可以组合了包含7000多个简体汉字,此外数学符号、罗马希腊的字母、日文的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的\"全角\"字符,而原来在127号以下的那些就叫\"半角\"字符了。 \r\n + GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了 21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等。 \r\n + GB18030:最新的中文码表。收录汉字70244个,采用多字节编码,每个字可以由1个、2个或4个字节组成。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等。 \r\n\r\n + **Unicode字符集** \r\n\r\n + Unicode编码系统为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国 码。\r\n + 它最多使用4个字节的数字来表达每个字母、符号,或者文字。有三种编码方案,UTF-8、UTF-16和UTF- 32。最为常用的UTF-8编码。 \r\n + UTF-8编码,可以用来表示Unicode标准中任何字符,它是电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。所以,我们开发Web应用,也要使用UTF-8编码。它使用一至四个字节为每个字符编码,编码规则: \r\n 1. 128个US-ASCII字符,只需一个字节编码。 \r\n 2. 拉丁文等字符,需要二个字节编码。 \r\n 3. 大部分常用字(含中文),使用三个字节编码。 \r\n 4. 其他极少使用的Unicode辅助字符,使用四字节编码。 \r\n\r\n \r\n\r\n### 7.2 编码引出的问题\r\n\r\n 在IDEA中,使用 FileReader 读取项目中的文本文件。由于IDEA的设置,都是默认的 UTF-8 编码,所以没有任何问题。但是,当读取Windows系统中创建的文本文件时,由于Windows系统的默认是GBK编码,就会出现乱码。 \r\n\r\n```java\r\npublic static void main(String[] args) throws IOException {\r\n FileReader fr = new FileReader(\"D:\\\\a.txt\");\r\n int len = 0;\r\n while((len=fr.read())!=-1){\r\n System.out.print((char)len); \r\n }\r\n fr.close();\r\n }\r\n// 结果:��� 乱码\r\n```\r\n\r\n那么如何读取GBK编码的文件呢? \r\n\r\n\r\n\r\n### 7.3 InputStreamReader类 \r\n\r\n 转换流 `java.io.InputStreamReader` ,是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。\r\n\r\n+ **构造方法**\r\n\r\n 1. `InputStreamReader(InputStream in) `: 创建一个使用默认字符集的字符流。\r\n 2. `InputStreamReader(InputStream in, String charsetName)` : 创建一个指定字符集的字符流。 \r\n\r\n+ **代码**\r\n\r\n ```java\r\n public static void main(String[] args) throws IOException {\r\n InputStreamReader fr = new InputStreamReader(new FileInputStream(\"D:\\\\a.txt\"),\"gbk\");\r\n int len = 0;\r\n while((len=fr.read())!=-1){\r\n System.out.print((char)len);\r\n }\r\n fr.close();\r\n }\r\n // 结果:你好\r\n ```\r\n\r\n\r\n\r\n### 7.4 OutputStreamWriter 类\r\n\r\n 转换流 java.io.OutputStreamWriter ,是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符 编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。 \r\n\r\n+ **构造函数**\r\n\r\n 1. `OutputStreamWriter(OutputStream in)` : 创建一个使用默认字符集的字符流。 \r\n 2. `OutputStreamWriter(OutputStream in, String charsetName)` : 创建一个指定字符集的字符流。 \r\n\r\n+ **代码**\r\n\r\n ```java\r\n public static void main(String[] args) throws IOException {\r\n OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(\"day08_IO\\\04円.txt\"),\"gbk\");\r\n osw.write(\"你好\");\r\n osw.close();\r\n }\r\n ```\r\n\r\n \r\n\r\n### 7.5 图解转换流原理\r\n\r\n\r\n\r\n\r\n## 第八章:序列化\r\n\r\n### 8.1 对象序列化介绍\r\n\r\n Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该 对象的数据 、 对象的 类型 和 对象中存储的属性 等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。 反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。 对象的数据 、 对象的类型 和 对象中 存储的数据 信息,都可以用来在内存中创建对象。看图理解序列化: \r\n\r\n\r\n\r\n\r\n\r\n\r\n### 8.2 ObjectOutputStream类 \r\n\r\n`java.io.ObjectOutputStream` 类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。 \r\n\r\n+ **构造方法**\r\n\r\n 1. `public ObjectOutputStream(OutputStream out)` : 创建一个指定OutputStream的ObjectOutputStream。 \r\n\r\n+ **序列化操作**\r\n\r\n 1. **一个对象要想序列化,必须满足两个条件:** \r\n\r\n + 该类必须实现` java.io.Serializable` 接口, `Serializable` 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出 `NotSerializableException` 。 \r\n\r\n + 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用 `transient` 关键字修饰。 \r\n\r\n + 代码:\r\n\r\n ```java\r\n public class Person implements Serializable { // 实现Serializable,表示可序列化\r\n private String name;\r\n private int age;\r\n private transient int height; // 该属性不会被序列化\r\n public Person(String name, int age, int height) {\r\n this.name = name;\r\n this.age = age;\r\n this.height = height;\r\n }\r\n public String getName() {\r\n return name;\r\n }\r\n public void setName(String name) {\r\n this.name = name;\r\n }\r\n public int getAge() {\r\n return age;\r\n }\r\n public void setAge(int age) {\r\n this.age = age;\r\n }\r\n public int getHeight() {\r\n return height;\r\n }\r\n public void setHeight(int height) {\r\n this.height = height;\r\n }\r\n @Override\r\n public String toString() {\r\n return \"姓名:\" + name + \",年龄:\"+ age + \",身高:\" + height;\r\n }\r\n }\r\n \r\n ```\r\n\r\n 2. **写出对象方法 **\r\n\r\n + `public final void writeObject (Object obj) : 将指定的对象写出 `\r\n\r\n + **代码**\r\n\r\n ```java\r\n public static void main(String[] args) throws IOException {\r\n // 创建一个人类对象\r\n Person p = new Person(\"张三\",18,180);\r\n // 创建一个序列化输出流对象\r\n ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(\"day08_IO\\\\person.txt\"));\r\n // 把指定的对象序列化后写入本地文件\r\n os.writeObject(p);\r\n os.close();\r\n System.out.println(\"save a Object success\");\r\n }\r\n ```\r\n\r\n \r\n\r\n### 8.3 ObjectInputStream类 \r\n\r\n ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。\r\n\r\n+ **构造方法**\r\n\r\n + `public ObjectInputStream(InputStream in); `: 创建一个指定InputStream的ObjectInputStream。 \r\n\r\n+ **反序列化操作1**\r\n\r\n + 如果能找到一个对象的class文件,我们可以进行反序列化操作,调用 ObjectInputStream 读取对象的方法: \r\n\r\n + public final Object readObject () : 读取一个对象。 \r\n\r\n + 代码:\r\n\r\n ```java\r\n public static void main(String[] args) throws IOException, ClassNotFoundException {\r\n ObjectInputStream os = new ObjectInputStream(new FileInputStream(\"day08_IO\\\\person.txt\"));\r\n Person p = (Person) os.readObject();\r\n System.out.println(p); // 姓名:张三,年龄:18,身高:0\r\n os.close();\r\n }\r\n \r\n // 对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个ClassNotFoundException 异常。\r\n ```\r\n\r\n+ **反序列化操作2**\r\n\r\n + 另外,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操 作也会失败,抛出一个 InvalidClassException 异常。发生这个异常的原因如下:\r\n\r\n + 该类的序列版本号与从流中读取的类描述符的版本号不匹配 \r\n + 该类包含未知数据类型 \r\n + 该类没有可访问的无参数构造方法 \r\n\r\n + 解决方案\r\n\r\n + Serializable 接口给需要序列化的类,提供了一个序列版本号。 serialVersionUID 该版本号的目的在于验证序 列化的对象和对应类是否版本匹配。 \r\n\r\n + 代码\r\n\r\n ```java\r\n public class Person implements Serializable { // 实现Serializable,表示可序列化\r\n // 定义序列化版本号\r\n private static final long serialVersionUID = 10L;\r\n private String name;\r\n public int age;\r\n private transient int height; // 该属性不会被序列化\r\n public Person(String name, int age, int height) {\r\n this.name = name;\r\n this.age = age;\r\n this.height = height;\r\n }\r\n public String getName() {\r\n return name;\r\n }\r\n public void setName(String name) {\r\n this.name = name;\r\n }\r\n public int getAge() {\r\n return age;\r\n }\r\n public void setAge(int age) {\r\n this.age = age;\r\n }\r\n public int getHeight() {\r\n return height;\r\n }\r\n public void setHeight(int height) {\r\n this.height = height;\r\n }\r\n @Override\r\n public String toString() {\r\n return \"姓名:\" + name + \",年龄:\"+ age + \",身高:\" + height;\r\n }\r\n }\r\n \r\n ```\r\n\r\n \r\n\r\n## 第九章:打印流\r\n\r\n### 9.1 打印流介绍\r\n\r\n 平时我们在控制台打印输出,是调用 print 方法和 println 方法完成的,这两个方法都来自于 java.io.PrintStream 类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。 \r\n\r\n\r\n\r\n### 9.2 PrintStream类 \r\n\r\n+ **构造方法**\r\n\r\n + `public PrintStream(String fileName) `: 使用指定的文件名创建一个新的打印流。 \r\n\r\n+ System.out 就是 PrintStream 类型的,只不过它的流向是系统规定的,打印在控制台上。不过,既然是流对象, 我们就可以玩一个\"小把戏\",改变它的流向。 \r\n\r\n+ **代码**\r\n\r\n ```java\r\n public static void main(String[] args) throws IOException {\r\n // 打印在控制台\r\n System.out.println(\"你好\");\r\n // 创建打印流,流向指定的本地文件\r\n PrintStream ps = new PrintStream(new FileOutputStream(\"day08_IO\\\\print.txt\"));\r\n // 设置System.out的打印流向指向ps\r\n System.setOut(ps);\r\n // 后续打印在print.txt文件中\r\n System.out.println(\"你好1\");\r\n System.out.println(\"你好2\");\r\n System.out.println(\"你好3\");\r\n ps.close();\r\n }\r\n ```\r\n\r\n ',36,14,'2020-06-05 16:02:36',0,'JavaSE-IO操作'),(26,'测试标题4','测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4测试4',NULL,14,'2020-06-05 16:39:22',1,'测试4'),(27,'测试标题5','测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5测试标题5',3,14,'2020-06-05 16:39:42',1,'测试标题5测试标题5测试标题5'),(28,'测试3','测试3测试3测试3测试3测试3测试3测试3测试3测试3测试3',6,14,'2020-06-05 23:22:55',1,'测试3'),(29,'测试图片2322222','## 测试图片23\r\n+ 图片\r\n+ 视频\r\n+ 代码\r\n\r\n应该要好很多吧\r\n应该要好很多吧\r\n应该要好很多吧',3,14,'2020-06-06 00:36:17',0,'测试图片23'),(30,'测试1级-1-1','测试1级-1-1测试1级-1-1测试1级-1-1测试1级-1-1',3,14,'2020-06-06 14:21:36',0,'测试1级-1-1'),(31,'测试1级-1-2','测试1级-1-2测试1级-1-2测试1级-1-2',3,14,'2020-06-06 14:22:12',0,'测试1级-1-2'),(32,'测试1级-1-1-11','测试1级-1-1-11测试1级-1-1-11测试1级-1-1-11',NULL,14,'2020-06-06 14:22:48',1,'测试1级-1-1-11'),(33,'测试1111112222','\r\n哈哈哈',4,14,'2020-06-06 19:14:58',1,'测试1111111111'),(34,'系统管理','系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理',3,14,'2020-06-10 12:40:09',0,'系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理系统管理'),(35,'测试112','测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112',4,14,'2020-06-10 12:40:47',0,'测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112测试112'),(36,'测试113','测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113',3,14,'2020-06-10 12:41:21',0,'测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113测试113'),(37,'测试114','测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114',5,14,'2020-06-10 12:42:04',0,'测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114测试114'),(38,'测试115','测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115',5,14,'2020-06-10 12:42:27',0,'测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115测试115'),(39,'测试116','测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116',5,14,'2020-06-10 12:42:46',0,'测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116测试116'),(40,'测试117','测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117',36,14,'2020-06-10 12:43:45',0,'测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117测试117'),(41,'测试119','测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119',3,14,'2020-06-10 12:44:33',0,'测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119测试119'),(42,'测试120','测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120',3,14,'2020-06-10 12:44:51',0,'测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120测试120'),(43,'测试121','测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121',3,14,'2020-06-10 12:45:05',0,'测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121测试121'),(44,'测试122','测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122',3,14,'2020-06-10 12:45:22',0,'测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122测试122'),(45,'测试123','',3,14,'2020-06-10 12:45:34',0,'测试123测试123测试123测试123测试123测试123测试123测试123测试123测试123测试123测试123测试123测试123测试123测试123测试123测试123测试123测试123'),(46,'测试124','测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124',3,14,'2020-06-10 12:45:51',0,'测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124测试124'),(47,'测试125','测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125',3,14,'2020-06-10 12:46:02',0,'测试125测试125测试125测试125测试125测试125测试125测试125测试125测试125'),(48,'测试126','测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126',3,14,'2020-06-10 12:46:25',0,'测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126测试126'),(49,'测试127','测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127',3,14,'2020-06-10 12:46:40',0,'测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127测试127'),(50,'测试128','测试128测试128测试128测试128测试128测试128测试128测试128测试128测试128测试128测试128测试128测试128测试128测试128测试128测试128测试128测试128测试128测试128测试128测试128测试128测试128测试128测试128测试128测试128',3,14,'2020-06-10 12:48:14',0,'测试128测试128测试128测试128测试128测试128测试128测试128测试128'),(51,'测试129','测试129测试129测试129测试129测试129测试129测试129测试129测试129测试129测试129测试129测试129测试129测试129测试129测试129测试129测试129测试129测试129测试129测试129',3,14,'2020-06-10 12:48:26',0,'测试129测试129测试129测试129测试129测试129测试129测试129测试129测试129测试129测试129测试129测试129测试129测试129测试129测试129测试129测试129'),(52,'测试130','测试130测试130测试130测试130测试130测试130测试130测试130测试130测试130测试130测试130测试130测试130测试130测试130测试130测试130测试130测试130',3,14,'2020-06-10 12:48:38',0,'测试130测试130测试130测试130测试130测试130测试130测试130测试130测试130测试130测试130测试130测试130测试130测试130测试130测试130测试130测试130'),(53,'测试131','测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131',3,14,'2020-06-10 12:48:50',0,'测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131测试131'),(54,'测试132','测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132',3,14,'2020-06-10 12:49:02',0,'测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132测试132'),(55,'测试133','测试133测试133测试133测试133测试133测试133测试133测试133测试133测试133测试133测试133测试133测试133测试133测试133测试133测试133测试133测试133测试133测试133测试133测试133',3,14,'2020-06-10 12:49:14',0,'测试133测试133测试133测试133测试133测试133测试133测试133测试133测试133测试133测试133测试133测试133测试133测试133测试133'),(56,'测试134','测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134',3,14,'2020-06-10 12:49:27',0,'测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134测试134'),(57,'测试135','测试135测试135测试135测试135测试135测试135测试135测试135测试135测试135测试135测试135测试135测试135测试135测试135测试135测试135测试135测试135测试135测试135测试135',3,14,'2020-06-10 12:50:17',0,'测试135测试135测试135测试135测试135测试135测试135测试135测试135测试135测试135测试135测试135测试135测试135测试135测试135测试135测试135测试135'),(58,'测试136','测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136',3,14,'2020-06-10 12:50:29',0,'测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136测试136'),(59,'测试137','测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137',3,14,'2020-06-10 12:50:42',0,'测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137测试137'),(60,'测试138','测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138',3,14,'2020-06-10 12:50:55',0,'测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138测试138'),(61,'测试139','测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139',3,14,'2020-06-10 12:51:07',0,'测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139测试139'),(62,'测试140','测试140测试140测试140测试140测试140测试140测试140测试140测试140测试140测试140测试140测试140测试140测试140测试140测试140测试140测试140测试140测试140测试140测试140测试140测试140',3,14,'2020-06-10 12:51:20',0,'测试140测试140测试140测试140测试140测试140测试140测试140测试140测试140测试140测试140'),(63,'测试141','测试141测试141测试141测试141测试141测试141测试141测试141测试141测试141测试141测试141测试141测试141测试141',3,14,'2020-06-10 12:51:32',0,'测试141测试141测试141测试141测试141测试141测试141测试141测试141测试141测试141测试141测试141测试141测试141测试141测试141测试141测试141测试141'),(64,'测试142','测试142测试142测试142测试142测试142测试142测试142测试142测试142测试142测试142测试142测试142测试142测试142测试142测试142测试142测试142测试142',3,14,'2020-06-10 12:51:46',0,'测试142测试142测试142测试142测试142测试142测试142测试142测试142测试142测试142测试142测试142测试142测试142测试142测试142测试142测试142测试142'),(65,'测试143','测试143测试143测试143测试143测试143测试143测试143测试143测试143测试143测试143测试143测试143测试143测试143测试143测试143测试143测试143测试143测试143测试143测试143测试143测试143测试143测试143测试143',3,14,'2020-06-10 12:52:00',0,'测试143测试143测试143测试143测试143测试143测试143测试143测试143测试143测试143'),(66,'我的博客介绍','# 个人博客\r\n+ 博客\r\n+ 微博\r\n+ 朋友圈\r\n\r\n## 图片\r\n',NULL,14,'2020-06-12 09:37:18',0,'个人博客。总结沉淀,分享技术');/*Table structure for table `article_category` */DROP TABLE IF EXISTS `article_category`;CREATE TABLE `article_category` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',`name` varchar(50) NOT NULL COMMENT '类别名称',`pid` int(11) DEFAULT NULL COMMENT '父id,一级分类父id是0,二级父id不是0',`createtime` timestamp NULL DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=40 DEFAULT CHARSET=utf8;/*Data for the table `article_category` */insert into `article_category`(`id`,`name`,`pid`,`createtime`) values (1,'前端编程',0,'2020-06-04 22:11:50'),(2,'后端开发',0,'2020-06-04 22:12:13'),(3,'HTML5',1,'2020-06-04 22:13:08'),(4,'CSS3',1,'2020-06-04 22:13:22'),(5,'JavaScript',1,'2020-06-04 22:13:42'),(6,'JQuery',1,'2020-06-04 22:13:58'),(7,'VueJs',1,'2020-06-04 22:14:30'),(8,'React',1,'2020-06-04 22:14:59'),(11,'Servlet',2,'2020-06-04 22:15:54'),(12,'JSP',2,'2020-06-04 22:16:02'),(13,'JDBC',2,'2020-06-04 22:16:24'),(14,'Spring',2,'2020-06-04 22:16:48'),(15,'SpringMVC',2,'2020-06-04 22:17:00'),(16,'数据库',0,'2020-06-04 22:17:30'),(17,'MySQL',16,'2020-06-04 22:17:49'),(18,'MongoDB',16,'2020-06-04 22:18:01'),(19,'Redis',16,'2020-06-04 22:18:27'),(36,'Java',2,'2020-06-06 15:41:33');/*Table structure for table `menu` */DROP TABLE IF EXISTS `menu`;CREATE TABLE `menu` (`id` int(11) NOT NULL AUTO_INCREMENT,`title` varchar(50) NOT NULL,`route_url` varchar(255) NOT NULL,`icon_classes` varchar(255) NOT NULL,PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;/*Data for the table `menu` */insert into `menu`(`id`,`title`,`route_url`,`icon_classes`) values (1,'用户管理','/admin/user','icon iconfont icon-yonghu'),(2,'文章管理','/admin/article','icon iconfont icon-navicon-wzgl'),(3,'文章分类','/admin/category','icon iconfont icon-wenzhangfenlei'),(4,'SEO管理','/admin/seo','icon iconfont icon-seotag'),(5,'菜单管理','/admin/menu','icon iconfont icon-drxx11');/*Table structure for table `seo_set` */DROP TABLE IF EXISTS `seo_set`;CREATE TABLE `seo_set` (`id` int(11) NOT NULL AUTO_INCREMENT,`description` varchar(255) NOT NULL,`keywords` varchar(255) NOT NULL,PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;/*Data for the table `seo_set` */insert into `seo_set`(`id`,`description`,`keywords`) values (1,'李鹏雷的个人网站lpl666.com-IT技术分享,前端,服务端,Java,JavaScript,HTML,SQL,nosql,CSS,Nodejs,IT培训,前端培训','IT教学,培训,前端,服务端,Java,JavaScript,HTML,SQL,nosql,CSS,Nodejs,IT培训,前端培训');/*Table structure for table `user` */DROP TABLE IF EXISTS `user`;CREATE TABLE `user` (`id` int(11) NOT NULL AUTO_INCREMENT,`username` varchar(255) NOT NULL,`pwd` varchar(255) NOT NULL,`nick` varchar(50) DEFAULT NULL,`createtime` timestamp NULL DEFAULT CURRENT_TIMESTAMP,`is_del` int(11) DEFAULT '0',`usertype` int(11) DEFAULT '0' COMMENT '用户种类,0普通用户,1管理员',PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8;/*Data for the table `user` */insert into `user`(`id`,`username`,`pwd`,`nick`,`createtime`,`is_del`,`usertype`) values (14,'lpl666','2ドルa10ドル$dv2I7yxXwkwyM7IP5M3HlOWZNmr/WgjcLtLVT0bwUsqH3kNmP7BRe','李鹏雷','2020-06-05 14:47:53',0,1);/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。