0%

基本用法

回顾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html><!--约束,声明-->
<html lang="zh_CN"><!--表示html的开始,后面表示语言为中文-->
<head><!--头部信息,一般包含三部分内容,title,css标签,js代码-->
<meta charset="UTF-8">
<title>这是一个标题</title>
</head>
<body bgcolor="white" ><!--html的主体内容,
标签拥有基本属性和动态属性
比如:
bgcolor是基本属性
onclick是动态属性
-->
hello
<button onclick="alert('你好啊啊')">按钮</button>

<!--标签分为单标签br:换行,hr横线-->
举头望明月,<br>低头思故乡<hr>
</body>
</html>

标签语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html><!--约束,声明-->
<html lang="zh_CN"><!--表示html的开始,后面表示语言为中文-->
<head><!--头部信息,一般包含三部分内容,title,css标签,js代码-->
<meta charset="UTF-8">
<title>这是一个标题</title>
</head>
<body >
<!--标签不能交叉使用,标签要正确关闭-->
<br/>1
<!--属性必须要有值,属性值必须加上引号,注释不能嵌套-->
<font color="blue">你好</font>

</body>
</html>

常用的标签

font

1
<font COLOR="red" face="宋体" size="5">字体标签,更改颜色等</font>

特殊字符

1
2
我是&lt;br&gt;标签
我是&nbsp;&nbsp;空格

image-20220322200303349

标题标签

1
2
3
4
5
6
7
<!--标题对齐位置-->
<h1 align="left">标题1</h1>
<h2 align="center">标题2</h2>
<h3 align="right">标题3</h3>
<h4>标题4</h4>
<h5>标题1</h5>
<h6>标题6</h6>

超链接

1
2
3
<!--a标签是超链接,拥有两种值,_self代表当前页面,而_blank表示打开新页面进行跳转-->
<a href="www.baidu.com" target="_blank">百度</a>
<a href="www.baidu.com" target="_self" >百度</a>

列表标签

1
2
3
4
5
6
<ul>
<li>第一</li>
<li>第二</li>
<li>第三</li>
<li>第四</li>
</ul>
1
2
3
4
5
6
<ol type="A">
<li>第一</li>
<li>第二</li>
<li>第三</li>
<li>第四</li>
</ol>

img标签

1
2
3
4
5
<!--. 表示当前文件所在的目录 ..表示当前文件所在的上一级目录
border表示设置文件边框
alt属性表示当指定路径找不到图片时,用来代替显示文本的内容
-->
<img src="D:\壁纸\彩云.png" width="50%" height="30%" alt="没有找到">

表格标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<table border="1" width="300" height="300" cellspacing="10">
<tr align="center">
<th >11</th>
<th>12</th>
<th>13</th>
</tr> <tr>
<td>21</td>
<td>22</td>
<td>23</td>
</tr> <tr>
<td>31</td>
<td>32</td>
<td>33</td>
</tr>
</table>

image-20220322204319879

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<table border="1" width="300" height="300" cellspacing="10">
<tr>
<th >11</th>
<th>12</th>
<th>13</th>
</tr>
<tr>
<td colspan="2">21</td>
<td>23</td>
</tr>
<tr>
<td rowspan="2">31</td>
<td>32</td>
<td>33</td>
</tr>
<tr>
<td>41</td>
<td>42</td>

</tr>
<tr>
<td>51</td>
<td>52</td>
<td>53</td>
</tr>

</table>

image-20220322204732063

iframe标签

1
2
3
4
5
6
7
8
9
10
11
<body >
我是一个完整的页面
<iframe src="Hello.html" name="abc"></iframe>
<!--iframe和超链接标签的使用步骤如下
1.在iframe标签中使用name属性定义一个名称
2.在a标签的target属性上设置iframe的name属性值

-->
<a href="Hello.html" target="abc">hello.html页面</a>
<a href="test.html" target="abc">test.html页面</a>
</body>

表单标签(重点)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<!--表单就是html中用来收集用户信息所有元素集合,然后把这些信息发送给服务器
input type=text标签是文本输入框
input type=password密码输入框
name可以进行分组,checked是默认
checkbox表示复选
-->
<form>
<label>
用户名称:
<input type="text" value="默认值">
</label><br/>
<label>
密码:
<input type="password">
</label><br/>
<label>
性别:
<input type="radio" name="sex" checked="checked">
</label><label>
<input type="radio" name="sex">
</label><br>
<label>
兴趣爱好:
<input type="checkbox">
</label>java<label>
java
<input type="checkbox">
</label><label>
javascript
<input type="checkbox">
</label>javaee<br>
<label>
国籍:
<select>
<option selected="selected">中国</option>
<option>美国</option>
</select>
</label><br>
<label>
自我评价:<textarea rows="5" cols="100" >默认值</textarea>
</label>
<br>
<input type="button" value="按钮">
<input type="file">
<!--重置-->
<input type="reset" >
<input type="submit" >
</form>

