前言

记录下SpringBoot下静态资源存储服务器的搭建。

环境

win10 + SpringBoot2.5.3

实现效果

  • 文件上传:

  • 文件存储位置:

  • 文件访问:

具体实现

文件上传

配置类

  • pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
  • application.yml
spring:
# 文件编码 UTF8
mandatory-file-encoding: UTF-8 server:
# 服务端口
port: 8000 #文件上传配置
file:
# 文件服务域名
domain: http://localhost:8000/
# 排除文件类型
exclude:
# 包括文件类型
include:
- .jpg
- .png
- .jpeg
# 文件最大数量
nums: 10
# 服务器文件路径
serve-path: assets/**
# 单个文件最大体积
single-limit: 2MB
# 本地文件保存位置
store-dir: assets/
  • yml读取工厂类
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
import java.io.IOException;
import java.util.List; /**
* @Description yml读取工厂类
* @author coisini
* @date Sep 7, 2021
* @Version 1.0
*/
public class YmlPropertySourceFactory implements PropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
List<PropertySource<?>> sources = new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource());
return sources.get(0);
}
}
  • 文件上传属性配置类
import com.coisini.file.factory.YmlPropertySourceFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component; /**
* @Description 文件上传属性配置类
* @author coisini
* @date Sep 7, 2021
* @Version 1.0
*/
@Component
@ConfigurationProperties(prefix = "file")
@PropertySource(value = "classpath:application.yml",
encoding = "UTF-8",factory = YmlPropertySourceFactory.class)
public class FilePropertiesConfiguration { private static final String[] DEFAULT_EMPTY_ARRAY = new String[0]; private String storeDir = "/assets"; private String singleLimit = "2MB"; private Integer nums = 10; private String domain; private String[] exclude = DEFAULT_EMPTY_ARRAY; private String[] include = DEFAULT_EMPTY_ARRAY; public String getStoreDir() {
return storeDir;
} public void setStoreDir(String storeDir) {
this.storeDir = storeDir;
} public String getSingleLimit() {
return singleLimit;
} public void setSingleLimit(String singleLimit) {
this.singleLimit = singleLimit;
} public Integer getNums() {
return nums;
} public void setNums(Integer nums) {
this.nums = nums;
} public String[] getExclude() {
return exclude;
} public void setExclude(String[] exclude) {
this.exclude = exclude;
} public String[] getInclude() {
return include;
} public void setInclude(String[] include) {
this.include = include;
} public String getDomain() {
return domain;
} public void setDomain(String domain) {
this.domain = domain;
}
}

上传接口

  • 文件上传控制器
import com.coisini.file.vo.FileVo;
import com.coisini.file.service.FileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.util.List; /**
* @Description 文件上传控制器
* @author coisini
* @date Sep 7, 2021
* @Version 1.0
*/
@RestController
@RequestMapping("/file")
public class FileController { @Autowired
private FileService fileService; /**
* 文件上传
* @param request
* @return
*/
@PostMapping("/upload")
public List<FileVo> upload(HttpServletRequest request) {
MultipartHttpServletRequest multipartHttpServletRequest = ((MultipartHttpServletRequest) request);
MultiValueMap<String, MultipartFile> fileMap = multipartHttpServletRequest.getMultiFileMap();
List<FileVo> files = fileService.upload(fileMap);
return files;
} }
  • 文件上传接口
import com.coisini.file.vo.FileVo;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
import java.util.List; /**
* @Description 文件上传接口
* @author coisini
* @date Sep 7, 2021
* @Version 1.0
*/
public interface FileService { /**
* 上传文件
* @param fileMap 文件map
* @return 文件数据
*/
List<FileVo> upload(MultiValueMap<String, MultipartFile> fileMap); }
  • 文件上传实现类
import com.coisini.file.model.FileModel;
import com.coisini.file.core.Uploader;
import com.coisini.file.vo.FileVo;
import com.coisini.file.service.FileService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.stream.Collectors; /**
* @Description 文件上传实现类
* @author coisini
* @date
* @Version 1.0
*/
@Service
public class FileServiceImpl implements FileService { @Autowired
private Uploader uploader; @Value("${file.domain}")
private String domain; @Value("${file.serve-path:assets/**}")
private String servePath; @Override
public List<FileVo> upload(MultiValueMap<String, MultipartFile> fileMap) {
return uploader.upload(fileMap).stream().map(item ->{
/**
* 这里可以拿到文件具体信息
* 在此做数据库保存记录操作等业务处理
*/
return transform(item);
}).collect(Collectors.toList());
} /**
* 出参序列化
* @param fileModel
* @return
*/
private FileVo transform(FileModel fileModel) {
FileVo model = new FileVo();
BeanUtils.copyProperties(fileModel, model);
String s = servePath.split("/")[0];
model.setUrl(domain + s + "/" + fileModel.getPath());
return model;
}
}

