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

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


  • 首页

  • 归档

  • 搜索

冲刺大厂每日算法&面试题,动态规划21天——第十六天 导读

发表于 2021-11-14

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

导读

在这里插入图片描述

肥友们为了更好的去帮助新同学适应算法和面试题,最近我们开始进行专项突击一步一步来。我们先来搞一下让大家最头疼的一类算法题,动态规划我们将进行为时21天的养成计划。还在等什么快来一起肥学进行动态规划21天挑战吧!!

21天动态规划入门

给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

在这里插入图片描述

1
2
3
4
5
6
java复制代码示例 1:


输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。
1
2
3
4
java复制代码示例 2:

输入:grid = [[1,2,3],[4,5,6]]
输出:12
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
java复制代码
由于路径的方向只能是向下或向右,因此网格的第一行的每个元素只能从左上角元素开始向右移动到达,网格的第一列的每个元素只能从左上角元素开始向下移动到达,此时的路径是唯一的,因此每个元素对应的最小路径和即为对应的路径上的数字总和。

对于不在第一行和第一列的元素,可以从其上方相邻元素向下移动一步到达,或者从其左方相邻元素向右移动一步到达,元素对应的最小路径和等于其上方相邻元素与其左方相邻元素两者对应的最小路径和中的最小值加上当前元素的值。由于每个元素对应的最小路径和与其相邻元素对应的最小路径和有关,因此可以使用动态规划求解。

创建二维数组 \textit{dp}dp,与原始网格的大小相同,\textit{dp}[i][j]dp[i][j] 表示从左上角出发到 (i,j)(i,j) 位置的最小路径和。显然,\textit{dp}[0][0]=\textit{grid}[0][0]dp[0][0]=grid[0][0]。对于 \textit{dp}dp 中的其余元素,通过以下状态转移方程计算元素值。

当 i>0i>0 且 j=0j=0 时,\textit{dp}[i][0]=\textit{dp}[i-1][0]+\textit{grid}[i][0]dp[i][0]=dp[i−1][0]+grid[i][0]。

当 i=0i=0 且 j>0j>0 时,\textit{dp}[0][j]=\textit{dp}[0][j-1]+\textit{grid}[0][j]dp[0][j]=dp[0][j−1]+grid[0][j]。

当 i>0i>0 且 j>0j>0 时,\textit{dp}[i][j]=\min(\textit{dp}[i-1][j],\textit{dp}[i][j-1])+\textit{grid}[i][j]dp[i][j]=min(dp[i−1][j],dp[i][j−1])+grid[i][j]。

最后得到 \textit{dp}[m-1][n-1]dp[m−1][n−1] 的值即为从网格左上角到网格右下角的最小路径和。




class Solution {
public int minPathSum(int[][] grid) {
if (grid == null || grid.length == 0 || grid[0].length == 0) {
return 0;
}
int rows = grid.length, columns = grid[0].length;
int[][] dp = new int[rows][columns];
dp[0][0] = grid[0][0];
for (int i = 1; i < rows; i++) {
dp[i][0] = dp[i - 1][0] + grid[i][0];
}
for (int j = 1; j < columns; j++) {
dp[0][j] = dp[0][j - 1] + grid[0][j];
}
for (int i = 1; i < rows; i++) {
for (int j = 1; j < columns; j++) {
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
}
}
return dp[rows - 1][columns - 1];
}
}

在一个由 ‘0’ 和 ‘1’ 组成的二维矩阵内,找到只包含 ‘1’ 的最大正方形,并返回其面积。

在这里插入图片描述

1
2
3
4
5
java复制代码示例 1:


输入:matrix = [["1","0","1","0","0"],["1","0","1","1","1"],["1","1","1","1","1"],["1","0","0","1","0"]]
输出:4

在这里插入图片描述

1
2
3
4
5
java复制代码示例 2:


输入:matrix = [["0","1"],["1","0"]]
输出:1
1
2
3
4
java复制代码示例 3:

输入:matrix = [["0"]]
输出:0
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
java复制代码可以使用动态规划降低时间复杂度。我们用 \textit{dp}(i, j)dp(i,j) 表示以 (i, j)(i,j) 为右下角,且只包含 11 的正方形的边长最大值。如果我们能计算出所有 \textit{dp}(i, j)dp(i,j) 的值,那么其中的最大值即为矩阵中只包含 11 的正方形的边长最大值,其平方即为最大正方形的面积。

那么如何计算 \textit{dp}dp 中的每个元素值呢?对于每个位置 (i, j)(i,j),检查在矩阵中该位置的值:

如果该位置的值是 00,则 \textit{dp}(i, j) = 0dp(i,j)=0,因为当前位置不可能在由 11 组成的正方形中;

如果该位置的值是 11,则 \textit{dp}(i, j)dp(i,j) 的值由其上方、左方和左上方的三个相邻位置的 \textit{dp}dp 值决定。具体而言,当前位置的元素值等于三个相邻位置的元素中的最小值加 11,状态转移方程如下:

dp(i, j)=min(dp(i−1, j), dp(i−1, j−1), dp(i, j−1))+1
dp(i,j)=min(dp(i−1,j),dp(i−1,j−1),dp(i,j−1))+1



class Solution {
public int maximalSquare(char[][] matrix) {
int maxSide = 0;
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return maxSide;
}
int rows = matrix.length, columns = matrix[0].length;
int[][] dp = new int[rows][columns];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++) {
if (matrix[i][j] == '1') {
if (i == 0 || j == 0) {
dp[i][j] = 1;
} else {
dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
}
maxSide = Math.max(maxSide, dp[i][j]);
}
}
}
int maxSquare = maxSide * maxSide;
return maxSquare;
}
}

面试题

这次讲一下关于二叉树的面试必备知识;
构建一个node对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码public class node {
public int val;
public node left;//左孩子
public node right;//右孩子
public node() {

}
public node(int val,node left,node right) {
this.val=val;
this.left=left;
this.right=right;
}
public node(int val, node left) {
super();
this.val = val;
this.left = left;
}

}

 求⼆叉树的最⼩深度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码
public class minDepth {
public static int depth(node root) {
if(root==null)return 0;
return getMin(root);
}
public static int getMin(node root) {
if(root==null)return Integer.MAX_VALUE;//int类型的最大值
if(root.left==null&& root.right==null){
return 1;
}
return Math.min(getMin(root.left), getMin(root.right))+1;
}
}

本文转载自: 掘金

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

高频算法面试题(二十八)- 单链表的排序

发表于 2021-11-14

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

刷算法题,从来不是为了记题,而是练习把实际的问题抽象成具体的数据结构或算法模型,然后利用对应的数据结构或算法模型来进行解题。个人觉得,带着这种思维刷题,不仅能解决面试问题,也能更多的学会在日常工作中思考,如何将实际的场景抽象成相应的算法模型,从而提高代码的质量和性能

单链表的排序

题目来源:LeetCode-148. 排序链表

题目描述

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表

进阶:

你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?

示例

示例 1

1.png

1
2
ini复制代码输入:head = [4,2,1,3]
输出:[1,2,3,4]

示例 2

2.png

1
2
ini复制代码输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]

示例 3

1
2
ini复制代码输入:head = []
输出:[]

提示:

解题

解法一:暴力解法

思路

首先看到是排序,我们肯定就想到那八大排序算法,但是因为要排序的是链表,就很难用那八大排序算法了,因为链表不支持根据下标随机访问

不能使用就想办法使用,把链表遍历一遍,把值取出来放到一个数组中,然后对数组中的元素进行排序。之后再按顺序将排序后的数组元素,连接成一个链表

暴力解法思路很简单,如果用快排,它的时间复杂度是O(nlog),因为需要一个新的链表,所以需要额外的空间,空间复杂度是链表元素的个数,也就是o(n)

这里就不放代码了,主要看下边这种排序方法

解法二:归并排序

思路

有人一看到归并排序,可能就想到它的空间复杂度并不是O(1),但是我们要排的元素是存在链表里的,不是数组,我们可以在O(1)的空间复杂度,实现两个链表的合并

归并排序本质上是一种分治思想,本题就是将链表不断的一分为二,当链表分到一定程度的时候,就只剩一个结点的时候,那它就是有序的,那再将有序链表合并,最后这个链表不就是有序的了吗?