一般情况下表单与嵌套在表格之中,更加美观

表单提交细节
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<!--
font标签是表单标签
action属性设置提交的服务器地址
method属性设置提交方式GET或Post
表单提交的时候,数据没有发给服务器的三种情况
1.表单没有name属性值
2.提交表单是没有有效的信息,单选复选下拉框中都需要添加value属性
3.表单项不在默认的form标签中
-->
<form ACTION="https://www.baidu.com" method="post">
<label>
用户名称:
<input type="text" value="默认值">
</label><br/>
<label>
密码:
<input type="password">
</label><br/>
<label>
性别:
<input type="radio" name="sex" checked="checked" value="男">
</label><label>
<input type="radio" name="sex">
</label><br>
<label>
兴趣爱好:
<input type="checkbox">
</label>java<label>
java
<input type="checkbox">
</label><label>
javascript
<input type="checkbox">
</label>javaee<br>
<label>
国籍:
<select>
<option selected="selected">中国</option>
<option>美国</option>
</select>
</label><br>
<label>
自我评价:<textarea rows="5" cols="100" >默认值</textarea>
</label>
<br>
<input type="button" value="按钮">
<input type="file">
<!--重置-->
<input type="reset" >
<input type="submit" >
</form>

get与post的不同

GET请求:

  1. 在浏览器中地址是 action属性+?+请求参数
  2. 不安全,其信息在浏览框中都可以看出来
  3. 有数据长度的限制

POST请求:

  1. 浏览器只有服务器地址
  2. 比较安全
  3. 理论上没有数据长度的限制

其他标签

1
2
3
4
5
6
7
8
9
10
11
<!--
div标签默认不在一行
span他的长度是封装数据的长度
p段落标签默认会在段落上方或者下方空出一行
-->
<div>div1</div>
<div>div2</div>
<div>div3</div>
<span>span1</span>
<span>span1</span>
<p>你好呀</p>

执行步骤

引入包

axios.min.js · moonshuo/fruit stand - Gitee.com

配置信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>01.演示Axios发送普通的参数值给服务器端</title>
<script language="JavaScript" src="script/vue.js"></script>
<script language="JavaScript" src="script/axios.min.js"></script>
<script language="JavaScript">
window.onload=function(){
var vue = new Vue({
"el":"#div0",
//数据区
data:{
uname:"lina",
pwd:"ok"
},
//方法区
methods:{
axios01:function(){
axios({
//需要定义方法,url
method:"POST",
url:"axios01.do",
在这里定义参数
params:{
uname:vue.uname,
pwd:vue.pwd
}
})
//如果成功了,执行这个,
.then(function (value) {
console.log(value);
})
.catch(function (reason) {
//失败则执行这个
console.log(reason);
});
}
}
});
}
</script>
</head>
<body>
<div id="div0">
双向绑定,与vue中的uname和pwd相互绑定
uname:<input type="text" v-model="uname"/><br/>
pwd:<input type="text" v-model="pwd"/><br/>
//点击之后调用axios01函数对象
<input type="button" @click="axios01" value="发送一个带普通请求参数值的异步请求"/>
</div>
</body>
</html>

后台响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@WebServlet("/axios01.do")
public class Axios01Servlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");

String uname = request.getParameter("uname");
String pwd = request.getParameter("pwd");

System.out.println("uname = " + uname);
System.out.println("pwd = " + pwd);

response.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.write(uname+"_"+pwd);

throw new NullPointerException("这里故意抛出一个空指针异常....");
}
}

客户端发送JSON格式

JSON表示一个数据格式,比如json表示两个学员的信息

[{sid:”s001”,age:18},{sid:”s002” ,age:18}],JSON表达的数据更加简介,更加可以节约网络带宽,而其与原来的差别只是将method中的date转换成了JSON格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
methods:{
axios01:function(){
axios({
method:"POST",
url:"axios01.do",
data:{
uname:vue.uname,
pwd:vue.pwd
}
})
.then(function (value) {
console.log(value);
})
.catch(function (reason) {
console.log(reason);
});
}
}

获取参数的方法

而json格式我们使用request.getParameter进行接受,同时需要导入JSON的jar包,进行格式转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@WebServlet("/axios02.do")
public class Axios02Servlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