上传实现

  • 文件上传配置类
import com.coisini.file.core.LocalUploader;
import com.coisini.file.core.Uploader;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order; /**
* @Description 文件上传配置类
* @author coisini
* @date Sep 7, 2021
* @Version 1.0
*/
@Configuration
public class UploaderConfiguration {
/**
* @return 本地文件上传实现类
*/
@Bean
@Order
@ConditionalOnMissingBean
public Uploader uploader(){
return new LocalUploader();
}
}
  • 文件上传服务接口
import com.coisini.file.model.FileModel;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
import java.util.List; /**
* @Description 文件上传服务接口
* @author coisini
* @date Sep 7, 2021
* @Version 1.0
*/
public interface Uploader { /**
* 上传文件
* @param fileMap 文件map
* @return 文件数据
*/
List<FileModel> upload(MultiValueMap<String, MultipartFile> fileMap); }
  • 本地上传实现类
import com.coisini.file.config.FilePropertiesConfiguration;
import com.coisini.file.model.FileModel;
import com.coisini.file.util.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.PostConstruct;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List; /**
* @Description 本地上传
* @author coisini
* @date Sep 7, 2021
* @Version 1.0
*/
@Slf4j
public class LocalUploader implements Uploader { @Autowired
private FilePropertiesConfiguration filePropertiesConfiguration; /**
* 初始化本地存储
* 依赖注入完成后初始化
*/
@PostConstruct
public void initStoreDir() {
System.out.println("initStoreDir start:" + this.filePropertiesConfiguration.getStoreDir());
FileUtil.initStoreDir(this.filePropertiesConfiguration.getStoreDir());
System.out.println("initStoreDir end");
} /**
* 文件上传
* @param fileMap 文件map
* @return
*/
@Override
public List<FileModel> upload(MultiValueMap<String, MultipartFile> fileMap) {
// 检查文件
checkFileMap(fileMap);
return handleMultipartFiles(fileMap);
} /**
* 文件配置
* @return
*/
protected FilePropertiesConfiguration getFilePropertiesConfiguration() {
return filePropertiesConfiguration;
} /**
* 单个文件体积限制
* @return
*/
private long getSingleFileLimit() {
String singleLimit = getFilePropertiesConfiguration().getSingleLimit();
return FileUtil.parseSize(singleLimit);
} /**
* 检查文件
* @param fileMap
*/
protected void checkFileMap(MultiValueMap<String, MultipartFile> fileMap){
if (fileMap.isEmpty()) {
throw new RuntimeException("file not found");
} // 上传文件数量限制
int nums = getFilePropertiesConfiguration().getNums();
if (fileMap.size() > nums) {
throw new RuntimeException("too many files, amount of files must less than" + nums);
}
} /**
* 文件处理
* @param fileMap
* @return
*/
protected List<FileModel> handleMultipartFiles(MultiValueMap<String, MultipartFile> fileMap) {
long singleFileLimit = getSingleFileLimit();
List<FileModel> res = new ArrayList<>();
fileMap.keySet().forEach(key -> fileMap.get(key).forEach(file -> {
if (!file.isEmpty()) {
handleOneFile(res, singleFileLimit, file);
}
}));
return res;
} /**
* 单文件处理
* @param res
* @param singleFileLimit
* @param file
*/
private void handleOneFile(List<FileModel> res, long singleFileLimit, MultipartFile file) {
byte[] bytes = FileUtil.getFileBytes(file);
String[] include = getFilePropertiesConfiguration().getInclude();
String[] exclude = getFilePropertiesConfiguration().getExclude();
String ext = UploadHelper.checkOneFile(include, exclude, singleFileLimit, file.getOriginalFilename(), bytes.length);
String newFilename = UploadHelper.getNewFilename(ext);
String storePath = getStorePath(newFilename);
// 生成文件的md5值
String md5 = FileUtil.getFileMD5(bytes);
FileModel fileModelData = FileModel.builder().
name(newFilename).
md5(md5).
key(file.getName()).
path(storePath).
size(bytes.length).
extension(ext).
build(); boolean ok = writeFile(bytes, newFilename);
if (ok) {
res.add(fileModelData);
}
} /**
* 写入存储
* @param bytes
* @param newFilename
* @return
*/
protected boolean writeFile(byte[] bytes, String newFilename) {
// 获取绝对路径
String absolutePath =
FileUtil.getFileAbsolutePath(filePropertiesConfiguration.getStoreDir(), getStorePath(newFilename));
System.out.println("absolutePath:" + absolutePath);
try {
BufferedOutputStream stream =
new BufferedOutputStream(new FileOutputStream(new File(absolutePath)));
stream.write(bytes);
stream.close();
} catch (Exception e) {
System.out.println("write file error:" + e);
return false;
}
return true;
} /**
* 获取缓存地址
* @param newFilename
* @return
*/
@SuppressWarnings("ResultOfMethodCallIgnored")
protected String getStorePath(String newFilename) {
Date now = new Date();
String format = new SimpleDateFormat("yyyy/MM/dd").format(now);
Path path = Paths.get(filePropertiesConfiguration.getStoreDir(), format).toAbsolutePath();
File file = new File(path.toString());
if (!file.exists()) {
file.mkdirs();
} return Paths.get(format, newFilename).toString();
}
}