归并排序主要是找中间位置,链表找中间位置很简单,用快慢指针。慢指针,一次移动一个结点,快指针一次移动两个节点,当快指针到达链表的尾部的时候,慢指针的位置就是链表的中点位置

不清楚归并排序过程的,可以看这里(有图)

代码

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
ini复制代码//链表排序
func sortList(head *ListNode) *ListNode {
return Sort(head, nil)
}

func Sort(head, tail *ListNode) *ListNode {
if head == nil {
return head
}
if head.Next == tail {
head.Next = nil
return head
}
slow, fast := head, head
for fast != tail {
slow = slow.Next
fast = fast.Next
if fast != tail {
fast = fast.Next
}
}
mid := slow

return MergeList(Sort(head, mid), Sort(mid, tail))
}

func MergeList(head1, head2 *ListNode) *ListNode {
dummy := &ListNode{} // 哨兵头结点
tmpNode, tmpNode1, tmpNode2 := dummy, head1, head2
for tmpNode1 != nil && tmpNode2 != nil {
if tmpNode1.Val <= tmpNode2.Val {
tmpNode.Next = tmpNode1
tmpNode1 = tmpNode1.Next
} else {
tmpNode.Next = tmpNode2
tmpNode2 = tmpNode2.Next
}

tmpNode = tmpNode.Next
}

if tmpNode1 != nil {
tmpNode.Next = tmpNode1
}
if tmpNode2 != nil {
tmpNode.Next = tmpNode2
}

return dummy.Next
}

本文转载自: 掘金

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

Go语言 viper与web服务的关机,重启

发表于 2021-11-14

基本使用

主要就是来读写配置文件的,和方便的库,应该说是目前go语言中 最成熟的一个配置方案了
github.com/spf13/viper

唯一要注意的是 vipe的 设置是有优先级的,另外对于设置的key也是大小写不敏感的

image.png

我们可以简单配置一个文件:

image.png

简单使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
go复制代码func main() {
viper.Set("fileDr", "./")
// 读取配置文件
viper.SetConfigName("config") // 配置文件的名称
viper.SetConfigType("json") // 配置文件的扩展名,这里除了json还可以有yaml等格式
// 这个配置可以有多个,主要是告诉viper 去哪个地方找配置文件
// 我们这里就是简单配置下 在当前工作目录下 找配置即可
viper.AddConfigPath(".")
err := viper.ReadInConfig()
if err != nil {
panic(err)
}
fmt.Println(viper.Get("name"))
}

image.png

除了读配置文件 我们当然也可以写入配置文件

1
2
javascript复制代码viper.Set("age", "181")
viper.WriteConfigAs("config.json")

还可以监控配置文件的变化:

1
2
3
4
go复制代码viper.WatchConfig()
viper.OnConfigChange(func(in fsnotify.Event) {
fmt.Println("config changed",in.name)
})

image.png

另外viper还可以支持 远程读取配置信息 比如读取etcd的配置信息

viper一样也支持访问环境变量,有兴趣的可以读一下文档,我这里因为不喜欢操作环境变量。所以就再演示了

viper 读值要注意的事项

没有配置的 会返回0值,所以我们一般用isSet来判断

1
2
3
arduino复制代码if !viper.IsSet("house") {
fmt.Println("no house key")
}

嵌套的key 可以用 . 来做分隔符

1
less复制代码fmt.Println(viper.Get("address.location"))

也可以直接序列化

json 形如:

1
2
3
4
5
6
7
json复制代码{
"age": "181",
"name": "wuyue",
"address": {
"location": "南京"
}
}

定义结构体

1
2
3
4
5
6
7
go复制代码type Config struct {
Age string `json:"age"`
Name string `json:"name"`
Address struct {
Location string `json:"location"`
} `json:"address"`
}
1
2
arduino复制代码var config Config
viper.Unmarshal(&config)

当然这个结构体还可以用mapstructure 来定义

1
2
3
4
5
6
7
8
9
go复制代码type Config2 struct {
Age int
Name string
Address `mapstructure:"address"`
}

type Address struct {
Location string
}

优雅关机与重启

当我们的web服务程序需要更新的时候,通常都是杀掉进程,然后重启即可。但是这样做稍微有点粗鲁,因为假设你杀进程的时候。有请求进来 那么此时进程被杀,这个请求的发起方的体验会很差。

理想种的情况是 我们kill这个web进程的时候 可以 这个web服务可以把手机的活全干完(已经连接上请求,但是程序仍旧在执行 还没有response) 然后再kill自己。

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
go复制代码func main() {
router := gin.Default()
router.GET("/", func(context *gin.Context) {
time.Sleep(5 * time.Second)
context.String(http.StatusOK, "welcome to gin server")
})
srv := &http.Server{
Addr: ":8080",
Handler: router,
}
go func() {
err := srv.ListenAndServe()
if err != nil {
fmt.Println(err)
}
}()
// 等待中断信号来关闭服务器
quit := make(chan os.Signal, 1)
//一般关闭服务器都是直接kill 命令杀进程
//kill -2 一般就是发送的SIGINT信号 我们常用的ctrl c 就是触发系统的sigint信号
//kill -9 一般就是发送的sigkill信号,一般不能捕获 所以也就不用管这个了
//notify 这个操作 是把收到的这些信号 SIGINT SIGTERM 转发给quit
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
// 如果chan里面没有数据 那就会阻塞在这
<-quit
log.Println("shutdown server")
// 创建一个5s超时的context
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("server shutdown ")
}
}

实验也很好做,我们在浏览器中访问一个web连接,这个时候gin的代码里我们设置好了要5秒以后才给返回,
然后我们ctrl c kill掉这个进程 你就会发现,程序不是马上就死的,而时等响应之后 才会kill掉自己
浏览器里面也能正常获得响应

image.png

同样的 做到优雅重启也不难

原理就是 当收到信号的时候,我们先fork 一个子进程出来,父进程你就干你还没干完的事情就可以了,
这个时候 新的请求都会到这个 子进程来,等父进程活干完以后 就自己结束就行了

有兴趣的话可以看 github.com/fvbock/endl…

要注意的是如果用了supervisor 类似的进程管理工具 则不需要做优雅重启,只需要稍微关心下优雅关机即可

本文转载自: 掘金

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

高频算法面试题(二十七)- 判断回文

发表于 2021-11-14

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

刷算法题,从来不是为了记题,而是练习把实际的问题抽象成具体的数据结构或算法模型,然后利用对应的数据结构或算法模型来进行解题。个人觉得,带着这种思维刷题,不仅能解决面试问题,也能更多的学会在日常工作中思考,如何将实际的场景抽象成相应的算法模型,从而提高代码的质量和性能

判断回文

题目来源:LeetCode-680. 验证回文字符串 Ⅱ

题目描述

给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串

示例

示例 1

1
2
ini复制代码输入: s = "aba"
输出: true

示例 2

1
2
3
ini复制代码输入: s = "abca"
输出: true
解释: 你可以删除c字符

示例 3

1
2
ini复制代码输入: s = "abc"
输出: false

提示:

  • 1 <= s.length <= 10^5
  • s 由小写英文字母组成

解题

解法一:暴力解法(贪心)

思路

首先我们知道什么样的字符串是回文字符串,题目中要求在原字符串不满足回文字符串的时候,可以删除一个字符,如果删除一个字符之后,能使它是个回文字符串,则也是满足要求

首先最容易想到的就是,我先判断原字符串是不是回文,如果是,直接返回true,如果不是,那我就逐一把每一个字符串都删除,判断删除后的字符串是否是回文