StringBuffer stringBuffer = new StringBuffer("");
//获得对应的读取流
BufferedReader bufferedReader = request.getReader();
String str = null ;
while((str=bufferedReader.readLine())!=null){
//将读到的数据进行追加
stringBuffer.append(str);
}
str = stringBuffer.toString() ;
//获得的值是键值对

//已知 String
//需要转化成 Java 对象

Gson gson = new Gson();
//Gson有两个API
//1.fromJson(string,T) 将字符串转化成java object
//2.toJson(java Object) 将java object转化成json字符串,这样才能响应给客户端

User user = gson.fromJson(str, User.class);

System.out.println(user);
}
}

后台发送JSON数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@WebServlet("/axios03.do")
public class Axios03Servlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

StringBuffer stringBuffer = new StringBuffer("");
BufferedReader bufferedReader = request.getReader();
String str = null ;
while((str=bufferedReader.readLine())!=null){
stringBuffer.append(str);
}
str = stringBuffer.toString() ;

//已知 String
//需要转化成 Java Object

Gson gson = new Gson();
//Gson有两个API
//1.fromJson(string,T) 将字符串转化成java object
//2.toJson(java Object) 将java object转化成json字符串,这样才能响应给客户端

User user = gson.fromJson(str, User.class);
user.setUname("鸠摩智");
user.setPwd("123456");

//假设user是从数据库查询出来的,现在需要将其转化成json格式的字符串,然后响应给客户端
String userJsonStr = gson.toJson(user);
response.setCharacterEncoding("UTF-8");
//MIME-TYPE,告诉浏览器发送的格式
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(userJsonStr);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<script language="JavaScript">
window.onload=function(){
var vue = new Vue({
"el":"#div0",
data:{
uname:"lina",
pwd:"ok"
},
methods:{
axios03:function(){
axios({
method:"POST",
url:"axios03.do",
data:{
uname:vue.uname,
pwd:vue.pwd
}
})
.then(function (value) {
var data = value.data;
// data对应的数据:
// {uname:"鸠摩智",pwd:"ok"}
vue.uname=data.uname;
vue.pwd=data.pwd;

//此处value中的data返回的是 js object,因此可以直接点出属性
//如果我们获取的是一个字符串: "{uname:\"鸠摩智\",pwd:\"ok\"}"

//js语言中 也有字符串和js对象之间互转的API
//string JSON.stringify(object) object->string
//object JSON.parse(string) string->object
})
.catch(function (reason) {
console.log(reason);
});
}
}
});
}
</script>

js与字符串与对象之间的相互转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>04.JS中的字符串和Object之间互转的API</title>
<script language="JavaScript">
function hello01(){
/*
//1. js string - > js object
var str = "{\"uname\":\"lina\",\"age\":20}";
var user = JSON.parse(str);
alert(typeof user);
alert(user.uname+"_"+user.age);
*/

//2. js object -> js string
var user = {"uname":"lina","age":99};
alert(typeof user);
var userStr = JSON.stringify(user);
alert(typeof userStr);
alert(userStr);
}
</script>
</head>
<body>
<div id="div0">
<input type="button" value="确定" onclick="hello01()"/>
</div>
</body>
</html>

(来自于尚硅谷|尚硅谷丨2022版JavaWeb教程(全新技术栈,全程实战)_哔哩哔哩_bilibili

01.Axios示例

语法规则

选择器:浏览器根据选择器决定受CSS样式影响的HTML标签

属性:要改变的样式名,并且每一个属性都有一个值

声明:一般有多个声明,需要用分号将每个声明分开

CSS与HTML的结合方式

第一种

在标签的style上设置键值对即可,可读性差,多个标签比较难以定义

1
<div style="color: chartreuse">div1</div>

第二种

在head标签中根据语法规则进行定义,利用style标签进行辅助,但是只能给一个页面使用

1
2
3
4
5
6
7
8
9
10
11
<style type="text/css">
div{
border: 1px solid green;
size: 10px;
}
</style>
</head>
<body >
<div>div1</div>
<div>div2</div>
<div>div3</div>

第三种

单独写一个css文件

1
2
3
4
5
6
<link rel="stylesheet" type="text/css" href="test.css">
</head>
<body >
<div>div1</div>
<div>div2</div>
<div>div3</div>
1
2
3
4
div{
border: 1px solid green;
size: 10px;
}

CSS选择器

标签名选择器

标签名{

属性:值

}

标签名选择器可以被动的选择

id选择器

1
2
3
4
5
6
7
8
9
10
11
12
13
<style>
#id1{
color: blue;
size: 19px;
}
#id2{
color: green;
}
</style>
</head>
<body >
<div id="id1">div1</div>
<div id="id2">div2</div>

