设计模式⑥ :访问数据结构

文章目录

  • 一、前言
  • 二、Visitor 模式
    • 1. 介绍
    • 2. 应用
    • 3. 总结
  • 三、Chain of Responsibility 模式
    • 1. 介绍
    • 2. 应用
    • 3. 总结
  • 参考内容

一、前言

有时候不想动脑子,就懒得看源码又不像浪费时间所以会看看书,但是又记不住,所以决定开始写"抄书"系列。本系列大部分内容都是来源于《 图解设计模式》(【日】结城浩 著)。该系列文章可随意转载。

二、Visitor 模式

Visitor 模式:访问数据结构并处理数据

1. 介绍

在 Visitor 模式中,数据结构和数据被分离开来。我们需要编写一个“访问者”的类来访问数据结构中的元素,并把对各元素的处理交给访问者类。这样当需要增加新的处理时,我们只需要编写新的访问者,然后让数据结构可以接受访问者的访问即可。


Visitor 模式 中出场的角色:

  • Visitor(访问者):该角色负责对数据结构中每个具体的元素(ConcreteElement角色)声明一个用于访问XXX 的 visit(XXX) 方法。visit(XXX) 是用于处理 XXX 的方法,负责实现该方法的是 ConcreteVisitor 橘色。
  • ConcreteVisitor(具体的访问者):ConcreteVisitor 角色负责实现 Visitor 角色所定义的接口。他要实现所有的 visit(XXX) 方法,即实现如何处理每个 ConcreteElement 角色。
  • Element(元素):Element 角色表示Visitor 角色的访问对象。他声明了接受访问者的 accept 方法。accept 方法接收到的参数是 Visitor 角色。
  • ConcreteElement :ConcreteElement 角色负责实现 Element 角色所定义的接口。
  • ObjectStructure(对象结构):ObjectStructure 角色负责处理 Element 角色的集合。ConcreteVisitor 角色为每个 Element 角色都准备了处理方法。

类图如下:

在这里插入图片描述


Demo如下:使用 ListVisitor 来访问 rootDir 目录下的资源