这是暴力解法的思路,时间复杂度比较高,是O(n^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
go复制代码//暴力解法
func ValidPalindrome2(s string) bool {
if IsPalindrome(s) {
return true
}

sNew := ""
for i := 0; i < len(s); i++ {
if i == 0 {
sNew = s[1:]
} else if i == len(s)-1 {
sNew = s[:len(s)-1]
} else {
sNew = s[0:i] + s[i+1:]
}
if IsPalindrome(sNew) {
return true
}
}

return false
}

//判断某一段字符串是否是回文字符串
func IsPalindrome(s string) bool {

left, right := 0, len(s)-1
for left <= right {
if s[left] != s[right] {
return false
}
left++
right--
}

return true
}

解法二:双指针

思路

在上边的暴力解法中,我们是逐一的删除每一个字符,然后去判断删除后的字符串是否是回文。我们是否有必要把每一个字符都删除一遍?

如果我们在遇到两边字符不等的时候,只需要考虑删除左边这个字符或者删除右边这个字符,不就可以了?只要两边任意删除一个,能使字符串是回文,那它就是满足的

代码

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
sql复制代码func ValidPalindrome(s string) bool {
if len(s) == 0 || len(s) == 1 {
return true
}

left, right := 0, len(s)-1
for left <= right {
if s[left] == s[right] {
left++
right--
} else {
//判断删除左边元素后的字符串是否是回文
flagLeft, flagRight := true, true
i, j := left+1, right
for i < j {
if s[i] != s[j] {
flagLeft = false
break
}
i++
j--
}
//判断删除右边元素后的字符串是否是回文
i, j = left, right-1
for i < j {
if s[i] != s[j] {
flagRight = false
break
}
i++
j--
}

return flagLeft || flagRight
}
}

return true
}

本文转载自: 掘金

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

Oracle控制文件丢失如何恢复(归档模式) 一、查看控制文

发表于 2021-11-14

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

一、查看控制文件路径和内容

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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
sql复制代码SQL> show parameter control_files

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
control_files string /oradata/orcl/control01.ctl, /
u01/app/oracle/fast_recovery_a
rea/orcl/control02.ctl

[oracle@orcl:/oradata/orcl]$ ll /oradata/orcl/control01.ctl
-rw-r----- 1 oracle oinstall 9748480 Apr 15 10:01 /oradata/orcl/control01.ctl

[oracle@orcl:/oradata/orcl]$ ll /u01/app/oracle/fast_recovery_area/orcl/control02.ctl
-rw-r----- 1 oracle oinstall 9748480 Apr 15 10:03 /u01/app/oracle/fast_recovery_area/orcl/control02.ctl

--确保开启归档
SQL> archive log list
Database log mode No Archive Mode
Automatic archival Disabled
Archive destination /archivelog
Oldest online log sequence 10
Current log sequence 12

SQL> select distinct dbms_rowid.rowid_block_number(rowid) from props$;

DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)
------------------------------------
801

--控制文件内容
--生成控制文件
SQL> alter database backup controlfile to trace as '/home/oracle/ctlbak.ctl';

Database altered.

--查看trace内容
[oracle@orcl:/home/oracle]$ cat ctlbak.ctl
-- The following are current System-scope REDO Log Archival related
-- parameters and can be included in the database initialization file.
--
-- LOG_ARCHIVE_DEST=''
-- LOG_ARCHIVE_DUPLEX_DEST=''
--
-- LOG_ARCHIVE_FORMAT=%t_%s_%r.dbf
--
-- DB_UNIQUE_NAME="orcl"
--
-- LOG_ARCHIVE_CONFIG='SEND, RECEIVE, NODG_CONFIG'
-- LOG_ARCHIVE_MAX_PROCESSES=4
-- STANDBY_FILE_MANAGEMENT=MANUAL
-- STANDBY_ARCHIVE_DEST=?/dbs/arch
-- FAL_CLIENT=''
-- FAL_SERVER=''
--
-- LOG_ARCHIVE_DEST_1='LOCATION=/archivelog'
-- LOG_ARCHIVE_DEST_1='OPTIONAL REOPEN=300 NODELAY'
-- LOG_ARCHIVE_DEST_1='ARCH NOAFFIRM NOEXPEDITE NOVERIFY SYNC'
-- LOG_ARCHIVE_DEST_1='REGISTER NOALTERNATE NODEPENDENCY'
-- LOG_ARCHIVE_DEST_1='NOMAX_FAILURE NOQUOTA_SIZE NOQUOTA_USED NODB_UNIQUE_NAME'
-- LOG_ARCHIVE_DEST_1='VALID_FOR=(PRIMARY_ROLE,ONLINE_LOGFILES)'
-- LOG_ARCHIVE_DEST_STATE_1=ENABLE

--
-- Below are two sets of SQL statements, each of which creates a new
-- control file and uses it to open the database. The first set opens
-- the database with the NORESETLOGS option and should be used only if
-- the current versions of all online logs are available. The second
-- set opens the database with the RESETLOGS option and should be used
-- if online logs are unavailable.
-- The appropriate set of statements can be copied from the trace into
-- a script file, edited as necessary, and executed when there is a
-- need to re-create the control file.
--
-- Set #1. NORESETLOGS case
--
-- The following commands will create a new control file and use it
-- to open the database.
-- Data used by Recovery Manager will be lost.
-- Additional logs may be required for media recovery of offline
-- Use this only if the current versions of all online logs are
-- available.

-- After mounting the created controlfile, the following SQL
-- statement will place the database in the appropriate
-- protection mode:
-- ALTER DATABASE SET STANDBY DATABASE TO MAXIMIZE PERFORMANCE

STARTUP NOMOUNT
CREATE CONTROLFILE REUSE DATABASE "ORCL" NORESETLOGS NOARCHIVELOG
MAXLOGFILES 16
MAXLOGMEMBERS 3
MAXDATAFILES 100
MAXINSTANCES 8
MAXLOGHISTORY 292
LOGFILE
GROUP 1 '/oradata/orcl/redo01.log' SIZE 120M BLOCKSIZE 512,
GROUP 2 '/oradata/orcl/redo02.log' SIZE 120M BLOCKSIZE 512,
GROUP 3 '/oradata/orcl/redo03.log' SIZE 120M BLOCKSIZE 512
-- STANDBY LOGFILE
DATAFILE
'/oradata/orcl/system01.dbf',
'/oradata/orcl/sysaux01.dbf',
'/oradata/orcl/undotbs01.dbf',
'/oradata/orcl/users01.dbf',
'/oradata/orcl/example01.dbf'
CHARACTER SET AL32UTF8
;

-- Commands to re-create incarnation table
-- Below log names MUST be changed to existing filenames on
-- disk. Any one log file from each branch can be used to
-- re-create incarnation records.
-- ALTER DATABASE REGISTER LOGFILE '/archivelog/1_1_1069941729.dbf';
-- Recovery is required if any of the datafiles are restored backups,
-- or if the last shutdown was not normal or immediate.
RECOVER DATABASE

-- Database can now be opened normally.
ALTER DATABASE OPEN;

-- Commands to add tempfiles to temporary tablespaces.
-- Online tempfiles have complete space information.
-- Other tempfiles may require adjustment.
ALTER TABLESPACE TEMP ADD TEMPFILE '/oradata/orcl/temp01.dbf'
SIZE 30408704 REUSE AUTOEXTEND OFF;
-- End of tempfile additions.
--
-- Set #2. RESETLOGS case
--
-- The following commands will create a new control file and use it
-- to open the database.
-- Data used by Recovery Manager will be lost.
-- The contents of online logs will be lost and all backups will
-- be invalidated. Use this only if online logs are damaged.

-- After mounting the created controlfile, the following SQL
-- statement will place the database in the appropriate
-- protection mode:
-- ALTER DATABASE SET STANDBY DATABASE TO MAXIMIZE PERFORMANCE

STARTUP NOMOUNT
CREATE CONTROLFILE REUSE DATABASE "ORCL" RESETLOGS NOARCHIVELOG
MAXLOGFILES 16
MAXLOGMEMBERS 3
MAXDATAFILES 100
MAXINSTANCES 8
MAXLOGHISTORY 292
LOGFILE
GROUP 1 '/oradata/orcl/redo01.log' SIZE 120M BLOCKSIZE 512,
GROUP 2 '/oradata/orcl/redo02.log' SIZE 120M BLOCKSIZE 512,
GROUP 3 '/oradata/orcl/redo03.log' SIZE 120M BLOCKSIZE 512
-- STANDBY LOGFILE
DATAFILE
'/oradata/orcl/system01.dbf',
'/oradata/orcl/sysaux01.dbf',
'/oradata/orcl/undotbs01.dbf',
'/oradata/orcl/users01.dbf',
'/oradata/orcl/example01.dbf'
CHARACTER SET AL32UTF8
;

-- Commands to re-create incarnation table
-- Below log names MUST be changed to existing filenames on
-- disk. Any one log file from each branch can be used to
-- re-create incarnation records.
-- ALTER DATABASE REGISTER LOGFILE '/archivelog/1_1_1069941729.dbf';
-- Recovery is required if any of the datafiles are restored backups,
-- or if the last shutdown was not normal or immediate.
RECOVER DATABASE USING BACKUP CONTROLFILE

