哈喽,大家好,我是了不起。
最近看到一个知名开源项目官宣停更,还是挺震惊的。
Java 's Blog
哈喽,大家好,我是了不起。
爬虫,也被称为网络爬虫或网络蜘蛛,是一种自动化的网络机器人,其主要功能是按照一定的规则,自动浏览互联网并从网页中提取信息。
作为一个开发人员,相信大家都尝试过写一些爬虫,合理的利用一些爬虫工具,对于我们一些还是挺有帮助的。
比如自动化测试,使用爬虫技术对网站进行自动化测试,检查链接是否有效,确保网站内容的正确显示。
今天就给大家介绍一款开源的爬虫工具,不用写代码即可完成爬虫。
在数字化高速发展的今天,确保在线服务的可靠性和可用性变得比以往任何时候都重要。这就需要一款强大的监控工具,以确保你的服务器、网站和API始终在线。
在众多监控工具中,Uptime Kuma作为一款开源、现代化的服务监控解决方案,正逐渐成为众多开发者和系统管理员的心头好。
本文将为您详细介绍Uptime Kuma的作用、特性以及如何轻松安装并使用它来守护您的在线业务。
Uptime Kuma是一个开源监控工具,它允许你监控你的服务,并确保你是第一个知道它们何时下线的人。与传统的SaaS监控服务(如UptimeRobot)不同,Uptime Kuma提供了一个可完全控制和定制的环境。
它有以下主要特点:
Uptime Kuma提供了多种安装选项,以适应不同的用户需求。可以通过Docker容器快速安装,也可以从源代码构建。这里基本演示Docker的安装方法:
首先确保服务器已经安装了Docker: 如果你还没有Docker,首先需要安装它。根据你的操作系统,你可以从Docker官网下载合适的安装包。
1 |
|
1 |
|
上面的命令会创建一个新的Uptime Kuma容器,名为uptime-kuma
,并且将容器的3001端口映射到主机的3001端口。
http://localhost:
,你将看到Uptime Kuma的登录页面,按照初次启动向导完成安装过程。1 |
|
编写docker-compose.yaml
文件,用docker compose up -d
启动即可。
初始化和配置和上述一致。
进一步的配置和定制教程可以在Uptime Kuma的GitHub文档中找到,包括如何添加监控项、配置通知方式等。
Uptime Kuma是一个功能强大且易于使用的监控工具,它是为希望完全控制监控过程的团队和个人设计的。
它的自托管特性意味着没有外部依赖,你数据的隐私性和安全性得到了保证。
除此之外,它开源且完全免费,适合希望有成本效益解决方案的企业和开发者。如果你正在寻找一个灵活而且强大的监控工具,Uptime Kuma无疑是一个值得考虑的选择。
Java 8 引入了强大的 Stream API,为处理集合数据提供了简洁、高效的解决方案。
其中,parallel()
方法为流处理引入了并行化能力,允许开发者充分利用多核处理器的优势,大幅提升大规模数据集的处理效率。
本篇文章将带你开启并行流处理之旅,认识 Java 8 Stream API 中的 parallel()
。
parallel()
是 Java 8 Stream API 中的一个方法,用于将一个顺序流转换为并行流。并行流是一种可以同时在多个线程上执行操作的流,它将流的元素分割成多个子集,每个子集在不同的线程上独立处理,最后将结果合并。使用 parallel()
方法可以轻松开启并行流处理模式,无需显式管理线程和同步。
1 |
|
在这个示例中,parallel()
方法将顺序流转换为并行流,后续的 filter()
、map()
和 forEach()
操作将在多个线程上并行执行,从而加速数据处理。
并行流处理背后的核心机制主要包括以下几个方面:
并行流根据数据集的大小、处理器核心数等因素动态调整并行度和任务划分策略。对于小规模数据集或不适合并行化的操作,Java 8 会自动退化为顺序流处理,避免不必要的线程开销。
总之,parallel()
方法通过将原始列表拆分成多个子任务,并在独立线程上并行执行流操作链的各个阶段,最后合并处理结果,实现了对列表数据的高效并行处理。
具体的拆分策略和并行执行细节由 JVM 自动管理,开发者无需关心底层实现,只需关注流式编程的高层抽象。
适合parallel()
并行流的应用场景有:
filter()
、map()
、flatMap()
、sorted()
等。场景:在一个数据分析项目中,需要对一个包含百万条记录的数据集进行复杂过滤和计算。使用并行流可以显著加快处理速度,充分利用多核处理器资源。 示例
1 |
|
场景: 假设有一个电商系统需要批量更新大量商品的价格,每个商品的更新过程涉及网络请求到不同服务获取最新价格信息,然后保存到数据库。
示例:
1 |
|
在这个示例中:
parallel()
流操作,使得后续的 map()
操作能并行执行。CompletableFuture
,通过 supplyAsync()
异步调用 PriceService
获取最新价格。thenAcceptAsync()
异步操作。在获取到最新价格之后更新数据库。CompletableFuture.allOf()
等待所有数据库更新操作完成。Java 8 Stream API 中的 parallel()
方法为处理集合数据提供了便捷的并行化途径。
在复杂的异步处理场景中,可以结合 CompletableFuture
与并行流,进一步提升程序的并发性和响应能力。
通过合理使用并行流,开发者可以显著提升大规模数据集处理的性能,充分发挥现代多核处理器的潜力。
然而,使用并行流时也应注意避免数据依赖、状态共享等问题,适时进行性能评估与调整。
哈喽,大家好,MySQL作为广泛使用的开源关系型数据库管理系统,应该没有Java开发没使用过吧。
关于MySQL,我们大部分时间都在聊,如何提高查询效率,今天我们来聊聊如何提高MySQL的插入效率。
一般情况下,数据库是运行在专门的服务器上,提高插入效率最明显的当然是提高服务器配置啦。 比如,使用高性能的CPU和SSD磁盘,使用分布式系统架构,将写入压力分散到多个节点。 这个方式的成本也是最高的,老板们当然不会使用这种方式了。
我们还可以从其他方面入手:
MyISAM
存储引擎,因为其简单的表锁机制和无事务开销而在插入速度上表现更优。考虑到实际的应用场景,我们最可能操作的就是使用第3种实现方式,通过批量插入的方式来提高效率。
常用的批量插入的方式有2种:
insert into xxx (...) values (...),(...),(...)
接下来我们来测试一下这几个方法。
测试的SQL
1 |
|
一、使用 batchXml
1 |
|
二、使用mybatis-flex提供的saveBatch
1 |
|
三、手动控制事务的提交,saveBatchSession
1 |
|
启动代码
1 |
|
使用了3种方式进行测试
未开启批处理,batchXml
未开启批处理,mybatis-flex提供的saveBatch
未开启批处理,saveBatchSession
从这里的结果可以看出,使用 batchXml
的效率是最高的,远远超越其他方式。
但是仔细一想,这些数据应该很不正常,插入1000条数据,竟然需要4秒左右,和单条插入1000次的时间几乎没有区别。
经过一番查询资料,并检查配置,发现果然另有玄机,连接数据库的时候没有开启批处理
开启方式:在spring的配置文件中,连接数据源时,url需要增加 allowPublicKeyRetrieval=true
然后重新测试一遍。
开启批处理,saveBatchXml
开启批处理,mybatis-flex提供的saveBatch
开启批处理,saveBatchSession
这次的结果就比较正常了,可以看出来:
saveBatchSession
最快mybatis-flex提供的saveBatch
因为有些额外的操作,多消耗了10ms左右的时间saveBatchXml
相较于另外两种方式,慢了30ms~40ms。接下来,把每批次的处理数据由1000次增加到10000次,再次进行测试。
开启批处理,saveBatchXml,10000条一批次
开启批处理,saveBatchSession,10000条一批次
开启批处理,mybatis-flex提供的saveBatch,10000条一批次
由此结果可以看出来:
saveBatchSession
和mybatis-flex提供的saveBatch
耗时基本一致saveBatchXml
就明显的慢一些,按照效率差算,差了将近50%的效率
\(\text { 效率差 }=\frac{\text { 较长时间 }- \text { 较短时间 }}{\text { 较短时间 }}=\frac{1200 \mathrm{~ms}-800 \mathrm{~ms}}{800 \mathrm{~ms}}\)综上,提高MySQL插入效率主要可通过调整数据库配置、选择适合的存储引擎以及运用批量插入策略等方式实现。 在实际应用中,尤其是在使用ORM框架进行数据操作时,应合理选择并充分利用批量插入功能,以最大程度提升插入效率。
allowPublicKeyRetrieval=true
saveBatch
即可。在现代软件开发中,异步编程已成为提升系统性能、响应能力和可扩展性的关键手段。Java 8 引入了 CompletableFuture
类,为 Java 平台带来了强大的异步编程能力。
本篇文章将带你认识这个异步编程神器:CompletableFuture
。
CompletableFuture
是 Java 8 引入的 java.util.concurrent
包下的一个类,它代表一个异步计算的结果,可以是已完成、正在进行或尚未开始。
CompletableFuture
提供了一种灵活、类型安全的方式来表达异步操作的生命周期,包括创建、组合、处理结果以及处理异常。其设计灵感来源于函数式编程中的 Promises/Futures 模式,旨在简化异步编程模型,提高代码的可读性和可维护性。
completedFuture(T value)
completedFuture(T value)
是一个静态工厂方法,用于创建一个已经处于完成状态且包含给定结果值的 CompletableFuture
。这适用于预先计算好的结果或常量值,使得其他组件可以以异步形式消费。
supplyAsync(Supplier<U> supplier, Executor executor)
supplyAsync()
方法接受一个 Supplier
函数和一个可选的 Executor
,异步执行 supplier.get()
,并将结果封装到一个新的 CompletableFuture
中。计算在 Executor
管理的线程中进行,不阻塞当前线程。
runAsync(Runnable runnable, Executor executor)
类似于 supplyAsync()
,runAsync()
接受一个 Runnable
任务和一个 Executor
,异步执行任务。由于 Runnable
没有返回值,runAsync()
返回的 CompletableFuture
完成时没有结果。
thenApply(Function<? super T,? extends U> fn)
在当前 CompletableFuture
完成后,应用给定的 Function
处理结果,并返回一个新的 CompletableFuture
,其结果为 Function
应用后的值。
thenAccept(Consumer<? super T> action)
当当前 CompletableFuture
完成后,执行给定的 Consumer
消费结果。由于 Consumer
没有返回值,返回的 CompletableFuture
完成时没有结果。
thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)
当当前 CompletableFuture
与另一个 CompletionStage
(如另一个 CompletableFuture
)都完成时,应用给定的 BiFunction
合并两个结果,并返回一个新的 CompletableFuture
。
exceptionally(Function<Throwable,? extends T> fn)
当当前 CompletableFuture
因异常而未能正常完成时,应用给定的 Function
处理异常,并返回一个新的 CompletableFuture
,其结果为 Function
应用后的值。
handle(BiFunction<? super T, Throwable, ? extends U> fn)
无论当前 CompletableFuture
正常完成还是因异常未能完成,都会应用给定的 BiFunction
处理结果或异常,并返回一个新的 CompletableFuture
。
allOf(CompletableFuture<?>... cfs)
创建一个新的 CompletableFuture
,当所有给定的 CompletableFuture
都完成(不论成功与否)时,新 CompletableFuture
完成。
anyOf(CompletableFuture<?>... cfs)
创建一个新的 CompletableFuture
,当任意一个给定的 CompletableFuture
完成时,新 CompletableFuture
完成。
CompletableFuture
的使用场景很广泛,例如
这里以第一个场景举例:
场景:在一个订单处理系统中,需要查询订单的详细信息、关联的商品信息以及用户的个人信息。为减少查询延迟,可以使用 CompletableFuture
对每个查询进行异步执行,并在所有查询完成后合并结果。
示例:
如果我们不使用Java8提供的这个CompletableFuture
来实现
1 |
|
使用CompletableFuture
实现
1 |
|
在这个示例中:
CompletableFuture
之后,代码量减少了,整洁度和可读性也得到提高。fetchOrderDetails
方法接受一个订单 ID,使用 CompletableFuture.supplyAsync()
异步查询订单、商品和用户信息。CompletableFuture.allOf()
监控所有查询的完成状态。thenApplyAsync()
合并结果,创建一个包含完整订单详情的 OrderDetails
对象。CompletableFuture
作为 Java 8 引入的重要异步编程工具,极大地提升了 Java 平台在应对高并发、高性能场景的能力。
结合 Java 8 的并行流(Stream.parallel()
)与 CompletableFuture
,可以轻松实现数据集的并行处理和结果聚合。
下次给大家聊聊Stream.parallel()
。
哈喽,大家好,我是了不起。
当你遇到PPT的时候,你的内心是什么样子的。
如果需要你写PPT了,那么有几个时间点,你要进行述职汇报,大概率是升职了。或者作为程序员,你的代码之路逐渐走向尾声,以后更多的是需要进行汇报工作,此时你需要领导他人,然后再也没有时间写代码了。关于PPT,每一个程序员都应该重视,毕竟这是你保留饭碗的一项硬技能!
哈喽,大家好,我是了不起。
相信大家在面试Java开发的时候,会遇到比较多的问题是Java的内存管理,这里面涉及到Java垃圾回收机制,以及JVM调优等等,那么今天跟大家讨论一个问题:Java对象不再使用时,为什么要赋值为 null ?
在Java编程中,日期和时间处理是一个常见但也是复杂的任务。尽管Java提供了内置的Date类来处理日期和时间,但在实际开发中,我们常常遇到一些问题和挑战,因此不建议过度依赖Date类。本文将深入探讨这些问题,并提供一些替代方案,以帮助开发人员更好地处理日期和时间。
Date类是可变的,这意味着它的状态可以在对象创建后被修改。因此,如果多个线程同时访问和修改同一个Date对象,可能会导致竞态条件或其他并发问题。例如,考虑以下代码片段:
1 |
|
在这个例子中,如果线程1正在格式化日期时,线程2修改了当前日期对象的时间,那么最终的格式化结果可能会与预期不符。
Date类虽然可以表示一个特定的日期和时间,但它不提供直接的方法来格式化日期。通常情况下,我们需要使用SimpleDateFormat类来格式化Date对象,但这也会带来一些问题。例如,考虑以下代码:
1 |
|
这段代码看起来很简单,但是如果在多线程环境中使用同一个SimpleDateFormat实例,会导致线程安全问题。此外,SimpleDateFormat的模式字符串不易于记忆和理解,容易出错。
Date类及其相关的API在Java 8中被认为是过时的。取而代之的是java.time包中的新日期和时间API。这些新API提供了更丰富的功能,更好的类型安全性和不可变性,以及更好的设计来应对一些常见的日期和时间问题。例如,考虑使用新API创建一个表示当前日期的示例:
1 |
|
这比使用Date类简单得多,并且避免了Date类的一些问题。
Date类在处理时区时可能会出现问题。它默认使用系统的时区,这可能会导致在跨时区应用程序中产生错误的日期和时间计算。例如,考虑以下代码:
1 |
|
这段代码在不同的时区中可能会产生不同的输出,这取决于系统的默认时区设置。
如果你是使用的jdk8以及之后的版本,建议使用java.time包: 使用Java 8引入的新日期和时间API,如LocalDate、LocalTime、LocalDateTime等。这些类是不可变的、线程安全的,而且提供了更丰富的功能,更适合现代Java应用程序的需求。
如果你是使用的jdk7以及之前的版本,建议使用第三方库来操作时间: 如Joda-Time。Joda-Time提供了类似于java.time的功能,并且在Java 8之前广受欢迎。
Java 8引入了全新的日期和时间API,位于java.time包中,用于更方便、更安全地处理日期和时间。这个API提供了许多新的类和方法,以及更丰富的功能,旨在解决以前Date类所存在的问题。下面详细介绍Java 8的时间API,并举一些使用示例:
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
通过使用这些新的类和方法,Java 8的时间API使得处理日期和时间变得更加简单、直观和安全。它提供了更丰富的功能和更好的设计,可以更好地满足现代Java应用程序的需求,并避免了以前Date类所存在的问题。
hello,今天带大家了解一下这个熟悉又陌生的关键字:volatile
。
在Java多线程编程中,保证线程安全性是至关重要的。而volatile关键字是实现线程安全性的一种关键机制。
为什么熟悉又陌生呢?Java开发者几乎全都用到过这个关键字,但是又不记得什么时候用了它。
volatile关键字主要用于保证变量在多线程环境下的可见性和禁止指令重排序。
当一个变量被volatile修饰时,线程在读取这个变量的值时将直接从主内存中读取,而不是从线程的本地缓存中读取。
同样地,当一个线程修改了volatile变量的值时,这个变化将立即写回到主内存中,而不是仅仅保存在线程的本地缓存中。
1 |
|
在这个示例中,我们有两个线程,一个线程调用startTask()方法来修改flag的值为true,另一个线程调用monitorTask()方法来检查flag的值是否为true。在flag没有被volatile修饰的情况下,可能会出现monitorTask()方法陷入死循环的情况,因为它无法及时获取到flag的最新值。但是,由于flag被volatile修饰,线程可以立即看到flag的变化,因此可以正确地退出循环,从而避免了可能出现的问题。
事实上,这个简单的示例代码,在实际使用中,几乎是用不到它这种写法;那到底是怎么使用的这个volatile
呢?
其实在Java中,java.util.concurrent.atomic包提供了一组原子类,比如AtomicInteger、AtomicLong、AtomicBoolean等,它们提供了一种无锁的线程安全机制,以确保对变量的操作是原子性的。
当谈到Atomic原子类的实现原理时,CAS(Compare and Swap)操作是其中的关键。CAS是一种乐观锁技术,它涉及比较内存中的值和预期值,如果相等,则使用新值替换内存中的值。在Java中,CAS是通过Unsafe
类实现的,它是一种硬件级别的原子性操作。
但是,CAS操作本身无法解决线程可见性的问题,这就是volatile
关键字的作用。volatile
关键字可以确保变量的写操作立即可见于其他线程,从而解决了线程之间的可见性问题。因此,Atomic原子类是结合了CAS和volatile关键字来实现线程安全。
因此,结合了CAS和volatile关键字,Atomic原子类能够在无锁的情况下实现线程安全,提供了一种高效的并发编程解决方案。CAS保证了原子性,volatile保证了可见性,两者结合起来提供了一个强大的多线程环境下的并发控制机制。
日常开发中,我们一般情况下都是直接使用的Atomic原子类来保证线程安全的情况,并不会去直接使用volatile
关键字,乍一看这个volatile
还真是熟悉又陌生呢!
哈喽,大家好,我是了不起。
日常开发中或者面试的时候经常会遇到身份验证的问题,下面这些都是关于身份验证和授权的常用技术。
Session、Cookie、JWT、Token、SSO和OAuth 2.0 这些都是什么,在我们的应用程序中有什么用,我们来看看!