public interface Element {void accept(Visitor visitor);
}public interface Entry extends Element {/*** 获取文件名** @return*/String getName();/*** 获取文件大小** @return*/int getSize();/*** 添加目录** @param entry* @return*/default Entry addEntry(Entry entry) {throw new RuntimeException();}/*** 生成迭代器* @return*/default Iterator<Entry> iterator() {throw new RuntimeException();}/*** 输出路径* @return*/default String thisPath() {return getName() + "(" + getSize() + ")";}/*** 暴露出方法供访问者访问* @param visitor*/default void accept(Visitor visitor) {visitor.visit(this);}
}// 访问者接口
public interface Visitor {/*** 访问* @param entry*/void visit(Entry entry);
}// 文件
public class File implements Entry {private String name;private int size;public File(String name, int size) {this.name = name;this.size = size;}@Overridepublic String getName() {return name;}@Overridepublic int getSize() {return size;}
}// 文件夹
public class Directory implements Entry {private String name;private List<Entry> entries = Lists.newArrayList();public Directory(String name) {this.name = name;}@Overridepublic String getName() {return name;}@Overridepublic int getSize() {return entries.stream().mapToInt(Entry::getSize).sum();}@Overridepublic Entry addEntry(Entry entry) {entries.add(entry);return this;}@Overridepublic Iterator<Entry> iterator() {return entries.iterator();}}public class ListVisitor implements Visitor {/*** 当前目录*/private String currentDir = "";@Overridepublic void visit(Entry entry) {System.out.println(currentDir + "/" + entry.thisPath());if (entry instanceof Directory) {String saveDir = currentDir;currentDir = currentDir + "/" + entry.getName();entry.iterator().forEachRemaining(sonEntry ->sonEntry.accept(this));currentDir = saveDir;}}
}public class VisitorDemoMain {public static void main(String[] args) {Entry rootDir = new Directory("root");Entry binDir = new Directory("bin");Entry tmpDir = new Directory("tmp");Entry usrDir = new Directory("usr");Entry hanakoDir = new Directory("hanako");usrDir.addEntry(hanakoDir);rootDir.addEntry(binDir);rootDir.addEntry(tmpDir);rootDir.addEntry(usrDir);hanakoDir.addEntry(new File("memo.tex", 10));binDir.addEntry(new File("vi", 1000));binDir.addEntry(new File("latex", 2000));// ListVisitor 访问者访问rootDir 目录下的文件和文件夹rootDir.accept(new ListVisitor());}
}

输出如下:
在这里插入图片描述

2. 应用

  • 印象中在哪看过,但是一时想不起来了。想起来再补


个人使用:该部分内容是写给自己看的,帮助自身理解,因此就不交代项目背景了,读者请自行忽略(◐ˍ◑)

  • 项目 A 中,某一数据内容固定,但是存在多处地方会获取该部分数据,可以通过访问者模式来控制不同的访问者来获取不同部分的数据。

    // 书的数据
    @Data
    public class BookData {/*** 作者*/private String author;/*** 表体*/private String title;/*** 内容*/private String content;/*** 接受访问者访问** @param bookVisitor*/public void accept(BookVisitor bookVisitor) {if (bookVisitor instanceof TitleBookVisitor) {bookVisitor.visit(title);} else if (bookVisitor instanceof AuthorBookVisitor) {bookVisitor.visit(author);} else if (bookVisitor instanceof ContentBookVisitor) {bookVisitor.visit(content);} else {throw new RuntimeException("未知的访问者");}}
    }// 书籍访问者
    public interface BookVisitor {/*** 访问数据* @param data*/void visit(String data);
    }// 作者访问
    public class AuthorBookVisitor implements BookVisitor {@Overridepublic void visit(String data) {System.out.println("author = " + data);}
    }
    // 标题访问
    public class TitleBookVisitor implements BookVisitor {@Overridepublic void visit(String data) {System.out.println("title = " + data);}
    }
    // 内容访问
    public class ContentBookVisitor implements BookVisitor {@Overridepublic void visit(String data) {System.out.println("content = " + data);}
    }public class DemoMain {public static void main(String[] args) {final BookData bookData = new BookData();bookData.setAuthor("夏义春");bookData.setTitle("夏义春的一生");bookData.setContent("夏义春一生天下第一");bookData.accept(new AuthorBookVisitor());bookData.accept(new TitleBookVisitor());bookData.accept(new ContentBookVisitor());}
    }

    输入内容

    在这里插入图片描述

3. 总结

数据结构是单一一个接口或实例,而数据是一个实例,当需要访问数据时,将 数据结构 API 传给数据就可以,这样的话对后面切换数据结构访问非常容易。当需要增加新的处理时,只需要编写新的访问者,然后让数据结构可以接受访问者访问即可。但是相对的,对于数据结构的设计需要具有一定前瞻性。

通常在以下情况可以考虑使用访问者(Visitor)模式:

  • 对象结构相对稳定,但其操作算法经常变化的程序。
  • 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
  • 对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。

扩展思路:

  • 双重分发:
    我们来整理下 Visitor 模式中方法的调用关系
    accept(接受)方法的调用方式如下:

    element.accept(visitor);
    

    visit(访问)方法的调用方式如下:

    visitor.visit(elemnt) 
    

    对比一下就会发现,上面两个方法是相反的关系。element 接受 visitor, 而 visitor 又访问 element。在Visitor 模式中 ConcreteElement 和 ConcreteVisitor 这两个角色共同决定了实际进行的处理。这种消息分发的方式一般被称为双重分发。

  • 为什么如此复杂:
    Visitor 把简单的问题复杂化了吗?如果需要循环处理,在数据结构的类中直接编写循环语句不就解决了吗?为什么要搞出 accept 方法 和 visit 方法之间那样复杂的调用关系呢?

    Visitor 模式的目的是将处理从数据结果中分离出来。数据结构很重要,它能将元素集合和关联在一起,但是需要注意的是,保存数据结构与以数据结构为基础进行处理是两个不同的东西。

    在示例程序中我们创建了 ListVisitor 类作为显示文件夹内容的 ConcreteVisitor 角色。通常,ConcreteVisitor 角色的开发可以独立于 File 类 和 Directory 类,也就是说 Visitor 模式提高了File类和 Directory 类作为组件的独立性。如果将进行处理的方法定义在 File 类和 Directory 类作为组件的独立性。如果将进行处理的方法定义在 File类和 Directory 类中,当每次要扩展功能,增加新的处理时就不得不去修改 File 类和 Directory类。

  • 易于增加 ConcreteVisitor 角色
    使用 Visitor 模式可以很容易地修改 ConcreteVisitor 角色,因为具体的处理被交给 ConcreteVisitor角色负责,因此完全不用修改 ConcreteElement 角色。

  • 难以增加 ConcreteElement 角色
    虽然使用 Visitor 模式可以很容易地增加 ConcreteVisitor 角色,不过他却很难应对 ConcreteElement 角色的增加。

  • Visitor 工作所需的条件 : Element 角色必须向 Visitor 角色公开足够多的信息。不过也可以基于此限制访问者能获取到的数据


相关设计模式:

  • Iterator 模式 :Iterator 模式 和 Visitor 模式都是在某种数据结构上进行处理。Iterator 模式用于逐个遍历保存在数据结构中的元素。Visitor 模式用于对保存在数据结构中的元素进行某种特定的处理。
  • Composite 模式 :有时访问者所访问的数据结构会使用 Composite 模式
  • Interpret 模式 : 在 Interpret 模式汇总,有时会使用 Visitor 模式。例如在生成了语法树后可能会使用 Visitor 模式访问语法树的各个节点进行处理。

一时的小想法,仅仅个人理解,无需在意 :

  • 访问者模式的最主要的作用是将数据和处理二者分离开,对于同一数据可以存在多种处理结果。而如果是为了完成这个目标有很多种其他模式可以选择(如策略模式、处理器模式等),而其中的关系就是主动和被动的区别,策略模式时,数据是被动的,只能被对应策略获取;而访问者模式下,数据则是主动的,由数据来确定暴露什么内容给访问者。
  • 访问者模式还可以通过Element 来控制 Visitor 的数据访问权限,通过暴露出的数据不同来控制 Visitor 获取的数据内容的范围。如上面的例子中 BookData 对于所有的访问者 BookVisitor,可以自由控制暴露的内容,甚至是抛出异常。在权限控制的场景是否适用?如每个角色都是一个访问者,去访问目录内容,根据角色不同会给于不同的可访问的内容?

三、Chain of Responsibility 模式

Chain of Responsibility 模式 : 推卸责任

1. 介绍

当外部请求程序进行某个处理,但程序暂时无法直接决定由哪个对象负责处理时,就需要推卸责任,在这种情况下,我们可以考虑将多个对象组成一条职责链。然后按照他们在职责链上的顺序一个一个地找出到底应该谁负来负责处理。这种模式称之为 Chain of Responsibility 模式。


Chain of Responsibility模式 中出场的角色:

  • handler (处理者) :Handler 角色定义了处理请求的接口(API),Handler 角色知道下一个处理者是谁,如果自己无法处理请求,会将请求转发给下一个处理者,下一个处理着也是 Handler角色。
  • ConcreteHandler(具体的处理者):ConcreteHandler角色是处理请求的具体角色。
  • Client(请求者):该角色是向第一个 ConcreteHandler角色发送请求的角色。

类图如下:

在这里插入图片描述


Demo 如下:可以看到不同的问题会交由不同的 Support 来解决,当一个 Support 不能解决时会交由 next 来处理

public abstract class Support {/*** 下一个解决器*/private Support next;/*** 设置下一个解决器* @param next* @return*/public Support setNext(Support next) {this.next = next;return this;}/*** 直接解决问题* @param trouble*/public final void support(String trouble) {if (resolve(trouble)) {done(trouble);} else if (next != null) {next.support(trouble);} else {fail(trouble);}}/*** 解决问题** @param trouble* @return*/protected abstract boolean resolve(String trouble);/*** 问题已解决** @param trouble*/protected void done(String trouble) {System.out.println(trouble + " 问题已被 " + this.getClass().getSimpleName() + " 解决");}/*** 问题未解决** @param trouble*/protected void fail(String trouble) {System.out.println(trouble + " 问题未解决");}
}// 解决特定编号的问题
public class SpecialSupport extends Support {@Overrideprotected boolean resolve(String trouble) {return "夏义春".equals(trouble);}
}// 解决小于指定长度的问题
public class LimitSupport extends Support {@Overrideprotected boolean resolve(String trouble) {return StringUtils.length(trouble) < 10;}
}// 不解决任何问题
public class NoSupport extends Support {@Overrideprotected boolean resolve(String trouble) {return false;}
}public class ChainDemoMain {public static void main(String[] args) {Support noSupport = new NoSupport();Support limitSupport = new LimitSupport();Support specialSupport = new SpecialSupport();// 构建责任链specialSupport.setNext(limitSupport.setNext(noSupport));specialSupport.support("你好");specialSupport.support("夏义春");specialSupport.support("1234567890");}
}

输出如下:

在这里插入图片描述

2. 应用

  • 责任链模式在很多地方都有所使用,如 Spring中的过滤器、拦截器、Dubbo中的 Filter, Spring Cloud Gateway 中的 GlobalFilter 等等。这里以Spring的 拦截器为例。HandlerInterceptorAdapter 定义如下,

    public abstract class HandlerInterceptorAdapter implements HandlerInterceptor {/*** This implementation always returns <code>true</code>.*/public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return true;}/*** This implementation is empty.*/public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception {}/*** This implementation is empty.*/public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {}}
    

    在 DispatcherServlet#doDispatch 中,Spring 在分发请求的前后都会将请求交由 HandlerInterceptor 的责任链来执行一遍,具体的代码在 HandlerExecutionChain 中,如下是其中一小部分:

    	... boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {// 遍历所有注册的拦截器并依次触发for (int i = 0; i < this.interceptorList.size(); i++) {HandlerInterceptor interceptor = this.interceptorList.get(i);if (!interceptor.preHandle(request, response, this.handler)) {triggerAfterCompletion(request, response, null);return false;}this.interceptorIndex = i;}return true;}void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)throws Exception {for (int i = this.interceptorList.size() - 1; i >= 0; i--) {HandlerInterceptor interceptor = this.interceptorList.get(i);interceptor.postHandle(request, response, this.handler, mv);}}...
    


个人使用:该部分内容是写给自己看的,帮助自身理解,因此就不交代项目背景了,读者请自行忽略(◐ˍ◑)

  • 在项目A 中,需要对客户进行计费,客户可以自定义计费规则组合,因此对于每种计费规则都是一个实现类,这里实现则是将每个多个客户的计费规则组成成一个链路,即责任链。和上面的区别在于,上面的场景当某一个 Support 处理完成后就不再执行下面的Support,而这里的情况则是需要将链路中所有可以处理当前情况的Support 全部执行,得到最终结果。Demo如下:

    public abstract class Rule {/*** 下一个规则*/@Setterprivate Rule next;/*** 处理费用** @param fee*/public final String handle(String fee) {fee = doHandle(fee);if (next != null) {return next.handle(fee);}return fee;}/*** 处理** @param fee*/protected abstract String doHandle(String fee);
    }public class VipRule extends Rule {// 处理 VIP 逻辑@Overrideprotected String doHandle(String fee) {return fee.contains("vip") ? fee + "vip nb" : fee;}
    }public class SpecialRule extends Rule {// 客户定制化逻辑@Overrideprotected String doHandle(String fee) {return fee.length() > 5 ? fee + " 长度大于5" : fee + " 长度小于5";}
    }public class DefaultRule extends Rule {@Overrideprotected String doHandle(String fee) {return fee;}
    }public class DemoMain {public static void main(String[] args) {final Rule vipRule = new VipRule();final Rule specialRule = new SpecialRule();final Rule defaultRule = new DefaultRule();vipRule.setNext(specialRule);specialRule.setNext(defaultRule);System.out.println(vipRule.handle("vip 001"));System.out.println("----------------");System.out.println(vipRule.handle("夏义春 001"));}
    }

    输出如下:

    在这里插入图片描述

  • 在项目B中,每个客户可以定制各自的单证处理逻辑,多个逻辑可以组成,形成链路后,依次执行逻辑,基本同项目 A 的情况,这里不再赘述。

3. 总结

扩展思路

  1. 弱化了发起请求方和处理请求的人之间的关系 :即弱化了发出请求的人(Client)与处理请求的人(ConcreteHandler)的之间的关系,当 Client 向第一个 ConcreteHandler 发出请求后,请求会在责任链中传播,最终处理请求的可能并不是第一个 ConcreteHandler,而是责任链中的某一个或者几个 Handler。
  2. 可以动态改变职责链 :我们可以很轻易的增加或减少责任链中的逻辑。
  3. 专注于自己的工作 :责任链模式可以让每个 Handler 专注自身的逻辑,而无需考虑一些额外的场景逻辑。

相关设计模式

  • Composite 模式:Handler 角色经常会使用 Composite 模式
  • Command 模式: 有时候会使用 Command 模式向 Handler 角色发送请求

一时的小想法,仅仅个人理解,无需在意 :

  • 责任链模式有两种情况,一种是执行即结束,即链路中的某个 Handler 可以处理这个请求后则阻断后续链路,另一种是责任链中所有支持当前请求的Handler都会将结果处理,并返回最终结果。两种情况需要考虑自己的业务场景去选择。

参考内容

https://www.cnblogs.com/yb-ken/p/15084837.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/239929.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

弟12章 网络编程

文章目录 网络协议概述 p164TCP协议与UDP协议的区别 p165TCP服务器端代码的编写 p166TCP服务器端流程 TCP客户端代码的编写 p167TCP客户端流程主机和客户端的通信流程 tcp多次通信服务器端代码 p168TCP多次通信客户端代码 p169UDP的一次双向通信 p170udp通信模型udp接收方代码u…

图像处理------亮度

from PIL import Imagedef change_brightness(img: Image, level: float) -> Image:"""按照给定的亮度等级&#xff0c;改变图片的亮度"""def brightness(c: int) -> float:return 128 level (c - 128)if not -255.0 < level < 25…

python基础学习

缩⼩图像&#xff08;或称为下采样&#xff08;subsampled&#xff09;或降采样&#xff08;downsampled&#xff09;&#xff09;的主要⽬的有两个&#xff1a;1、使得图像符合显⽰区域的⼤⼩&#xff1b;2、⽣成对应图像的缩略图。 放⼤图像&#xff08;或称为上采样&#xf…

如何在 Python3 中使用变量

介绍 变量是一个重要的编程概念&#xff0c;值得掌握。它们本质上是在程序中用于表示值的符号。 本教程将涵盖一些变量基础知识&#xff0c;以及如何在您创建的 Python 3 程序中最好地使用它们。 理解变量 从技术角度来说&#xff0c;变量是将存储位置分配给与符号名称或标…

AI对决:ChatGPT与文心一言的深度比较

. 个人主页&#xff1a;晓风飞 专栏&#xff1a;数据结构|Linux|C语言 路漫漫其修远兮&#xff0c;吾将上下而求索 文章目录 引言ChatGPT与文心一言的比较Chatgpt的看法文心一言的看法Copilot的观点chatgpt4.0的回答 模型的自我评价自我评价 ChatGPT的优势在这里插入图片描述 文…

mathtype2024版本下载与安装(mac版本也包含在内)

安装包补丁主要是mathtype的安装包&#xff0c;与它的补丁。 详细安装过程&#xff1a; step1&#xff1a; 使用方法是下载完成后先安装MathType-win-zh.exe文件&#xff0c;跟着步骤走直接安装就行。 step2&#xff1a; 关闭之后&#xff0c;以管理员身份运行MathType7PJ.exe…

Jsqlparser简单学习

文章目录 学习链接模块访问者模式parser模块statement模块Expression模块deparser模块 测试TestDropTestSelectTestSelectVisitor 学习链接 java设计模式&#xff1a;访问者模式 github使用示例参考 测试 JSqlParser使用示例 JSqlParse&#xff08;一&#xff09;基本增删改…

Linux 系统编程:文件系统的底层逻辑 - inode

《Linux 程序设计》的第三章讲文件操作。在提到 目录 时有这么一段文字&#xff1a; 文件&#xff0c;除了本身包含的 内容 以外&#xff0c;它还会有一个 名字 和一些 属性&#xff0c;即“管理信息”&#xff0c;包括文件的创建 / 修改日期和它的访问权限。这些属性被保存在文…

用LED数码显示器伪静态显示数字1234

#include<reg51.h> // 包含51单片机寄存器定义的头文件 void delay(void) //延时函数&#xff0c;延时约0.6毫秒 { unsigned char i; for(i0;i<200;i) ; } void main(void) { while(1) //无限循环 { P20xfe; …

图像分类 | 基于 Labelme 数据集和 VGG16 预训练模型实现迁移学习

Hi&#xff0c;大家好&#xff0c;我是源于花海。本文主要使用数据标注工具 Labelme 对自行车&#xff08;bike&#xff09;和摩托车&#xff08;motorcycle&#xff09;这两种训练样本进行标注&#xff0c;使用预训练模型 VGG16 作为卷积基&#xff0c;并在其之上添加了全连接…

el-date-picker组件设置时间范围限制

需求&#xff1a; 如图所示&#xff0c;下图为新增的一个弹层页面&#xff0c;同时有个需求&#xff0c;日期选择需要限制一个月的时间范围&#xff08;一月默认为30天&#xff09;&#xff1a; 查看官方文档我们需要主要使用到如下表格的一些东西&#xff1a; 参数说明类型可…

FFmpeg之SWScale

文章目录 一、概述二、函数调用结构图三、Libswscale处理数据流程四、重要结构体4.1、SwsContext4.2、SwsFilter 五、重要函数5.1、sws_getContext5.1.1、sws_alloc_context5.1.2、sws_init_context 5.2、sws_scale5.2.1、SwsContext中的swscale()5.2.2、check_image_pointers5…

8个Python必备的PyCharm插件

大家好&#xff0c;在PyCharm中浏览插件列表并尝试很多人推荐的插件后&#xff0c;总结了几个瑰宝插件&#xff0c;它们各自以独特的方式帮助开发者快速、简便、愉悦地开发&#xff0c;接下来将逐个介绍它们。 1. Key Promoter X 【下载链接】&#xff1a;https://plugins.je…

【Python数据可视化】matplotlib之增加图形内容:设置图例、设置中文标题、设置网格效果

文章传送门 Python 数据可视化matplotlib之绘制常用图形&#xff1a;折线图、柱状图&#xff08;条形图&#xff09;、饼图和直方图matplotlib之设置坐标&#xff1a;添加坐标轴名字、设置坐标范围、设置主次刻度、坐标轴文字旋转并标出坐标值matplotlib之增加图形内容&#x…

Vue3+ElementPlus实例_select选择器(不连续搜索)

1.开发需求 在各大UI框架的select选择器中&#xff0c;在搜索时都是输入连续的搜索内容&#xff0c;比如“app-store”选项&#xff0c;你要输入“app-xxx”&#xff0c;才能匹配这个选择&#xff0c;要是想输入“a-s”这种不连续的匹配方式&#xff0c;就实现不了&#xff0c…

电脑安装 Python提示“api-ms-win-crt-process-l1-1-0.dll文件丢失,程序无法启动”,快速修复方法,完美解决

在windows 10系统安装完python后&#xff0c;启动的时候&#xff0c;Windows会弹出错误提示框“无法启动此程序&#xff0c;因为计算机中丢失了api-ms-win-crt-process-l1-1-0.dll&#xff0c;尝试重新安装该程序以解决此问题。” api-ms-win-crt-process-l1-1-0.dll是一个动态…

软件架构之事件驱动架构

一、定义 事件驱动的架构是围绕事件的发布、捕获、处理和存储&#xff08;或持久化&#xff09;而构建的集成模型。 某个应用或服务执行一项操作或经历另一个应用或服务可能想知道的更改时&#xff0c;就会发布一个事件&#xff08;也就是对该操作或更改的记录&#xff09;&am…

新增PostgreSQL数据库管理功能,1Panel开源面板v1.9.3发布

2024年1月15日&#xff0c;现代化、开源的Linux服务器运维管理面板1Panel正式发布v1.9.3版本。 在这一版本中&#xff0c;1Panel新增了PostgreSQL数据库管理功能&#xff0c;并且支持设置PHP运行环境扩展模版。此外&#xff0c;我们进行了30多项功能更新和问题修复。1Panel应用…

IDEA 2022.3.3 安装教程

1.下载2022.3.3版本IDEA 链接&#xff1a;https://pan.baidu.com/s/1z-Yfl7fWHgqz8SQLn2-u0g?pwd949u 提取码&#xff1a;949u 2.安装 下载完成后&#xff0c;双击exe安装包&#xff0c; 点击next 3.选择方式3 4.将下面文件复制到任意位置&#xff08;不要有中文路径&…

Java 使用 EasyExcel 爬取数据

一、爬取数据的基本思路 分析要爬取数据的来源 1. 查找数据来源&#xff1a;浏览器按 F12 或右键单击“检查”打开开发者工具查看数据获取时的请求地址 2. 查看接口信息&#xff1a;复制请求地址直接到浏览器地址栏输入看能不能取到数据 3. 推荐安装插件&#xff1a;FeHelper&a…