-- Database can now be opened zeroing the online logs.
ALTER DATABASE OPEN RESETLOGS;

-- Commands to add tempfiles to temporary tablespaces.
-- Online tempfiles have complete space information.
-- Other tempfiles may require adjustment.
ALTER TABLESPACE TEMP ADD TEMPFILE '/oradata/orcl/temp01.dbf'
SIZE 30408704 REUSE AUTOEXTEND OFF;
-- End of tempfile additions.
--

当前数据库存在两份控制文件,分别位于数据文件路径和快速闪回区路径。

二、模拟控制文件丢失情况:

1、未丢失全部控制文件

删除快闪区中的控制文件,强制关闭数据库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
sql复制代码[oracle@orcl:/oradata/orcl]$ rm -rf /u01/app/oracle/fast_recovery_area/orcl/control02.ctl 
[oracle@orcl:/oradata/orcl]$ ll /u01/app/oracle/fast_recovery_area/orcl/control02.ctl
ls: cannot access /u01/app/oracle/fast_recovery_area/orcl/control02.ctl: No such file or directory

--开一个新的session,此时数据库已经处于报错状态,无论什么操作都会报错,此时强制关闭数据库
SQL> select open_mode from v$database;
select open_mode from v$database
*
ERROR at line 1:
ORA-00210: cannot open the specified control file
ORA-00202: control file:
'/u01/app/oracle/fast_recovery_area/orcl/control02.ctl'
ORA-27041: unable to open file
Linux-x86_64 Error: 2: No such file or directory
Additional information: 3

SQL> shutdown abort
ORACLE instance shut down.
SQL>

从数据文件路径复制一份控制文件到快闪区中,尝试再次关闭是否报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
sql复制代码--复制控制文件
[oracle@orcl:/u01/app/oracle/fast_recovery_area/orcl]$ cp /oradata/orcl/control01.ctl control02.ctl
[oracle@orcl:/u01/app/oracle/fast_recovery_area/orcl]$ ls
control02.ctl

--开启数据库
SQL> startup
ORACLE instance started.

Total System Global Area 1603411968 bytes
Fixed Size 2253664 bytes
Variable Size 452988064 bytes
Database Buffers 1140850688 bytes
Redo Buffers 7319552 bytes
Database mounted.
Database opened.

2、全部丢失,控制文件存在备份

有两种方式:

1、通过备份的文件进行重建控制文件

2、通过rman恢复控制文件,需要通过resetlogs方式打开数据库

一、通过备份文件重建控制文件

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
bash复制代码--通过备份控制文件获取创建控制文件脚本
CREATE CONTROLFILE REUSE DATABASE "ORCL" NORESETLOGS NOARCHIVELOG
MAXLOGFILES 16
MAXLOGMEMBERS 3
MAXDATAFILES 100
MAXINSTANCES 8
MAXLOGHISTORY 292
LOGFILE
GROUP 1 '/oradata/orcl/redo01.log' SIZE 120M BLOCKSIZE 512,
GROUP 2 '/oradata/orcl/redo02.log' SIZE 120M BLOCKSIZE 512,
GROUP 3 '/oradata/orcl/redo03.log' SIZE 120M BLOCKSIZE 512
-- STANDBY LOGFILE
DATAFILE
'/oradata/orcl/system01.dbf',
'/oradata/orcl/sysaux01.dbf',
'/oradata/orcl/undotbs01.dbf',
'/oradata/orcl/users01.dbf',
'/oradata/orcl/example01.dbf'
CHARACTER SET AL32UTF8;

--开始恢复
SQL> startup nomount
ORACLE instance started.

Total System Global Area 1603411968 bytes
Fixed Size 2253664 bytes
Variable Size 452988064 bytes
Database Buffers 1140850688 bytes
Redo Buffers 7319552 bytes
SQL> CREATE CONTROLFILE REUSE DATABASE "ORCL" NORESETLOGS NOARCHIVELOG
2 MAXLOGFILES 16
3 MAXLOGMEMBERS 3
4 MAXDATAFILES 100
5 MAXINSTANCES 8
6 MAXLOGHISTORY 292
7 LOGFILE
8 GROUP 1 '/oradata/orcl/redo01.log' SIZE 120M BLOCKSIZE 512,
9 GROUP 2 '/oradata/orcl/redo02.log' SIZE 120M BLOCKSIZE 512,
10 GROUP 3 '/oradata/orcl/redo03.log' SIZE 120M BLOCKSIZE 512
11 -- STANDBY LOGFILE
12 DATAFILE
13 '/oradata/orcl/system01.dbf',
14 '/oradata/orcl/sysaux01.dbf',
15 '/oradata/orcl/undotbs01.dbf',
16 '/oradata/orcl/users01.dbf',
17 '/oradata/orcl/example01.dbf'
18 CHARACTER SET AL32UTF8;

Control file created.

SQL> recover database;
Media recovery complete.
SQL> alter database open;

Database altered.

SQL> ALTER TABLESPACE TEMP ADD TEMPFILE '/oradata/orcl/temp01.dbf';

Tablespace altered.

SQL> select open_mode from v$database;

OPEN_MODE
--------------------
READ WRITE

成功恢复控制文件。

二、通过rman恢复控制文件

rman备份控制文件

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
bash复制代码RMAN> backup current controlfile format '/home/oracle/ctrl.ora';

Starting backup at 15-APR-21
allocated channel: ORA_DISK_1
channel ORA_DISK_1: SID=29 device type=DISK
channel ORA_DISK_1: starting full datafile backup set
channel ORA_DISK_1: specifying datafile(s) in backup set
including current control file in backup set
channel ORA_DISK_1: starting piece 1 at 15-APR-21
channel ORA_DISK_1: finished piece 1 at 15-APR-21
piece handle=/home/oracle/ctrl.ora tag=TAG20210415T104457 comment=NONE
channel ORA_DISK_1: backup set complete, elapsed time: 00:00:01
Finished backup at 15-APR-21

RMAN> list backup of controlfile;


List of Backup Sets
===================


BS Key Type LV Size Device Type Elapsed Time Completion Time
------- ---- -- ---------- ----------- ------------ ---------------
1 Full 9.64M DISK 00:00:01 15-APR-21
BP Key: 1 Status: AVAILABLE Compressed: NO Tag: TAG20210415T104457
Piece Name: /home/oracle/ctrl.ora
Control File Included: Ckp SCN: 1219869 Ckp time: 15-APR-21

rm删除所有控制文件,尝试添加数据文件,强制关闭数据库,尝试开启到mount状态。

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
bash复制代码[oracle@orcl:/u01/app/oracle/fast_recovery_area/orcl]$ rm control02.ctl 
[oracle@orcl:/u01/app/oracle/fast_recovery_area/orcl]$ rm /oradata/orcl/control01.ctl
[oracle@orcl:/u01/app/oracle/fast_recovery_area/orcl]$ ll
total 0
[oracle@orcl:/u01/app/oracle/fast_recovery_area/orcl]$ ll /oradata/orcl/control01.ctl
ls: cannot access /oradata/orcl/control01.ctl: No such file or directory

SQL> alter tablespace USERS add datafile;
alter tablespace USERS add datafile
*
ERROR at line 1:
ORA-00603: ORACLE server session terminated by fatal error
ORA-00210: cannot open the specified control file
ORA-00202: control file: '/oradata/orcl/control01.ctl'
ORA-27041: unable to open file
Linux-x86_64 Error: 2: No such file or directory
Additional information: 3
ORA-00210: cannot open the specified control file
ORA-00202: control file: '/oradata/orcl/control01.ctl'
ORA-27041: unable to open file
Linux-x86_64 Error: 2: No such file or directory
Additional information: 3
Process ID: 2333
Session ID: 1 Serial number: 9

SQL> shutdown abort;
ORACLE instance shut down.

SQL> startup mount
ORACLE instance started.

Total System Global Area 1603411968 bytes
Fixed Size 2253664 bytes
Variable Size 452988064 bytes
Database Buffers 1140850688 bytes
Redo Buffers 7319552 bytes
ORA-00205: error in identifying control file, check alert log for more info

开启数据库到nomount状态,rman恢复控制文件,恢复数据库

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
bash复制代码--开启到nomount状态
SQL> startup nomount
ORACLE instance started.

