开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

Java中发送Http请求之HttpURLConnectio

发表于 2021-11-20

这是我参与11月更文挑战的第11天,活动详情查看:11月更文挑战

Java中发送Http请求的方式有很多,记录一下相关的请求方式,本次记录Jdk自带的HttpURLConnection,简单简洁,使用简单.

1 HttpURLConnection

HttpURLConnection是Jdk自带的请求工具,不用依赖第三方jar包,适用简单的场景使用.

使用方式是, 通过调用URL.openConnection方法,得到一个URLConnection对象,并强转为HttpURLConnection对象.

java.net.URL部分源码:

1
2
3
java复制代码    public URLConnection openConnection() throws java.io.IOException {
return handler.openConnection(this);
}

java.net.HttpURLConnection部分源码:

1
2
3
java复制代码abstract public class HttpURLConnection extends URLConnection {
// ...
}

从代码可知HttpURLConnection是URLConnection子类, 其内类中主要放置的是一些父类的方法和请求码信息.

发送GET请求, 其主要的参数从URI中获取,还有请求头,cookies等数据.

发送POST请求, HttpURLConnection实例必须设置setDoOutput(true),其请求体数据写入由HttpURLConnection的getOutputStream()方法返回的输出流来传输数据.

1 准备一个SpringBoot项目环境

2 添加一个控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码@Controller
@Slf4j
public class HelloWorld {

@Override
@GetMapping("/world/getData")
@ResponseBody
public String getData(@RequestParam Map<String, Object> param) {
System.out.println(param.toString());
return "<h1> Hello World getData 方法</h1>";
}

@PostMapping("/world/getResult")
@ResponseBody
public String getResult(@RequestBody Map<String, Object> param) {
System.out.println(param.toString());
return "<h1> Hello World getResult 方法</h1>";
}
}

3 添加一个发送请求的工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
java复制代码package com.cf.demo.http;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

/**
* Http请求工具类
*/
@Slf4j
@Data
public class JdkHttpUtils {

/**
* 获取 POST请求
*
* @return 请求结果
*/
public static String getPost(String url, Integer connectTimeout,
Integer readTimeout, String contentType, Map<String, String> heads,
Map<String, String> params) throws IOException {

URL u;
HttpURLConnection connection = null;
OutputStream out;
try {
u = new URL(url);
connection = (HttpURLConnection) u.openConnection();
connection.setRequestMethod("POST");
connection.setConnectTimeout(connectTimeout);
connection.setReadTimeout(readTimeout);
connection.setRequestProperty("Content-Type", contentType);
// POST请求必须设置该属性
connection.setDoOutput(true);
connection.setDoInput(true);
if (heads != null) {
for (Map.Entry<String, String> stringStringEntry : heads.entrySet()) {
connection.setRequestProperty(stringStringEntry.getKey(),
stringStringEntry.getValue());
}
}

out = connection.getOutputStream();
if (params != null && !params.isEmpty()) {
out.write(toJSONString(params).getBytes());
}
out.flush();
out.close();

// 获取请求返回的数据流
InputStream is = connection.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 封装输入流is,并指定字符集
int i;
while ((i = is.read()) != -1) {
baos.write(i);
}
return baos.toString();

} catch (Exception e) {
log.error("请求发生异常,信息为= {} ", e.getMessage());
}
return null;
}


/**
* 获取 POST请求
*
* @return 请求结果
*/
public static String getGet(String url, Integer connectTimeout,
Integer readTimeout, String contentType, Map<String, String> heads,
Map<String, String> params) throws IOException {

// 拼接请求参数
if (params != null && !params.isEmpty()) {
url += "?";
if (params != null && !params.isEmpty()) {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> stringObjectEntry : params.entrySet()) {
try {
sb.append(stringObjectEntry.getKey()).append("=").append(
URLEncoder.encode(stringObjectEntry.getValue(), "UTF-8"))
.append("&");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
sb.delete(sb.length() - 1, sb.length());
url += sb.toString();
}
}

URL u;
HttpURLConnection connection;
u = new URL(url);
connection = (HttpURLConnection) u.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(connectTimeout);
connection.setReadTimeout(readTimeout);
connection.setRequestProperty("Content-Type", contentType);
if (heads != null) {
for (Map.Entry<String, String> stringStringEntry : heads.entrySet()) {
connection.setRequestProperty(stringStringEntry.getKey(),
stringStringEntry.getValue());
}
}

// 获取请求返回的数据流
InputStream is = connection.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 封装输入流is,并指定字符集
int i;
while ((i = is.read()) != -1) {
baos.write(i);
}
return baos.toString();

}


/**
* Map转Json字符串
*/
public static String toJSONString(Map<String, String> map) {
Iterator<Entry<String, String>> i = map.entrySet().iterator();
if (!i.hasNext()) {
return "{}";
}

StringBuilder sb = new StringBuilder();
sb.append('{');
for (; ; ) {
Map.Entry<String, String> e = i.next();
String key = e.getKey();
String value = e.getValue();
sb.append("\"");
sb.append(key);
sb.append("\"");
sb.append(':');
sb.append("\"");
sb.append(value);
sb.append("\"");
if (!i.hasNext()) {
return sb.append('}').toString();
}
sb.append(',').append(' ');
}
}


}

4 添加测试工具类

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
java复制代码@Slf4j
public class HttpTest {

// 请求地址
public String url = "";
// 请求头参数
public Map<String, String> heads = new HashMap<>();
// 请求参数
public Map<String, String> params = new HashMap<>();
// 数据类型
public String contentType = "application/json";
// 连接超时
public Integer connectTimeout = 15000;
// 读取超时
public Integer readTimeout = 60000;

@Test
public void testGET() {
// 1 添加数据
url = "http://localhost:8080/world/getData";
heads.put("token", "hhhhhhhhhhhaaaaaaaa");
params.put("username", "libai");

String result = null;
try {
// 2 发送Http请求,获取返回结果
result = JdkHttpUtils
.getGet(url, connectTimeout, readTimeout, contentType, heads, params);
} catch (IOException e) {
e.printStackTrace();
}
// 3 打印结果
log.info(result);
}

@Test
public void testPOST() {
// 1 添加数据
url = "http://localhost:8080/world/getResult";
heads.put("token", "hhhhhhhhhhhaaaaaaaa");
params.put("username", "libai");

String result = null;
try {
// 2 发送Http请求,获取返回结果
result = JdkHttpUtils
.getPost(url, connectTimeout, readTimeout, contentType, heads, params);
} catch (IOException e) {
e.printStackTrace();
}
// 3 打印结果
log.info(result);
}
}

5 测试结果

POST请求测试结果

1
2
3
4
5
java复制代码/*
[main] INFO com.cf.demo.HttpTest - <h1> Hello World getResult 方法</h1>

{username=libai}
*/

GET请求测试结果

1
2
3
4
5
6
java复制代码/*
[main] INFO com.cf.demo.HttpTest - <h1> Hello World getData方法</h1>

{username=libai}

*/

参考资料:

www.baeldung.com/java-http-r…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【设计模式系列】建造者模式 前言 建造者模式结构 建造者模式

发表于 2021-11-20

这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战

前言

建造者设计模式和工厂模式,抽象工厂模式一样也是创建型设计模式之一。

如果在需要创建一个对象时,该对象包含大量的参数,使用工厂模式或抽象工厂模式会存在一些问题。为了解决这些问题,可以使用建造者模式。

工厂模式和抽象工厂模式在这种情况下主要存在下列问题:

  • 有太多参数需要从客户端传递给工厂类,这很容易出错,尤其是当有很多数据类型一样的参数时,很难正确维护参数的顺序;
  • 如果有参数是可选的,则需要在参数列表中传递NULL值;
  • 如果对象很重,创建起来很复杂,那么这些复杂性都会侵入到工厂类中;
  • 这种问题虽然可以通过提供一个包含所有必须参数的构造方法,然后其他可选参数使用setter方法来设置,但是这样容易造成对象状态的不一致。

为了解决这一系列问题,在建造者模式中,通过提供一种逐步构建对象的方法,并将最终对象返回,可以解决大量参数和对象状态不一致的问题。

建造者模式结构

如果实现建造者设计模式,主要分以下步骤:

  • 首先创建一个静态内部类,将所有需要构建对象的属性赋值到内部类中;按照约定的命名规范,一般命名为XXXBuilder,比如需要创建一个Computer对象,则静态内部类为ComputerBuilder;
  • 这个Builder类需要提供一个包含所有参数的构造方法;
  • 然后Builder类还需要提供设置可选参数的方法,该设置方法需要将Builder对象返回;
  • 最后需要在Builder类中提供build()方法,该方法将返回客户端最终需要的对象,也就是Computer对象。

代码结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
java复制代码package com.heiz.design.builder;

/**
* @author 小黑说Java
* @ClassName Computer
* @Description
* @date 2021/11/20
**/

public class Computer {

//必选参数
/**
* 硬盘
*/
private String HDD;
/**
* 内存
*/
private String RAM;

// 可选参数
/**
* 显卡
*/
private boolean isGraphicsCardEnabled;
/**
* 蓝牙
*/
private boolean isBluetoothEnabled;


public String getHDD() {
return HDD;
}

public String getRAM() {
return RAM;
}

public boolean isGraphicsCardEnabled() {
return isGraphicsCardEnabled;
}

public boolean isBluetoothEnabled() {
return isBluetoothEnabled;
}

private Computer(ComputerBuilder builder) {
this.HDD = builder.HDD;
this.RAM = builder.RAM;
this.isGraphicsCardEnabled = builder.isGraphicsCardEnabled;
this.isBluetoothEnabled = builder.isBluetoothEnabled;
}

//Builder Class
public static class ComputerBuilder {

//必选参数
/**
* 硬盘
*/
private String HDD;
/**
* 内存
*/
private String RAM;

// 可选参数
/**
* 显卡
*/
private boolean isGraphicsCardEnabled;
/**
* 蓝牙
*/
private boolean isBluetoothEnabled;

public ComputerBuilder(String hdd, String ram) {
this.HDD = hdd;
this.RAM = ram;
}

public ComputerBuilder setGraphicsCardEnabled(boolean isGraphicsCardEnabled) {
this.isGraphicsCardEnabled = isGraphicsCardEnabled;
return this;
}

public ComputerBuilder setBluetoothEnabled(boolean isBluetoothEnabled) {
this.isBluetoothEnabled = isBluetoothEnabled;
return this;
}

public Computer build() {
return new Computer(this);
}
}
}

需要注意,Computer类只有getter方法,没有公共构造函数。因此,获得Computer对象的唯一方法是通过ComputerBuilder类。

然后我们使用一个简单的测试,来测一下我们的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码package com.heiz.design.builder;

/**
* @author 小黑说Java
* @ClassName BuilderTest
* @Description
* @date 2021/11/20
**/
public class BuilderTest {
public static void main(String[] args) {
Computer computer = new Computer.ComputerBuilder("500GB", "2 GB")
.setBluetoothEnabled(false)
.setGraphicsCardEnabled(true)
.build();
}
}

建造者模式的优点

使用建造者模式将创建对象的可变属性和不可变属性进行分离,对于客户端只能使用建造者创建对象,不用关心创建细节,所以建造者模式更易于扩展,便于控制细节风险。

JDK中的建造者模式

在JDK中最典型的建造者模式就是我们常用的StringBuilder和StringBuffer:

  • java.lang.StringBuilder#append() 线程不安全
  • java.lang.StringBuffer#append() 线程安全

以上就是本期建造者模式的全部内容,如果对你有所帮助,点个赞是对我最大的鼓励。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

563 二叉树的坡度 563 二叉树的坡度

发表于 2021-11-20

「这是我参与11月更文挑战的20天,活动详情查看:2021最后一次更文挑战」

563. 二叉树的坡度

  • 给定一个二叉树,计算 整个树 的坡度 。
  • 一个树的 节点的坡度 定义即为,该节点左子树的节点之和和右子树节点之和的 差的绝对值 。
  • 如果没有左子树的话,左子树的节点之和为 0 ;没有右子树的话也是一样。空结点的坡度是0。整个树 的坡度就是其所有节点的坡度之和。

示例一
图片.png

1
2
3
4
5
6
7
ini复制代码输入:root = [1,2,3]
输出:1
解释:
节点 2 的坡度:|0-0| = 0(没有子节点)
节点 3 的坡度:|0-0| = 0(没有子节点)
节点 1 的坡度:|2-3| = 1(左子树就是左子节点,所以和是 2 ;右子树就是右子节点,所以和是 3 )
坡度总和:0 + 0 + 1 = 1

示例二
图片.png

1
2
3
4
5
6
7
8
9
10
ini复制代码输入:root = [4,2,9,3,5,null,7]
输出:15
解释:
节点 3 的坡度:|0-0| = 0(没有子节点)
节点 5 的坡度:|0-0| = 0(没有子节点)
节点 7 的坡度:|0-0| = 0(没有子节点)
节点 2 的坡度:|3-5| = 2(左子树就是左子节点,所以和是 3 ;右子树就是右子节点,所以和是 5 )
节点 9 的坡度:|0-7| = 7(没有左子树,所以和是 0 ;右子树正好是右子节点,所以和是 7 )
节点 4 的坡度:|(3+5+2)-(9+7)| = |10-16| = 6(左子树值为 3、5 和 2 ,和是 10 ;右子树值为 9 和 7 ,和是 16 )
坡度总和:0 + 0 + 0 + 2 + 7 + 6 = 15

思路