类选择器

.class{

属性:值

}

1
2
3
4
5
6
7
8
9
10
11
12
<style>
.class1{
color: blue;
}
.class2{
color: red;
}
</style>
</head>
<body >
<div class="class1">div1</div>
<div class="class2">div2</div>

组合选择器

选择器1,选择器2.。。。{

属性:值

}

1
2
3
4
5
6
<style>
.class1,#id1{
color: blue;
}

</style>

CSS常用样式

颜色:color

宽度高度:width,height

背景颜色:background-color

字体样式:font-size字体大小

边框:border

div居中:margin-left:auto;margin-right:auto

超链接去除下划线:text-decoration:none

列表修饰去除:list-style:none

刷题路线和资料来自:代码随想录知识星球 | 代码随想录 (programmercarl.com)

理论基础

数组在Java中的存储与赋值机制Java中基本数据类型赋值机制与数组的赋值机制 | 偷掉月亮 (moonshuo.cn)

可以知道在Java中数组值存储在堆中,而在栈空间中存储的是数组的地址,与基本数据类型不同

image-20220331123609262

二分查找

704. 二分查找 - 力扣(LeetCode) (leetcode-cn.com)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public int search(int[] nums, int target) {
int left=0;
int right=nums.length-1;
while (left<=right){
int mid =(left+right)/2;
if (nums[mid]==target){
return mid;
}
if (target>nums[mid]){
left=mid+1;
}else{
right=mid-1;
}
}
return -1;
}
}

上面的二分查找用法是两边都闭合的,所以可以肯定int right=nums.length-1;如果是两边的闭合不同,需要使right值取值不同。

移除元素

27. 移除元素 - 力扣(LeetCode) (leetcode-cn.com)

巧妙交换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
每一次找到之后,将找到的数与最后一个数交换,并将length--,防止死循环,同时i--,将交换过来的数在检查一次!!!
class Solution {
public int removeElement(int[] nums, int val) {
int length=nums.length;
for (int i=0;i<length;i++){
if (nums[i]==val){
int temp=nums[length-1];
nums[nums.length-1]=nums[i];
nums[i]=temp;
i--;
length--;
}
}
return length;
}
}

双指针法

快慢指针都从0开始,如果遇到目标的数,看快指针增加一,满指针不改变,但是一旦遇到不是目标的数,慢指针立即接受快指针的数,将的目标数覆盖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public int removeElement(int[] nums, int val) {

// 快慢指针
int fastIndex = 0;
int slowIndex;
for (slowIndex = 0; fastIndex < nums.length; fastIndex++) {
if (nums[fastIndex] != val) {
nums[slowIndex] = nums[fastIndex];
slowIndex++;
}
}
return slowIndex;

}
}

有序数组的平方

977. 有序数组的平方 - 力扣(LeetCode) (leetcode-cn.com)

暴力解法

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public int[] sortedSquares(int[] nums) {
int length=nums.length;
for (int i=0;i<length;i++){
nums[i]=nums[i]*nums[i];
}
Arrays.sort(nums);
return nums;

}
}

双指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public int[] sortedSquares(int[] nums) {
int length=nums.length;
int []array=new int[length];
int right=array.length-1;
int flag=array.length-1;
int left=0;
while (right>=left) {
if (Math.abs(nums[right]) >= Math.abs(nums[left])) {
array[flag]=nums[right]*nums[right];
flag--;
right--;
}
else {
array[flag]=nums[left]*nums[left];
flag--;
left++;
}

}
return array;
}
}

长度最小的子数组

暴力解法

这里是求连续的数组,所以可以确定开始的位置,和结束的位置,就可以确定所有的情况,不需要去考虑太多情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int length=nums.length;
int result=length;
boolean flag=false;
for (int i=0;i<length;i++){
int sum=0;
for (int j=i;j<length;j++){
sum=sum+nums[j];
if (sum>=target){
if (result>=j-i+1){
flag=true;
result=j-i+1;
break;
}
}
}
}
if(flag){
return result;
}else{
return 0;
}
}
}

滑动窗口