Total System Global Area 1603411968 bytes
Fixed Size 2253664 bytes
Variable Size 452988064 bytes
Database Buffers 1140850688 bytes
Redo Buffers 7319552 bytes

--rman恢复控制文件
RMAN> restore controlfile from '/home/oracle/ctrl.ora';

Starting restore at 15-APR-21
using target database control file instead of recovery catalog
allocated channel: ORA_DISK_1
channel ORA_DISK_1: SID=20 device type=DISK

channel ORA_DISK_1: restoring control file
channel ORA_DISK_1: restore complete, elapsed time: 00:00:01
output file name=/oradata/orcl/control01.ctl
output file name=/u01/app/oracle/fast_recovery_area/orcl/control02.ctl
Finished restore at 15-APR-21

--开启数据库到mount状态
SQL> alter database mount;

Database altered.

--恢复数据库
RMAN> recover database;

Starting recover at 15-APR-21
released channel: ORA_DISK_1
Starting implicit crosscheck backup at 15-APR-21
allocated channel: ORA_DISK_1
channel ORA_DISK_1: SID=20 device type=DISK
Finished implicit crosscheck backup at 15-APR-21

Starting implicit crosscheck copy at 15-APR-21
using channel ORA_DISK_1
Finished implicit crosscheck copy at 15-APR-21

searching for all files in the recovery area
cataloging files...
no files cataloged

using channel ORA_DISK_1
RMAN-06900: WARNING: unable to generate V$RMAN_STATUS or V$RMAN_OUTPUT row
RMAN-06901: WARNING: disabling update of the V$RMAN_STATUS and V$RMAN_OUTPUT rows
ORACLE error from target database:
ORA-19922: there is no parent row with id 0 and level 1


starting media recovery

archived log for thread 1 with sequence 12 is already on disk as file /oradata/orcl/redo03.log
archived log file name=/oradata/orcl/redo03.log thread=1 sequence=12
media recovery complete, elapsed time: 00:00:00
Finished recover at 15-APR-21

通过resetlogs方式打开数据库

1
2
3
bash复制代码SQL> alter database open resetlogs;

Database altered.

3、全部丢失,没有备份

删除全部控制文件,并且没有备份控制文件

1
2
3
4
5
6
7
8
9
10
11
12
sql复制代码SQL> select open_mode from v$database;
select open_mode from v$database
*
ERROR at line 1:
ORA-00210: cannot open the specified control file
ORA-00202: control file: '/oradata/orcl/control01.ctl'
ORA-27041: unable to open file
Linux-x86_64 Error: 2: No such file or directory
Additional information: 3

SQL> shutdown abort
ORACLE instance shut down.

手动创建控制文件

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
bash复制代码--通过spfile或者pfile文件获取信息

--1.db_name
[oracle@orcl:/home/oracle]$ grep "db_name" pfile.ora
*.db_name='orcl'

--2.字符集(通过dd if查看system01数据文件)
dd if=system01.dbf of=lucifer bs=8192 skip=801



--3.获取数据文件和日志文件名称
[oracle@orcl:/oradata/orcl]$ ll
total 2083264
-rw-r-----. 1 oracle oinstall 328343552 Apr 15 15:14 example01.dbf
-rw-r-----. 1 oracle oinstall 125829632 Apr 15 15:14 redo01.log
-rw-r-----. 1 oracle oinstall 125829632 Apr 15 15:14 redo02.log
-rw-r-----. 1 oracle oinstall 125829632 Apr 15 15:17 redo03.log
-rw-r-----. 1 oracle oinstall 545267712 Apr 15 15:14 sysaux01.dbf
-rw-r-----. 1 oracle oinstall 786440192 Apr 15 15:14 system01.dbf
-rw-r-----. 1 oracle oinstall 30416896 Apr 15 15:14 temp01.dbf
-rw-r-----. 1 oracle oinstall 89137152 Apr 15 15:14 undotbs01.dbf
-rw-r-----. 1 oracle oinstall 5251072 Apr 15 15:14 users01.dbf

--重建控制文件(这里不需要加临时文件,开启数据库之后需要reuse)
--开启数据库到nomount
STARTUP NOMOUNT;
--创建控制文件
CREATE CONTROLFILE REUSE DATABASE "ORCL" NORESETLOGS NOARCHIVELOG
MAXLOGFILES 16
MAXLOGMEMBERS 3
MAXDATAFILES 100
MAXINSTANCES 8
MAXLOGHISTORY 292
LOGFILE
GROUP 1 '/oradata/orcl/redo01.log' SIZE 120M BLOCKSIZE 512,
GROUP 2 '/oradata/orcl/redo02.log' SIZE 120M BLOCKSIZE 512,
GROUP 3 '/oradata/orcl/redo03.log' SIZE 120M BLOCKSIZE 512
-- STANDBY LOGFILE
DATAFILE
'/oradata/orcl/system01.dbf',
'/oradata/orcl/sysaux01.dbf',
'/oradata/orcl/undotbs01.dbf',
'/oradata/orcl/users01.dbf',
'/oradata/orcl/example01.dbf'
CHARACTER SET AL32UTF8
;
--恢复数据库
RECOVER DATABASE;


--执行过程
SQL> CREATE CONTROLFILE REUSE DATABASE "ORCL" NORESETLOGS NOARCHIVELOG
2 MAXLOGFILES 16
3 MAXLOGMEMBERS 3
4 MAXDATAFILES 100
5 MAXINSTANCES 8
6 MAXLOGHISTORY 292
7 LOGFILE
8 GROUP 1 '/oradata/orcl/redo01.log' SIZE 120M BLOCKSIZE 512,
9 GROUP 2 '/oradata/orcl/redo02.log' SIZE 120M BLOCKSIZE 512,
10 GROUP 3 '/oradata/orcl/redo03.log' SIZE 120M BLOCKSIZE 512
11 -- STANDBY LOGFILE
12 DATAFILE
13 '/oradata/orcl/system01.dbf',
14 '/oradata/orcl/sysaux01.dbf',
15 '/oradata/orcl/undotbs01.dbf',
16 '/oradata/orcl/users01.dbf',
17 '/oradata/orcl/example01.dbf'
18 CHARACTER SET AL32UTF8
19 ;

Control file created.

SQL> RECOVER DATABASE;
Media recovery complete.

打开数据库,成功恢复控制文件

1
2
3
4
5
6
7
8
sql复制代码SQL> ALTER DATABASE OPEN;

Database altered.

--这里需要将临时文件重用
SQL> ALTER TABLESPACE TEMP ADD TEMPFILE '/oradata/orcl/temp01.dbf' REUSE;

Tablespace altered.

三、建议

1、创建多份控制文件,建议分别存放在多个存储的多个文件夹上,防止文件被误删或者存储损坏。

2、打开归档模式。

3、保存当前库的控制文件生成脚本(包括重要信息:dbname,字符集,文件路径)。

4、定期备份数据库文件,防止丢失,建议将备份放置到源端进行保存。

本文转载自: 掘金

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

Java代理模式原理详解 Java代理模式

发表于 2021-11-14

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

Java代理模式

代理模式是指,为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户类和目标对象之间起到中介的作用。

换句话说,使用代理对象,是为了在不修改目标对象的基础上,增强主业务逻辑。

客户类真正的想要访问的对象是目标对象,但客户类真正可以访问的对象是代理对象。客户类对目标对象的访问是通过访问代理对象来实现的。当然,代理类与目标类要实现同一个接口。

例如: 有A,B,C三个类, A原来可以调用C类的方法, 现在因为某种原因C类不允许A类调用其方法,但B类可以调用C类的方法。A类通过B类调用C类的方法。这里B是C的代理。 A通过代理B访问C.

原来的访问关系:

通过代理的访问关系:

Window系统的快捷方式也是一种代理模式。快捷方式代理的是真实的程序,双击快捷方式是启动它代表的程序。

在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。

在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。

介绍

意图:为其他对象提供一种代理以控制对这个对象的访问。

主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

何时使用:想在访问一个类时做一些控制。

如何解决:增加中间层。

关键代码:实现与被代理类组合。

应用实例:

⒈Windows 里面的快捷方式。

⒉猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。

⒊买火车票不一定在火车站买,也可以去代售点。

⒋一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。 5、spring aop。

优点:

⒈清晰。

⒉高扩展性。

