有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误

Java 是一种复杂的编程语言,很长一段时间以来一直主导着许多生态系统。可移植性、自动垃圾回收及其温和的学习曲线是使其成为软件开发的绝佳选择的一些因素。但是,与任何其他编程语言一样,它仍然容易受到开发人员错误的影响。

本文探讨了 Java 开发人员最常犯的 10 大错误以及避免这些错误的一些方法。

Java 是一种编程语言Oracle,但随着时间的推移,它已经广泛存在于可以使用软件的任何地方。Java 采用面向对象编程的概念设计,消除了其他语言(如 C 或 C++)、垃圾回收和架构无关的虚拟机的复杂性,创造了一种新的编程方式。此外,它的学习曲线很平缓,并且似乎成功地遵守了自己的座右铭——一次编写,到处运行,这几乎总是正确的; 但 Java 问题仍然存在。我将解决我认为是最常见错误的 10 个 Java 问题。

常见错误 #1:忽略现有库

Java 开发人员忽视无数用 Java 编写的库绝对是一个错误。在重新发明轮子之前,请尝试搜索可用的库 - 其中许多库已经在其存在的多年中得到了完善,并且可以免费使用。这些可以是日志库(如 logback 和 Log4j),也可以是网络相关库(如 NettyNIO)。一些库(如 Joda-Time)已成为事实上的标准。

常见错误 #2:在 switch-case 块中缺少 ‘break’ 关键字

这些 Java 问题可能非常令人尴尬,有时直到在生产环境中运行后才会被发现。switch 语句中的透传行为通常很有用;但是,如果不希望出现此类行为,则缺少 “break” 关键字可能会导致灾难性的结果。如果你忘记在下面的代码示例中的 “case 0” 中输入一个 “break”,程序将写 “Zero” 后跟 “One”,因为这里的控制流将遍历整个 “switch” 语句,直到它到达 “break”。例如:

public static void switchCasePrimer() {int caseIndex = 0;switch (caseIndex) {case 0:System.out.println("Zero");case 1:System.out.println("One");break;case 2:System.out.println("Two");break;default:System.out.println("Default");}
}

在大多数情况下,更简洁的解决方案是使用多态性并将具有特定行为的代码移动到单独的类中。可以使用静态代码分析器(例如 FindBugs 和 PMD)来检测诸如此类的 Java 错误。

常见错误 #3:忘记释放资源

每次程序打开文件或网络连接时,Java 初学者在使用完资源后释放资源非常重要。如果在对此类资源执行操作期间引发任何异常,则应采取类似的谨慎措施。有人可能会争辩说 FileInputStream 有一个终结器,它在垃圾回收事件上调用 close() 方法;但是,由于我们无法确定垃圾回收周期何时开始,因此 Importing 流可能会无限期地消耗计算机资源。事实上,Java 7 中专门针对这种情况引入了一个非常有用且简洁的语句,称为 try-with-resources:

private static void printFileJava7() throws IOException {try(FileInputStream input = new FileInputStream("file.txt")) {int data = input.read();while(data != -1){System.out.print((char) data);data = input.read();}}
}

常见错误 #4:内存泄漏

此类内存泄漏背后的另一个潜在原因是一组对象相互引用,导致循环依赖关系,因此垃圾回收器无法决定是否需要这些具有交叉依赖关系引用的对象。另一个问题是使用 JNI 时非堆内存中的泄漏。

可能如下所示:

final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
final Deque<BigDecimal> numbers = new LinkedBlockingDeque<>();
final BigDecimal divisor = new BigDecimal(51);scheduledExecutorService.scheduleAtFixedRate(() -> {BigDecimal number = numbers.peekLast();if (number != null && number.remainder(divisor).byteValue() == 0) {System.out.println("Number: " + number);System.out.println("Deque size: " + numbers.size());}
}, 10, 10, TimeUnit.MILLISECONDS);scheduledExecutorService.scheduleAtFixedRate(() -> {numbers.add(new BigDecimal(System.currentTimeMillis()));}, 10, 10, TimeUnit.MILLISECONDS);try {scheduledExecutorService.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {e.printStackTrace();
}

此示例创建两个计划任务。第一个任务从名为 “numbers” 的 deque 中获取最后一个数字,并打印数字和 deque 大小,以防数字能被 51 整除。第二个任务将数字放入 deque 中。这两个任务都以固定速率计划,每 10 毫秒运行一次。如果执行了代码,您将看到 deque 的大小永久增加。这最终将导致 deque 被占用所有可用堆内存的对象填充。为了防止这种情况同时保留此程序的语义,我们可以使用不同的方法从 deque 中获取数字:“pollLast”。与方法 “peekLast” 相反,“pollLast” 返回元素并将其从双端队列中删除,而 “peekLast” 仅返回最后一个元素。

要了解有关 Java 中内存泄漏的更多信息,请参阅我们揭开此问题神秘面纱的文章。

常见错误 #5:过度的垃圾分配

当程序创建大量生存期较短的对象时,可能会发生过多的垃圾分配。垃圾回收器持续工作,从内存中删除不需要的对象,这会对应用程序的性能产生负面影响。一个简单的例子:

String oneMillionHello = "";
for (int i = 0; i < 1000000; i++) {oneMillionHello = oneMillionHello + "Hello!";
}
System.out.println(oneMillionHello.substring(0, 6));

在 Java 开发中,字符串是不可变的。因此,在每次迭代时,都会创建一个新字符串。为了解决这个问题,我们应该使用一个可变的 StringBuilder:

StringBuilder oneMillionHelloSB = new StringBuilder();for (int i = 0; i < 1000000; i++) {oneMillionHelloSB.append("Hello!");}
System.out.println(oneMillionHelloSB.toString().substring(0, 6));

虽然第一个版本需要相当长的时间来执行,但使用 StringBuilder 的版本在明显更少的时间内生成结果。

常见错误 #6:无需使用 Null 引用

避免过度使用 null 是一种很好的做法。例如,最好从方法返回空数组或集合,而不是 null,因为它可以帮助防止 NullPointerException

List<String> accountIds = person.getAccountIds();
for (String accountId : accountIds) {processAccount(accountId);
}

如果 getAccountIds() 在某人没有帐户时返回 null,则将引发 NullPointerException。要解决此问题,需要进行 null 检查。但是,如果返回的不是 null,而是空列表,则 NullPointerException 不再是问题。此外,代码更简洁,因为我们不需要对变量 accountId 进行 null 检查。

为了处理想要避免 null 的其他情况,可以使用不同的策略。这些策略之一是使用 Optional 类型,它可以是空对象或某个值的包装:

Optional<String> optionalString = Optional.ofNullable(nullableString);
if(optionalString.isPresent()) {System.out.println(optionalString.get());
}

事实上,Java 8 提供了一个更简洁的解决方案:

Optional<String> optionalString = Optional.ofNullable(nullableString);
optionalString.ifPresent(System.out::println);

常见错误 #7:忽略异常

处理异常通常很诱人。但是,对于初学者和经验丰富的 Java 开发人员来说,最佳实践是处理它们。异常是故意引发的,因此在大多数情况下,我们需要解决导致这些异常的问题。不要忽视这些事件。如有必要,您可以重新引发它,向用户显示错误对话框,或向日志添加消息。至少,应该解释为什么没有处理异常,以便让其他开发人员知道原因。

selfie = person.shootASelfie();
try {selfie.show();
} catch (NullPointerException e) {// Maybe, invisible man. Who cares, anyway?
}

突出显示异常无关紧要的更清晰方法是将此消息编码到异常的变量名称中,如下所示:

try { selfie.delete(); } catch (NullPointerException unimportant) {  }

常见错误 #8:并发修改异常

当使用迭代器对象提供的方法以外的方法迭代集合时修改集合时,会发生此异常。例如,我们有一个帽子列表,我们想删除所有有耳罩的帽子:

List<IHat> hats = new ArrayList<>();
hats.add(new Ushanka()); // that one has ear flaps
hats.add(new Fedora());
hats.add(new Sombrero());
for (IHat hat : hats) {if (hat.hasEarFlaps()) {hats.remove(hat);}
}

如果我们运行此代码,将引发 “ConcurrentModificationException”,因为代码在迭代集合时修改了集合。如果处理同一列表的多个线程之一正在尝试修改集合,而其他线程正在迭代该集合,则可能会发生相同的异常。在多个线程中并发修改集合是很自然的事情,但应该使用并发编程工具箱中的常用工具来处理,例如同步锁、为并发修改采用的特殊集合等。在单线程和多线程情况下解决此 Java 问题的方式存在细微差别。下面简要讨论了在单线程方案中可以处理此问题的一些方法:

收集对象并在另一个循环中删除它们
List<IHat> hatsToRemove = new LinkedList<>();
for (IHat hat : hats) {if (hat.hasEarFlaps()) {hatsToRemove.add(hat);}
}
for (IHat hat : hatsToRemove) {hats.remove(hat);
}
使用 Iterator.remove 方法

此方法更简洁,并且不需要创建其他集合:

Iterator<IHat> hatIterator = hats.iterator();
while (hatIterator.hasNext()) {IHat hat = hatIterator.next();if (hat.hasEarFlaps()) {hatIterator.remove();}
}
使用 ListIterator 的方法

当修改后的集合实现 List 接口时,使用 list 迭代器是合适的。实现 ListIterator 接口的迭代器不仅支持 remove 操作,还支持 add 和 set 操作。ListIterator 实现了 Iterator 接口,因此该示例看起来与 Iterator remove 方法几乎相同。唯一的区别是 hat 迭代器的类型,以及我们使用 “listIterator()” 方法获取该迭代器的方式。下面的代码段显示了如何使用 “ListIterator.remove” 和 “ListIterator.add” 方法将每顶帽子替换为带有宽边帽的耳罩:

IHat sombrero = new Sombrero();
ListIterator<IHat> hatIterator = hats.listIterator();
while (hatIterator.hasNext()) {IHat hat = hatIterator.next();if (hat.hasEarFlaps()) {hatIterator.remove();hatIterator.add(sombrero);}
}

使用 ListIterator,remove 和 add 方法调用可以替换为对 set:

IHat sombrero = new Sombrero();
ListIterator<IHat> hatIterator = hats.listIterator();
while (hatIterator.hasNext()) {IHat hat = hatIterator.next();if (hat.hasEarFlaps()) {hatIterator.set(sombrero); // set instead of remove and add}
}

使用 Java 8 中引入的流方法在 Java 8 中,程序员能够将集合转换为流,并根据某些条件筛选该流。下面是一个 stream api 如何帮助我们过滤 hat 并避免 “ConcurrentModificationException” 的示例。

hats = hats.stream().filter((hat -> !hat.hasEarFlaps())).collect(Collectors.toCollection(ArrayList::new));

“Collectors.toCollection” 方法将创建一个包含过滤后帽子的新 ArrayList。如果大量项目要满足过滤条件,从而导致 ArrayList 较大,则这可能是一个问题;因此,应谨慎使用。使用 Java 8 中提供的 List.removeIf 方法Java 8 中提供的另一种解决方案,显然也是最简洁的,是使用“removeIf”方法:

hats.removeIf(IHat::hasEarFlaps);

就是这样。在后台,它使用 “Iterator.remove” 来完成该行为。

使用专用集合

还有其他针对不同情况进行调整的集合,例如 “CopyOnWriteSet” 和 “ConcurrentHashMap”。

并发集合修改的另一个可能错误是从集合创建流,并在流迭代期间修改后备集合。流的一般规则是避免在流查询期间修改基础集合。以下示例将显示处理流的错误方式:

List<IHat> filteredHats = hats.stream().peek(hat -> {if (hat.hasEarFlaps()) {hats.remove(hat);}
}).collect(Collectors.toCollection(ArrayList::new));

常见错误 #9:破坏契约

有时,由标准库或第三方供应商提供的代码依赖于应遵守的规则才能使工作正常。例如,它可以是 hashCode 和 equals contract,当遵循它们时,可以保证 Java 集合框架中的一组集合以及使用 hashCode 和 equals 方法的其他类的工作。不遵守 Contract 并不是那种总是导致异常或破坏代码编译的错误;这更棘手,因为有时它会在没有任何危险迹象的情况下更改应用程序行为。错误代码可能会滑入生产版本并导致一大堆不需要的效果。这可能包括不良的 UI 行为、错误的数据报告、应用程序性能不佳、数据丢失等。幸运的是,这些灾难性的错误并不经常发生。我已经提到了 hashCode 和 equals contract。它用于依赖于哈希和比较对象的集合,如 HashMap 和 HashSet。简单地说,该合约包含两个规则:

  • 如果两个对象相等,则它们的哈希码应该相等。
  • 如果两个对象具有相同的哈希代码,则它们可能相等,也可能不相等。

违反合约的第一条规则会导致在尝试从 hashmap 中检索对象时出现问题。第二条规则表示具有相同哈希代码的对象不一定相等。让我们来看看打破第一条规则的影响:

public static class Boat {private String name;Boat(String name) {this.name = name;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Boat boat = (Boat) o;return !(name != null ? !name.equals(boat.name) : boat.name != null);}@Overridepublic int hashCode() {return (int) (Math.random() * 5000);}
}

如您所见,类 Boat 覆盖了 equals 和 hashCode 方法。但是,它破坏了协定,因为 hashCode 每次调用时都会返回同一对象的随机值。以下代码很可能在哈希集中找不到名为 “Enterprise” 的船,尽管我们之前添加了这种船:

public static void main(String[] args) {Set<Boat> boats = new HashSet<>();boats.add(new Boat("Enterprise"));System.out.printf("We have a boat named 'Enterprise' : %b\n", boats.contains(new Boat("Enterprise")));
}

Contract 的另一个示例涉及 finalize 方法。以下是官方 java 文档中描述其功能的引述:

finalize 的一般约定是,当 JavaTM 虚拟机已确定任何线程(尚未终止)都无法再访问此对象时,将调用它,除非是由于某个其他对象或类的 finalization 所执行的操作已准备好终止。finalize 方法可以执行任何操作,包括使此对象再次可供其他线程使用;但是,FINALIZE 的通常目的是在对象被不可撤销地丢弃之前执行清理操作。例如,表示 input/output 连接的对象的 finalize 方法可能会执行显式 I/O 事务,以便在永久丢弃对象之前中断连接。

常见错误 #10:使用 Raw 类型而不是参数化类型

根据 Java 规范,原始类型是未参数化的类型,或者是未从 R 的超类或超接口继承的类 R 的非静态成员。在 Java 中引入泛型之前,没有原始类型的替代品。它从 1.5 版本开始支持泛型编程,泛型无疑是一个重大的改进。但是,由于向后兼容性的原因,留下了一个可能会破坏类型系统的陷阱。让我们看一下以下示例:

List listOfNumbers = new ArrayList();
listOfNumbers.add(10);
listOfNumbers.add("Twenty");
listOfNumbers.forEach(n -> System.out.println((int) n * 2));

这里我们有一个定义为原始 ArrayList 的数字列表。由于它的 type 没有用 type 参数指定,我们可以向其中添加任何对象。但在最后一行中,我们将元素转换为 int,将其加倍,并将加倍的数字打印到标准输出。此代码将编译而不会出错,但一旦运行,它将引发运行时异常,因为我们尝试将字符串强制转换为整数。显然,如果我们向类型系统隐藏必要的信息,它就无法帮助我们编写安全的代码。要解决这个问题,我们需要指定要存储在集合中的对象类型:

List<Integer> listOfNumbers = new ArrayList<>();listOfNumbers.add(10);
listOfNumbers.add("Twenty");listOfNumbers.forEach(n -> System.out.println((int) n * 2));

与原始版本的唯一区别是定义集合的行:

List<Integer> listOfNumbers = new ArrayList<>();

修复的代码无法编译,因为我们尝试将字符串添加到预期仅存储整数的集合中。编译器将显示一个错误,并指向我们尝试将字符串 “Twenty” 添加到列表中的行。参数化泛型类型总是一个好主意。这样,编译器就能够进行所有可能的类型检查,并将类型系统不一致导致运行时异常的可能性降至最低。

总结

Java 作为平台简化了软件开发中的许多事情,它依赖于复杂的 JVM 和语言本身。但是,它的功能,例如删除手动内存管理或体面的 OOP 工具,并不能消除普通 Java 开发人员面临的所有问题。与往常一样,知识、实践和此类 Java 教程是避免和解决应用程序错误的最佳方法 - 因此,了解您的库、请阅读 Java、阅读 JVM 文档和编写程序尤为重要。也不要忘记静态代码分析器,因为它们可以指向实际的 bug 并突出显示潜在的 bug。

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

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

相关文章

CVE-2022-26965靶机渗透

​ 开启环境 ​ ​ 进入环境 ​ ​ 使用弱口令admin登录 ​ ​ 利用cms主题构造木马 ​ 需要将主题中的info.php文件修改&#xff0c;再打包成zip再上传&#xff0c;通过网络搜索找到Github中的Pluck CMS&#xff0c;进入后随便下载任一主题 https://github.com/sear…

服务性能优化之mybatis-plus 开启与关闭 SQL 日志打印

Hello&#xff01;欢迎各位新老朋友来看小弟博客&#xff0c;祝大家事业顺利&#xff0c;财源广进&#xff01;&#xff01; 主题&#xff1a;mybatis-plus 开启与关闭 SQL 日志打印 第一&#xff1a;开启打印 Mybatis-plus 需要通过下面的方式开启控制台 SQL 日志打印 myba…

和鲸科技创始人范向伟:拐点即将来临,AI产业当前的三个瓶颈

在科技迅猛发展的时代&#xff0c;人工智能&#xff08;AI&#xff09;无疑已经成为引领新一轮产业革命的核心动力之一。全球企业纷纷拥抱AI技术&#xff0c;试图借助其变革力量在竞争中突围&#xff0c;然而业界对AI产业化的拐点何时来临却众说纷纭。毕竟AI技术从实验室到商业…

4K变倍镜头特点

1、高分辨率成像&#xff1a; ① 能够呈现清晰、细腻的图像&#xff0c;可清晰快速地识别出被测物体的微小细节、特征以及潜在的缺陷等。例如在芯片外观瑕疵检测中&#xff0c;能清晰地分辨出芯片上的刮痕、污渍、破损、引脚缺失等问题。 ② 相比传统的变倍镜头&#xff0c;在…

LabVIEW提高开发效率技巧----队列使用

在LabVIEW开发中&#xff0c;队列是实现并行处理、数据传递和任务调度的关键机制之一&#xff0c;合理使用队列可以有效提高程序性能并避免内存问题。结合队列长度限制和其他队列相关技巧&#xff0c;以下是队列使用的详细说明&#xff1a; 1. 队列长度限制 限制队列的长度可以…

全面讲解C++

数据类型 1.1 基本数据类型 1.1.1 整型&#xff08;Integer Types&#xff09; 整型用于表示整数值&#xff0c;分为以下几种类型&#xff1a; int&#xff1a;标准整数类型&#xff0c;通常为4字节&#xff08;32位&#xff09;。short&#xff1a;短整型&#xff0c;通常…

量子数字签名概述

我们都知道&#xff0c;基于量子力学原理研究密钥生成和使用的学科称为量子密码学。其内容包括了量子密钥分发、量子秘密共享、量子指纹识别、量子比特承诺、量子货币、秘密通信扩展量子密钥、量子安全计算、量子数字签名、量子隐性传态等。虽然各种技术发展的状态不同&#xf…

FreeRTOS学习总结

背景&#xff1a;在裸机开发上&#xff0c;有时候我们需要等待某个信号或者需要延迟时&#xff0c;CPU的运算是白白浪费掉了的&#xff0c;CPU的利用率并不高&#xff0c;我们希望当一个函数在等待的时候&#xff0c;可以去执行其他内容&#xff0c;提高CPU的效率&#xff0c;同…

windows修改文件最后修改时间

一、需要修改日期的文件 背景:有时候我们需要做一些文件定期删除的操作,但是测试时候并不一定有符合测试的文件,这时候就需要可以方便的修改文件的最后修改时间。 系统环境:windows 测试文件:如上 修改时间方式:windows 脚本。 二、测试脚本 (1) 脚本 # 指定文件路径 …

自然语言处理:第五十三章 Ollama

代码&#xff1a; ollama/ollama: Get up and running with Llama 3.1, Mistral, Gemma 2, and other large language models. (github.com) 官网&#xff1a; Ollama 写在前面: 笔者更新不易&#xff0c;希望走过路过点个关注和赞&#xff0c;笔芯!!! 写在前面: 笔者更新不易…

Android Framework默认授予app通知使用权限

安卓通知使用权限 在安卓系统中&#xff0c;应用程序需要获取通知使用权限才能向用户发送通知。以下是关于安卓通知使用权限的一些信息&#xff1a; 权限获取方式 当用户安装应用时&#xff0c;系统可能会在安装过程中提示用户授予应用通知权限。用户可以选择允许或拒绝。 应…

架构设计笔记-18-安全架构设计理论与实践

知识要点 常见的安全威胁&#xff1a; 信息泄露&#xff1a;信息被泄露或透露给某个非授权的实体。破坏信息的完整性&#xff1a;数据被非授权地进行增删、修改或破坏而受到损失。拒绝服务&#xff1a;对信息或其他资源的合法访问被无条件地阻止。攻击者向服务器发送大量垃圾…

OCM认证考试须知:掌握这些关键点,轻松应对考试

在Oracle认证体系中&#xff0c;OCM&#xff08;OracleCertifiedMaster&#xff09;是最高级别的认证。它代表了在Oracle数据库技术领域的顶尖水平。 OCM认证不仅要求你具备深厚的理论知识&#xff0c;还要求你能够解决复杂的数据库问题&#xff0c;并具备高级的项目管理能力。…

数据结构期中代码注意事项(二叉树及之前)1-11

注意&#xff1a;链表为空。是否越界访问。每写一步都要思考该步是否会有越界&#xff08;多/少&#xff09;等问题。这一步是否有不能走的条件&#xff08;删除的时候不为空&#xff09;。只有该节点开辟了空间&#xff0c;该节点才能被指向。能用c就用c。#include <iostre…

TensorRT-LLM七日谈 Day4

在Day2 中&#xff0c;我们梳理了trt-llm对于TinyLLama的调用&#xff0c;在Day3,我们也熟悉了一下Trt-llm常规的三步流程。 这里其实有个问题&#xff0c;在针对tiny-llama的部署中&#xff0c;其实没有显式的进行模型转换&#xff0c;那麽其推理接口中到底包含了什么&#x…

46 C 语言文件的打开与关闭、写入与读取函数:fopen、fclose、fputc、fputs、fprintf、fgetc、fgets、fscanf

目录 1 文件的存储形式 2 打开文件——fopen() 函数 2.1 功能描述 2.2 函数原型 2.3 文件打开方式&#xff08;模式&#xff09; 3 关闭文件——fclose() 函数 3.1 功能描述 3.2 函数原型 4 常见的文件写入方式 4.1 fputc() 函数 4.1.1 功能描述 4.1.2 函数原型 4…

windows自动化(一)---windows关闭熄屏和屏保

电脑设置关闭屏幕和休眠时间不起作用解决方案 一共三个方面注意&#xff1a; 一、关闭屏保设置&#xff1a; 二、电源管理设置 三、关闭盖子不做操作&#xff1a; 第一点很重要&#xff0c;就算二三都做了&#xff0c;一没做&#xff0c;照样不行。

一篇python的pandas数据分析,分组与聚合使用!

在数据分析中,数据分组与聚合是常用的操作,能够帮助我们从大量数据中提取出有用的信息.我们讨论了描述性统计,了解了如何通过均值、方差等统计量概述数据的特征.而在本篇中,我们将学习如何对数据进行分组和聚合,以便进行更深入的分析.最后,我们将在后续的章节中使用这些分析结果…

PHP政务招商系统——高效连接共筑发展蓝图

政务招商系统——高效连接&#xff0c;共筑发展蓝图 &#x1f3db;️ 一、政务招商系统&#xff1a;开启智慧招商新篇章 在当今经济全球化的背景下&#xff0c;政务招商成为了推动地方经济发展的重要引擎。而政务招商系统的出现&#xff0c;更是为这一进程注入了新的活力。它…

ES(Elasticsearch)SSL集群部署

8.x后ES不在需要自行准备JDK环境&#xff0c;部署的服务包含ES、Kibana、Logstash&#xff0c;使用二进制方式部署&#xff0c;为了提高安全性&#xff0c;加密logstash、kibana及其他客户端到ES间的通信。 1、准备工作 1.1、 es无法使用root用户启动 useradd -m -s /bin/bas…