我的错误解法,忽略的窗口滑动过程中窗口的逐渐缩小的过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public int minSubArrayLen(int target, int[] nums) {
int slowIndex;
int fastIndex=0;
int result=nums.length;
int sum=0;
boolean flag=false;
for (slowIndex=0;fastIndex<nums.length;fastIndex++){
sum=sum+nums[fastIndex];
if (sum>=target&&result>=fastIndex-slowIndex+1){
flag=true;
sum=sum-nums[slowIndex]-nums[fastIndex];
result=fastIndex-slowIndex+1;
slowIndex++;
fastIndex--;
}

}
if(flag){
return result;
}else{
return 0;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {

// 滑动窗口
public int minSubArrayLen(int s, int[] nums) {
int left = 0;
int sum = 0;
//利用result的赋值巧妙赋值避开result=num.length的错误
int result = Integer.MAX_VALUE;
for (int right = 0; right < nums.length; right++) {
sum += nums[right];
while (sum >= s) {
result = Math.min(result, right - left + 1);
sum -= nums[left++];
}
}
return result == Integer.MAX_VALUE ? 0 : result;
}
}

代码随想录 (programmercarl.com)

螺旋矩阵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import java.util.Arrays;

public class Solution {
/*我们
1.首先需要确定在何时转弯
2.向什么方向放生转弯*/
public int[][] generateMatrix(int n) {
int [][]array=new int[n][n];
//代表当前填充的数
int num=1;
//代表每一组已经填充的数
int count=0;
//flag代表圈数
int flag=0;
//代表方向
int position=1;
int hang=n;
int temp=n;
if (n%2!=0){
array[n/2][n/2]=hang*hang;
}
while (num<=hang*hang&&temp>0){
count=0;
if (position==1){
while (count<temp-1){
array[flag][flag+count]=num;
num++;
count++;
}
position++;
}
else if (position==2){
while (count<temp-1) {
array[count+flag][n - flag-1] = num;
num++;
count++;
}
position++;
}
else if (position==3){
while (count<temp-1){
array[n-flag-1][n-count-flag-1]=num;
count++;
num++;
}
position++;
}
else if (position==4){
while (count<temp-1){
array[n-count-flag-1][flag]=num;
num++;
count++;
}
position=1;
flag++;
temp=temp-2;
}
else {break;}

}

return array;
}



public static void main(String[] args) {
Solution solution=new Solution();
System.out.println(Arrays.deepToString(solution.generateMatrix(3)));
//System.out.println(solution.minSubArrayLe2(4, new int[]{1,4,4}));
}
}

在判断时候,一定要对其进行分组,并且注意二维数组的行列等等

总结

  1. 对于数组要删除的元素,是不能完成删除的,但是可以将这个进行覆盖,利用双指针方法可以巧妙将删除的数进行覆盖(图片来自代码随想录 (programmercarl.com)),当然也可以将其和最后的一个交换。27.移除元素-双指针法

  2. 而对于要求数组连续的值,可以使用双指针的变形,滑动窗口方法进行破解。图片来自代码随想录 (programmercarl.com)

    209.长度最小的子数组

  3. 而对于螺旋矩阵,需要思路清晰,分析好每一个方向的情况,同样比较复杂的其他情况要进行分类。

资料来自代码随想录 (programmercarl.com)

基础知识

链表由数据域和指针域两部分组成,最后一个节点的指针域指向null

  • 单链表:只能单向查询
  • 双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点,可以双向查询
  • 循环链表:形成一个环的链表

链表的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ListNode{
//节点的值
int val;
//下一个节点
ListNode next;
//无参构造器
public ListNode(){

}
//节点的构造函数
public ListNode(int val){
this.val=val;
}
public ListNode(int val,ListNode next){
this.val=val;
this.next=next;
}
}

移除链表的元素

203. 移除链表元素 - 力扣(LeetCode) (leetcode-cn.com)

普通方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public ListNode removeElements(ListNode head, int val) {

while ( head!=null&&head.val==val ){
head=head.next;
}
if(head==null){
return head;
}
ListNode preNode=head;
ListNode curNode=head.next;
while (curNode != null) {
if (curNode.val==val){
preNode.next=curNode.next;
}else {
preNode=curNode;
}
curNode=curNode.next;
}

return head;
}

特殊方法

添加虚拟节点的方式,使得头节点也加入常规之中,规避了头节点为空的复杂情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public ListNode removeElements(ListNode head, int val) {
if(head==null){
return head;
}

ListNode dummy=new ListNode(-1,head);
ListNode preNode=dummy;
ListNode curNode=head;
while (curNode != null) {
if (curNode.val==val){
preNode.next=curNode.next;
}else {
preNode=curNode;
}
curNode=curNode.next;
}
//在这里返回dummy.next;是因为上面的方法中头节点在一直改变,head所代表的节点在一直发生改变,在这里可能原来的head.val==val,head已经消失了
return dummy.next;
}

举例

1
2
3
4
public interface TestService {
void sayHi();
void sayGood();
}
1
2
3
4
5
6
7
8
9
10
11
12
public class TestServiceImpl implements TestService {

@Override
public void sayHi() {
System.out.println("HI");
}

@Override
public void sayGood() {
System.out.println("GOOD");
}
}

main

1
2
3
4
5
6
7
8
9
10
public class AppTest 
{
@Test
public void test01() {
TestService testService=new TestServiceImpl();
testService.sayHi();
testService.sayGood();
}

}

需求

现在我需要在以上的两个方法之前分别要求输出现在的时间,以及在执行完之后输出“完成”。我们拥有以下几个方法

  1. 直接在sayHi,与saygood方法中添加
  2. 写一个工具类,调用工具类进行添加

以上的两个方法虽然有效,但是如果需要我们更改100个这样的方法,那样岂不是要更改100行

动态代理实现

动态代理可以在程序的执行过程创建代理对象,通过代理对象执行方法,增加额外方法

实现IncocationHandler,功能增加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.zss.handle;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;

public class MyIncation implements InvocationHandler {
//动态代理的目标对象
private Object object;

public MyIncation(Object object) {
this.object = object;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res=null;
System.out.println(new Date());
//通过代理对象执行方法shi,会执行这个invoke
res=method.invoke(object,args);
System.out.println("完成");
return res;
}
}

创建代理

1
2
3
4
5
6
7
8
9
10
11
12
13
public class AppTest 
{
@Test
public void test01() {
//创建目标对象
TestService testService=new TestServiceImpl();
InvocationHandler invocationHandler=new MyIncation(testService);
//使用Proxy创建代理
TestService proxy=(TestService) Proxy.newProxyInstance(testService.getClass().getClassLoader(), testService.getClass().getInterfaces(),invocationHandler);
proxy.sayGood();
proxy.sayHi();
}
}

image-20220505154812603

当然如果我们不想要sayhi方法执行一些代码,我们可以在实现IncocationHandler通过判断方法的名称,来选择执行哪一些代码

JDK动态代理是需要声明接口的,创建一个动态代理类必须得给这个”虚拟“的类一个接口。可以看到,这时候经动态代理类创造之后的每个bean已经不是原来那个对象了。

参考:

动态代理实际上是程序在运行中,根据被代理的接口来动态生成代理类的class文件,并加载class文件运行的过程,通过反编译被生成的**$Proxy0.class**文件发现:

class类定义为:为何jdk动态代理必须有接口,不支持仅实现类的代理 - 简书 (jianshu.com)

public final class $Proxy0 extends Proxy implements Interface {

​ *public $Proxy0(InvocationHandler paramInvocationHandler) {*

​ *super(paramInvocationHandler);*

​ ***}***

原理

原理不清楚,插眼!!

什么是单例设计模式

所谓单例设计模式,就是采取一定的方法保证整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。

目的:使用着在main方法中就不可以自己创建实例对象以及定义一些变量。

单例设计模式的两种方法:饿汉式,懒汉式

饿汉式

  1. 将我们的构造器私有化,防止我们在类的外部可以直接new一个对象
  2. 在类的内部直接创建该静态对象(否则下面的static方法无法调用)
  3. 提供一个公共的static方法,返回我们想要的对象
  4. 实现代码

疑问:

为什么getInsatnce方法要用静态static???

如果我们不使用静态方法,想要调用我们的方法时,就必须首先new一个实例对象,与我们的最初使用的目的相违背。

为什么叫饿汉式??

我们可能在以后的使用中不会使用到dog的名字,但是我们的类的内部已经给出了相应的属性元素。

查看以下一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Test {
public static void main(String[] args) {
Dog dog=Dog.getInstance();
System.out.println(dog.toString());
}
}
class Dog{
private String name;
private static Dog ming=new Dog("小狼");
/**创建一个私有的构造器*/
private Dog(String name){
this.name=name;
}
/**创建一个公共的静态方法*/
public static Dog getInstance(){
return ming;
}

@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
'}';
}
}

image-20220322090055669

懒汉式

基于上述的饿汉式的缺点:创建对象却可以没有被使用,下面我们看看懒汉式

  1. 构造器私有化
  2. 定义一个静态属性,但是不去创建
  3. 提供一个公共的静态方法
  4. 只有用户使用对象时,才会返回这个对象,如果我们再次去调用这个函数,我们会返回上一次我们创建的对象。

我们来查看以下代码:可以发现我们在使用dog.age时,并未创建新的实例对象,也就没有占用空间,如果我们不使用name就不会去调用instannce方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Test {
public static void main(String[] args) {
System.out.println(Dog.age);
Dog dog=Dog.getInstance();
System.out.println(dog.toString());
}
}
class Dog{
private String name;
public static int age=10;
/** 创建一个私有的对象却不进行初始化*/
private static Dog ming;
/**创建一个私有的构造器*/
private Dog(String name){
System.out.println("构造器被调用");
this.name=name;
}
/**创建一个公共的静态方法*/
public static Dog getInstance(){
if (ming==null){
ming=new Dog("小狼");
}
return ming;
}

@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
'}';
}
}

image-20220322090118858

小结

饿汉式:在类加载时创建,可能存在资源浪费的问题

懒汉式:线程安全问题,多个线程同时访问,会发生冲突

反射相关类

  1. Class代表一个类,Class对象表示某个类加载后在堆中的对象
  2. Method:代表类的方法
  3. Field:代表类的成员变量
  4. Constructor:代表类的构造方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.zss.reflact;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

/**
* @author zss
*/
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Properties properties=new Properties();
properties.load(new FileReader("src/com/zss/reflact/test.properties"));
String classPath=properties.getProperty("class");
String method=properties.getProperty("method");
//首先我们得到了类名称以及方法,但是在这里我们无法创建新的类和调用方法
//使用反射机制,第一步加载类,返回Class了类型的对象clas
Class clas=Class.forName(classPath);
//通过clas得到加载类,Cat的实例对象
Object o=clas.newInstance();
System.out.println("运行类型为"+o.getClass());
//通过clas得到加载类的方法,在反射机制中将方法视为对象(万物皆对象)
Method method1=clas.getMethod(method);
//通过方法对象类调用对象
method1.invoke(o);
//只能得到公有的
Field name=clas.getField("name");
System.out.println(name);
//得到值
System.out.println(name.get(o));
//得到构造器
Constructor constructor=clas.getConstructor();
//得到有形参的构造器
Constructor constructor1 = clas.getConstructor(String.class, int.class);
System.out.println(constructor1);
System.out.println(constructor);


}
}