⒊智能化。

缺点:

⒈由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。

⒉实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

使用场景:按职责来划分,通常有以下使用场景:

⒈远程代理。

⒉虚拟代理。

⒊Copy-on-Write 代理。

⒋保护(Protect or Access)代理。

⒌Cache代理。

⒍防火墙(Firewall)代理。

⒎同步化(Synchronization)代理。

⒏智能引用(Smart Reference)代理。

注意事项:

⒈和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。

⒉和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。

代理模式作用

A、 控制访问

B、 增强功能

代理模式分类

可以将代理分为两类:静态代理与动态代理

代理的实现方式

静态代理和动态代理

代理模式需求

需求:用户需要购买u盘, u盘厂家不单独接待零散购买,厂家规定一次最少购买1000个以上,用户可以通过淘宝的代理商,或者微商哪里进行购买。

淘宝上的商品,微商都是 u 盘工厂的代理商, 他们代理对u盘的销售业务。

用户购买——-代理商(淘宝,微商)—– u 厂家(金士顿,闪迪等不同的厂家)

设计这个业务需要的类:

  1. 商家和厂家都是提供 sell 购买u盘的方法。定义购买u盘的接口 UsbSell
  2. 金士顿(King)对购买1千以上的价格是 85, 3千以上是80, 5千以上是75。 单个120元。定义UsbKingFactory类,实现UsbSell
  3. 闪迪(San)对购买1千以上的价格是 82, 3千以上是78, 5千以上是72。 单个120元。定义UsbSanFactory类,实现UsbSell
  4. 定义淘宝的代理商 TaoBao ,实现UsbSell
  5. 定义微商的代理商 WeiShang, 实现UsbSell
  6. 定义测试类,测试通过淘宝, 微商购买u盘

实现

我们将创建一个 Image 接口和实现了 Image 接口的实体类。ProxyImage 是一个代理类,减少 RealImage 对象加载的内存占用。

ProxyPatternDemo,我们的演示类使用 ProxyImage 来获取要加载的 Image 对象,并按照需求进行显示。

步骤 1

创建一个接口。

1
2
3
csharp复制代码public interface Image {
void display();
}

步骤 2