  • 首先根据例子我们可以看出
    • 是要求遍历每个节点,然后将左右子树都累加和的差绝对值
    • 最后我们返回所有的坡度之和
  • 所以我们先不断遍历左子树,得到左子树的累加和
    • 右子树同样也是,但是每次的左右子树和的差绝对值会被累加到res中
    • 终止节点就是遍历为空
    • 每次进入下一层的条件就是以node为根节点的左右子树和加上此节点的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码class Solution {
int res = 0;
public int findTilt(TreeNode root) {
dfs(root);
return res;
}

public int dfs(TreeNode node) {
if (node == null) return 0;
int leftRes = dfs(node.left);
int rightRes = dfs(node.right);

res += Math.abs(leftRes - rightRes);
return leftRes + rightRes + node.val;
}
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【spring源码-8】bean的实例化(5)Factory

发表于 2021-11-20

上一篇【spring源码-7】bean的实例化(4)后置处理

应用示例:

定义类实现 FactoryBean 接口:
image.png

测试结果:
image.png
image.png

现象:

  1. 通过 “myFactoryBean” 获取到的并非是 MyFactoryBean 实例,而是 Student 实例。
  2. 通过 “&myFactoryBean” 获取到了 MyFactoryBean 实例。
  3. spring容器加载完成后,实例化了 MyFactoryBean,没有实例化 Student,获取的时候才完成了实例化。

源码解析

image.png
877 行:实例化bean时会先判断当前bean是否实现了 FactoryBean 接口。

878 行:FactoryBean 的 getBean();

getBean(FACTORY_BEAN_PREFIX + beanName)

image.png
246 行:转换 beanName。调用到本类方法如下:

1. 转换 beanName

image.png

说白了就是 “&beanName” 的 “&” 去掉,返回beanName。

2. 从一级缓存中获取

250 行:先从一级缓存中获取实例。

前提条件:此时上下文对象已经初始化完成,一级缓存中是存在 “MyFactoryBean” 实例的。
beanName 又是转换过后的名称,肯定没有 “&”,此时都能获取到 MyFactoryBean 实例。

  1. applicationContext.getBean(“myFactoryBean”);

可以拿到 MyFactoryBean 实例。sharedInstance 就是 MyFactoryBean 的实例。
2. applicationContext.getBean(“&myFactoryBean”);

可以拿到实例,sharedInstance 也是 MyFactoryBean 的实例。

3. getObjectForBeanInstance(sharedInstance, name, beanName, null)

image.png

此时:name 可能有 “&”,beanName 一定没有“&”(前面已经去掉了)。

  1. applicationContext.getBean(“myFactoryBean”);

入参:sharedInstance 是 MyFactoryBean 的实例,name = “myFactoryBean“,beanName = “myFactoryBean”,mbd = null
2. applicationContext.getBean(“&myFactoryBean”);

入参:sharedInstance 是 MyFactoryBean 的实例,name = “&myFactoryBean“,beanName = “myFactoryBean”,mbd = null


1792 行:如下图所示:判断当前bean是否是“&”,如果有就说明当前获取bean时传的是 “&myFactoryBean”。
image.png

1796 行:再检查下当前bean是否实现了 FactoryBean 接口,如果没有就抛出异常,因为当前就是 FactoryBean 的逻辑。

1802 行:直接返回 MyFactoryBean 实例。

总结:

1792 - 1803 行:applicationContext.getBean(“&myFactoryBean”)进入 if 判断,最终返回从一级缓存中获取到的 MyFactoryBean 实例


方法走下来,是 applicationContext.getBean(“myFactoryBean”)的逻辑。

1808 - 1810 行:如果 beanInstance(刚才从一级缓存中获取到实例) 不是 FactoryBean 类型的,就返回。

1817 行:从 factoryBeanObjectCache 缓存中获取实例。

注意:此时的缓存并非是spring容器的一级缓存。

factoryBeanObjectCache 缓存是存储 FactoryBean 类的 getObject() 返回的实例。

1827 行:缓存拿不到就创建。

image.png
image.png
169 行:调用到 FactoryBean 的 getObject();创建Student对象并添加到factoryBeanObjectCache 缓存中。

总结:

  1. applicationContext.getBean(“”)时,传 “&factoryBean” 获取 FactoryBean 实例,传 “factoryBean” 获取 getObject() 返回的对象。
  2. FactoryBean 类会随着IOC容器的创建 完成实例化,但是 getObject() 中的对象不会,只有当获取时才会完成实例化。
  3. getObject() 中的对象也是由IOC完成创建的,但是并没有放在一级缓存中管理,而是放在了factoryBeanObjectCache 缓存中。

注:本文通过源码 + 行说明的方式进行描述,若不好理解可留言。本文仅为个人学习记录,有错误的地方,请大佬们指正。

下一篇【spring源码-9】bean的实例化(6)循环依赖

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

浏览器前进后退功能的简单实现 浏览器前进后退功能的简单实现

发表于 2021-11-20

浏览器前进后退功能的简单实现

这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战

笔者最近在学习《数据结构与算法之美》,正好借着这个机会边练习边记录一下自己学习的知识点。不啰嗦,直接开始。

假设我们依次浏览了页面 A、页面 B、页面 C 和 页面D,当我们在页面 D 点击浏览器的后退按钮时,浏览器会返回放上一次浏览的页面 C,此时再点击前进按钮则会返回到页面 D。这就是浏览器的前进和后退功能,让我们能够随意的返回前一个页面和后一个页面,那么这个功能是如何实现的呢?这里我们先按下不表,先看看栈这种数据结构。

一、什么是栈

  • 栈也是线性数据结构
  • 栈是一种操作受限的线性表,只允许在一端对数据进行操作。
  • 正因为只能在一端进行操作,也造就了栈具有的后进先出的特点,也即后加入的数据,先被移除出去。就像是餐厅叠在一起的盘子,新洗好的盘子每次放都会放在最上面,取时也会从最上面取。

二、栈的简单实现

栈有两种实现方式,使用数组实现的栈叫顺序栈,使用链表实现则叫链式栈。栈添加数据操作叫入栈,取出数据的操作叫出栈。

2.1 顺序栈实现思路及代码

使用数组实现栈,因为只能在一端进行操作,这里我们选择在数组的后端进行操作。因为后端加入和移除数据都不会进行数据搬移的操作,也更好保持数据在数组中的有序性。入栈只需要将 size++ ,出栈 size– 即可。可能有人会问,数组大小是固定的,当数组满了在入栈怎么办?有两个简单的处理方式,一个是使用动态扩容的数组,二是数组满了再入栈提示入栈失败即可。

ok,下面是具体的实现代码:

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
java复制代码/**
* @author xuls
* @date 2021/11/17 20:30
* 数组实现栈
*/
public class ArrayStack <T>{
private Object[] dataArray;
private int size;

public ArrayStack(int capacity) {
dataArray = new Object[capacity];
size = 0;
}

//入栈
public boolean push(T data){
if (size == dataArray.length){
//数组满了,入栈失败
return false;
}
//也可以写 dataArray[size++] = data;
dataArray[size] = data;
size ++ ;
return true;
}

//出栈
public T pop(){
if (isEmpty()){
throw new IndexOutOfBoundsException("stack is Empty");
}
//也可以写成 dataArray[size--]
Object result = dataArray[size - 1];
size--;
return (T) result;
}

public boolean isEmpty(){
return size == 0;
}
}

2.2 链式栈实现思路及代码

链表实现栈思路也很简单,入栈只需要将新数据插入到链表头部,出栈也移除链表头部的数据即可,无需考虑扩容的问题。

下面是具体实现代码:

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
java复制代码/**
* @author xuls
* @date 2021/11/17 20:47
* 链表实现栈
*/
public class LinkedListStack <T>{
private Node<T> headNode;
private int size;

public LinkedListStack() {
size = 0;
}

//入栈
public void push(T data){
Node<T> tNode = new Node<>(data, null);
if (headNode != null){
tNode.next = headNode;
}
this.headNode = tNode;
size ++ ;
}

//出栈
public T pop(){
if (isEmpty()){
throw new IndexOutOfBoundsException("stack is empty");
}
T data = headNode.data;
headNode = headNode.next;
size--;
return data;
}

public boolean isEmpty(){
return size == 0;
}

public void clear(){
headNode = null;
size=0;
}

private static class Node<T>{
private T data;
private Node<T> next;

public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
}
}

三、浏览器前进后退简单实现

有了上面对栈这种数据结构的介绍,你可能已经想到了如何去实现一个简单的浏览器前进后退功能了。没错,我们可以使用两个栈,一个是前进栈,一个是后退栈。

例如,还是依次访问页面 A、页面 B、页面 C 和页面 D。

当我们从页面 A 到页面 B 时,就将页面 A 的网址在后退栈入栈,页面 B 到页面 C 时,将页面 B 的网址在后退栈入栈。页面 C 到页面 D 也是如此。

好了,这时如果从页面 D 回退到页面 C ,只需要将页面 D 在前进栈入栈,后退栈进行出栈就能拿到页面 C 的访问地址。从页面 C 前进到页面 D,也是一样将页面 C 的网址在后退栈入栈,同时前进栈进行出栈操作就能拿到页面 D 的网址了。

但如果返回到页面 C 时,再打开一个新的页面 E,此时就无法进行前进操作了,也因此需要对前进栈里的数据进行清空。这样就实现了一个简单的前进后退功能。

未命名文件 (1).png

下面是用链式栈实现的具体代码:

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
java复制代码/**
* @author xuls
* @date 2021/11/17 21:21
*/
public class CustomBrowser {
private String currentUrl;
private LinkedListStack<String> backStack;
private LinkedListStack<String> forwardStack;

public CustomBrowser() {
backStack = new LinkedListStack<>();
forwardStack = new LinkedListStack<>();
}

public void open(String url){
if (this.currentUrl != null){
backStack.push(this.currentUrl);
//打开新页面时,需要清空前进栈的内容
forwardStack.clear();
}
this.currentUrl = url;
System.out.println("open "+currentUrl);
}

public void back(){
if (!backStack.isEmpty()){
forwardStack.push(this.currentUrl);
this.currentUrl = backStack.pop();
System.out.println("back " + this.currentUrl);
}
}

public void forward(){
if (!forwardStack.isEmpty()){
backStack.push(this.currentUrl);
this.currentUrl = forwardStack.pop();
System.out.println("forward "+ this.currentUrl);
}
}

public String getCurrentUrl(){
return currentUrl;
}

public static void main(String[] args) {
CustomBrowser browser = new CustomBrowser();
browser.open("http://www.baidu1.com");
browser.open("http://www.baidu2.com");
browser.open("http://www.baidu3.com");
browser.back();
browser.back();
browser.forward();
browser.open("http://www.qq.com");
browser.forward();
browser.back();
browser.back();
browser.back();
browser.back();
browser.back();
browser.back();
System.out.println(browser.getCurrentUrl());
}
}

四、总结

  • 栈是一种受限的线性数据结构,只能在一端进行操作,也造成了后进先出的特点(重点,复习三遍)。
  • 栈的两种类型,顺序栈和链式栈。
  • 受限并不意味不易使用,相反特殊的场景更能发挥栈的威力,除了浏览器的前进后退之外,还有括号匹配、表达式求值或者是迷宫问题等等,这就需要我们能将具体的问题的特点抽象出来了。

传送门:

如何实现单链表

如何实现动态扩容数组

XDM ,天冷了除了保暖也要多运动哦,动动小手点赞评论吧!

catdan.gif

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

二叉树刷题记(六-二叉搜索树的第k大节点)

发表于 2021-11-20

这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战

前言

  • 今天更新到了第七天,终于达到了更文第一关的要求 ,写文章费了不少的时间(小嘟本身就写的很慢,再加上我对文章的质量有一定的要求,所以就...),但是一想到更文奖励,我就又动力啦!!!哈哈哈。
  • 小嘟还是会保证文章质量的,不会因为为了参加活动就发一些水文,觉得这样既浪费了读者的时间,也浪费了小嘟的时间,最后,文章没什么价值可言,这也是我不愿意看到的。
  • 希望读者看完能有所收获。
  • 今日,还是回到更新二叉树系列,文章不会很长,请读者耐心阅读。
  • 注:本嘟是个算法渣渣,(面试的时候让写个非递归的后序遍历都没有写出来,唉),so,我写文章的目的是为了帮助那些想学习算法,但是算法题目看了之后依然很迷茫的读者,大神请绕过哦!

目的

  • 本文目的:小嘟带着读者做做二叉树相关的题目,然后小嘟把自己的感悟总结出来。
  • 之前已经带大家将二叉树的三种遍历算法都学完了(包括递归和非递归),不会的请滑到文章底部,小嘟在底部放着链接哦!

正文

  • 首先,还是先看题目
    image.png
    image.png
  • 题目描述,让我们找一个二叉搜索树的第K大节点,小嘟还是在放一张图,看图直观点
    image.png
  • 这里有一个二叉树的名词,二叉搜索树,那么什么是二叉搜索树呢?
  • 小嘟叨叨时刻:二叉搜索树,小嘟的理解就是一个结点的左孩子小于它,它的右孩子大于它,故此,我们可以知道二叉搜索树有一个性质:一个结点的左子树的val都小于等于它,它的右子树的val都大于等于它。
    image.png
  • 概念搞清楚了,那就好好分析题目
+ 首先由题目的限制条件可以知道,**root不是一个空树**(因为它`k的限制`条件告诉我们它肯定能找到一个元素)
+ 我们要找到第K大元素,那么我们就要**先遍历二叉树**?怎样遍历这是一个问题。仔细想想:要遍历而且要找到第K大元素,那么我们首先是不是要找到一个递增或递减的顺序呢?哦,悟了悟了!我们要找这样的一个顺序,要么递增,要么递减。
+ 现在就该想想我们该用那种遍历方式呢?前序?中序?后序?想想它们的遍历顺序,再结合二叉树的性质,还有就是我们要找的是一个递增或者递减的顺序。
+ 分析之后,`我们选择了中序遍历`,这是一个递增的顺序。(还有一种方法哦!不知道给起个什么名字,暂且叫我假中序遍历,哈哈哈)
  • 直接看代码呗
  • 递归版的
1
2
3
4
5
6
7
8
9
10
11
12
ini复制代码var kthLargest = function(root, k) {
let res=[];
const Kmax = (root01)=>{
if(root01 == null) return;
Kmax(root01.left);
res.push(root01.val);
Kmax(root01.right)
}
Kmax(root);
let length = res.length;
return res [length-k];
};

image.png

  • 迭代版的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ini复制代码var kthLargest = function(root, k) {
let res=[];
let stack = [];
while(root || stack.length ){
while(root){
stack.push(root);
root = root.left;
}
let node = stack.pop();
res.push(node.val);
root = node.right;
}
let length = res.length;
return res [length-k];
};

image.png

另一种方法

  • 我们的思路还是不变,还是要找一个有序的顺序,我们找的是一个递减的顺序。
  • 递归版的
1
2
3
4
5
6
7
8
9
10
11
ini复制代码var kthLargest = function(root, k) {
let res=[];
const Kmax = (root01)=>{
if(root01 == null) return;
Kmax(root01.right);
res.push(root01.val);
Kmax(root01.left)
}
Kmax(root);
return res[k-1];
};

image.png

  • 迭代版的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ini复制代码 var kthLargest = function(root, k) {
let res=[];
let stack = [];
while(root || stack.length ){
if(res.length == k) return res[k-1];
while(root){
stack.push(root);
root = root.right;
}
let node = stack.pop()
res.push(node.val);
root = node.left;
}
return res[k-1];
};

image.png

  • 啦啦啦,小嘟讲完啦,感觉还是二叉树问题离不开三种遍历方式,所以我们要做二叉树相关的题目,首先还是要掌握三种最基本的遍历顺序。
    溜啦溜啦...

结尾

  • 希望各位学习算法的朋友能够坚持下去,加油!
  • 若有任何问题,欢迎下方留言,小嘟看到会第一时间回复的。
  • 创作不易,觉得还不错的,欢迎支持支持!
  • 多想多做多尝试,实在不会的话,直接看代码,用代码来理解。

附件

  • 二叉树前序遍历: juejin.cn/post/702968…
  • 二叉树中序遍历
+ 递归版:[juejin.cn/post/702748…](https://dev.newban.cn/7027482621089153038)
+ 迭代版:[juejin.cn/post/702774…](https://dev.newban.cn/7027747932711419935)
  • 二叉树后序遍历: juejin.cn/post/702911…
  • 二叉树遍历(五-最终篇):juejin.cn/post/703058…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

基金详情信息获取之 Java 爬虫 1 简介 2基金详情

发表于 2021-11-20
  1. 简介

上篇 基金列表获取之Java”伪”爬虫 简单讲解了如何获取基金列表详情,浅显易懂,没有什么难度。加下来将要继续分享如何使用 Java 爬虫分析如何抓取基金的详情信息及内容的展示。

2.基金详情抓取

1
2
3
bash复制代码 # 这里直接给大家一个基金的详情页面地址,进行分析
 http://fund.eastmoney.com/009265.html
 # 这里悄悄地告诉你一个小秘密,简单分析下这个url就会知道,前半部分就固定的格式,后半部分 '009265' 即为基金代码

基金详情页面展示如下:

image-20211120102408791

点击基金概况进入该基金的相关内容介绍信息,如下:

image.png

在基金的概况页面,可以获取基金全称,基金简称、发行日期、成立日期/规模等等相关信息,可以让基民快速的了解当前基金的一些信息,用户根据自己的需要进行收藏关注、后期购买等相关操作。

小伙伴可能会疑惑,进入基金页面后,如何自动进入基金概况页面呢,如何去解析对应的 “基金概况” 访问链接呢。额,其实我也不知道,你信吗 。

1
2
3
4
shell复制代码 # 基金概况访问地址
     http://fundf10.eastmoney.com/jbgk_009265.html
 # 小伙伴仔细的瞅一瞅,地址是不是很简单呢
  #   http://fundf10.eastmoney.com/ + "基金概况"的首字母 "jbgk_" + 基金代码 + ".html"
  1. 解析详情内容

根据页面进行分析,获取对应信息在Html 中的标签内容,如图,可以根据**class= " txt_cont "** 标签进行解析对应的数据信息

image.png

怎么分析呢,Java 爬虫常用 Jsoup 进行Html 网页的分析。

1
2
3
4
5
6
xml复制代码 <!-- 引入Jsoup 依赖 -->
 <dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.13.1</version>
 </dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
ini复制代码 // 实现具体逻辑如下
 ​
 Connection connection = Jsoup.connect("http://fundf10.eastmoney.com/jbgk_009265.html");
 Document doc = connection.get();
 // 根据 class标签进行解析
 Elements element = doc.getElementsByClass("txt_cont");
 // 获取表格中的 "th" "td" 文本信息
 Elements th = element.select("th");
 Elements td = element.select("td");
    for (int i = 0;  i < th.size(); i++) {
        // 打印获取到的内容信息
        System.out.println(th.get(i).text() + ": " + td.get(i).text());
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
erlang复制代码 基金全称: 易方达消费精选股票型证券投资基金
 基金简称: 易方达消费精选股票
 基金代码: 009265(前端)
 基金类型: 股票型
 发行日期: 2020年04月08日
 成立日期/规模: 2020年04月13日 / 79.147亿份
 资产规模: 71.23亿元(截止至:2021年09月30日)
 份额规模: 58.9184亿份(截止至:2021年09月30日)
 基金管理人: 易方达基金
 基金托管人: 建设银行
 基金经理人: 萧楠
 成立来分红: 每份累计0.00元(0次)
 管理费率: 1.50%(每年)
 托管费率: 0.25%(每年)
 销售服务费率: ---(每年)
 最高认购费率: 1.20%(前端) 天天基金优惠费率:0.12%(前端)
 最高申购费率: 1.50%(前端) 天天基金优惠费率:0.15%(前端)
 最高赎回费率: 1.50%(前端)
 业绩比较基准: 中证内地消费主题指数收益率×50%+中证香港300消费指数收益率×35%+中债总指数收益率×15%
 跟踪标的: 该基金无跟踪标的

以上就是对基金概况信息的简单解析。

  1. Jsoup 常用 Api

可以选择官方文档进行查看,官方地址,可以根据自己的需要进行选择合适的方法进行 Html 文档的解析。

image.png

本文主要使用了 getsByClass 和 select 两个方法,简单讲解下。

  • getElementsByClass

通过 class 标签属性获取对应的 Html 元素

  • select 返回的是 Elements 元素集合
1
dart复制代码Element 元素里面存在超链接等 Html 属性,所以最终获取文本的时候使用 ".text()" 方法

喜欢的小伙伴记得点赞,欢迎留言进行探讨

【参考文章】 juejin.cn/post/703119…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

解决ip和域名都能够ping通但是启动nginx无法访问网页

发表于 2021-11-20

解决思路

  1. 最近双11逛西部数码的官网看看有没有什么服务器优惠的时候,发现了可以申请一个一块钱用一整年的SSL证书,立马心动下单了,想想俺也可以用https装装X了哈哈
  2. 不过在部署完证书,并调整nginx代理将初始端口指向443端口之时,突然发现个人站点访问不到了,有点奇怪
  3. 但是,遇到问题先别慌,先检查服务器的运行状态,一切OK,再检查是否能够ping通我的IP和域名,好没问题
  4. 咦这么奇怪的嘛,在我脑子没有转过弯之前,我一直没注意我的防火墙端口只开放到了初始端口,并没有开放443端口,啊,我在搞什么啊
  5. 于是,在/etc/sysconfig/iptables文件中开放443端口,重启防火墙,OK,网页访问正常了
  6. 总结,我真是个大傻X哈哈哈哈哈

开启443端口流程

  1. cd /etc/sysconfig进入该目录,检查是否存储了iptables文件
  2. vim iptables使用vim编辑器修改iptables文件,按下i进入编辑模式
  3. 在初始端口那行下面添加-A INPUT -p tcp -m state --state NEW -m tcp --dport 443 -j ACCEPT,开放443端口
  4. service iptables restart重启防火墙即可

liunx防火墙命令

linux中主要分为旧版的iptables防火墙和新版的firewall防火墙

iptables防火墙

  1. 查看防火墙状态 service iptables status
  2. 停止防火墙 service iptables stop
  3. 启动防火墙 service iptables start
  4. 重启防火墙 service iptables restart
  5. 永久关闭防火墙 chkconfig iptables off
  6. 永久关闭防火墙后重启 chkconfig iptables on

firewall防火墙

  1. 查看防火墙服务状态 systemctl status firewalld
  2. 查看防火墙状态 firewall-cmd --state
  3. 停止防火墙 service firewalld stop
  4. 启动防火墙 service firewalld start
  5. 重启防火墙 service firewalld restart
  6. 查看防火墙规则 firewall-cmd --list-all
  7. 查看80端口是否开放 firewall-cmd --query-port=80/tcp
  8. 开放80端口 firewall-cmd --permanent --add-port=80/tcp
  9. 移除80端口 firewall-cmd --permanent --remove-port=80/tcp
  10. 开放和移除端口都是对配置文件做出了修改,需要重启防火墙,下面是8/9命令中的参数解析
    • firewall-cmd 是linux提供的操作firewall的一个工具
    • --permanent 表示设置为持久
    • --add-port 表示添加的端口

我是 fx67ll.com,如果您发现本文有什么错误,欢迎在评论区讨论指正,感谢您的阅读!

如果您喜欢这篇文章,欢迎访问我的 本文github仓库地址,为我点一颗Star,Thanks~ :)

转发请注明参考文章地址,非常感谢!!!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【Java入门100例】13修改文件扩展名——字符串替换

发表于 2021-11-20

这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战

点赞再看,养成习惯。微信搜索【一条coding】关注这个在互联网摸爬滚打的程序员。

本文收录于技术专家修炼,里面有我的学习路线、系列文章、面试题库、自学资料、电子书等。欢迎star⭐️

题目描述

难度:简单

现有文件路径path="c:\\myfile\\2021\\yitiao.txt";

要求打印文件名并把文件名的后缀改为.java文件。

如:

1
2
3
4
> 复制代码yitiao.txt
> yitiao.java
>
>

知识点

  • 字符串查找
  • 字符串截取
  • 字符串替换

解题思路

1.查找指定字符串的坐标

观察路径,不难发现文件名位于\\后面,我们需要确定\\的坐标,然后做字符串截取即可。

获取坐标,可以用indeOf()实现,棘手的是\\不止一个,所有需要使用lastIndexOf("\\")。

2.字符串截取

有了坐标,直接使用substring()截取即可,不要忘了坐标+1。

3.字符串替换

Java有replace和replaceAll用来替换字符串。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码/**
* 修改文件扩展名——字符串替换
*/
public class question_13 {
public static void main(String[] args) {
String path="c:\\myfile\\2021\\yitiao.txt";
int index=path.lastIndexOf("\\");
String fileName=path.substring(index+1);
String newName=fileName.replaceAll(".txt",".java");
System.out.println(path);
System.out.println(fileName);
System.out.println(newName);
}
}

输出结果

扩展总结

关于字符串操作无论是算法还是以后工作,应用都非常多,一定要熟练使用字符串的方法。

最后

独脚难行,孤掌难鸣,一个人的力量终究是有限的,一个人的旅途也注定是孤独的。当你定好计划,怀着满腔热血准备出发的时候,一定要找个伙伴,和唐僧西天取经一样,师徒四人团结一心才能通过九九八十一难。
所以,

如果你想学好Java

想进大厂

想拿高薪

想有一群志同道合的伙伴

请加入技术交流

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

LeetCode-114-二叉树展开为链表

发表于 2021-11-20

「这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战」。

二叉树展开为链表

题目描述:给你二叉树的根结点 root ,请你将它展开为一个单链表:

  • 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
  • 展开后的单链表应该与二叉树 先序遍历 顺序相同。

示例说明请见LeetCode官网。

来源:力扣(LeetCode)

链接:leetcode-cn.com/problems/fl…

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解法一:递归

使用递归的方式求解,递归过程如下:

  • 如果当前根节点为null时,直接返回;
  • 如果当前根节点的左右子节点都为空时,不需要调整,直接返回;
  • 如果当前根节点的左子树为空,则只需要递归处理当前根节点的右子树;
  • 如果当前根节点的右子树为空时,则需要递归处理当前根节点的左子树,然后将当前根节点的左子节点指向null,右子节点指向处理后的左子树;
  • 如果当前根节点的左右子树都不为空,则分别递归处理当前根节点的左右子树,然后将根节点的左子节点指向null,右子节点指向处理后的左子树,然后将左子树的最后一个节点指向处理后的右子树。
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
java复制代码import com.kaesar.leetcode.TreeNode;

public class LeetCode_114 {
/**
* 递归
*
* @param root
*/
public static void flatten(TreeNode root) {
// 当前根节点为null时,直接返回
if (root == null) {
return;
}
// 当前根节点的左右子节点都为空时,不需要调整,直接返回
if (root.left == null && root.right == null) {
return;
}
if (root.left == null) {
// 当前根节点的左子树为空,则只需要递归处理当前根节点的右子树
flatten(root.right);
return;
}
if (root.right == null) {
// 当前根节点的右子树为空时,则需要递归处理当前根节点的左子树,然后将当前根节点的左子节点指向null,右子节点指向处理后的左子树
TreeNode left = root.left;
flatten(left);
root.right = left;
root.left = null;
return;
}
// 如果当前根节点的左右子树都不为空
TreeNode left = root.left;
// 递归处理左子树,并找到处理后的最后一个节点
flatten(left);
TreeNode leftLast = left;
while (leftLast.right != null) {
leftLast = leftLast.right;
}
TreeNode right = root.right;
// 递归处理右子树
flatten(right);
// 最后将根节点的左子节点指向null,右子节点指向处理后的左子树,然后将左子树的最后一个节点指向处理后的右子树
root.right = left;
leftLast.right = right;
root.left = null;
}

public static void main(String[] args) {
TreeNode root = new TreeNode(1);
root.left = new TreeNode(2);
root.right = new TreeNode(5);
root.left.left = new TreeNode(3);
root.left.right = new TreeNode(4);
root.right.right = new TreeNode(6);

System.out.println("展开为链表之前");
root.print();
System.out.println("展开为链表之后");
flatten(root);
root.print();
}
}

【每日寄语】 我们的人生像云霄飞车,充满高低与刺激,我们经历多,心理建设要强壮,眼界开,胸怀要放大。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…267268269…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%