image-20220321161913538

反射的优化

反射的优点:可以动态创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架机制失去底层支撑。

反射缺点:使用反射基本时=是解释执行,对执行的速度有影响,直接调用方法与与反射调用方法的速度可能会相差上百倍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.zss.reflact;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

/**
* @author zss
*/
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Properties properties=new Properties();
properties.load(new FileReader("src/com/zss/reflact/test.properties"));
String classPath=properties.getProperty("class");
String method=properties.getProperty("method");
Class clas=Class.forName(classPath);
Object o=clas.newInstance();
Method method1=clas.getMethod(method);
long startTime=System.currentTimeMillis();
for (int i=0;i<900000000;i++) {
method1.invoke(o);
}
long endTime=System.currentTimeMillis();
System.out.println(endTime-startTime);
}
}

image-20220321163324656

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.zss.reflact;

import java.lang.reflect.Field;

/**
* @author zss
*/
public class Cat {
public static void main(String[] args) {
Cat cat=new Cat();
long startTime =System.currentTimeMillis();
for (int i=0;i<900000000;i++){
cat.hi();
}
long endTime =System.currentTimeMillis();
System.out.println(endTime-startTime);
}
public String name="阿华";
private int age=2;
public void hi(){
System.out.println("你好啊小猫猫");
}
public void cry(){
//System.out.println("小猫咪哭泣");
}

public Cat() {
}

public Cat(String name, int age) {
this.name = name;
this.age = age;
}
}