创建实现接口的实体类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typescript复制代码public class RealImage implements Image {

private String fileName;

public RealImage(String fileName){
this.fileName = fileName;
loadFromDisk(fileName);
}

@Override
public void display() {
System.out.println("Displaying " + fileName);
}

private void loadFromDisk(String fileName){
System.out.println("Loading " + fileName);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typescript复制代码public class ProxyImage implements Image{

private RealImage realImage;
private String fileName;

public ProxyImage(String fileName){
this.fileName = fileName;
}

@Override
public void display() {
if(realImage == null){
realImage = new RealImage(fileName);
}
realImage.display();
}
}

步骤 3

当被请求时,使用 ProxyImage 来获取 RealImage 类的对象。

1
2
3
4
5
6
7
8
9
10
11
12
arduino复制代码public class ProxyPatternDemo {

public static void main(String[] args) {
Image image = new ProxyImage("test_10mb.jpg");

// 图像将从磁盘加载
image.display();
System.out.println("");
// 图像不需要从磁盘加载
image.display();
}
}

步骤 4

执行程序,输出结果:

1
2
3
4
复制代码Loading test_10mb.jpg
Displaying test_10mb.jpg

Displaying test_10mb.jpg

本文转载自: 掘金

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

MATLAB--数字图像处理 图像直方图规定化 前言

发表于 2021-11-14

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

前言

Hello!小伙伴!

非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出~

自我介绍 ଘ(੭ˊᵕˋ)੭

昵称:海轰

标签:程序猿|C++选手|学生

简介:因C语言结识编程,随后转入计算机专业,有幸拿过一些国奖、省奖…已保研。目前正在学习C++/Linux/Python

学习经验:扎实基础 + 多做笔记 + 多敲代码 + 多思考 + 学好英语!

直方图规定化

原理:
所谓直方图规定化,就是通过一个灰度映像函数,将原灰度直方图改造成所希望的直方图。说的通俗一点就是,原图像的灰度是从0~255的,其分布是随机的,在一些情况下,我们可能需要一些特定的灰度值,比如我们只需要灰度值为0 3 40 240 255 这些值,除此之外的灰度值我们不需要,那么从原图像到我们需要的图像就可以理解成图像的规定化。

具体事例:
左图是原图像的灰度直方图,右图是我们需要的图(这里的需要是指需要灰度由原来的0~7变成规定的1 3 6,规定化后图像的纵坐标是会变的,这里只需要横坐标吻合就行!)
在这里插入图片描述
规定化后的图像(横坐标和规定一样,纵坐标是会变化的):
在这里插入图片描述
实现步骤:

  1. 分别计算出原图像和规定图像的累加直方图
  2. 利用SML或者GML映射灰度值
  3. 利用更新后的映射表转换原图像的灰度值

看完之后,应该还是不懂?那么直接实战一题!题目见下图:
在这里插入图片描述
解题步骤:

  1. 这里是直接给出的原图像各灰度的像素个数,为了得到我们需要的直方累加图(其实是概率累加值,当然也可以不用概率,直接用像素点数也行,就是看起来数字比较大),需要先算出原图像各灰度的概率(占总像素个数的比例),然后在计算累加值在这里插入图片描述
  2. 根据给出的规定直方图,同样的方法计算累加直方图,见下图在这里插入图片描述
  3. 利用SML映射规则得出结果在这里插入图片描述
  4. 利用映射变化表,更新原图像

提示:

这里可能不懂SML映射规则,我说说自己简单的理解(自己也可以自行百度):
SML就是单映射,这里我们关注原图累加直方图和规定累加直方图,所谓SML,就是从原图累加直方图开始,在规定累加直方图寻找和自己最接近的值,然后把它的灰度值变成自己的。具体来说,原图第一个累加概率是0.19,在规定累加直方图中,最接近它的就是0.15,那么原图的灰度0变成规定的3,第二个累加概率是0.44,最接近规定累加直方图的0.35,所以由1变成4,同理,依次遍历完原图累加直方图的概率就行,在规定累加直方图找到最靠近自己的值,最后进行灰度变化。

SML规则懂了,GML规则也就好理解了(GML规则其实编程更难):

SML中,我们是依次遍历原图累加直方图,在规定累加直方图寻找最靠近自己的。在GML(组映射)中,就变成了依次从规定累加直方图中,对比原图累加直方图,也是找到最靠近的值,进行灰度变换,只是这里的变换规则变了。
在这里插入图片描述
具体举例来说,从上图看,这里我们这里从规定累加直方图的0.15看,前面的0其实不用看,再从原始累加直方图找到最接近0.15的值,是0.19,那么0.19对应和它对应灰度值前面的灰度变成规定累加直方图中0.15所对应的灰度值:3;第二个看规定累加直方图的0.35,靠近原图的0.44,那么原图的1变成4(假设这里0.44对应的原图灰度值为4,那么在前一个不为0和4之间变成4)
这里有的不好理解,举个例子:
原数组:1 0 0 2 0 3 0 0 0 4 0 0 6
GML映射变化规则的目标数组就是:1 2 2 2 3 3 4 4 4 4 6 6 6
解释:1前面的数变为1,1和2之间变成2,2和3之间变成3,3和4之间变成4,4和6之间变成6,简单的说就是变0变成后面最靠近的一个不为0的数。(这里算法自己需要掌握,即如何从原数组变成目标数组,这里先给个C++的验证算法)

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
ini复制代码#include<iostream>
using namespace std;
int main()
{
int t[8]={2,0,1,0,3,0,0,6};
int i,j;
int tem;
int flag=0;
for (i=0;i<8;i++)
{
if(t[i]==0)
{

for(j=i;j<8;j++)
{
if(t[j]!=0)
{
tem=t[j];
break;}
}
t[i]=tem;

}
}
for(i=0;i<8;i++)
cout<<t[i]<<" ";
return 0;
}

如果还是不懂SML和GML规则的话?那自己根据下面两种图片再理解理解吧。在这里插入图片描述
在这里插入图片描述

MATLAB实战
原理懂了,肯定就是要开始实战了啊!
这里我先上全部代码:

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
scss复制代码 t=imread('a1.jpg')
%获取图片的长和宽,用于计算总像素,即m*n
[m,n]=size(t);

%n_1 这里是先统计各灰度的像素数,后面会变换为概率
n_1=zeros(1,256);

%统计各灰度的像素数
for i=1:m
for j=1:n
n_1(t(i,j)+1)=n_1(t(i,j)+1)+1;
end
end

%转换为概率
n_1=n_1/m/n; %计算各个灰度级的概率

%p_1 记录原图的累积直方图概率
p_1=zeros(1,256);

%p_1 计算原始累积直方图的概率
for i=1:256
for j=1:i
p_1(i)=p_1(i)+n_1(j);
end
end

%n_2 规定直方图的灰度概率分布
n_2=[zeros(1,50),0.1,zeros(1,50),0.2,zeros(1,50),0.3,zeros(1,50),0.2,zeros(1,20),0.1,zeros(1,30),0.1];

%p_2 记录规定直方图的累积概率
p_2=zeros(1,256);

% 计算规定直方图累积概率
for i=1:256
for j=1:i
p_2(i)=p_2(i)+n_2(j);
end
end

%SML映射算法
data_1=zeros(1,256);%data_1 SML映射表

for i=1:256
min=abs(p_1(i)-p_2(1));
for j=2:256
if abs(p_1(i)-p_2(j))<min
min=abs(p_1(i)-p_2(j));
data_1(i)=j-1;
end
end
end

%GML
data_2=zeros(1,256); %data_2记录GML映射表

for i=1:256
if n_2(i)~=0
tem=1;
min=abs(p_2(i)-p_1(1));
for j=2:256
if abs(p_2(i)-p_1(j))<min
min=abs(p_2(i)-p_1(j));
tem=j;
end
end
data_2(tem)=i-1;
end
end

%将上面得到的data_2 转换为目标数组(原理解释有c++算法)
for i=1:256
if data_2(i)==0
for j=i:256
if data_2(j)~=0
tem=data_2(j);
break;
end
end
data_2(i)=tem;
end
end


%利用SML、GML映射表,转换原图像
t2=t;
t3=t;

%转换算法
for i=1:m
for j=1:n
t2(i,j)=data_1(t(i,j)+1);
end
end
for i=1:m
for j=1:n
t3(i,j)=data_2(t(i,j)+1);
end
end

%显示
subplot(3,2,1),imshow(t),title('原图')
subplot(3,2,2),imhist(t),title('原图')
subplot(3,2,3),imshow(t2),title('SML')
subplot(3,2,4),imhist(t2),title('SML')
subplot(3,2,5),imshow(t3),title('GML')
subplot(3,2,6),imhist(t3),title('GML')

效果图:
在这里插入图片描述

总结

SML、GML两个算法用来一天才搞清楚原理,书上的公式开始看真的是好难啊,看不进去,网上的方法,真的是,没有自己想要的。算法其实一天就搞定了,当时由于MATLAB语法还不是很熟,走了很多弯路。下面总结下自己的踩坑吧:

  • MATLAB里面的循环写法 for i=1:256 c++:for(i=1;i<=256;i++)
  • for、if等用end结束,end个数必须和if、for配对,比如,3个for,2个if,就必须有5个end结束,不然程序会一直执行下去
  • 程序语句最后有无 ; 的区别:写了 ; 本地代码区不会显示具体数据,反之不写,则会显示,建议还是写吧,和c++语法类似。
  • 多打印数据,一步一步调试,便于寻找bug

本文转载自: 掘金

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

工作多年,Linux文件系统还不太了解?

发表于 2021-11-14

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

我们作为“码农”,每天都在接触编程,接触 linux 系统,那么常见的 Linux 目录和文件都有哪些,它们又有什么作用呢?

想必大家经常会忽略这点,作为工作多年的你而言也不例外,今天就带大家来一起了解一下吧~

1 初识目录结构

2 初识文件

2.1 文件类型

2.1.1 文件

第一个属性为 [-]。最常用的类型,包括:

  • 纯文本文件(ASCII);
  • 二进制文件(binary);
  • 数据格式的文件(data);
  • 各种压缩文件。

2.1.2 目录

第一个属性为 [d]

2.1.3 链接文件

第一个属性为 [l]

2.1.4 管道

第一个属性为 [p]

2.1.5 块设备

第一个属性为 [b] 。储数据以供系统存取的接口设备,简单而言就是硬盘。

1
bash复制代码ll /dev/vda1brw-rw---- 1 root disk 253, 1 11月 28 19:17 /dev/vda1

复制代码

2.1.6 字符设备文件

第一个属性为 [c] 。即串行端口的接口设备,例如键盘、鼠标等等。

2.1.7 套接字

第一个属性为 [s]。最常在 /var/run 目录中看到这种文件类型。

查看文件类型的方法:

1
shell复制代码#file, stat, ls

复制代码

讲到这里,可以有些疑问,到底文件是个什么东东?

2.2 文件是什么?

如上图所示,一般文件包括两部分:元数据和用户数据

  • **元数据:**文件的附属信息:文件大小,文件创建时间,文件拥有者,Inode 编号(文件的唯一标识)
  • **用户数据:**记录文件真实内容的地方。

为了有效管理文件,引入了文件系统。

2.3 文件系统

文件系统,本身是对存储设备上的文件,进行组织管理的机制。所以根据组织机制的不同,可以分为不同的文件系统。

文件系统系统包括 4 大要素:索引节点,目录项,逻辑块,超级块。

2.3.1 索引节点

同上面讲的元数据部分,索引节点和文件是一一对应的,它和文件内容一样,是存储在磁盘中的。所以索引节点是占用磁盘空间的。所以它不会随着进程的消亡而消失。

2.3.2 目录项

简称 dentry,用来记录文件的名称,索引节点指针以及目录项之间的关联关系,会形成一棵树状结构。多个关联的目录项,就形成了目录结构。

它是由内存维护的一个内存数据结构,所以通常被称作“目录项缓冲”。

2.3.3 逻辑块

索引节点和目录项记录了文件的元数据,那么文件内容是怎么存储的?

实际上,磁盘上最小的读写单位是“扇区”,只有 512B 大小,也就是 0.5K,为了读写效率,系统会将连续的扇区组合一个”块“,一般一个“块”是 8 个扇区,也就是 4K,4096B。

从磁盘中读取内容是特别慢的,所以系统会将读取的内容存到“页缓冲”中。

2.3.4 超级块

一个超级块对应一个文件系统。超级块会保存文件系统的大小和状态、块设备的引用和元数据信息(比如空闲列表等等)

文件系统有多种,比如 Ext4, NFS 等,根据存储位置不一样,可以分为三类。

  • **基于磁盘的文件系统:**也就是数据直接存储在计算机本地挂载的磁盘中。常见的 Ext4, Ext3,XFS。
  • **基于内存的文件系统:**也就是虚拟文件系统。这类文件系统,不需要任何磁盘分配存储空间,但会占用内存。比如/proc 文件系统,/sys 文件系统。
  • **网络文件系统:**用来访问其他计算机数据的文件系统,比如 NFS,SMB。

每个文件系统是独立的,有自己的组织方法,操作方法。那么对于用户来说,不可能所有的文件系统都了解,那么怎么做到让用户透明的去处理文件呢?

2.4 VFS 虚拟文件系统

虚拟文件系统定义了一组所有文件系统都支持的数据结构和标准接口。用户进程和内核中的其他子系统,只需要跟 VFS 提供的统一接口交互就 OK,不需要关心底层文件系统实现细节。

磁盘的操作是非常慢的,所以为了协调磁盘的访问速度,内核提供了“索引节点缓存”,“目录项缓存”,“页缓冲”。

2.5 文件系统 I/O

根据文件读写方式的各种差异,导致 I/O 的分类多种多种。最常见的为以下 4 类:

2.5.1 缓冲 IO 和非缓冲 IO

第一种,根据是否利用标准库缓存,分为:

  • **缓冲 IO:**只利用标准库的缓冲来加速文件的访问。标准库内部会通过系统调用来访问文件。
  • **非缓冲 IO:**直接系统调用。

需要说明的是:“缓冲”指的是标准库的缓冲,而不是内核提供的高速缓冲区(也就是页缓冲)

  • 无缓存 IO 操作数据流向路径:数据——内核缓存区——磁盘
  • 标准 IO 操作数据流向路径:数据——流缓存区——内核缓存区——磁盘

2.5.2 直接 IO 和非直接 IO

是否跳过页缓冲。

要想实现直接 IO,可以在系统调用的时候,指定 O_DIRECT 标志。

直接 IO 和非直接 IO,本质上还是和文件系统打交道,如果跳过文件系统直接读写磁盘,就是我们通常说的裸 IO。

2.5.3 阻塞和非阻塞 IO

设置 O_NONBLOCK

  • **阻塞 IO:**是指应用程序执行 IO 操作,如果没有获得响应,则会阻塞当前线程,不会做其他任务。
  • **非阻塞 IO:**是指应用程序执行 IO 操作,不阻塞当前线程,可以继续执行其他任务,随后会轮询或者事件通知的形式,获取调用结果。

2.5.4 同步和非同步 IO

设置 O_SYNC 或者 O_DSYNC 标志,就代表同步 IO。设置 O_DSYNC,会等待数据写入磁盘才返回;设置 O_SYNC,则是在 O_DSYNC 的基础上,要求文件元数据也要返回磁盘后,才返回。

  • **同步 IO:**是指应用程序执行 IO 操作后,要一直等待 IO 完成后,才能获得 IO 响应。
  • **异步 IO:**是指用用程序执行 IO 操作后,不用等待完成或完成后的响应,而是继续执行就可以。等待 IO 完成,响应会用事件通知的形式,告诉应用程序。
  • END -

作者:架构精进之路,十年研发风雨路,大厂架构师,CSDN 博客专家,专注架构技术沉淀学习及分享,职业与认知升级,坚持分享接地气儿的干货文章,期待与你一起成长。

关注并私信我回复“01”,送你一份程序员成长进阶大礼包,欢迎勾搭。

Thanks for reading!

本文转载自: 掘金

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

剑指 Offer II 056 二叉搜索树中两个节点之和

发表于 2021-11-14

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

给定一个二叉搜索树的 根节点 root 和一个整数 k , 请判断该二叉搜索树中是否存在两个节点它们的值之和等于 k 。假设二叉搜索树中节点的值均唯一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
makefile复制代码示例 1:

输入: root = [8,6,10,5,7,9,11], k = 12
输出: true
解释: 节点 5 和节点 7 之和等于 12

示例 2:

输入: root = [8,6,10,5,7,9,11], k = 22
输出: false
解释: 不存在两个节点值之和为 22 的节点



提示:

二叉树的节点个数的范围是 [1, 104].
-104 <= Node.val <= 104
root 为二叉搜索树
-105 <= k <= 105

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/op…

思路

  • 首先看到这道题,我们会想到最初的LeetCode的两数之和
    • 那么不妨来看一下LeetCode的第一题两数之和的思路
1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer,Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
if (map.containsKey(target - nums[i])) {
return new int[]{map.get(target - nums[i]),i};
} else {
map.put(nums[i],i);
}
}
return new int[]{};
}
}

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
ini复制代码/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public boolean findTarget(TreeNode root, int k) {
Set<Integer> set = new HashSet<>();
Stack<TreeNode> stack= new Stack<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()) {
while (cur != null) {
stack.add(cur);
cur = cur.left;
}
cur = stack.pop();
if (set.contains(k - cur.val)) {
return true;
}
set.add(cur.val);
cur = cur.right;
}
return false;
}
}
  • 可以看到使用一个辅助的map进行存储
    • 所以对应二叉搜索树
    • 我们可以一直往左子树进行遍历
    • 一直进行存储左子树的节点,每次进行弹出的时候
    • 再看Set中是否存在值为target - val

      当然这道题也可以将二叉搜索树的性质发挥的淋漓尽致,可以将二叉搜索树看成一个排序的数组,再进行双指针操作