辅助类

  • 文件上传Helper
import com.coisini.file.util.FileUtil;
import java.util.UUID; /**
* @Description 文件上传Helper
* @author coisini
* @date Sep 7, 2021
* @Version 1.0
*/
public class UploadHelper { /**
* 单个文件检查
* @param singleFileLimit 单个文件大小限制
* @param originName 文件原始名称
* @param length 文件大小
* @return 文件的扩展名,例如: .jpg
*/
public static String checkOneFile(String[] include, String[] exclude, long singleFileLimit, String originName, int length) {
// 写到了本地
String ext = FileUtil.getFileExt(originName);
// 检测扩展
if (!UploadHelper.checkExt(include, exclude, ext)) {
throw new RuntimeException(ext + "文件类型不支持");
}
// 检测单个大小
if (length > singleFileLimit) {
throw new RuntimeException(originName + "文件不能超过" + singleFileLimit);
}
return ext;
} /**
* 检查文件后缀
* @param ext 后缀名
* @return 是否通过
*/
public static boolean checkExt(String[] include, String[] exclude, String ext) {
int inLen = include == null ? 0 : include.length;
int exLen = exclude == null ? 0 : exclude.length;
// 如果两者都有取 include,有一者则用一者
if (inLen > 0 && exLen > 0) {
return UploadHelper.findInInclude(include, ext);
} else if (inLen > 0) {
// 有include,无exclude
return UploadHelper.findInInclude(include, ext);
} else if (exLen > 0) {
// 有exclude,无include
return UploadHelper.findInExclude(exclude, ext);
} else {
// 二者都没有
return true;
}
} /**
* 检查允许的文件类型
* @param include
* @param ext
* @return
*/
public static boolean findInInclude(String[] include, String ext) {
for (String s : include) {
if (s.equals(ext)) {
return true;
}
}
return false;
} /**
* 检查不允许的文件类型
* @param exclude
* @param ext
* @return
*/
public static boolean findInExclude(String[] exclude, String ext) {
for (String s : exclude) {
if (s.equals(ext)) {
return true;
}
}
return false;
} /**
* 获得新文件的名称
* @param ext 文件后缀
* @return 新名称
*/
public static String getNewFilename(String ext) {
String uuid = UUID.randomUUID().toString().replace("-", "");
return uuid + ext;
}
}
  • 文件工具类
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.unit.DataSize;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path; /**
* @Description 文件工具类
* @author coisini
* @date Sep 7, 2021
* @Version 1.0
*/
public class FileUtil { /**
* 获取当前文件系统
* @return
*/
public static FileSystem getDefaultFileSystem() {
return FileSystems.getDefault();
} /**
* 是否绝对路径
* @param str
* @return
*/
public static boolean isAbsolute(String str) {
Path path = getDefaultFileSystem().getPath(str);
return path.isAbsolute();
} /**
* 初始化存储文件夹
* @param dir
*/
@SuppressWarnings("ResultOfMethodCallIgnored")
public static void initStoreDir(String dir) {
String absDir;
if (isAbsolute(dir)) {
absDir = dir;
} else {
String cmd = getCmd();
Path path = getDefaultFileSystem().getPath(cmd, dir);
absDir = path.toAbsolutePath().toString();
}
File file = new File(absDir);
if (!file.exists()) {
file.mkdirs();
}
} /**
* 获取程序当前路径
* @return
*/
public static String getCmd() {
return System.getProperty("user.dir");
} /**
* 获取文件绝对路径
* @param dir
* @param filename
* @return
*/
public static String getFileAbsolutePath(String dir, String filename) {
if (isAbsolute(dir)) {
return getDefaultFileSystem()
.getPath(dir, filename)
.toAbsolutePath().toString();
} else {
return getDefaultFileSystem()
.getPath(getCmd(), dir, filename)
.toAbsolutePath().toString();
}
} /**
* 获取文件扩展名
* @param filename
* @return
*/
public static String getFileExt(String filename) {
int index = filename.lastIndexOf('.');
return filename.substring(index);
} /**
* 获取文件MD5值
* @param bytes
* @return
*/
public static String getFileMD5(byte[] bytes) {
return DigestUtils.md5DigestAsHex(bytes);
} /**
* 文件体积
* @param size
* @return
*/
public static Long parseSize(String size) {
DataSize singleLimitData = DataSize.parse(size);
return singleLimitData.toBytes();
} /**
* 是否是绝对路径
* @param path
* @return
*/
public static boolean isAbsolutePath(String path) {
if (StringUtils.isEmpty(path)) {
return false;
} else {
return '/' == path.charAt(0) || path.matches("^[a-zA-Z]:[/\\\\].*");
}
} /**
* 文件字节
* @param file 文件
* @return 字节
*/
public static byte[] getFileBytes(MultipartFile file) {
byte[] bytes;
try {
bytes = file.getBytes();
} catch (Exception e) {
throw new RuntimeException("read file date failed");
}
return bytes;
}
}