image-20220321163403286

优化

在反射之中,拥有一个方法可以关闭反射的安全检查机制,提高运行的速度

1
method1.setAccessible(false);

image-20220321163737496

获取Class对象的方式

方式一:在代码编译阶段

使用Class.forName(),但是此时已经知道了一个类的全类名和该类在类路径下。可以通过Class类的静态方法获取 。多用于全类名,了解类的全路径,加载类等

1
2
String classPath="com.zss.reflact.Cat";
Class<?> cla=Class.forName(classPath);

方式二:在类加载阶段

类.class,多用于参数的传递,比如通过反射得到对应构造器的对象

1
2
Class cla2=Cat.class;
System.out.println(cla2);

方式三:在运行阶段

对象.getClass(),此时已经知道了实例化对象,将其对应的运行类型进行传递。而我们学到这里也可以了解到所谓的运行类型其实也是通过反射指向了我们.class文件加载之后真正的类

1
2
3
Cat cat=new Cat();
Class cla3=cat.getClass();
System.out.println(cla3);

方式四:在类加载器中

1
2
3
4
5
6
Cat cat=new Cat();
//第一步:先得到类加载器
ClassLoader classLoader=cat.getClass().getClassLoader();
//通过类加载其得到Class对象,得到路径
Class cla4=classLoader.loadClass(classPath);
System.out.println(cla4);