本文转载自: 掘金

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

laravel后台项目优化,前台首页数据api 一、后台项目

发表于 2021-11-14

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

一、后台项目问题

1.1 用户列表的禁用启用字段返回

可以看到现在用户列表没有禁用获取启用的字段:
在这里插入图片描述


添加:
在这里插入图片描述
效果:
在这里插入图片描述


1.2 给所有搜索增加索引

给所有搜索增加索引(需要搜索的字段)可以提高我们的搜索的效率(索引也不是越多越好,越多的话,它也会占空间)。
给商品迁移文件增加索引:
在这里插入图片描述


给评论迁移文件增加索引:
在这里插入图片描述


给订单迁移文件增加索引:
在这里插入图片描述


1.3 订单表迁移文件,快递和单号可以为空

在这里插入图片描述


1.4 放弃并重新执行所有的迁移

执行命令php artisan migrate:refresh --seed,刷新所有的迁移同时运行数据填充:
在这里插入图片描述
至此我们关于后台相关的api基本上已经完成了。接下去开始弄前台相关的功能。

二、前台首页数据

2.1 创建控制器

运行命令php artisan make:controller Web/IndexController
在这里插入图片描述
在这里插入图片描述
写入首页获取数据方法:

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
php复制代码<?php

namespace App\Http\Controllers\Web;

use App\Http\Controllers\BaseController;
use App\Models\Good;
use App\Models\Slide;

class IndexController extends BaseController
{
// 首页数据
function index() {
// 轮播图数据
$slides = Slide::where('status', 1)
->orderBy('seq') // 排序 默认正序
->get();

// 分类数据 没被禁用的分类
$categories = cache_category();

// 推荐商品
$goods = Good::where('is_on', 1) // 上架的商品
->where('is_recommend', 1) // 推荐的商品
->get();

return $this->response->array([
'slides' => $slides,
'categories' => $categories,
'goods' => $goods,
]);
}
}

2.2 前台路由

routes/api.php这里写我们的前台路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
php复制代码<?php

use App\Http\Controllers\Web\IndexController;

$api = app('Dingo\Api\Routing\Router');

$api->version('v1', function ($api) {

// 首页数据,不需要登陆就能获取
$api->get('/index', [IndexController::class, 'index']);

// 需要登陆的路由
$api->group(['middleware' => ['api.auth', 'check.permission']], function ($api) {

});
});

2.3 创建分类填充

运行命令php artisan make:seed CategorySeeder 创建分类的填充文件:
在这里插入图片描述


对分类进行数据添加:

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
php复制代码<?php

namespace Database\Seeders;

use App\Models\Category;
use Illuminate\Database\Seeder;

class CategorySeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
// 填充分类信息
$categories = [
[
'name' => '电子产品',
'group' => 'goods',
'pid' => 0,
'level' => 1,
'children' => [
[
'name' => '手机',
'group' => 'goods',
'level' => 2,
'children' => [
[
'name' => '华为',
'group' => 'goods',
'level' => 3,
],
[
'name' => 'oppo',
'group' => 'goods',
'level' => 3,
],
]
],
[
'name' => '相机',
'group' => 'goods',
'level' => 2,
'children' => [
[
'name' => '索尼',
'group' => 'goods',
'level' => 3,
],
[
'name' => '佳能',
'group' => 'goods',
'level' => 3,
],
]
]
]
],
[
'name' => '车',
'group' => 'goods',
'pid' => 0,
'level' => 1,
'children' => [
[
'name' => '轿车',
'group' => 'goods',
'level' => 2,
'children' => [
[
'name' => '红旗',
'group' => 'goods',
'level' => 3,
],
[
'name' => '吉利',
'group' => 'goods',
'level' => 3,
],
]
],
[
'name' => '自行车',
'group' => 'goods',
'level' => 2,
'children' => [
[
'name' => '小黄车',
'group' => 'goods',
'level' => 3,
],
[
'name' => '小蓝车',
'group' => 'goods',
'level' => 3,
],
]
]
]
]
];

// 填充到数据库
foreach($categories as $one) {
$l1 = $one;
unset($l1['children']);
$l1_model = Category::create($l1);
foreach($one['children'] as $two) {
$l2 = $two;
unset($l2['children']);
$l2['pid'] = $l1_model->id;
$l2_model = Category::create($l2);
$l2_model->children()->createMany($two['children']);
}
}

// 清除缓存
forget_cache_category_all();

}
}

执行填充命令:php artisan db:seed --class=CategorySeeder
在这里插入图片描述
效果:
在这里插入图片描述

在学习的php的路上,如果你觉得本文对你有所帮助的话,那就请关注点赞评论三连吧,谢谢,你的肯定是我写博的另一个支持。

本文转载自: 掘金

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

1…348349350…956

开发者博客

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