实体

  • 文件具体信息
import lombok.*;

/**
* @Description 文件具体信息,可存储数据库
* @author coisini
* @date Sep 7, 2021
* @Version 1.0
*/
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class FileModel { /**
* url
*/
private String url; /**
* key
*/
private String key; /**
* 文件路径
*/
private String path; /**
* 文件名称
*/
private String name; /**
* 扩展名,例:.jpg
*/
private String extension; /**
* 文件大小
*/
private Integer size; /**
* md5值,防止上传重复文件
*/
private String md5;
}
  • 文件出参
import lombok.Data;

/**
* @Description 文件出参
* @author coisini
* @date Sep 7, 2021
* @Version 1.0
*/
@Data
public class FileVo { /**
* 文件 key
*/
private String key; /**
* 文件路径
*/
private String path; /**
* 文件 URL
*/
private String url;
}

上传测试

  • 上传

  • 文件存储位置为当前项目/assets目录

文件访问

配置类

  • SpringBoot访问静态资源有两种方式:模板引擎和改变资源映射,这里采用改变资源映射来实现
  • Spring MVC配置类
import com.coisini.file.util.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.nio.file.FileSystems;
import java.nio.file.Path; /**
* @Description Spring MVC 配置
* @author coisini
* @date Sep 7, 2021
* @Version 1.0
*/
@Configuration(proxyBeanMethods = false)
@Slf4j
public class WebConfiguration implements WebMvcConfigurer { @Value("${file.store-dir:assets/}")
private String dir; @Value("${file.serve-path:assets/**}")
private String servePath; /**
* 跨域设置
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
.allowCredentials(true)
.maxAge(3600)
.allowedHeaders("*");
} /**
* 拦截处理请求信息
* 添加文件真实地址
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler(getDirServePath())
.addResourceLocations("file:" + getAbsDir() + "/");
} /**
* 获取服务器url
* @return
*/
private String getDirServePath() {
return servePath;
} /**
* 获得文件夹的绝对路径
*/
private String getAbsDir() {
if (FileUtil.isAbsolutePath(dir)) {
return dir;
}
String cmd = System.getProperty("user.dir");
Path path = FileSystems.getDefault().getPath(cmd, dir);
return path.toAbsolutePath().toString();
}
}
  • 访问结果

项目源码

Gitee: https://gitee.com/maggieq8324/java-learn-demo/tree/master/springboot-file-simple

- End -



梦想是咸鱼
关注一下吧