那些类拥有Class对象

  1. 外部类,内部类
  2. 接口,数组
  3. 枚举,注解
  4. 基本数据类型
  5. void
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.zss.reflact;


import java.io.Serializable;

/**
* @author zss
*/

public class Test {
public static void main(String[] args) {
Class<String> cls=String.class;
Class<Serializable> cls2=Serializable.class;
Class<Integer[]> cls3=Integer[].class;
Class<Deprecated> cls4=Deprecated.class;
System.out.println(cls);
System.out.println(cls2);
System.out.println(cls3);
System.out.println(cls4);

}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.zss.reflact;


/**
* @author zss
*/
public class Cat {
public String name="asac";
public int age=0;
private String sex="娜娜";
int tall=15;
public void cry(){
System.out.println("HI");
}
void hello(){
System.out.println("NIAO");
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.zss.reflact;

import java.util.Arrays;

/**
* @author zss
*/
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Class cla=Class.forName("com.zss.reflact.Cat");
//获得全类名
System.out.println(cla.getName());
//获得简单类名
System.out.println(cla.getSimpleName());
//获得所有公共的属性
System.out.println(Arrays.toString(cla.getFields()));
//获得本类所有的属性
System.out.println(Arrays.toString(cla.getDeclaredFields()));
//获得public方法
System.out.println(Arrays.toString(cla.getMethods()));
//所有方法
System.out.println(Arrays.toString(cla.getDeclaredMethods()));
//public构造器
System.out.println(Arrays.toString(cla.getConstructors()));
//所有构造器
System.out.println(Arrays.toString(cla.getDeclaredConstructors()));
//包名
System.out.println(cla.getPackage());
//父类
System.out.println(cla.getSuperclass());
//接口
System.out.println(Arrays.toString(cla.getInterfaces()));

}
}

image-20220321205903318

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.zss.reflact;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
* @author zss
*/
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Class cla=Class.forName("com.zss.reflact.Cat");
//输出属性的修饰符,0代表默认,1代表public,2代表private,4代表protected,static是8,final是16,如果重复则会相加
//得到属性的类名信息
Field[] fields= cla.getFields();
for (Field f:fields){
System.out.println(f.getName()+f.getModifiers()+f.getType());
}



//方法的修饰符,返回方法修饰的对象
Method[] methods= cla.getMethods();
for (Method m:methods){
System.out.println(m.getName()+m.getModifiers()+m.getReturnType());
}

//构造器
Constructor []constructors= cla.getConstructors();
System.out.println(constructors[0].getName()+constructors[0].getModifiers()+ Arrays.toString(constructors[0].getTypeParameters()));
}
}

image-20220321211933358

Class类

  1. Class也是类,因此继承Object类
  2. Class类对象不是new出来的,而是系统创建的
  3. 对于某个类的Class类对象,在内存中只有一份,因此类只加载一次
  4. 每一个类的实例都会记得自己由那个Class实例化生成
  5. 通过Class可以得到一个类的完整结构
  6. Class对象是放在堆中的
  7. 注意在方法区中,会存在cat类的二进制字节码数据

传统对象debug与我们反射对象的debug可以发现都是通过ClassLoader类的loadClass加载到堆中,而且我们cat.class对象只会存在份,不管我们new多少次,还是会存在一次,类不同才会不同

class拥有许多的方法,可以得到对象的很多的信息。

Class类的常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.zss.reflact;


/**
* @author zss
*/
public class Cat {
public String name="asac";
public int age=0;
public String sex="娜娜";

@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.zss.reflact;

import java.io.IOException;
import java.lang.reflect.Field;

/**
* @author zss
*/

public class Test {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
String classPath="com.zss.reflact.Cat";
//?代表不确定的类型
Class<?> cla=Class.forName(classPath);
//尝试输出cla,这里显示那个类的Class对象
System.out.println(cla);
//得到其运行类型
System.out.println(cla.getClass());
//得到包名
System.out.println(cla.getPackage().getName());
//得到全类名
System.out.println(cla.getName());
//创建对象实例
Cat cat=(Cat) cla.newInstance();
System.out.println(cat);
//通过反射获取属性
Field name=cla.getField("name");
System.out.println(name.get(cat));
//通过反射设置值
name.set(cat,"sec");
System.out.println(cat);
//得到所有的字段属性
Field[] fields=cla.getFields();
//System.out.println(fields);
for (Field f:fields){
System.out.println(f.getName());
}
}
}

image-20220321172447804