SpringBoot - 搭建静态资源存储服务器的更多相关文章

  1. 利用 MinIO 轻松搭建静态资源服务

    目录 1 引言 2 MinIO 简介 3 MinIO 运行与静态资源使用 3.1 MinIO 获取 3.2 MinIO 启动与运行 3.2.1 前台简单启动 3.2.2 后台指定参数运行 3.2.3 ...

  2. 使用Node.js搭建静态资源服务器

    对于Node.js新手,搭建一个静态资源服务器是个不错的锻炼,从最简单的返回文件或错误开始,渐进增强,还可以逐步加深对http的理解.那就开始吧,让我们的双手沾满网络请求! Note: 当然在项目中如 ...

  3. SpringBoot 配置静态资源映射

    SpringBoot 配置静态资源映射 (嵌入式servlet容器)先决知识 request.getSession().getServletContext().getRealPath("/& ...

  4. springboot设置静态资源不拦截的方法

    springboot设置静态资源不拦截的方法 springboot不拦截静态资源需配置如下的类: import org.springframework.context.annotation.Confi ...

  5. springboot下静态资源的处理(转)

    在SpringBoot中有默认的静态资源文件相关配置,需要通过如下源码跟踪: WebMvcAutoConfiguration-->configureResourceChain(method)-- ...

  6. IntelliJ IDEA+SpringBoot中静态资源访问路径陷阱:静态资源访问404

    IntelliJ IDEA+SpringBoot中静态资源访问路径陷阱:静态资源访问404 .embody{ padding:10px 10px 10px; margin:0 -20px; borde ...

  7. 使用 Nginx 搭建静态资源 web 服务器

    在搭建网站的时候,往往会加载很多的图片,如果都从 Tomcat 服务器来获取静态资源,这样会增加服务器的负载,使得服务器运行 速度非常慢,这时可以使用 Nginx 服务器来加载这些静态资源,这样就可以 ...

  8. springboot配置静态资源访问路径

    其实在springboot中静态资源的映射文件是在resources目录下的static文件夹,springboot推荐我们将静态资源放在static文件夹下,因为默认配置就是classpath:/s ...

  9. 【SpringBoot】06.SpringBoot访问静态资源

    SpringBoot访问静态资源 1.SpringBoot从classpath/static的目录 目录名称必须是static 启动项目,访问http://localhost:8080/0101.jp ...

随机推荐

  1. jvm源码解读--09 创建oop对象,将static静态变量放置在oop的96 offset处 第二篇

    先打断点systemDictionary.cpp 1915行 Universe::fixup_mirrors(CHECK); 进入 void Universe::fixup_mirrors(TRAPS ...

  2. 阿里云IoT初试

    本文从概念到实战,以一个假想产品--"电子货架标签"(Electronic Shelf Label,以下简称ESL)为例,介绍基于阿里云IoT的物联网应用开发. 数据交互流程 以云 ...

  3. Centos8部署jdk、mysql8、tomcat,并部署项目到tomcat中

    目录 Linux系统的学习与使用(Centos8) Linux系统的介绍 为什么要选择Linux作为服务器运行的操作系统 目录结构 使Linux系统能够联网(登录root用户) 常用命令 cd命令(用 ...

  4. 3、基于Python建立任意层数的深度神经网络

    一.神经网络介绍: 神经网络算法参考人的神经元原理(轴突.树突.神经核),在很多神经元基础上构建神经网络模型,每个神经元可看作一个个学习单元.这些神经元采纳一定的特征作为输入,根据自身的模型得到输出. ...

  5. Bootstrap Blazor 更新版本 5.6.0

    Bootstrap Blazor 是一款基于 Bootstrap 的 企业级 Blazor UI 组件库,目前内置近 90 个组件,欢迎大家尝试使用.本次更新全面升级支持 Bootstrap V5.6 ...

  6. goproxy.io

    goproxy.io 是全球最早的 Go modules 镜像代理服务之一, 采用 CDN 加速服务为开发者提供依赖下载, 该服务由一批热爱开源, 热爱 Go 语言的年轻人开发维护.从 Go 1.11 ...

  7. matlab快速入门

    matlab快速入门 1矩阵 生成矩阵 ​ % 直接法 a = [1,2,3;4,5,6;7,8,9]; % 冒号一维矩阵 a = 开始:步长:结束,步长为1可省略 b = 1:1:10; % 1,2 ...

  8. java基础技术集合面试【笔记】

    java基础技术集合面试[笔记] Hashmap: 基于哈希表的 Map 接口的实现,此实现提供所有可选的映射操作,并允许使用 null 值和 null 键(除了不同步和允许使用 null 之外,Ha ...

  9. 配置VRRP的多备份组

    实验拓扑和端口IP见上一个博客 实验步骤: 1.继续创建虚拟组2 2. 2. 查看 3.验证: PC1 PC2 PC1通过R2,PC2通过R3访问外网 二.验证VRRP的抢占特性 可以看到,即使R2的 ...

  10. 【死磕 Java 基础】— 我同事一个 select 分页语句查出来了 3000W 条数据

    大家好,我是大明哥,一个专注于[死磕 Java]系列创作的男人 个人网站:https://www.cmsblogs.com/.专注于 Java 优质系列文章分享,提供一站式 Java 学习资料 某天我 ...