HowToDoInJava Java 教程(二)
JVM 内存模型/结构和组件
原文: https://howtodoinjava.com/java/garbage-collection/jvm-memory-model-structure-and-components/
每当执行 Java 程序时,都会保留一个单独的存储区,用于存储应用程序代码的各个部分,这些部分您大致称为 JVM 内存。 尽管不是必需的,但是具有一定的知识对该存储区进行构造是有益的。 当您开始进行更深层次的工作(例如性能调整)时,它变得尤为重要。 如果没有很好地了解 JVM 实际如何使用内存以及垃圾回收器如何使用该内存的不同部分,您可能会错过一些重要的注意事项,以进行更好的内存管理。 从而获得更好的性能。
在本教程中,我将讨论 JVM 内存内部的各个部分,然后您将在以后的一篇文章中讨论如何使用此信息进行应用程序的性能调整。
Table of Contents
JVM memory areas / components
- Heap area
- Method area and runtime constant pool
- JVM stack
- Native method stacks
- PC registers
JVM 内存模型/结构
Java 虚拟机定义了在程序执行期间使用的各种运行时数据区域。 其中一些数据区域是在 Java 虚拟机启动时创建的,仅在 Java 虚拟机退出时才被销毁。 其他数据区域是每个线程的。 在创建线程时创建每个线程的数据区域,并在线程退出时销毁每个数据区域。
让我们看一下运行时内存中各个部分的最基本分类。
JVM 内存区域部分
让我们根据 JVM 规范中提到的内容,快速浏览每个组件。
堆区域
堆区代表运行时数据区,从中为所有类实例和数组分配内存,并在虚拟机启动期间创建。
自动存储管理系统回收对象的堆存储。 堆可以是固定大小,也可以是动态大小(基于系统的配置),并且分配给堆区域的内存不必是连续的。
Java 虚拟机实现可以为程序员或用户提供对堆初始大小的控制,并且,如果可以动态扩展或收缩堆,则可以控制最大和最小堆大小。
如果计算需要的堆多于自动存储管理系统所能提供的堆,则Java虚拟机将抛出OutOfMemoryError
。
方法区域和运行时常量池
方法区域存储每个类的结构,例如运行时常量池;字段和方法数据; 方法和构造器的代码,包括用于类,实例和接口初始化的特殊方法。
方法区域是在虚拟机启动时创建的。 尽管从逻辑上讲它是堆的一部分,但是可以或不能将其进行垃圾收集,而我们已经读到堆中的垃圾收集不是可选的; 这是强制性的。 方法区域可以是固定大小的,或者可以根据计算的需要进行扩展,如果不需要更大的方法区域,则可以缩小。 方法区域的内存不必是连续的。
如果方法区域中的内存不可用于满足分配请求,则 Java 虚拟机将抛出OutOfMemoryError
。
JVM 栈
每个 JVM 线程都有一个与该线程同时创建的私有栈。 栈存储帧。 框架用于存储数据和部分结果,并执行动态链接,方法的返回值和调度异常。
它保存局部变量和部分结果,并在方法调用和返回中起作用。 因为除了压入和弹出帧外,从不直接操纵此栈,所以可以对帧进行堆分配。 与堆类似,此栈的内存不必是连续的。
该规范允许栈的大小可以是固定的,也可以是动态的。 如果具有固定大小,则在创建该栈时可以独立选择每个栈的大小。
如果线程中的计算所需的 Java 虚拟机栈超出允许的范围,则 Java 虚拟机将引发StackOverflowError
。如果可以动态扩展 Java 虚拟机栈,并尝试进行扩展,但是可能没有足够的内存来实现扩展,或者如果没有足够的内存可用于为新线程创建初始 Java 虚拟机栈,则 Java 虚拟机将抛出OutOfMemoryError
。
本机方法栈
本机方法栈称为 C 栈; 它支持本机方法(用 Java 编程语言以外的其他语言编写的方法),通常在创建每个线程时为每个线程分配。 无法加载本机方法并且自身不依赖于常规栈的 Java 虚拟机实现不需要提供本机方法栈。
本机方法栈的大小可以是固定的,也可以是动态的。
如果线程中的计算所需的本机方法堆栈超出允许的范围,则Java虚拟机将抛出StackOverflowError
。如果可以动态扩展本机方法堆栈并尝试进行本机方法堆栈扩展,但可用内存不足,或者如果无法提供足够的内存来为新线程创建初始本机方法堆栈,则Java虚拟机将抛出OutOfMemoryError
。
PC 寄存器
每个 JVM 线程都有其自己的程序计数器(pc)寄存器。 在任何时候,每个 JVM 线程都在执行单个方法的代码,即该线程的当前方法。
由于 Java 应用程序可以包含一些本机代码(例如,使用本机库),因此本机和非本机方法有两种不同的方式。 如果该方法不是本机的(即 Java 代码),则 PC 寄存器包含当前正在执行的 JVM 指令的地址。 如果该方法是本地方法,则未定义 JVM 的 PC 寄存器的值。
Java 虚拟机的 pc 寄存器足够宽,可以在特定平台上保存返回地址或本机指针。
目前,这一切都与 JVM 内部的内存区域结构有关。 在接下来的文章中,我将提出一些想法,以使用此信息进行性能调整。
祝您学习愉快!
参考:http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html
Java 内存管理 – 垃圾回收算法
我们都知道 Java 中垃圾收集器(GC)的工作职责。 但是只有极少数的尝试深入研究垃圾收集的工作原理。 您不是其中之一,这就是为什么您在这里。
在本 Java 内存管理教程中,我们将尝试了解 Java 垃圾回收的最新算法,并了解这些算法的发展。
Table of Contents
1\. Memory management in Java
2\. Reference counting mechanism
3\. Mark and sweep mechanism
4\. Stop and copy GC
5\. Generational stop and copy
6\. How to improve memory utilization in Java
1. Java 中的内存管理
Java 中的内存管理是垃圾收集器的职责。 这与 Java 之前的实践相反,在 Java 之前,程序员负责分配程序中的内存。
正式而言,垃圾收集器负责:
- 分配内存
- 确保所有引用的对象都保留在内存中,并且
- 恢复由执行代码中的引用无法访问的对象使用的内存。
在应用程序运行时,应用程序会创建许多对象,每个对象都有其生命周期。 在内存中,被其他对象引用的对象称为活动对象。 不再由任何活动对象引用的对象被视为死对象,并被称为垃圾。 查找和释放(也称为回收)这些对象使用的空间的过程称为垃圾回收。
垃圾回收解决了许多但不是全部的内存分配问题。 例如,我们可以无限期地创建对象并继续引用它们,直到没有更多可用的内存为止(内存不足错误)。 垃圾收集是一项复杂的任务,需要花费时间和资源。 它在通常由称为堆的大型内存池分配的空间上运行。
垃圾收集的时机由垃圾收集器决定。 通常,整个堆或堆的一部分会在堆满或达到占用率的百分比时收集。
从 J2SE 5.0 开始,Java HotSpot 虚拟机包括四个垃圾收集器。 所有的收藏家都是世代相传的。 我们将在后面的部分中了解有关世代 GC 的更多信息。
阅读更多:垃圾收集算法(针对 Java 9 更新)
2. 引用计数机制
从初始版本开始,这已经是非常古老的 GC 机制。 在引用计数技术中,每个对象都有从其他对象和栈指向该对象的指针数。 每次引用新对象时,计数器都会增加一。 同样,当任何对象丢失其引用时,计数器将减一。 当计数达到“0”时,垃圾回收器可以取消分配对象。
引用计数算法的主要优势在分配给新对象时,每次写入内存的工作量很少。 但是,它具有数据周期的严重问题。 这意味着当第一个对象被第二个对象引用,第二个对象被第一个对象引用(循环引用)时,计数永远不会为零,因此它们永远也不会被垃圾回收。
3. 标记和扫描机制
标记和扫描算法
标记清除算法是第一个要开发的垃圾收集算法,它能够回收循环数据结构。 在这种算法中,GC 将首先将某些对象标识为默认可达对象,这些对象通常是栈中的全局变量和局部变量。 有所谓的活动对象。
在下一步中,算法开始从这些活动对象中跟踪对象,并将它们也标记为活动对象。 继续执行此过程,直到检查所有对象并将其标记为活动。 完全跟踪后未标记为活动的对象被视为死对象。
使用标记扫描时,未引用的对象不会立即被回收。 取而代之的是,允许垃圾收集累积,直到所有可用内存都用完为止。 发生这种情况时,该程序的执行将暂时暂停(称为 Stop the world ),而标记清除算法将收集所有垃圾。 一旦回收了所有未引用的对象,就可以恢复程序的正常执行。
除了暂停应用程序一段时间外,此技术还需要经常对内存地址空间进行碎片整理,这是另一项开销。
4. 停止和复制 GC
像“标记和清除”一样,该算法还取决于识别活动对象并对其进行标记。 区别在于它处理活动对象的方式。
停止和复制技术将整个堆设计在两个半空间中。 一次只有一个半空间处于活动状态,而为新创建的对象分配的内存仅发生在单个半空间中,而另一个保持平静。
GC 运行时,它将开始标记当前半空间中的活动对象,完成后,它将所有活动对象复制到其他半空间中。 当前半空间中的所有其余对象都被视为已死,并已被垃圾回收。
与以前的方法一样,它具有的一些优点,就像它仅接触活动物体一样。 另外,不需要分段,因为在切换半空间时,会完成内存收缩。
这种方法的主要缺点是需要将所需的内存大小增加一倍,因为在给定的时间点仅使用了一半。 除此之外,它还需要在切换半空间时停止世界。
5. 分代停止和复制
像“停止并复制”技术一样,它也将内存划分为半空间,但现在它们是三个半空间。 这些半空间在这里称为世代。 因此,该技术中的内存分为三代-新生代,老年代和永久代。
大多数对象最初是在新生代中分配的。 老年代包含的对象在许多新生代集合中幸存下来,还有一些大型对象可以直接在老年代中分配。 永久生成包含 JVM 认为便于垃圾回收器管理的对象,例如描述类和方法的对象,以及类和方法本身。
当新生代填满时,将执行该一代的新生代垃圾回收(有时称为次要垃圾回收)。 当老年代或永久代填满时,通常会完成所谓的完整垃圾收集(有时称为主要收集)。 即,收集了所有的世代。
通常,首先使用专门为该代设计的垃圾收集算法来收集年轻代,因为它通常是识别年轻代中最有效的垃圾算法。 幸存于 GC 跟踪中的对象被推入更早的年代。 出于明显的原因,较老的一代被收集的频率较低,即他们在那里的原因是时间更长。 除上述情况外,如果发生碎片/压缩,则每一代都将单独压缩。
该技术的主要优点是在较年轻的一代中早期回收死对象,而无需每次都扫描整个内存以识别死对象。 较早的对象已经经历了一些 GC 周期,因此假定它们在系统中的存在时间更长,因此无需频繁扫描它们(不是每次都完美的情况,但大多数情况下应该如此)。
缺点仍然相同,即在 GC 运行全扫描时,需要对存储区进行碎片整理,并且需要停止世界(应用程序)。
6. 如何提高 Java 的内存利用率
- 不要分配过多的内存。 仅根据需要分配内存。 这特别适用于 Java 数组。
- 不要坚持引用。 一旦使用了对象且不再需要该对象,则为其分配
null
引用。 - 查找并解析内存泄漏
- 在每个发行版上执行系统性能分析来验证内存增加
- 不要依赖
System.gc()
运行垃圾收集
希望对垃圾收集机制有所帮助,该机制可为 Java 程序实现自动内存管理。 这可以帮助您回答 Java 内存管理面试问题。
学习愉快!
Java 序列化教程
Java 序列化 – 执行正确的序列化
Java 序列化允许将 Java 对象写入文件系统以进行永久存储,也可以将其写入网络以传输到其他应用程序。 Java 中的序列化是通过Serializable
接口实现的。 Java Serializable
接口保证可以序列化对象的能力。 此接口建议我们也使用serialVersioUID
。
现在,即使您在应用程序类中同时使用了两者,您是否知道哪怕现在会破坏您的设计? 让我们确定类中将来的更改,这些更改将是兼容的更改,而其他类别将证明不兼容的更改。
Table of contents
1\. Java serialization incompatible changes
2\. Java serialization compatible changes
3\. serialVersionUID
4\. readObject() and writeObject() methods
5\. More serialization best practices
6\. Sample class following serialization best practices
7\. Serialization and deserialization example
1. Java 序列化不兼容的更改
对类的不兼容更改是指不能保持互操作性的那些更改。 下面给出了在演化类时可能发生的不兼容更改(考虑默认序列化或反序列化):
- 删除字段 – 如果在类中删除了字段,则写入的流将不包含其值。 当较早的类读取流时,该字段的值将设置为默认值,因为流中没有可用的值。 但是,此默认值可能会不利地损害早期版本履行其契约的能力。
- 将类上移或下移 – 不允许这样做,因为流中的数据显示顺序错误。
- 将非静态字段更改为静态或将非瞬态字段更改为瞬态 – 当依赖默认序列化时,此更改等效于从类中删除字段。 该版本的类不会将该数据写入流,因此该类的早期版本将无法读取该数据。 与删除字段时一样,早期版本的字段将被初始化为默认值,这可能导致类以意外方式失败。
- 更改原始字段的声明类型 – 该类的每个版本都使用其声明类型写入数据。 尝试读取该字段的早期版本的类将失败,因为流中的数据类型与该字段的类型不匹配。
- 更改
writeObject
或readObject
方法,使其不再写入或读取默认字段数据,或对其进行更改,以使其尝试写入或读取以前的版本时不进行读取。 默认字段数据必须一致地出现在流中或不出现在流中。 - 将类从
Serializable
更改为Externalizable
,反之亦然是不兼容的更改,因为流将包含与可用类的实现不兼容的数据。 - 将类从非枚举类型更改为枚举类型,反之亦然,因为流将包含与可用类的实现不兼容的数据。
- 删除
Serializable
或Externalizable
是不兼容的更改,因为在编写时它将不再提供该类的较早版本所需的字段。 - 如果该行为会产生与该类的任何旧版本不兼容的对象,则将
writeReplace
或readResolve
方法添加到类是不兼容的。
2. Java 序列化兼容更改
- 添加字段 – 当重构的类具有流中未出现的字段时,对象中的该字段将被初始化为其类型的默认值。 如果需要特定于类的初始化,则该类可以提供一个
readObject
方法,该方法可以将字段初始化为非默认值。 - 添加类 – 流将包含流中每个对象的类型层次结构。 将流中的此层次结构与当前类进行比较可以检测到其他类。 由于流中没有用于初始化对象的信息,因此该类的字段将被初始化为默认值。
- 删除类 – 将流中的类层次结构与当前类的层次结构进行比较可以检测到某个类已被删除。 在这种情况下,从该流中读取与该类相对应的字段和对象。 原始字段被丢弃,但是创建了由已删除类引用的对象,因为可以在流中稍后引用它们。 当流被垃圾回收或重置时,它们将被垃圾回收。
- 添加
writeObject
/readObject
方法 – 如果读取流的版本具有这些方法,则通常希望readObject
读取通过默认序列化写入流中的所需数据。 在读取任何可选数据之前,应先调用defaultReadObject
。 通常,writeObject
方法将调用defaultWriteObject
写入所需的数据,然后再写入可选数据。 - 删除
writeObject
/readObject
方法 – 如果读取流的类没有这些方法,则默认情况下将序列化读取所需数据,而可选数据将被丢弃。 - 添加
java.io.Serializable
– 这等同于添加类型。 该类的流中将没有任何值,因此其字段将被初始化为默认值。 对子类化不可序列化类的支持要求该类的超类型具有无参构造器,并且该类本身将被初始化为默认值。 如果无参构造器不可用,则抛出InvalidClassException
。 - 更改对字段的访问 – 公共,包,保护和私有的访问修饰符对序列化为字段分配值的能力没有影响。
- 将字段从静态更改为非静态,或将瞬态更改为非瞬态 – 当依赖默认序列化来计算可序列化字段时,此更改等效于将字段添加到类中。 新字段将被写入流,但是较早的类将忽略该值,因为序列化不会为静态或瞬态字段分配值。
3. serialVersionUID
serialVersionUID
是Serializable
类的通用版本标识符。 反序列化使用此数字来确保已加载的类与序列化的对象完全对应。 如果找不到匹配项,则抛出InvalidClassException
。
- 始终将其包含为字段,例如:
private static final long serialVersionUID = 7526472295622776147L;
,即使在类的第一个版本中也要包含此字段,以提醒其重要性。 - 请勿在以后的版本中更改此字段的值,除非您有意对类进行更改,以使其与旧的序列化对象不兼容。 如果需要,请遵循上述给定的准则。
4. readObject
和writeObject
方法
- 反序列化必须视为任何构造器:在反序列化结束时验证对象状态 – 这意味着
readObject
几乎应始终在Serializable
类中实现,以便执行此验证。 - 如果构造器为可变对象字段制作防御性副本,则必须读取对象。
5. 更多序列化最佳实践
- 使用 javadoc 的
@serial
标记表示可序列化字段。 .ser
扩展名通常用于表示序列化对象的文件。- 没有静态或瞬态字段接受默认序列化。
- 除非必要,否则可扩展类不应是可序列化的。
- 内部类很少(如果有的话)实现
Serializable
。 - 容器类通常应遵循
Hashtable
的样式,该样式通过存储键和值来实现Serializable
,而不是大型哈希表数据结构。
6. 遵循序列化最佳实践的示例类
package staticTest;
import java.io.Serializable;
import java.text.StringCharacterIterator;
import java.util.*;
import java.io.*;
public final class UserDetails implements Serializable {
/**
* This constructor requires all fields
*
* @param aFirstName
* contains only letters, spaces, and apostrophes.
* @param aLastName
* contains only letters, spaces, and apostrophes.
* @param aAccountNumber
* is non-negative.
* @param aDateOpened
* has a non-negative number of milliseconds.
*/
public UserDetails(String aFirstName, String aLastName, int aAccountNumber,
Date aDateOpened)
{
super();
setFirstName(aFirstName);
setLastName(aLastName);
setAccountNumber(aAccountNumber);
setDateOpened(aDateOpened);
// there is no need here to call verifyUserDetails.
}
// The default constructor
public UserDetails() {
this("FirstName", "LastName", 0, new Date(System.currentTimeMillis()));
}
public final String getFirstName() {
return fFirstName;
}
public final String getLastName() {
return fLastName;
}
public final int getAccountNumber() {
return fAccountNumber;
}
/**
* Returns a defensive copy of the field so that no one can change this
* field.
*/
public final Date getDateOpened() {
return new Date(fDateOpened.getTime());
}
/**
* Names must contain only letters, spaces, and apostrophes. Validate before
* setting field to new value.
*
* @throws IllegalArgumentException
* if the new value is not acceptable.
*/
public final void setFirstName(String aNewFirstName) {
verifyNameProperty(aNewFirstName);
fFirstName = aNewFirstName;
}
/**
* Names must contain only letters, spaces, and apostrophes. Validate before
* setting field to new value.
*
* @throws IllegalArgumentException
* if the new value is not acceptable.
*/
public final void setLastName(String aNewLastName) {
verifyNameProperty(aNewLastName);
fLastName = aNewLastName;
}
/**
* Validate before setting field to new value.
*
* @throws IllegalArgumentException
* if the new value is not acceptable.
*/
public final void setAccountNumber(int aNewAccountNumber) {
validateAccountNumber(aNewAccountNumber);
fAccountNumber = aNewAccountNumber;
}
public final void setDateOpened(Date aNewDate) {
// make a defensive copy of the mutable date object
Date newDate = new Date(aNewDate.getTime());
validateAccountOpenDate(newDate);
fDateOpened = newDate;
}
/**
* The client's first name.
*
* @serial
*/
private String fFirstName;
/**
* The client's last name.
*
* @serial
*/
private String fLastName;
/**
* The client's account number.
*
* @serial
*/
private int fAccountNumber;
/**
* The date the account was opened.
*
* @serial
*/
private Date fDateOpened;
/**
* Determines if a de-serialized file is compatible with this class.
* Included here as a reminder of its importance.
*/
private static final long serialVersionUID = 7526471155622776147L;
/**
* Verify that all fields of this object take permissible values
*
* @throws IllegalArgumentException
* if any field takes an unpermitted value.
*/
private void verifyUserDetails() {
validateAccountNumber(fAccountNumber);
verifyNameProperty(fFirstName);
verifyNameProperty(fLastName);
validateAccountOpenDate(fDateOpened);
}
/**
* Ensure names contain only letters, spaces, and apostrophes.
*
* @throws IllegalArgumentException
* if field takes an unpermitted value.
*/
private void verifyNameProperty(String aName) {
boolean nameHasContent = (aName != null) && (!aName.equals(""));
if (!nameHasContent) {
throw new IllegalArgumentException(
"Names must be non-null and non-empty.");
}
StringCharacterIterator iterator = new StringCharacterIterator(aName);
char character = iterator.current();
while (character != StringCharacterIterator.DONE) {
boolean isValidChar = (Character.isLetter(character)
|| Character.isSpaceChar(character) || character == ''');
if (isValidChar) {
// do nothing
} else {
String message = "Names can contain only letters, spaces, and apostrophes.";
throw new IllegalArgumentException(message);
}
character = iterator.next();
}
}
/**
* AccountNumber must be non-negative.
*
* @throws IllegalArgumentException
* if field takes an unpermitted value.
*/
private void validateAccountNumber(int aAccountNumber) {
if (aAccountNumber < 0) {
String message = "Account Number must be greater than or equal to 0.";
throw new IllegalArgumentException(message);
}
}
/**
* DateOpened must be after 1970.
*
* @throws IllegalArgumentException
* if field takes an unpermitted value.
*/
private void validateAccountOpenDate(Date aDateOpened) {
if (aDateOpened.getTime() < 0) {
throw new IllegalArgumentException(
"Date Opened must be after 1970.");
}
}
/**
* Always treat deserialization as a full-blown constructor, by validating
* the final state of the de-serialized object.
*/
private void readObject(ObjectInputStream aInputStream)
throws ClassNotFoundException, IOException {
// always perform the default deserialization first
aInputStream.defaultReadObject();
// make defensive copy of the mutable Date field
fDateOpened = new Date(fDateOpened.getTime());
// ensure that object state has not been corrupted or tampered with
// malicious code
verifyUserDetails();
}
/**
* This is the default implementation of writeObject. Customise if
* necessary.
*/
private void writeObject(ObjectOutputStream aOutputStream)
throws IOException {
// perform the default serialization for all non-transient, non-static
// fields
aOutputStream.defaultWriteObject();
}
}
现在让我们看看如何在 Java 中进行序列化和反序列化。
序列化和反序列化示例
package serializationTest;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Calendar;
import java.util.Date;
public class TestUserDetails {
public static void main(String[] args) {
// Create new UserDetails object
UserDetails myDetails = new UserDetails("Lokesh", "Gupta", 102825,
new Date(Calendar.getInstance().getTimeInMillis()));
// Serialization code
try {
FileOutputStream fileOut = new FileOutputStream("userDetails.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(myDetails);
out.close();
fileOut.close();
} catch (IOException i) {
i.printStackTrace();
}
// deserialization code
@SuppressWarnings("unused")
UserDetails deserializedUserDetails = null;
try {
FileInputStream fileIn = new FileInputStream("userDetails.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
deserializedUserDetails = (UserDetails) in.readObject();
in.close();
fileIn.close();
// verify the object state
System.out.println(deserializedUserDetails.getFirstName());
System.out.println(deserializedUserDetails.getLastName());
System.out.println(deserializedUserDetails.getAccountNumber());
System.out.println(deserializedUserDetails.getDateOpened());
} catch (IOException ioe) {
ioe.printStackTrace();
} catch (ClassNotFoundException cnfe) {
cnfe.printStackTrace();
}
}
}
Output:
Lokesh
Gupta
102825
Wed Nov 21 15:06:34 GMT+05:30 2012
参考文献:
http://docs.oracle.com/javase/7/docs/platform/serialization/spec/serialTOC.html
Java serialVersionUID
– 如何生成serialVersionUID
原文: https://howtodoinjava.com/java/serialization/serialversionuid/
Java 序列化是将对象转换为字节流的过程,因此我们可以执行类似的操作,例如将其存储在磁盘上或通过网络发送。 反序列化是相反的过程 – 将字节流转换为内存中的对象。
在序列化期间,java 运行时将版本号与每个可序列化的类相关联。 称为serialVersionUID
的数字,在反序列化期间用于验证序列化对象的发送者和接收者是否已加载了该对象的与序列化兼容的类。 如果接收者为对象加载的类serialVersionUID
与相应发送者的类不同,则反序列化将导致InvalidClassException
。
1. Java serialVersionUID
语法
可序列化的类可以通过声明一个名为“serialVersionUID
”的字段来显式声明其自己的serialVersionUID
,该字段必须是静态的,最终的且类型为long
。
private static final long serialVersionUID = 4L;
在这里,serialVersionUID
表示类的版本,如果您对类的当前版本进行了修改,以使其不再与先前的版本向后兼容,则应该对它进行递增。
2. Java 序列化和反序列化示例
让我们看一个如何将类序列化然后反序列化的示例。
package com.howtodoinjava.demo.serialization;
import java.io.*;
import java.util.logging.Logger;
public class DemoClass implements java.io.Serializable {
private static final long serialVersionUID = 4L; //Default serial version uid
private static final String fileName = "DemoClassBytes.ser"; //Any random name
private static final Logger logger = Logger.getLogger("");
//Few data fields
//Able to serialize
private static String staticVariable;
private int intVariable;
//Not able to serialize
transient private String transientVariable = "this is a transient instance field";
private Thread threadClass;
public static void main(String[] args) throws IOException, ClassNotFoundException
{
//Serialization
DemoClass test = new DemoClass();
test.intVariable = 1;
staticVariable = "this is a static variable";
writeOut(test);
System.out.println("DemoClass to be saved: " + test);
//De-serialization
System.out.println("DemoClass deserialized: " + readIn());
}
private static Object readIn() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File(fileName)));
return ois.readObject();
}
private static void writeOut(java.io.Serializable obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File(fileName)));
oos.writeObject(obj);
oos.close();
}
@Override public String toString() {
return "DemoClass: final static fileName=" + fileName + ", final static logger=" + logger
+ ", non-final static staticVariable=" + staticVariable + ", instance intVariable=" + intVariable
+ ", transient instance transientVariable=" + transientVariable + ", non-serializable instance field threadClass:=" + threadClass;
}
}
程序输出。
DemoClass to be saved: DemoClass:
final static fileName=DemoClassBytes.ser,
final static logger=java.util.logging.LogManager$RootLogger@1d99a4d,
non-final static staticVariable=this is a static variable,
instance intVariable=1,
transient instance transientVariable=this is a transient instance field,
non-serializable instance field threadClass:=null
//Execute readIn() function from a separate main() method
//to get given below output correctly. It will flush out the static fields.
DemoClass deserialized: DemoClass:
final static fileName=DemoClassBytes.ser,
final static logger=java.util.logging.LogManager$RootLogger@cd2c3c,
non-final static staticVariable=null,
instance intVariable=1,
transient instance transientVariable=null,
non-serializable instance field threadClass:=null
如果可序列化的类未显式声明
serialVersionUID
,则序列化运行时将根据该类的各个方面计算该类的默认serialVersionUID
值。
3. 如何生成serialVersionUID
Joshua Bloch 在《Effective Java》中说,自动生成的 UID 是基于类名称,已实现的接口以及所有公共和受保护成员生成的。 以任何方式更改其中任何一个都将更改serialVersionUID
。
但是,强烈建议所有可序列化的类显式声明serialVersionUID
值,因为默认的serialVersionUID
计算对类详细信息高度敏感,类详细信息可能会根据编译器的实现而有所不同,并且可以在不同的环境中产生不同的serialVersionUID
。 这可能导致反序列化期间出现意外的InvalidClassException
。
因此,为了在不同的 Java 编译器实现中保证一致的serialVersionUID
值,可序列化的类必须声明一个显式的serialVersionUID
值。 强烈建议在可能的情况下,显式serialVersionUID
声明在serialVersionUID
中使用private
修饰符,因为此类声明仅适用于立即声明的类。
另请注意,serialVersionUID
字段不能用作继承成员。
基于我的短暂职业,我可以说长时间存储序列化数据(空间序列化)并不是很常见的用例。 使用序列化机制将数据临时写入(时间序列化)到例如高速缓存,或通过网络将其发送到另一个程序以利用信息,这是更为常见的。
在这种情况下,我们对保持向后兼容性不感兴趣。 我们只关心确保在网络上通信的代码库确实具有相同版本的相关类。 为了方便进行此类检查,我们必须保持serialVersionUID
不变,并且不要对其进行更改。 另外,在网络上的两个应用程序上对类进行不兼容的更改时,请不要忘记更新它。
4. 没有serialVersionUID
的 Java 类
这不是我们永远想要面对的情况。 但是,这是现实,有时甚至会发生(我应该很少说吗?)。 如果我们需要以不兼容的方式更改此类,但又想使用该类的旧版本维护序列化/反序列化特性,则可以使用 JDK 工具“serialver”。 该工具在旧类上生成serialVersionUID
,并在新类上显式设置它。 不要忘记实现readObject()
和writeObject()
方法,因为内置的反序列化机制(in.defaultReadObject()
)将拒绝从旧版本的数据中反序列化。
如果我们定义自己的readObject()
函数,可以读取回旧数据。 此自定义代码应检查serialVersionUID
,以便了解数据所在的版本并决定如何对其进行反序列化。 如果我们存储可以在您的代码的多个版本中保留的序列化数据,则此版本控制技术很有用。
阅读更多: Java 序列化兼容和不兼容的更改
5. Java serialVersionUID
– 总结
-
transient
和static
字段在序列化中被忽略。 反序列化之后,transient
字段和非最终静态字段将为null
。final
和static
字段仍具有值,因为它们是类数据的一部分。 -
ObjectOutputStream.writeObject(obj)
和ObjectInputStream.readObject()
用于序列化和反序列化。 -
在序列化期间,我们需要处理
IOException
; 在反序列化期间,我们需要处理IOException
和ClassNotFoundException
。 因此,反序列化的类类型必须在类路径中。 -
允许使用未初始化的,不可序列化的,非瞬态的实例字段。
添加“
private Thread th;
”时,Serializable
没有错误。 但是,“private Thread threadClass = new Thread();
”将导致异常:Exception in thread "main" java.io.NotSerializableException: java.lang.Thread at java.io.ObjectOutputStream.writeObject0(Unknown Source) at java.io.ObjectOutputStream.defaultWriteFields(Unknown Source) at java.io.ObjectOutputStream.writeSerialData(Unknown Source) at java.io.ObjectOutputStream.writeOrdinaryObject(Unknown Source) at java.io.ObjectOutputStream.writeObject0(Unknown Source) at java.io.ObjectOutputStream.writeObject(Unknown Source) at com.howtodoinjava.demo.serialization.DemoClass.writeOut(DemoClass.java:42) at com.howtodoinjava.demo.serialization.DemoClass.main(DemoClass.java:27)
-
序列化和反序列化可用于复制和克隆对象。 它比常规克隆慢,但是可以很容易地产生深拷贝 。
-
如果我需要序列化
Serializable
类Employee
,但是其超类之一不是可序列化的,Employee
类是否仍可以序列化和反序列化? 答案是肯定的,前提是不可序列化的超类具有无参构造器,在反序列化时调用该构造器以初始化该超类。 -
我们在修改实现
java.io.Serializable
的类时必须小心。 如果类不包含serialVersionUID
字段,则其serialVersionUID
将由编译器自动生成。不同的编译器或同一编译器的不同版本将生成潜在的不同值。
-
serialVersionUID
的计算不仅基于字段,而且还基于类的其他方面,例如Implement
子句,构造器等。因此,最佳实践是显式声明serialVersionUID
字段以保持向后兼容性。 如果我们需要实质性地修改可序列化的类,并希望它与以前的版本不兼容,则需要增加serialVersionUID
以避免混合使用不同的版本。
学习愉快!
Java 静态 – 变量,方法,块,类和导入语句
原文: https://howtodoinjava.com/java/basics/java-static-keyword/
Java 中的static
关键字可以应用于变量,方法,块,导入和内部类。 在本教程中,我们将通过示例学习在这些地方使用static
关键字的效果。
Table of Contents
1\. Static Variable
2\. Static Method
3\. Static Import Statement
4\. Static Block
5\. Static Class
6\. Summary
1. 静态变量
要语句静态变量,请在变量语句中使用static
关键字。 静态变量语法为:
ACCESS_MODIFER static DATA_TYPE VARNAME;
例如,以这种方式语句Integer
类型的公共静态变量。
public static Integer staticVar;
关于静态变量的最重要的事情是它们属于类级别。 这意味着在运行时中只能有变量的一个副本。
在类定义中定义静态变量时,类的每个实例都可以访问该单个副本。 类的单独实例不会像非静态变量一样拥有自己的本地副本。
public class JavaStaticExample
{
public static void main(String[] args)
{
DataObject objOne = new DataObject();
objOne.staticVar = 10;
objOne.nonStaticVar = 20;
DataObject objTwo = new DataObject();
System.out.println(objTwo.staticVar); //10
System.out.println(objTwo.nonStaticVar); //null
DataObject.staticVar = 30; //Direct Access
System.out.println(objOne.staticVar); //30
System.out.println(objTwo.staticVar); //30
}
}
class DataObject {
public static Integer staticVar;
public Integer nonStaticVar;
}
Output:
10
null
30
30
注意我们如何将值更改为 30,并且两个对象现在都看到更新后的值 30。
您应该已经注意到的另一件事是,我们如何能够使用其类名即DataObject.staticVar
访问静态变量。 我们无需创建任何实例即可访问static
变量。 它清楚地表明静态变量属于类范围。
2. 静态方法
要语句静态方法,请在方法语句中使用static
关键字。 静态方法语法为:
ACCESS_MODIFER static RETURN_TYPE METHOD_NAME;
例如,以这种方式声明了一个Integer
类型的公共静态变量。
public static Integer staticVar;
public static Integer getStaticVar(){
return staticVar;
}
几件事要记住。
- 您只能访问静态方法中的静态变量。 如果您尝试访问任何非静态变量,则会生成编译器错误,并显示消息“无法对非静态字段
nonStaticVar
进行静态引用”。 - 静态方法可以通过其类引用进行访问,而无需创建类的实例。 尽管您也可以使用实例引用进行访问,但是与通过类引用进行访问相比,它没有任何区别。
- 静态方法也属于类级别范围。
public class JavaStaticExample
{
public static void main(String[] args)
{
DataObject.staticVar = 30; //Direct Access
Integer value1 = DataObject.getStaticVar(); //access with class reference
DataObject objOne = new DataObject();
Integer value2 = objOne.getStaticVar(); //access with instance reference
System.out.println(value1);
System.out.println(value2);
}
}
class DataObject
{
public Integer nonStaticVar;
public static Integer staticVar; //static variable
public static Integer getStaticVar(){
return staticVar;
}
}
Output:
30
30
3. 静态导入语句
普通的导入语句从包中导入类,因此可以在不引用包的情况下使用它们。 同样,静态导入语句从类导入静态成员,并允许在没有类引用的情况下使用它们。
静态导入语句也有两种形式:单静态导入和静态按需导入。 单静态导入语句从类型中导入一个静态成员。 静态按需导入语句将导入类型的所有静态成员。
//Single-static-import declaration:
import static <<package name>>.<<type name>>.<<static member name>>;
//Static-import-on-demand declaration:
import static <<package name>>.<<type name>>.*;
例如,System.out
是
//Static import statement
import static java.lang.System.out;
public class JavaStaticExample
{
public static void main(String[] args)
{
DataObject.staticVar = 30;
out.println(DataObject.staticVar); //Static import statement example
}
}
class DataObject
{
public static Integer staticVar; //static variable
}
Output:
30
阅读更多: Java 中的静态导入语句
4. 静态块
静态块是类初始化代码的一部分,并用static
关键字包装。
public class Main {
//static initializer
static {
System.out.println("Inside static initializer");
}
}
当将类加载到内存中时,将执行静态块。 一个类可以具有多个静态块,并且这些静态块将按照它们在类定义中出现的相同顺序执行。
import static java.lang.System.out;
class DataObject
{
public Integer nonStaticVar;
public static Integer staticVar; //static variable
//It will be executed first
static {
staticVar = 40;
//nonStaticVar = 20; //Not possible to access non-static members
}
//It will be executed second
static {
out.println(staticVar);
}
}
Output:
40
5. 静态类
在 Java 中,可以将静态类作为内部类。 就像其他静态成员一样,嵌套类也属于类范围,因此可以在没有外部类对象的情况下访问内部静态类。
public class JavaStaticExample
{
public static void main(String[] args)
{
//Static inner class example
System.out.println( DataObject.StaticInnerClas.innerStaticVar );
}
}
class DataObject
{
public Integer nonStaticVar;
public static Integer staticVar; //static variable
static class StaticInnerClas {
Integer innerNonStaticVar = 60;
static Integer innerStaticVar = 70; //static variable inside inner class
}
}
请注意,静态内部类无法访问外部类的非静态成员。 它只能访问外部类的静态成员。
public class JavaStaticExample
{
public static void main(String[] args)
{
//Static inner class example
DataObject.StaticInnerClas.accessOuterClass();
}
}
class DataObject
{
public Integer nonStaticVar;
public static Integer staticVar; //static variable
static {
staticVar = 40;
//nonStaticVar = 20; //Not possible to access non-static members
}
public static Integer getStaticVar(){
return staticVar;
}
static class StaticInnerClas
{
public static void accessOuterClass()
{
System.out.println(DataObject.staticVar); //static variable of outer class
System.out.println(DataObject.getStaticVar()); //static method of outer class
}
}
}
Output:
40
6. 总结
让我们总结一下有关 Java 中static keyword
使用情况的所有信息。
- 静态成员属于类。 无需创建类实例即可访问静态成员。
- 静态成员(变量和方法)只能在静态方法和静态块内访问。
- 非静态成员不能在静态方法,块和内部类中访问。
- 一个类可以有多个静态块,并且将按照它们在类定义中出现的顺序执行它们。
- 一个类只有在外部类内部语句为内部类时才能是静态的。
- 静态导入可用于从类中导入所有静态成员。 可以在没有任何类引用的情况下引用这些成员。
学习愉快!
参考文献:
Java 外部化示例 – 更有效的序列化
原文: https://howtodoinjava.com/java/serialization/java-externalizable-example/
默认的 Java 序列化效率不高。 如果您序列化了一个具有许多属性和属性的胖对象,则您不希望出于任何原因进行序列化(例如,它们始终被分配默认值),您将需要处理繁重的对象并通过网络发送更多字节,这在某些情况下可能会非常昂贵。
要解决此问题,您可以通过实现Externalizable
接口并覆盖其方法writeExternal()
和readExternal()
来编写自己的序列化逻辑。 通过实现这些方法,您将告诉 JVM 如何编码/解码对象。
出于示例目的,我创建了这个简单的类,我们将使用writeExternal()
和readExternal()
方法进行序列化和反序列化。
class UserSettings implements Externalizable {
//This is required
public UserSettings(){
}
private String doNotStoreMe;
private Integer fieldOne;
private String fieldTwo;
private boolean fieldThree;
public String getDoNotStoreMe() {
return doNotStoreMe;
}
public void setDoNotStoreMe(String doNotStoreMe) {
this.doNotStoreMe = doNotStoreMe;
}
public Integer getFieldOne() {
return fieldOne;
}
public void setFieldOne(Integer fieldOne) {
this.fieldOne = fieldOne;
}
public String getFieldTwo() {
return fieldTwo;
}
public void setFieldTwo(String fieldTwo) {
this.fieldTwo = fieldTwo;
}
public boolean isFieldThree() {
return fieldThree;
}
public void setFieldThree(boolean fieldThree) {
this.fieldThree = fieldThree;
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
fieldOne = in.readInt();
fieldTwo = in.readUTF();
fieldThree = in.readBoolean();
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(fieldOne);
out.writeUTF(fieldTwo);
out.writeBoolean(fieldThree);
}
@Override
public String toString() {
return "UserSettings [doNotStoreMe=" + doNotStoreMe + ", fieldOne="
+ fieldOne + ", fieldTwo=" + fieldTwo + ", fieldThree="
+ fieldThree + "]";
}
}
Externalizable
的writeExternal()
示例
writeExternal()
方法用于提供序列化逻辑,即将类的字段写入字节。 在读回序列化的对象之后,您可以自由地仅存储要返回的那些字段,忽略其余字段。
public void writeExternal(ObjectOutput out) throws IOException {
//We are not storing the field 'doNotStoreMe'
out.writeInt(fieldOne);
out.writeUTF(fieldTwo);
out.writeBoolean(fieldThree);
}
Externalizable
的readExternal()
示例
唯一需要记住的是 – readExternal()
方法必须读取与writeExternal()
写入的序列相同且类型相同的值。
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
fieldOne = in.readInt();
fieldTwo = in.readUTF();
fieldThree = in.readBoolean();
}
完整的例子
现在,让我们编写代码进行序列化并读回字节,以验证 JVM 是否遵守契约。
public class ExternalizableExample
{
public static void main(String[] args)
{
UserSettings settings = new UserSettings();
settings.setDoNotStoreMe("Sensitive info");
settings.setFieldOne(10000);
settings.setFieldTwo("HowToDoInJava.com");
settings.setFieldThree(false);
//Before
System.out.println(settings);
storeUserSettings(settings);
UserSettings loadedSettings = loadSettings();
System.out.println(loadedSettings);
}
private static UserSettings loadSettings() {
try {
FileInputStream fis = new FileInputStream("object.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
UserSettings settings = (UserSettings) ois.readObject();
ois.close();
return settings;
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
private static void storeUserSettings(UserSettings settings)
{
try {
FileOutputStream fos = new FileOutputStream("object.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(settings);
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Output:
UserSettings [doNotStoreMe=Sensitive info, fieldOne=10000, fieldTwo=HowToDoInJava.com, fieldThree=false]
UserSettings [doNotStoreMe=null, fieldOne=10000, fieldTwo=HowToDoInJava.com, fieldThree=false]
显然,我们可以取回所需的字段,而忽略不需要的字段,这就是Externalizable
接口的全部目的。
学习愉快!
Java 中Externalizable
与Serializable
之间的区别
原文: https://howtodoinjava.com/java/serialization/externalizable-vs-serializable/
知道Externalizable
与Serializable
之间的差异在两个方面都很重要,一个是可以作为面试问题询问,另外一个是您可以利用该知识做出更明智的决策,将序列化应用到您的应用中来追求性能提升。
1. Externalizable
与Serializable
之间的区别
让我们列出 Java 中Externalizable
和Serializable
接口之间的主要区别。
Externalizable |
Serializable |
---|---|
Serializable 是标记接口,即不包含任何方法。 |
Externalizable 接口包含实现类必须覆盖的两个方法writeExternal() 和readExternal() 。 |
Serializable 接口将序列化的职责传递给 JVM 及其默认算法。 |
Externalizable 向程序员提供串行化逻辑的控制-编写自定义逻辑。 |
通常,默认序列化易于实现,但具有较高的性能成本。 | 使用Externalizable 完成的序列化为程序员增加了更多责任,但通常会带来更好的性能。 |
很难分析和修改类结构,因为任何更改都可能会破坏序列化。 | 由于可以完全控制序列化逻辑,因此分析和修改类结构更加容易。 |
默认序列化不调用任何类构造器。 | 使用Externalizable 接口时,需要一个公共的无参数构造器。 |
请注意,Externalizable
接口是Serializable
的子接口,即Externalizable extends Serializable
。 因此,如果任何类实现Externalizable
接口并覆盖其writeExternal()
和readExternal()
方法,则这些方法优先于 JVM 提供的默认序列化机制。
阅读更多:如何在 Java 中覆盖默认的序列化机制
2. 阅读有关Externalizable
与Serializable
的更多信息
请在与 Java 中的Externalizable
与Serializable
接口有关的评论部分中提出您的问题。
学习愉快!
将 Java 对象序列化为 XML – XMLEncoder
和XMLDecoder
示例
原文: https://howtodoinjava.com/java/serialization/xmlencoder-and-xmldecoder-example/
默认的 Java 序列化将 Java 对象转换为字节以通过网络发送。 但是很多时候,您将需要更多的跨平台媒体来发送此信息,例如 XML,以便使用不同技术的应用程序也可以利用此序列化信息的优势。 在此示例中,我们将学习将 Java 对象序列化为 XML 文件,然后反序列化回原始 Java 对象。
为了演示用法,我创建了一个具有 3 个字段的类UserSettings
,我们将序列化为 xml,然后将 xml 反序列化为 Java 对象。
public class UserSettings {
public UserSettings(){}
private Integer fieldOne;
private String fieldTwo;
private boolean fieldThree;
public Integer getFieldOne() {
return fieldOne;
}
public void setFieldOne(Integer fieldOne) {
this.fieldOne = fieldOne;
}
public String getFieldTwo() {
return fieldTwo;
}
public void setFieldTwo(String fieldTwo) {
this.fieldTwo = fieldTwo;
}
public boolean isFieldThree() {
return fieldThree;
}
public void setFieldThree(boolean fieldThree) {
this.fieldThree = fieldThree;
}
@Override
public String toString() {
return "UserSettings [fieldOne=" + fieldOne + ", fieldTwo=" + fieldTwo
+ ", fieldThree=" + fieldThree + "]";
}
}
使用XMLEncoder
从 Java 对象序列化为 XML 文件
首先来看XMLEncoder
类的示例,该类用于将 Java 对象序列化或编码为 XML 文件。
private static void serializeToXML (UserSettings settings) throws IOException
{
FileOutputStream fos = new FileOutputStream("settings.xml");
XMLEncoder encoder = new XMLEncoder(fos);
encoder.setExceptionListener(new ExceptionListener() {
public void exceptionThrown(Exception e) {
System.out.println("Exception! :"+e.toString());
}
});
encoder.writeObject(settings);
encoder.close();
fos.close();
}
XMLEncoder
使用反射来找出它们包含哪些字段,但不是以二进制形式编写这些字段,而是以 XML 编写。 待编码的对象不需要是可序列化的,但是它们确实需要遵循 Java Beans 规范,例如
- 该对象具有一个公共的空(无参数)构造器。
- 该对象具有每个受保护/私有财产的公共获取器和设置器。
运行以上代码将生成具有以下内容的 XML 文件:
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.8.0_92" class="java.beans.XMLDecoder">
<object class="com.howtodoinjava.log4j2.examples.UserSettings">
<void property="fieldOne">
<int>10000</int>
</void>
<void property="fieldTwo">
<string>HowToDoInJava.com</string>
</void>
</object>
</java>
请注意,如果要写入的对象的属性的默认值未更改,则XmlEncoder
不会将其写出。 例如,在我们的示例中,第三个字段的类型为boolean
,其默认值为false
– 因此,它已从 XML 内容中省略。 这样可以灵活地更改类版本之间的默认值。
使用XMLDecoder
反序列化从 XML 到 Java 对象
现在,我们来看一下XMLDecoder
的示例,该示例已用于将 xml 文件反序列化为 java 对象。
private static UserSettings deserializeFromXML() throws IOException {
FileInputStream fis = new FileInputStream("settings.xml");
XMLDecoder decoder = new XMLDecoder(fis);
UserSettings decodedSettings = (UserSettings) decoder.readObject();
decoder.close();
fis.close();
return decodedSettings;
}
XMLEncoder
和XMLDecoder
比序列化框架宽容得多。 解码时,如果属性更改了类型,或者属性被删除/添加/移动/重命名,则解码将“尽其所能”进行解码,同时跳过无法解码的属性。
完整的例子
让我们看一下使用XMLEncoder
和XMLDecoder
的整个示例。
import java.beans.ExceptionListener;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class XMLEncoderDecoderExample
{
public static void main(String[] args) throws IOException
{
UserSettings settings = new UserSettings();
settings.setFieldOne(10000);
settings.setFieldTwo("HowToDoInJava.com");
settings.setFieldThree(false);
//Before
System.out.println(settings);
serializeToXML ( settings );
UserSettings loadedSettings = deserializeFromXML();
//After
System.out.println(loadedSettings);
}
private static void serializeToXML (UserSettings settings) throws IOException
{
FileOutputStream fos = new FileOutputStream("settings.xml");
XMLEncoder encoder = new XMLEncoder(fos);
encoder.setExceptionListener(new ExceptionListener() {
public void exceptionThrown(Exception e) {
System.out.println("Exception! :"+e.toString());
}
});
encoder.writeObject(settings);
encoder.close();
fos.close();
}
private static UserSettings deserializeFromXML() throws IOException {
FileInputStream fis = new FileInputStream("settings.xml");
XMLDecoder decoder = new XMLDecoder(fis);
UserSettings decodedSettings = (UserSettings) decoder.readObject();
decoder.close();
fis.close();
return decodedSettings;
}
}
Output:
UserSettings [fieldOne=10000, fieldTwo=HowToDoInJava.com, fieldThree=false]
UserSettings [fieldOne=10000, fieldTwo=HowToDoInJava.com, fieldThree=false]
将我的问题放在评论部分。
学习愉快!
Java 中反序列化过程如何发生?
原文: https://howtodoinjava.com/java/serialization/how-deserialization-process-happen-in-java/
在我上一篇与“在 Java 中实现Serializable
接口的指南”相关的文章中,Bitoo 先生问了一个好问题:“在反序列化时,JVM 如何创建 JVM 没有调用构造器的对象?”。 我曾想在评论中的同一帖子中回复他,但又过一会儿,我想到了这个非常有趣的话题,需要另外撰写详细的文章,并与大家进行讨论。 因此,在这里,我将以对这一主题的有限知识开始讨论,并鼓励大家提出您的想法/疑问,以便使我们所有人都清楚这个主题。 我从这里开始。
我们已经介绍了许多与 Java 中的序列化相关的内容,以及与 Java 中的反序列化相关的一些内容。 我将不再重复同一件事,而是直接进入主要讨论主题,即反序列化如何在 Java 中工作?
反序列化是将先前序列化的对象重建回其原始形式(即对象实例)的过程。 反序列化过程的输入是字节流,它是从网络的另一端获取的,或者我们只是从文件系统/数据库中读取字节流。 立即出现一个问题,在此字节流中写入了什么?
阅读更多信息:用于实现
Serializable
接口的迷你指南
确切地说,字节流(或称序列化数据)具有有关由序列化过程序列化的实例的所有信息。 该信息包括类的元数据,实例字段的类型信息以及实例字段的值。 将对象重新构造回新的对象实例时,也需要相同的信息。 在反序列化对象时,JVM 从字节流中读取其类元数据,该字节流指定对象的类是实现Serializable
还是Externalizable
接口。
请注意,要无缝进行反序列化,执行反序列化的 JVM 中必须存在要反序列化对象的类的字节码。 否则,将抛出ClassNotFoundException
。 这不是很明显吗?
如果实例实现了Serializable
接口,则将创建类的实例而无需调用其任何构造器。 真? 那么如果不调用构造器,该如何创建对象呢?
让我们看一个简单的电子程序的字节码:
public class SimpleProgram
{
public static void main(String[] args)
{
System.out.println("Hello World!");
}
}
Byte code:
public class SimpleProgram extends java.lang.Object{
public SimpleProgram();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3; //String Hello World!
5: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
上面的字节码看起来很真实,不是吗? 在第一行中,我们将把“局部变量表”中的值压入栈。 在这种情况下,我们实际上只是将隐式引用推到“this
”,因此它并不是最令人兴奋的说明。 第二条指令是主要内容。 实际上,它调用了最高级类的构造器,在上述情况下,它是Object.java
。 一旦调用了最高级类的构造器(即本例中的对象),其余的代码就会执行用代码编写的特定指令。
符合以上概念,即最高级的构造器,我们在反序列化中也有类似的概念。 在反序列化过程中,要求实例的所有父类均应可序列化; 如果层次结构中的任何超类都不可序列化,则它必须具有默认构造器。 现在有道理了。 因此,在反序列化时,将首先搜索超类,直到找到任何不可序列化的类。 如果所有超类都可以序列化,那么 JVM 最终会到达Object
类本身,并首先创建Object
类的实例。 如果在搜索超类之间,发现任何无法序列化的类,则将使用其默认构造器在内存中分配实例。
如果要在不可序列化的情况下反序列化实例的任何超类,并且也没有默认构造函数,那么JVM会抛出“NotSerializableException
”。
另外,在继续进行对象重建之前,JVM 会检查字节流中提到的serialVersionUID
是否与该对象类的serialVersionUID
相匹配。 如果不匹配,则抛出“InvalidClassException
”。
因此,到目前为止,我们使用超类的默认构造器之一将实例保存在内存中。 请注意,此后将不会为任何类调用构造器。 执行超类构造器后,JVM 读取字节流并使用实例的元数据来设置实例的类型信息和其他元信息。
创建空白实例后,JVM 首先设置其静态字段,然后调用默认的readObject()
方法(如果未覆盖),否则会调用被覆盖的方法,该方法负责将值从字节流设置为空白实例。
readObject()
方法完成后,反序列化过程完成,您可以使用新的反序列化实例了。
请在评论区域中发表您对该主题的想法/看法/查询。 这些绝对值得欢迎。
祝您学习愉快!
使用readObject
和writeObject
的 Java 自定义序列化
原文: https://howtodoinjava.com/java/serialization/custom-serialization-readobject-writeobject/
在某些情况下,您可能需要在 Java 中使用自定义序列化。 例如,您有遗留的 Java 类,由于任何原因都不愿意对其进行修改。 也可能存在一些设计约束。 甚至简单地说,该类将在将来的发行版中进行更改,这可能会破坏先前序列化对象的反序列化。
Table of Contents
1\. Custom Serialization
2\. Default Serialization with Added Validation
3\. Summary
1. Java 自定义序列化
在大多数情况下,当自定义 Java 序列化时,您将按顺序逐一写入字段。 它最常用的方法将覆盖默认的 Java 序列化进程。
假设我们有一个User
对象,我们想自定义它的序列化过程。
public class User implements Serializable {
private static final long serialVersionUID = 7829136421241571165L;
private String firstName;
private String lastName;
private int accountNumber;
private Date dateOpened;
public User(String firstName, String lastName, int accountNumber, Date dateOpened) {
super();
this.firstName = firstName;
this.lastName = lastName;
this.accountNumber = accountNumber;
this.dateOpened = dateOpened;
}
public User() {
super();
}
public final String getFirstName() {
return firstName;
}
public final String getLastName() {
return lastName;
}
public final int getAccountNumber() {
return accountNumber;
}
public final Date getDateOpened() {
return new Date(dateOpened.getTime());
}
public final void setFirstName(String aNewFirstName) {
firstName = aNewFirstName;
}
public final void setLastName(String aNewLastName) {
lastName = aNewLastName;
}
public final void setAccountNumber(int aNewAccountNumber) {
accountNumber = aNewAccountNumber;
}
public final void setDateOpened(Date aNewDate) {
Date newDate = new Date(aNewDate.getTime());
dateOpened = newDate;
}
}
1.1 readObject()
和writeObject()
方法
要自定义序列化和反序列化,请在此类中定义readObject()
和writeObject()
方法。
- 在
writeObject()
方法内部,使用ObjectOutputStream
提供的writeXXX
方法编写类属性。 - 在
readObject()
方法内部,使用ObjectInputStream
提供的readXXX
方法读取类属性。 - 请注意,读写方法中类属性的序列必须与相同。
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable {
private static final long serialVersionUID = 7829136421241571165L;
private String firstName;
private String lastName;
private int accountNumber;
private Date dateOpened;
public User(String firstName, String lastName, int accountNumber, Date dateOpened) {
super();
this.firstName = firstName;
this.lastName = lastName;
this.accountNumber = accountNumber;
this.dateOpened = dateOpened;
}
public User() {
super();
}
//Setters and Getters
private void readObject(ObjectInputStream aInputStream) throws ClassNotFoundException, IOException
{
firstName = aInputStream.readUTF();
lastName = aInputStream.readUTF();
accountNumber = aInputStream.readInt();
dateOpened = new Date(aInputStream.readLong());
}
private void writeObject(ObjectOutputStream aOutputStream) throws IOException
{
aOutputStream.writeUTF(firstName);
aOutputStream.writeUTF(lastName);
aOutputStream.writeInt(accountNumber);
aOutputStream.writeLong(dateOpened.getTime());
}
}
现在,我们来测试代码。
1.2 测试自定义序列化
package com.howtodoinjava.io.example;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Calendar;
import java.util.Date;
public class TestCustomSerialization
{
public static void main(String[] args)
{
// Create new User object
User myDetails = new User("Lokesh", "Gupta", 102825, new Date(Calendar.getInstance().getTimeInMillis()));
// Serialization code
try
{
FileOutputStream fileOut = new FileOutputStream("User.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(myDetails);
out.close();
fileOut.close();
}
catch (IOException i)
{
i.printStackTrace();
}
// De-serialization code
User deserializedUser = null;
try
{
FileInputStream fileIn = new FileInputStream("User.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
deserializedUser = (User) in.readObject();
in.close();
fileIn.close();
// verify the object state
System.out.println(deserializedUser.getFirstName());
System.out.println(deserializedUser.getLastName());
System.out.println(deserializedUser.getAccountNumber());
System.out.println(deserializedUser.getDateOpened());
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
catch (ClassNotFoundException cnfe)
{
cnfe.printStackTrace();
}
}
}
//输出
Lokesh
Gupta
102825
Wed May 24 13:05:25 IST 2017
2. 覆盖默认序列化以添加验证
有时,您可能只需要执行任何特定的验证,或者在反序列化的对象上运行一些业务规则,而不会影响默认的 Java 序列化机制。 当您决定使用readObject()
和writeObject()
方法时,这也是可能的。
在此用例中,可以在readObject()
和writeObject()
方法中使用defaultReadObject()
和defaultWriteObject()
- 启用默认的序列化和反序列化。 然后,您可以将您的自定义验证或业务规则插入读/写方法中。
这样,在默认的序列化和反序列化过程发生后,JVM 将立即自动调用验证方法。
public class User implements Serializable {
//class attributes, constructors, setters and getters as shown above
/**
* Always treat de-serialization as a full-blown constructor, by validating the final state of the de-serialized object.
*/
private void readObject(ObjectInputStream aInputStream) throws ClassNotFoundException, IOException
{
// perform the default de-serialization first
aInputStream.defaultReadObject();
// make defensive copy of the mutable Date field
dateOpened = new Date(dateOpened.getTime());
// ensure that object state has not been corrupted or tampered with malicious code
//validateUserInfo();
}
/**
* This is the default implementation of writeObject. Customize as necessary.
*/
private void writeObject(ObjectOutputStream aOutputStream) throws IOException {
//ensure that object is in desired state. Possibly run any business rules if applicable.
//checkUserInfo();
// perform the default serialization for all non-transient, non-static fields
aOutputStream.defaultWriteObject();
}
}
再次测试代码,您将看到以下输出:
Lokesh
Gupta
102825
Wed May 24 13:10:18 IST 2017
3. 总结
如我们所见,自定义序列化在 Java 中非常容易,并且涉及非常简单的设计,即实现readObject()
和writeObject()
方法; 并添加任何其他逻辑以支持应用程序业务逻辑。
尽管在大多数情况下,默认的序列化/反序列化就足够了; 在需要时仍应在 Java 应用程序中使用自定义序列化。
将我的问题放在评论部分。
学习愉快!
使用内存序列化的 Java 深层复制
众所周知,深度克隆(和某些性能开销)或深层复制的最简单方法是序列化。 Java 序列化涉及将对象序列化为字节,然后再次从字节序列化为对象。
我建议您在仅仅需要它且不需要持久保存对象以备将来使用的情况下,使用内存深度克隆。 在这个 Java 深度克隆示例中,我将提出一种内存中深度克隆的机制供您参考。
请记住,对于单例模式来说,深克隆是邪恶的。 它使拥有多个单例类实例成为可能。
阅读更多: Java 对象克隆指南
1. Java 深层复制示例
在演示程序中,我创建了一个名为SerializableClass
的演示类。 这具有三个变量,即firstName
,lastName
和permissions
。 我将向此类添加deepCopy()
实例级方法。 每当在SerializableClass
实例上调用时,它将返回该实例的精确克隆/深层副本。
对于深度克隆,我们必须先进行序列化,然后进行反序列化。 对于序列化,我使用了 ByteArrayOutputStream
和ObjectOutputStream
。 对于反序列化,我使用了ByteArrayInputStream
和ObjectInputStream
。
package serializationTest;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
public class SerializableClass implements Serializable
{
private static final long serialVersionUID = 1L;
private String firstName = null;
private String lastName = null;
@SuppressWarnings("serial")
private List permissions = new ArrayList()
{
{
add("ADMIN");
add("USER");
}
};
public SerializableClass(final String fName, final String lName)
{
//validateNameParts(fName);
//validateNameParts(lName);
this.firstName = fName;
this.lastName = lName;
}
public SerializableClass deepCopy() throws Exception
{
//Serialization of object
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos);
out.writeObject(this);
//De-serialization of object
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream in = new ObjectInputStream(bis);
SerializableClass copied = (SerializableClass) in.readObject();
//Verify that object is not corrupt
//validateNameParts(fName);
//validateNameParts(lName);
return copied;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
@Override
public String toString() {
return new StringBuilder().append(getFirstName()+",")
.append(getLastName()+",")
.append(permissions)
.toString();
}
}
2. 演示
让我们测试该类并创建实例的深层副本,以验证其是否按预期工作。
package serializationTest;
public class ImMemoryTest
{
public static void main(String[] args) throws Exception
{
//Create instance of serializable object
SerializableClass myClass = new SerializableClass("Lokesh","Gupta");
//Verify the content
System.out.println(myClass);
//Now create a deep copy of it
SerializableClass deepCopiedInstance = myClass.deepCopy();
//Again verify the content
System.out.println(deepCopiedInstance);
}
}
程序输出。
Lokesh,Gupta,[ADMIN, USER]
Lokesh,Gupta,[ADMIN, USER]
在考虑应用程序中的内存深层复制对象之前,您可能需要阅读有关序列化指南的信息,这将防止将来的设计中断。
学习愉快!
阅读更多:
字符串方法
Java String.concat()
方法示例
原文: https://howtodoinjava.com/java/string/java-string-concat-method-example/
Java String.concat()
方法将方法参数字符串连接到字符串对象的末尾。
1. String.concat(String str)
方法
在内部,Java 用字符串对象和参数字符串的组合长度创建一个新的字符数组,并将所有内容从这两个字符串复制到此新数组中。 最后,将合并器字符数组转换为字符串对象。
public String concat(String str)
{
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
2. Java String.concat
示例
Java 程序将连接两个字符串以产生组合的字符串。 我们可以传递空字符串作为方法参数。 在这种情况下,方法将返回原始字符串。
public class StringExample
{
public static void main(String[] args)
{
System.out.println("Hello".concat(" world"));
}
}
程序输出。
Hello world
3. 不允许为null
不允许使用null
参数。 它将抛出NullPointerException
。
public class StringExample
{
public static void main(String[] args)
{
System.out.println("Hello".concat( null ));
}
}
程序输出:
Exception in thread "main" java.lang.NullPointerException
at java.lang.String.concat(String.java:2014)
at com.StringExample.main(StringExample.java:9)
学习愉快!
参考文献:
Java String.hashCode()
方法示例
原文: https://howtodoinjava.com/java/string/string-hashcode-method/
Java String.hashCode()
方法返回String
的哈希码。 哈希码值用于基于哈希的集合中,例如HashMap
,HashTable
等。在覆盖equals()
方法的每个类中都必须覆盖此方法。
1. String.hashCode()
方法
String
对象的哈希码计算为:
s[0] * 31^(n-1) + s[1] * 31^(n-2) + … + s[n-1]
其中:
s[i]
– 字符串的第i
个字符
n
– 字符串的长度,
^
– 表示幂
String.hashCode()
覆盖Object.hashCode()
。 它以整数值的形式返回哈希码。
2. Java String.hashCode()
示例
Java 程序,用于计算字符串的哈希码。
public class StringExample
{
public static void main(String[] args)
{
String blogName = "howtodoinjava.com";
System.out.println( blogName.hashCode() );
System.out.println( "hello world".hashCode() );
}
}
程序输出。
1894145264
1794106052
参考:
Java String.contains()
方法示例
原文: https://howtodoinjava.com/java/string/java-string-contains-example/
Java String.contains()
搜索给定字符串中的子字符串。 如果在该字符串中找到子字符串,则返回true
,否则返回false
。
1. String.contains()
方法
使用String.contains()
查找给定字符串中是否存在子字符串。 请记住,此方法区分区分大小写。
1.1 方法语法
该方法在内部利用indexOf()
方法来检查子字符串的索引。 如果存在子字符串,则索引将始终大于'0'
。
/**
* @param substring - substring to be searched in this string
*
* @return - true if substring is found,
* false if substring is not found
*/
public boolean contains(CharSequence substring) {
return indexOf(s.toString()) > -1;
}
1.2 null
不是有效的方法参数
String.contains()
方法不接受null
参数。 如果方法参数为null
,它将抛出NullPointerException
。
Exception in thread "main" java.lang.NullPointerException
at java.lang.String.contains(String.java:2120)
at com.StringExample.main(StringExample.java:7)
2. String.contains()
示例
查找给定字符串中是否存在查找子字符串的 Java 程序。
public class StringExample
{
public static void main(String[] args)
{
System.out.println("Hello World".contains("Hello")); //true
System.out.println("Hello World".contains("World")); //true
System.out.println("Hello World".contains("WORLD")); //false - case-sensitive
System.out.println("Hello World".contains("Java")); //false
}
}
程序输出。
true
true
false
false
在此示例中,我们学会了使用 Java String.contains()
方法来检查给定字符串中是否存在子字符串。
参考文献:
Java 中的静态导入语句
原文: https://howtodoinjava.com/java/basics/static-import-declarations-in-java/
普通的导入语句从包中导入类,因此可以在不引用包的情况下使用它们。 同样,静态导入语句从类中导入静态成员,并允许在不使用类引用的情况下使用它们。
静态导入语句也有两种形式:单静态导入和按需静态导入。 单静态导入语句从类型中导入一个静态成员。 静态按需导入语句将导入类型的所有静态成员。 静态导入语句的一般语法如下:
//Single-static-import declaration:
import static <<package name>>.<<type name>>.<<static member name>>;
//Static-import-on-demand declaration:
import static <<package name>>.<<type name>>.*;
静态导入示例
例如,您记得使用System.out.println()
方法在标准输出中打印消息。 System
是java.lang
包中的类,具有一个名为out
的静态变量。 当您使用System.out
时,您是在System
类之外引用该静态变量。 您可以使用静态导入语句从System
类中导入out
静态变量,如下所示:
import static java.lang.System.out;
您的代码现在可以在程序中使用名称out
来表示System.out
。 编译器将使用静态导入语句将名称out
解析为System.out
。
public class StaticImportTest {
public static void main(String[] args) {
out.println("Hello static import!");
}
}
静态导入规则
以下是有关静态导入语句的一些重要规则。
1)如果导入了两个具有相同简单名称的静态成员,一个使用单静态导入语句,另一个使用静态按需导入语句,则使用单静态导入语句导入的成员优先。
假设有两个类别,package1.Class1
和package2.Class2
。 这两个类都有一个称为methodA
的静态方法。 以下代码将使用package1.Class1.methodA()
方法,因为它是使用单静态导入语句导入的:
import static package1.Class1.methodA; // Imports Class1.methodA() method
import static package2.Class2.*; // Imports Class2.methodA() method too
public class Test {
public static void main(String[] args) {
methodA(); // Class1.methodA() will be called
}
}
2)不允许使用单静态导入语句导入具有相同简单名称的两个静态成员。 以下静态导入语句会产生错误,因为它们都使用相同的简单名称methodA
导入静态成员:
import static package1.Class1.methodA;
import static package1.Class2.methodA; // An error
3)如果使用单静态导入语句导入静态成员,并且在同一类中存在具有相同名称的静态成员,则使用该类中的静态成员。
// A.java
package package1;
public class A {
public static void test() {
System.out.println("package1.A.test()");
}
}
// Test.java
package package2;
import static package1.A.test;
public class Test {
public static void main(String[] args) {
test(); // Will use package2.Test.test() method, not package1.A.test() method
}
public static void test() {
System.out.println("package2.Test.test()");
}
}
Output:
package2.Test.test()
静态导入似乎可以帮助您使用静态成员的简单名称来简化程序的编写和读取。 有时,静态导入可能会在程序中引入一些细微的错误,这些错误可能很难调试。 建议您完全不使用静态导入,或仅在极少数情况下使用静态导入。
祝您学习愉快!
Java String.compareTo()
方法示例
原文: https://howtodoinjava.com/java/string/java-string-compareto-method/
Java 字符串compareTo()
方法按字典顺序比较两个字符串。 我们可以考虑基于字典的比较。
1. 字符串比较
如果字符串'str1'
在字典中的另一个字符串'str2'
之前,则在字符串比较中str2
大于'str1'
。
string1 > string2
–string1
在字典中出现string2
之后。
string1 < string2
-string1
在字典中的string2
之前。
string1 = string2
-string1
和string2
相等。
2. String.compareTo()
方法
在compareTo()
方法中,按字典顺序(字典顺序)比较两个字符串。 第一个字符串是在其上调用方法的String
对象本身。 第二个字符串是方法的参数。
此方法根据字符串中每个字符的 Unicode 值进行字符串比较。
2.1 方法返回类型
此方法的结果为整数值,其中:
- 正整数 – 表示按字典顺序字符串对象在变量字符串之后。
- 负整数 – 表示按字典顺序的字符串对象在变量字符串之前。
- 零 – 表示两个字符串相等。
2.2 方法语法
Java compareTo()
方法实现。
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
return len1 - len2;
}
3. Java String.compareTo()
示例
了解如何在 Java 字符串上调用compareTo()
方法。
public class Main
{
public static void main(String[] args)
{
System.out.println( "apple".compareTo("banana") ); //-1 - apple comes before banana
System.out.println( "apple".compareTo("cherry") ); //-2 - apple comes before cherry
System.out.println( "cherry".compareTo("banana") ); //1 - cherry comes after banana
System.out.println( "cherry".compareTo("cherry") ); //0 - Both strings are equal
}
}
4. Java String.compareToIgnoreCase()
示例
Java 程序以不区分大小写的方式比较两个字符串。 请注意,compareTo()
和compareToIgnoreCase()
方法的行为方式相同,只是后者不区分大小写。
在给定的示例中,请注意前两个语句中的字符串比较,如何更改字符串的大小写可能如何更改结果和顺序。
再次注意,在将每个字符转换为 unicode 值之后,将对两个字符串进行逐字符比较。
public class Main
{
public static void main(String[] args)
{
System.out.println( "apple".compareTo("BANANA") ); //31
System.out.println( "apple".compareToIgnoreCase("banana") ); //-1
System.out.println( "cherry".compareTo("cherry") ); //0
System.out.println( "cherry".compareToIgnoreCase("CHERRY") ); //0
}
}
学习愉快!
Java String.compareToIgnoreCase()
方法示例
原文: https://howtodoinjava.com/java/string/string-comparetoignorecase-example/
Java 字符串compareToIgnoreCase()
方法按字典顺序比较两个字符串,忽略大小写。 该方法与String.compareTo()
方法相同,但compareTo()
方法区分大小写。
1. String.compareToIgnoreCase()
方法
在compareToIgnoreCase()
方法中,按照字典顺序(字典顺序)忽略大小写比较两个字符串。 第一个字符串是在其上调用方法的String
对象本身。 第二个字符串是方法的参数。
此方法根据字符串中每个字符的 Unicode 值进行字符串比较。
1.1 方法返回类型
此方法的结果为整数值,其中:
- 正整数 – 表示按字典顺序字符串对象在变量字符串之后。
- 负整数 – 表示按字典顺序的字符串对象在变量字符串之前。
- 零 – 表示两个字符串相等。
1.2 方法实现
此方法使用CaseInsensitiveComparator
类,它是字符串类的静态内部类。 字符串比较是通过compare()
方法完成的。
public int compare(String s1, String s2) {
int n1 = s1.length();
int n2 = s2.length();
int min = Math.min(n1, n2);
for (int i = 0; i < min; i++) {
char c1 = s1.charAt(i);
char c2 = s2.charAt(i);
if (c1 != c2) {
c1 = Character.toUpperCase(c1);
c2 = Character.toUpperCase(c2);
if (c1 != c2) {
c1 = Character.toLowerCase(c1);
c2 = Character.toLowerCase(c2);
if (c1 != c2) {
// No overflow because of numeric promotion
return c1 - c2;
}
}
}
}
return n1 - n2;
}
2. Java String.compareToIgnoreCase()
示例
Java 程序以不区分大小写的方式比较两个字符串。 请注意,compareTo()
和compareToIgnoreCase()
方法的行为方式相同,只是后者不区分大小写。
public class Main
{
public static void main(String[] args)
{
System.out.println( "apple".compareTo("BANANA") ); //31
System.out.println( "apple".compareToIgnoreCase("banana") ); //-1
System.out.println( "cherry".compareTo("cherry") ); //0
System.out.println( "cherry".compareToIgnoreCase("CHERRY") ); //0
}
}
3. compareToIgnoreCase()
与equalsIgnoreCase()
了解compareToIgnoreCase()
与equalsIgnoreCase()
方法之间的主要区别。
compareToIgnoreCase()
在字典上进行比较(字典顺序)。
equalsIgnoreCase()
检查两个字符串是否相等的字符串相等性。 虽然两者都不区分大小写。compareToIgnoreCase()
的返回类型是整数类型,它表示一个字符串大于,小于或等于另一个字符串。
equalsIgnoreCase()
返回类型是布尔值,这意味着两个字符串相等或不相等。
4. Java String.compareTo()
示例
Java 程序使用String.compareTo()
方法比较字符串。
public class Main
{
public static void main(String[] args)
{
System.out.println( "apple".compareTo("banana") ); //-1 - apple comes before banana
System.out.println( "apple".compareTo("cherry") ); //-2 - apple comes before cherry
System.out.println( "cherry".compareTo("banana") ); //1 - cherry comes after banana
System.out.println( "cherry".compareTo("cherry") ); //0 - Both strings are equal
}
}
学习愉快!
Java String.equals()
方法 – 字符串比较
原文: https://howtodoinjava.com/java/string/string-equals-method/
Java 字符串equals()
方法用于将字符串与方法参数对象进行比较。
1. Java String.equals()
方法
/**
* @param anObject - The object to compare
* @return true - if the non-null argument object represents the same sequence of characters to this string
* false - in all other cases
*/
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
- 字符串类将覆盖
Object
类中的equals()
方法。 相等性以区分大小写的方式完成。 - 使用
equals()
方法检查字符串内容的相等性。 - 不要使用
'=='
运算符。 它检查对象引用,这在大多数情况下是不希望的。 - 允许将
null
传递给方法。 它将返回false
。 - 使用
equalsIgnoreCase()
方法以不区分大小写的方式检查相等的字符串。
2. Java String.equals()
方法示例
检查两个字符串是否相等的 Java 程序(区分大小写)。
public class Main
{
public static void main(String[] args)
{
String blogName = "howtodoinjava.com";
String authorName = "Lokesh gupta";
//1 - check two strings for same character sequence
boolean isEqualString = blogName.equals(authorName); //false
//2
isEqualString = blogName.equals("howtodoinjava.com"); //true
//3
isEqualString = blogName.equals(null); //false
//4 - case-sensitive
isEqualString = blogName.equals("HOWTODOINJAVA.COM"); //false
}
}
3. Java String.equalsIgnoreCase()
示例
检查两个字符串是否相等的 Java 程序(不区分大小写)。 请注意,equals()
和equalsIgnoreCase()
方法的行为方式相同,只是后者不区分大小写。
public static void main(String[] args)
{
String blogName = "howtodoinjava.com";
String authorName = "Lokesh gupta";
//1 - check two strings for same character sequence
boolean isEqualString = blogName.equalsIgnoreCase(authorName); //false
//2
isEqualString = blogName.equalsIgnoreCase("howtodoinjava.com"); //true
//3
isEqualString = blogName.equalsIgnoreCase(null); //false
//4 - case-insensitive
isEqualString = blogName.equalsIgnoreCase("HOWTODOINJAVA.COM"); //TRUE
}
}
4. Java 中==
和equals
之间的区别
如前所述,==
运算符检查相同的对象引用。 它不检查字符串内容。
而equals()
方法检查字符串内容。
public class Main
{
public static void main(String[] args)
{
String blogName1 = new String("howtodoinjava.com");
String blogName2 = new String("howtodoinjava.com");
boolean isEqual1 = blogName1.equals(blogName2); //true
boolean isEqual2 = blogName1 == blogName2; //false
}
}
学习愉快!
参考:
Java String.equalsIgnoreCase()
方法 – 不区分大小写的比较
原文: https://howtodoinjava.com/java/string/string-equalsignorecase-method/
Java 字符串equalsIgnoreCase()
方法用于将字符串与方法参数对象进行比较,而忽略大小写考虑。
在equalsIgnoreCase()
方法中,如果两个字符串长度相同,并且忽略大小写,则两个字符串中的对应字符相等,则认为它们相等。
1. Java String.equalsIgnoreCase()
方法
/**
* @param anObject - The object to compare
* @return true - if the non-null argument string represents the same sequence of characters to this string
* false - in all other cases
*/
public boolean equalsIgnoreCase(String anotherString) {
return (this == anotherString) ? true
: (anotherString != null)
&& (anotherString.value.length == value.length)
&& regionMatches(true, 0, anotherString, 0, value.length);
}
- Java
equalsIgnoreCase()
方法用于以不区分大小写的方式检查相等的字符串。 - 不要使用
'=='
运算符。 它检查对象引用,这在大多数情况下是不希望的。 - 允许将
null
传递给方法。 它将返回false
。
2. Java String.equalsIgnoreCase()
示例
检查两个字符串是否相等的 Java 程序(不区分大小写)。 请注意,equals()
和equalsIgnoreCase()
方法的行为方式相同,只是后者不区分大小写。
public static void main(String[] args)
{
String blogName = "howtodoinjava.com";
String authorName = "Lokesh gupta";
//1 - case-insensitive comparison
isEqualString = blogName.equalsIgnoreCase("HOWTODOINJAVA.COM"); //true
//2 - case-insensitive comparison
isEqualString = blogName.equalsIgnoreCase("howtodoinjava.com"); //true
//3 - check two strings for same character sequence ignoring case
boolean isEqualString = blogName.equalsIgnoreCase(authorName); //false
//4 - null is allowed
isEqualString = blogName.equalsIgnoreCase(null); //false
}
}
3. Java String.equals()
示例
Java 程序使用equals
方法检查两个字符串是否相等(区分大小写)。
public class Main
{
public static void main(String[] args)
{
String blogName = "howtodoinjava.com";
String authorName = "Lokesh gupta";
//1 - check two strings for same character sequence
boolean isEqualString = blogName.equals(authorName); //false
//2
isEqualString = blogName.equals("howtodoinjava.com"); //true
//3
isEqualString = blogName.equals(null); //false
//4 - case-sensitive
isEqualString = blogName.equals("HOWTODOINJAVA.COM"); //false
}
}
4. equals
和equalsIgnoreCase
之间的区别
显然,在执行字符串比较时,Java 中equals
和equalsIgnoreCase
之间的差异是区分大小写。
equals()
方法进行区分大小写的比较。equalsIgnoreCase()
方法进行不区分大小写的比较。
学习愉快!
Java String.charAt()
方法示例
原文: https://howtodoinjava.com/java/string/string-charat-method-example/
方法java.lang.String.charAt(int index)
返回字符串对象中指定的index
变量处的字符。
众所周知,Java 字符串内部存储在char
数组中。 此方法仅使用index
从字符串对象中的后备char
数组获取字符。
1. charAt()
方法参数
唯一的方法参数是index
。 它必须是int
类型。index
参数必须为:
- 大于等于“0”
- 小于字符串字符的长度,即
str.length()
任何无效的索引参数将导致StringIndexOutOfBoundsException
。
2. Java String.charAt()
方法示例
让我们学习结合使用String.charAt()
方法和实时示例。
public class StringExample
{
public static void main(String[] args) throws Exception
{
String blogName = "howtodoinjava.com";
char c1 = blogName.charAt(0); //first character
char c2 = blogName.charAt(blogName.length() - 1); //last character
char c3 = blogName.charAt( 5 ); //random character
System.out.println("Character at 0 index is: "+c1);
System.out.println("Character at last is: "+c2);
System.out.println("Character at 5 index is: "+c3);
char c4 = blogName.charAt( 50 ); //invalid index
}
}
程序输出:
Character at 0 index is: h
Character at last is: m
Character at 5 index is: d
Exception in thread "main" java.lang.StringIndexOutOfBoundsException:
String index out of range: 50
at java.lang.String.charAt(String.java:658)
at com.howtodoinjava.demo.StringExample.main(StringExample.java:17)
在此示例中,我们通过示例了解了String
类的charAt()
方法。
学习愉快!
参考:
Java String.indexOf()
方法示例
原文: https://howtodoinjava.com/java/string/java-string-indexof-method-example/
Java 字符串indexOf()
方法返回给定参数字符或字符串的索引。 如果在字符串中找不到参数,则方法返回-1
。 字符串的索引计数器从零开始。
Java String.indexOf()
方法语法
String.indexOf()
方法具有四种重载形式:
序号 | 方法语法 | 描述 |
---|---|---|
1. | int indexOf(String substring) |
返回给定子字符串的索引位置 |
2. | int indexOf(String substring, int fromIndex) |
从fromIndex 位置返回给定子字符串的索引位置 |
3. | int indexOf(int ch) |
返回给定char 值的索引位置 |
4. | int indexOf(int ch, int fromIndex) |
返回给定char 值和fromIndex 位置的索引位置 |
不允许使用null
参数
不允许将null
参数传递给indexOf()
方法。 这将导致NullPointerException
异常。
String blogName = "howtodoinjava.com";
System.out.println( blogName.indexOf(null) );
//Program output
Exception in thread "main" java.lang.NullPointerException
at java.lang.String.indexOf(String.java:1705)
at java.lang.String.indexOf(String.java:1685)
at com.StringExample.main(StringExample.java:9)
1. Java String.indexOf(String substring)
示例
Java 程序使用indexOf(String substring)
方法在给定的字符串对象中查找子字符串的索引。
public class StringExample
{
public static void main(String[] args)
{
String blogName = "howtodoinjava.com";
System.out.println( blogName.indexOf("java") ); //9
System.out.println( "hello world".indexOf("world") ); //6
System.out.println( "hello world".indexOf("earth") ); //-1
}
}
程序输出。
9
6
-1
2. Java String.indexOf(String substring, int fromIndex)
示例
Java 程序,使用给定的indexOf(String substring, int fromIndex, int fromIndex)
方法,在给定的fromIndex
中查找给定字符串对象中的substring
索引。
请注意,找到子字符串时,索引计数仅从 0 索引开始,并且仅从字符串开头开始。
public class StringExample
{
public static void main(String[] args)
{
String blogName = "howtodoinjava.com";
System.out.println( blogName.indexOf("java", 5) ); //9
System.out.println( "hello world".indexOf("world", 6) ); //6
System.out.println( "hello world".indexOf("world", 2) ); //6
System.out.println( "hello world".indexOf("world", 10) ); //-1
}
}
程序输出:
9
6
6
-1
3. Java String.indexOf(char ch)
示例
Java 程序使用indexOf(char ch)
方法在给定的字符串对象中查找给定字符'ch'
的索引。
public class StringExample
{
public static void main(String[] args)
{
String blogName = "howtodoinjava.com";
System.out.println( blogName.indexOf('j') ); //9
System.out.println( "hello world".indexOf('w') ); //6
System.out.println( "hello world".indexOf('k') ); //-1
}
}
程序输出:
9
6
-1
4. Java String.indexOf(int ch, int fromIndex)
示例
Java 程序使用给定的indexOf(String substring, int fromIndex)
方法从给定的fromIndex
位置开始在给定的字符串对象中查找字符'ch'
的索引。
请注意,找到字符后,索引计数仅从 0 索引开始,并且仅从字符串的开头开始。
public class StringExample
{
public static void main(String[] args)
{
String blogName = "howtodoinjava.com";
System.out.println( blogName.indexOf('j', 4) ); //9
System.out.println( "hello world".indexOf('w', 2) ); //6
System.out.println( "hello world".indexOf('w', 6) ); //6
System.out.println( "hello world".indexOf('k') ); //-1
}
}
程序输出:
9
6
6
-1
学习愉快!
Java String.lastIndexOf()
方法示例
原文: https://howtodoinjava.com/java/string/string-lastindexof-method/
Java 字符串lastIndexOf()
方法返回指定参数字符或字符串的最后一个索引。 如果在字符串中找不到参数,则方法返回-1
。 字符串的索引计数器从零开始。
Java String.lastIndexOf()
方法语法
String.lastIndexOf()
方法具有四种重载形式:
| 序号 | 方法语法 | 描述 |
| --- | --- |
| 1. | int lastIndexOf(String substring)
| 返回给定substring
的最后一个索引位置 |
| 2. | int lastIndexOf(String substring, int fromIndex int)
| 返回给定substring
的最后一个索引位置,从指定的fromIndex
开始向后搜索 |
| 3. | int lastIndexOf(int ch)
| 返回给定char
值的最后一个索引位置 |
| 4. | int lastIndexOf(int ch, int fromIndex)
| 返回给定char
值的索引位置,从指定的fromIndex
开始向后搜索 |
不允许使用null
参数
不允许将null
参数传递给lastIndexOf()
方法。 这将导致NullPointerException
异常。
String blogName = "howtodoinjava.com";
System.out.println( blogName.lastIndexOf(null) );
//Program output
Exception in thread "main" java.lang.NullPointerException
at java.lang.String.lastIndexOf(String.java:1705)
at java.lang.String.lastIndexOf(String.java:1685)
at com.StringExample.main(StringExample.java:9)
1. Java String.lastIndexOf(String substring)
示例
Java 程序使用lastIndexOf(String substring)
方法在给定的字符串对象中查找子字符串的最后一个索引。
public class StringExample
{
public static void main(String[] args)
{
String blogName = "howtodoinjava.com";
System.out.println( blogName.lastIndexOf("java") ); //9
System.out.println( "hello world".lastIndexOf("world") ); //6
System.out.println( "hello world".lastIndexOf("earth") ); //-1
}
}
程序输出。
9
6
-1
2. Java String.lastIndexOf(String substring, int fromIndex)
示例
Java 程序查找给定字符串对象中substring
的最后一个索引,并使用indexOf(String substring, int fromIndex)
方法从指定的fromIndex
开始向后搜索。
请注意,找到子字符串时,索引计数仅从 0 索引开始,并且仅从字符串开头开始。
public class StringExample
{
public static void main(String[] args)
{
String blogName = "howtodoinjava.com";
System.out.println( blogName.indexOf("java", 5) ); //9
System.out.println( "hello world".indexOf("world", 6) ); //6
System.out.println( "hello world".indexOf("world", 2) ); //6
System.out.println( "hello world".indexOf("world", 10) ); //-1
}
}
程序输出:
9
6
6
-1
3. Java String.lastIndexOf(char ch)
示例
Java 程序使用lastIndexOf(char ch)
方法在给定的字符串对象中找到给定字符'ch'
的最后一个索引。
public class StringExample
{
public static void main(String[] args)
{
String blogName = "howtodoinjava.com";
System.out.println( blogName.lastIndexOf('j') ); //9
System.out.println( "hello world".lastIndexOf('w') ); //6
System.out.println( "hello world".lastIndexOf('k') ); //-1
}
}
程序输出:
9
6
-1
4. Java String.lastIndexOf(int ch, int fromIndex)
示例
Java 程序在给定的字符串对象中查找字符'ch'
的最后一个索引,然后使用lastIndexOf(String substring, int fromIndex)
方法从指定的fromIndex
开始向后搜索。
请注意,找到字符后,索引计数仅从 0 索引开始,并且仅从字符串的开头开始。
public class StringExample
{
public static void main(String[] args)
{
String blogName = "howtodoinjava.com";
System.out.println( blogName.lastIndexOf('j', 4) ); //9
System.out.println( "hello world".lastIndexOf('w', 2) ); //6
System.out.println( "hello world".lastIndexOf('w', 6) ); //6
System.out.println( "hello world".lastIndexOf('k') ); //-1
}
}
程序输出:
9
6
6
-1
学习愉快!
参考:
Java String.intern()
方法示例
原文: https://howtodoinjava.com/java/string/java-string-intern-method-example/
Java String.intern()
返回字符串池中存在的相等字符串字面值的引用。 如果字符串池中存在现有的字符串字面值,则返回其引用。 否则,将创建具有相同内容的新字符串,并返回对新字符串的引用。
使用String.equals()
方法检查字符串是否相等。
1. 字符串池
字符串池是堆内存中的保留内存区域,Java 用于存储字符串常量。 请注意,默认情况下 Java 字符串是不可变的。
Java 在字符串池中仅存储每个不同String
值的一个副本。 它有助于在程序执行期间重用String
对象以节省内存。 在运行的程序中可能有很多对字符串的引用,但是在字符串池中只有字符串的副本。
1.1 两种创建字符串的方法
在 Java 中,我们可以通过两种方式创建字符串。
String str1 = new String("hello world");
String str2 = "hello world";
在上面的示例中,两种方法都用于创建字符串,但是建议稍后使用字符串字面值。 字符串字面值总是进入字符串池。
当我们使用new
关键字创建字符串时,将创建两个对象,即一个在堆区中,另一个在字符串常量池中。 创建的字符串对象引用始终指向堆区域对象。
要获取在字符串池中创建的相同对象的引用,请使用intern()
方法。
2. Java String.intern()
方法
String.intern()
返回对字符串池中存在的相等字符串字面值的引用。
众所周知,所有字符串字面值都是在字符串池中自动创建的,因此intern()
方法适用于通过'new'
关键字创建的String
对象。
String.intern()
是原生方法。 借助intern()
方法,可以获得原始字符串对象对应的String
常量池对象的引用。
3. Java String.intern()
示例
Java 程序使用String.intern()
方法插入字符串。
public class StringExample
{
public static void main(String[] args)
{
//String object in heap
String str1 = new String("hello world");
//String literal in pool
String str2 = "hello world";
//String literal in pool
String str3 = "hello world";
//String object interned to literal
//It will refer to existing string literal
String str4 = str1.intern();
System.out.println(str1 == str2); //false
System.out.println(str2 == str3); //true
System.out.println(str2 == str4); //true
}
}
程序输出。
false
true
true
在此示例中,我们学习了内联 Java 中的字符串。 这是本机方法,可提供高性能。
参考文献:
Java String.split()
方法示例
原文: https://howtodoinjava.com/java/string/java-string-split-example/
Java String.split()
在将字符串拆分为方法参数正则表达式的匹配项之后,返回字符串数组。
1. String.split()
方法
通过使用分隔符子字符串或正则表达式进行分割,可以使用String.split()
将字符串拆分为字符串数组。
1.1 方法语法
String.split()
方法已重载,有两种变体。
/**
* @param regex - the delimiting regular expression
* @param limit - the result threshold
*
* @return - the array of strings
*/
public String[] split(String regex);
public String[] split(String regex, int limit);
1.2 抛出PatternSyntaxException
请注意,如果正则表达式的语法无效,则split()
会引发PatternSyntaxException
。 在给定的示例中,"["
是无效的正则表达式。
public class StringExample
{
public static void main(String[] args)
{
String[] strArray = "hello world".split("[");
}
}
程序输出。
Exception in thread "main" java.util.regex.PatternSyntaxException: Unclosed character class near index 0
[
^
at java.util.regex.Pattern.error(Pattern.java:1955)
at java.util.regex.Pattern.clazz(Pattern.java:2548)
at java.util.regex.Pattern.sequence(Pattern.java:2063)
at java.util.regex.Pattern.expr(Pattern.java:1996)
at java.util.regex.Pattern.compile(Pattern.java:1696)
at java.util.regex.Pattern.<init>(Pattern.java:1351)
at java.util.regex.Pattern.compile(Pattern.java:1028)
at java.lang.String.split(String.java:2367)
at java.lang.String.split(String.java:2409)
at com.StringExample.main(StringExample.java:9)
1.3 null
不是有效的方法参数
方法不接受null
参数。 如果方法参数为null
,它将抛出NullPointerException
。
Exception in thread "main" java.lang.NullPointerException
at java.lang.String.split(String.java:2324)
at com.StringExample.main(StringExample.java:11)
2. Java String.split(String regex)
示例
2.1 Java 用字或分隔符分割字符串
Java 程序根据某些标记拆分字符串。 在给定的示例中,我为分隔符连字符"-"
拆分字符串。
public class StringExample
{
public static void main(String[] args)
{
String str = "how to do-in-java-provides-java-tutorials";
String[] strArray = str.split("-");
System.out.println(Arrays.toString(strArray));
}
}
程序输出:
[how to do, in, java, provides, java, tutorials]
与之相似:
2.2 Java 按空格分割字符串
Java 程序按空格分割字符串。
public class StringExample
{
public static void main(String[] args)
{
String str = "how to do in java provides java tutorials";
String[] strArray = str.split("\\s");
System.out.println(Arrays.toString(strArray));
}
}
程序输出:
[how, to, do, in, java, provides, java, tutorials]
2.2 Java 用多个分隔符分割字符串
Java 程序用多个分隔符分割字符串。 在多个分隔符之间使用正则表达式或运算符('|'
)符号。
public class StringExample
{
public static void main(String[] args)
{
String str = "how-to-do-in-java. provides-java-tutorials.";
String[] strArray = str.split("-|\\.");
System.out.println(Arrays.toString(strArray));
}
}
程序输出:
[how, to, do, in, java, provides, java, tutorials]
3. Java String.split(String regex, int limit)
示例
此版本的方法也拆分字符串,但是标记的最大数量不能超过limit
参数。
Java 程序以空格分割字符串,例如最大标记数不能超过'5'
。
public class StringExample
{
public static void main(String[] args)
{
String str = "how to do in java provides java tutorials";
String[] strArray = str.split("\\s", 5);
System.out.println(strArray.length); //5
System.out.println(Arrays.toString(strArray));
}
}
程序输出:
5
[how, to, do, in, java provides java tutorials]
在此示例中,我们学习了在 Java 中将字符串拆分为数组。 我们看到了示例如何使用分隔符在 Java 中拆分字符串。
参考文献:
Java String.replace()
方法示例
原文: https://howtodoinjava.com/java/string/java-string-replace-method/
Java String.replace()
方法替换与目标子字符串匹配的该字符串的每个子字符串。 子字符串匹配过程从字符串的开头(索引 0)开始。
1. String.replace()
方法
String.replace()
方法是 Java 中的重载方法。 它有两个变体。
public String replace(char oldChar, char newChar)
– 返回一个字符串,该字符串是用newChar
替换此字符串中所有出现的oldChar
的结果。public String replace(CharSequence target, CharSequence replacement)
– 返回将此字符串中所有出现的target
子字符串替换为replacement
子字符串而产生的字符串。
2. Java String.replace(char oldChar, char newChar)
示例
Java 程序,用新字符替换所有出现的给定字符。 在给定的示例中,我将所有出现的字母“o
”(小写)替换为字母“O
”(大写)。
public class StringExample
{
public static void main(String[] args)
{
String originalString = "Hello world !!";
String newString = originalString.replace('o', 'O'); //HellO wOrld !!
System.out.println(originalString);
System.out.println(newString);
}
}
程序输出。
Hello world !!
HellO wOrld !!
2. Java String.replace(CharSequence target, CharSequence replacement)
示例
Java 程序,用新的子字符串replacement
替换所有出现的给定子字符串'target'
。
在给定的示例中,我将所有出现的子字符串“java
”替换为大写的“JAVA
”字符串。
public class StringExample
{
public static void main(String[] args)
{
String originalString = "how to do in java - java tutotials";
String newString = originalString.replace("java", "JAVA");
System.out.println(originalString);
System.out.println(newString);
}
}
程序输出:
how to do in java - java tutotials
how to do in JAVA - JAVA tutotials
请注意,不允许正则表达式作为方法参数。 如果要使用正则表达式,请使用 String
replaceAll()
方法。
3. 不允许为null
两个方法的参数均不允许使用null
。 它将抛出NullPointerException
。
public class StringExample
{
public static void main(String[] args)
{
String newString = "hello world".replace("world", null);
//or
//String newString = "hello world".replace(null, "world");
}
}
程序输出:
Exception in thread "main" java.lang.NullPointerException
at java.lang.String.replace(String.java:2227)
at com.StringExample.main(StringExample.java:7)
学习愉快!
参考文献:
Java hashCode()
和equals()
– 契约,规则和最佳实践
原文: https://howtodoinjava.com/java/basics/java-hashcode-equals-methods/
了解 Java hashCode()
和equals()
方法,它们的默认实现以及如何正确覆盖它们的信息。 另外,请学习使用 Apache Commons 包的工具类HashCodeBuilder
和EqualsBuilder
实现这些方法。
hashCode()
和equals()
方法已在Object
类中定义,该类是 Java 对象的父类。 因此,所有 java 对象都继承这些方法的默认实现。
Table of Contents:
1) Usage of hashCode() and equals() Methods
2) Override the default behavior
3) EqualsBuilder and HashCodeBuilder
4) Generate hashCode() and equals() using Eclipse
5) Important things to remember
6) Special Attention When Using in ORM
1. hashCode()
和equals()
方法的用法
-
equals(Object otherObject)
– 顾名思义,该方法用于简单地验证两个对象的相等性。 默认实现是简单地检查两个对象的对象引用以验证它们的相等性。 默认情况下,当且仅当两个对象存储在相同的内存地址中时,两个对象才相等。 -
hashcode()
– 在运行时为对象返回唯一的整数值。 默认情况下,整数值主要来自堆中对象的内存地址(但并非总是强制性的)。当此对象需要存储在某些
HashTable
之类的数据结构中时,此哈希码用于确定存储桶位置。
1.1 hashCode()
和equals()
之间的协定
通常,无论何时覆盖equals()
方法,都必须覆盖hashCode()
方法,以维护hashCode()
方法的常规协定,该协定规定相等的对象必须具有相等的哈希码。
- 在 Java 应用程序执行期间,只要在同一个对象上多次调用它,
hashCode
方法必须一致地返回相同的整数,前提是未修改该对象在equals
比较中使用的信息。
从一个应用程序的一次执行到同一应用程序的另一次执行,此整数不必保持一致。 - 如果根据
equals(Object)
方法两个对象相等,则在两个对象中的每个对象上调用hashCode
方法必须产生相同的整数结果。 - 如果不是,则根据
equals(java.lang.Object)
方法如果两个对象不相等,则在两个对象中的每一个上调用hashCode
方法必须产生不同的整数结果。
但是,程序员应该意识到,为不相等的对象生成不同的整数结果可能会提高哈希表的性能。
2. 覆盖hashCode()
和equals()
的默认行为
一切正常,直到您没有在类中覆盖这些方法中的任何一个。 但是,有时应用程序需要更改某些对象的默认行为。 让我们理解为什么我们需要覆盖equals
和hashCode
方法。
2.1 默认行为
让我们以您的应用程序具有Employee
对象的示例为例。 让我们创建Employee
类的最小可能结构:
public class Employee
{
private Integer id;
private String firstname;
private String lastName;
private String department;
//Setters and Getters
}
在Employee
类之上具有一些非常基本的属性及其访问器方法。 现在考虑一个简单的情况,您需要比较两个员工对象。
public class EqualsTest {
public static void main(String[] args) {
Employee e1 = new Employee();
Employee e2 = new Employee();
e1.setId(100);
e2.setId(100);
System.out.println(e1.equals(e2)); //false
}
}
没有猜中奖。 上述方法将打印false
。 但是,在知道两个对象代表同一位员工之后,这真的正确吗? 在实时应用程序中,这应该返回true
。
2.2 我们应该只覆盖equals()
方法吗?
为了实现正确的应用程序行为,我们需要覆盖equals()
方法,如下所示:
public boolean equals(Object o) {
if(o == null)
{
return false;
}
if (o == this)
{
return true;
}
if (getClass() != o.getClass())
{
return false;
}
Employee e = (Employee) o;
return (this.getId() == e.getId());
}
将此方法添加到Employee
类中,EqualsTest
将开始返回true
。
我们完成了吗? 还没。 让我们以不同的方式再次在修改后的Employee
类上进行测试。
import java.util.HashSet;
import java.util.Set;
public class EqualsTest
{
public static void main(String[] args)
{
Employee e1 = new Employee();
Employee e2 = new Employee();
e1.setId(100);
e2.setId(100);
//Prints 'true'
System.out.println(e1.equals(e2));
Set<Employee> employees = new HashSet<Employee>();
employees.add(e1);
employees.add(e2);
System.out.println(employees); //Prints two objects
}
}
上面的类在第二个打印语句中打印两个对象。 如果两个员工对象都相等,则在仅存储唯一对象的Set
中,在所有两个对象都引用同一员工之后,HashSet
中必须只有一个实例。 我们缺少什么?
2.3 还覆盖hashCode()
方法
我们缺少第二种重要方法hashCode()
。 如 Java 文档所述,如果您覆盖equals()
方法,则必须覆盖hashCode()
方法。 因此,让我们在Employee
类中添加另一个方法。
@Override
public int hashCode()
{
final int PRIME = 31;
int result = 1;
result = PRIME * result + getId();
return result;
}
在Employee
类中添加上述方法后,第二条语句仅开始打印第二条语句中的单个对象,并且从而验证e1
和e2
的真实相等性。
3. EqualsBuilder
和HashCodeBuilder
工具类
Apache Commons 提供了两个出色的工具类,HashCodeBuilder
和EqualsBuilder
,用于生成hashCode
和equals
方法。 以下是其用法:
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
public class Employee
{
private Integer id;
private String firstname;
private String lastName;
private String department;
//Setters and Getters
@Override
public int hashCode()
{
final int PRIME = 31;
return new HashCodeBuilder(getId()%2==0?getId()+1:getId(), PRIME).toHashCode();
}
@Override
public boolean equals(Object o) {
if (o == null)
return false;
if (o == this)
return true;
if (o.getClass() != getClass())
return false;
Employee e = (Employee) o;
return new EqualsBuilder().
append(getId(), e.getId()).
isEquals();
}
}
4. 使用 Eclipse 生成hashCode()
和equals()
如果您使用任何代码编辑器,那么大多数编辑器也能够为您生成一些良好的结构。 例如, Eclipse IDE 可以为您生成hashCode()
和equals()
的非常好的实现。
右键单击“Java 文件->源->生成
hashCode()
和equals()
…”
在 Eclipse 中生成hashCode()
和equals()
5. Java hashCode()
和equals()
最佳实践
- 始终使用对象的相同属性来生成
hashCode()
和equals()
。 在本例中,我们使用了id
雇员。 equals()
必须与一致(如果未修改对象,则它必须保持返回相同的值)。- 每当
a.equals(b)
时,则a.hashCode()
必须与b.hashCode()
相同。 - 如果覆盖一个,则应覆盖另一个。
6. 在 ORM 中使用时应特别注意
如果您要处理 ORM,请确保始终使用获取器,并且切勿在hashCode()
和equals()
中使用字段引用。 这是有原因的,在 ORM 中,字段有时是延迟加载的,直到调用它们的获取器方法才可用。
例如,在我们的Employee
类中,如果我们使用e1.id == e2.id
。 id
字段很可能是延迟加载的。 因此,在这种情况下,一个可能为零或为null
,从而导致错误的行为。
但是如果使用*e1.getId() == e2.getId()*
,即使字段是延迟加载,我们也可以确保; 调用获取器将首先填充该字段。
这就是我对 hashCode()
和equals()
方法所了解的全部。 我希望这会对某人有所帮助。
如果您感觉到我在某处缺少任何东西或有什么不对,请发表评论。 我将再次更新此帖子以帮助他人。
学习愉快!
Java String.replaceFirst()
方法示例
原文: https://howtodoinjava.com/java/string/java-string-replacefirst-example/
Java String replaceFirst()
方法用给定的替换子字符串替换找到的第一个子字符串'regex'
,该子字符串与给定的参数子字符串(或正则表达式)匹配。 子字符串匹配过程从字符串的开头(索引 0)开始。
1. String.replaceFirst(String regex,String replacement)
方法
String.replaceFirst()
方法使用正则表达式查找子字符串并将其替换为replacement
子字符串参数。
/**
* @param regex - the regular expression to which this string is to be matched
* @param replacement - the string to be substituted for the first match
*
* @return The resulting string after replacement is done
*/
public String replaceFirst(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceFirst(replacement);
}
2. Java String.replaceFirst()
示例
Java 程序,用新的子字符串替换字符串中给定字符串或正则表达式的第一个匹配项。 在给定的示例中,我将大写的“JAVA
”字符串替换为第一次出现的子字符串“java
”。
public class StringExample
{
public static void main(String[] args)
{
String str = "Java says hello world. Java String tutorial";
//Replace first occurrence of substring "Java" with "JAVA"
String newStr = str.replaceFirst("Java", "JAVA");
//Replace first occurrence of substring "a" with "A"
String regexResult = str.replaceFirst("[a]", "A");
System.out.println(newStr);
System.out.println(regexResult);
}
}
程序输出。
JAVA says hello world. Java String tutorial
JAva says hello world. Java String tutorial
3. 不允许为null
两个方法的参数均不允许使用null
。 它将抛出NullPointerException
。
public class StringExample
{
public static void main(String[] args)
{
String str = "Java says hello world. Java String tutorial";
String newStr = str.replaceFirst("Java", null);
System.out.println(newStr);
}
}
程序输出:
Exception in thread "main" java.lang.NullPointerException: replacement
at java.util.regex.Matcher.replaceFirst(Matcher.java:999)
at java.lang.String.replaceFirst(String.java:2165)
at com.StringExample.main(StringExample.java:9)
在此示例中,我们学习了替换 Java 中字符串中首次出现的字符。
学习愉快!
参考文献:
Java String
方法和示例
Java String
Doc
Java String.replaceAll()
方法示例
原文: https://howtodoinjava.com/java/string/java-string-replaceall-example/
Java String.replaceAll()
用给定的替换替换与给定的正则表达式匹配的每个子字符串后,将返回一个字符串。
1. String.replaceAll()
方法
使用String.replaceAll(String regex, String replacement)
将所有出现的子字符串(匹配参数regex
)替换为replacement
字符串。
1.1 方法语法
/**
* @param regex - regular expression to match in given string
* @param replacement : replacement string to be replaced
*
* @return result string after replacing all occurrence of
* matching 'regex' with replacement 'substring'
*/
public String replaceAll(String regex, String replacement)
{
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}
1.2 抛出PatternSyntaxException
请注意,如果正则表达式的语法无效,则replaceAll()
会引发PatternSyntaxException
。 在给定的示例中,"["
是无效的正则表达式。
public class StringExample
{
public static void main(String[] args)
{
String newStr = "hello world".replaceAll("[", "");
}
}
程序输出。
Exception in thread "main" java.util.regex.PatternSyntaxException: Unclosed character class near index 0
[
^
at java.util.regex.Pattern.error(Pattern.java:1955)
at java.util.regex.Pattern.clazz(Pattern.java:2548)
at java.util.regex.Pattern.sequence(Pattern.java:2063)
at java.util.regex.Pattern.expr(Pattern.java:1996)
at java.util.regex.Pattern.compile(Pattern.java:1696)
at java.util.regex.Pattern.<init>(Pattern.java:1351)
at java.util.regex.Pattern.compile(Pattern.java:1028)
at java.lang.String.replaceAll(String.java:2210)
at com.StringExample.main(StringExample.java:9)
2. Java String.replaceAll()
示例
2.1 替换所有出现的子串或单词
Java 程序替换字符串中所有出现的单词。 在此示例中,我们将单词“java
”替换为“scala
”。
public class StringExample
{
public static void main(String[] args)
{
String str = "how to do in java provides java tutorials";
String newStr = str.replaceAll("java", "scala");
System.out.println(newStr);
}
}
程序输出:
how to do in scala provides scala tutorials
2.2 替换所有空白
Java 程序替换字符串中所有出现的空格。
public class StringExample
{
public static void main(String[] args)
{
String str = "how to do in java provides java tutorials";
String newStr = str.replaceAll("\\s", "");
System.out.println(newStr);
}
}
程序输出:
howtodoinjavaprovidesjavatutorials
参考文献:
Java String.substring()
方法示例
原文: https://howtodoinjava.com/java/string/java-string-substring-example/
Java 字符串substring()
方法返回一个新字符串,该字符串是该字符串的子字符串。 substring()
方法是重载方法,有两种变体:
String substring(int beginIndex)
String substring(int beginIndex, int endIndex)
其中方法参数为:
beginIndex
– 起始索引,包括端点。endIndex
– 结束索引,不包括。
1. Java String.substring(int beginIndex)
示例
它返回一个字符串,该字符串是该字符串的子字符串。 子字符串从指定'beginIndex'
处的字符开始,到字符串的末尾。
- 请注意,索引从“0”开始,指的是字符串的第一个字符。
- 如果参数索引不是有效索引,则方法将引发
StringIndexOutOfBoundsException
错误。 有效索引始终大于或等于零; 小于或等于字符串的长度。
0 >=
有效索引<=
字符串的长度
Java 程序从给定索引获取字符串的子字符串。
public class StringExample
{
public static void main(String[] args)
{
String blogName = "howtodoinjava.com";
System.out.println(blogName.substring(3)); //todoinjava.com
System.out.println("hello world".substring(6)); //world
}
}
程序输出。
todoinjava.com
world
2. Java String.substring(int beginIndex, int endIndex)
示例
它返回一个字符串,该字符串是该字符串的子字符串。 子字符串从指定'beginIndex'
(包括)的字符开始,到'endIndex'
(排除)位置。
Java 程序,用于从给定的开始索引到结束索引获取字符串的子字符串。
public class StringExample
{
public static void main(String[] args)
{
String blogName = "howtodoinjava.com";
System.out.println(blogName.substring(14, blogName.length())); //com
System.out.println("hello world".substring(6,9)); //wor
System.out.println("0123456789".substring(3, 7)); //3456
}
}
程序输出:
com
wor
3456
在最后一条语句中,示例更有意义,因为每个字符都表示其在字符串中的索引位置。 第 0 个索引为“0”,第一个索引为“1”,依此类推,直到第 9 个位置为“9”。
当我们找到从索引 3 到索引 7 的子字符串时,我们得到字符串3456
。 包括开始索引,排除了结束索引“7”。
学习愉快!
参考文献:
Java String.startsWith()
示例
原文: https://howtodoinjava.com/java/string/java-string-startswith-example/
Java 字符串startsWith()
方法用于检查字符串的前缀。 它验证给定的字符串是否以参数字符串开头。
startsWith()
方法是重载方法,具有两种形式:
boolean startsWith(String str)
– 如果str
是字符串的前缀,则返回true
。boolean startsWith(String str, int fromIndex)
– 如果字符串从指定索引fromIndex
开始以str
开头,则返回true
。
1. String.startsWith(String str)
示例
检查字符串是否以前缀参数字符串开头的 Java 程序。
public class StringExample
{
public static void main(String[] args)
{
String blogName = "howtodoinjava.com";
System.out.println( blogName.startsWith("how") ); //true
System.out.println( "howtodoinjava.com".startsWith("howto") ); //true
System.out.println( "howtodoinjava.com".startsWith("hello") ); //false
}
}
程序输出。
true
true
false
String.startsWith()
方法不接受正则表达式作为参数。 如果我们以正则表达式模式作为参数传递,它将仅被视为普通字符串。
1.1 不允许使用null
方法参数
请注意,不允许null
作为方法参数。 如果传递了null
,它将抛出NullPointerException
。
public class StringExample
{
public static void main(String[] args)
{
String blogName = "howtodoinjava.com";
blogName.startsWith(null);
}
}
程序输出:
Exception in thread "main" java.lang.NullPointerException
at java.lang.String.startsWith(String.java:1392)
at java.lang.String.startsWith(String.java:1421)
at com.StringExample.main(StringExample.java:9)
2. Java String.startsWith(String str, int fromIndex)
示例
与startsWith(str)
方法类似,此方法也检查前缀。 区别在于它检查从指定的fromIndex
开始的前缀str
。
此方法也不接受该方法的null
参数。
public class StringExample
{
public static void main(String[] args)
{
String blogName = "howtodoinjava.com";
System.out.println( blogName.startsWith("howto", 0) ); //true
System.out.println( "howtodoinjava.com".startsWith("howto", 2) ); //false
}
}
程序输出:
true
false
参考:
Java String.endsWith()
方法示例
原文: https://howtodoinjava.com/java/string/java-string-endswith-method/
Java 字符串endsWith()
方法用于检查字符串的后缀。 它验证给定的字符串是否以参数字符串结尾。
1. Java String.endsWith(String str)
方法
检查字符串是否以后缀参数字符串'str'
结尾的 Java 程序。
public class Main
{
public static void main(String[] args)
{
String blogName = "howtodoinjava.com";
System.out.println( blogName.endsWith("com") ); //true
System.out.println( blogName.endsWith("net") ); //false
}
}
程序输出。
true
false
String.endsWith()
方法不接受正则表达式作为参数。 如果我们以正则表达式模式作为参数传递,它将仅被视为普通字符串。
1.1 不允许使用null
方法参数
请注意,不允许将null
作为endsWith()
方法的方法参数。 如果传递了null
,它将抛出NullPointerException
。
public class StringExample
{
public static void main(String[] args)
{
String blogName = "howtodoinjava.com";
System.out.println( blogName.endsWith(null) );
}
}
程序输出:
Exception in thread "main" java.lang.NullPointerException
at java.lang.String.endsWith(String.java:1436)
at com.StringExample.main(Main.java:11)
参考:
Java String.toUpperCase()
方法示例
原文: https://howtodoinjava.com/java/string/java-string-touppercase-method/
Java String.toUpperCase()
返回一个字符串,该字符串是将给定字符串中的所有字符转换为大写的结果。
阅读更多: Java 示例将字符串大写
1. String.toUpperCase()
方法
使用String.toUpperCase()
将任何字符串转换为大写字母。
1.1 方法语法
String.toUpperCase()
方法已重载,有两种变体。
/**
* @param locale - locale use the case transformation rules for given locale
*
* @return - string converted to uppercase
*/
public String toUpperCase();
public String toUpperCase(Locale locale);
1.2 null
不是有效的方法参数
方法不接受null
参数。 如果方法参数为null
,它将抛出NullPointerException
。
Exception in thread "main" java.lang.NullPointerException
at java.lang.String.toUpperCase(String.java:2710)
at com.StringExample.main(StringExample.java:11)
2. Java 将字符串转换为大写示例
Java 程序使用默认语言环境规则将字符串转换为大写。
public class StringExample
{
public static void main(String[] args)
{
String string = "hello world";
String uppercaseString = string.toUpperCase();
System.out.println(uppercaseString);
}
}
程序输出。
HELLO WORLD
toUpperCase()
方法等于调用toUpperCase(Locale.getDefault())
。
3. Java String.toUpperCase(Locale)
示例
Java 程序使用默认语言环境规则将字符串转换为大写。
public class StringExample
{
public static void main(String[] args)
{
System.out.println("hello world".toUpperCase(Locale.getDefault()));
System.out.println("Γειά σου Κόσμε".toUpperCase(Locale.US));
}
}
程序输出:
HELLO WORLD
ΓΕΙΆ ΣΟΥ ΚΌΣΜΕ
在此示例中,我们学会了将字符串转换为大写。
参考文献:
Java String.toLowerCase()
方法示例
原文: https://howtodoinjava.com/java/string/java-string-tolowercase-method/
Java String.toLowerCase()
返回一个字符串,该字符串是将给定字符串中的所有字符转换为小写字母的结果。
1. String.toLowerCase()
方法
使用String.toLowerCase()
将任何字符串转换为小写字母。
1.1 方法语法
String.toLowerCase()
方法已重载,有两种变体。
/**
* @param locale - locale use the case transformation rules for given locale
*
* @return - string converted to lowercase
*/
public String toLowerCase();
public String toLowerCase(Locale locale);
1.2 null
不是有效的方法参数
方法不接受null
参数。 如果方法参数为null
,它将抛出NullPointerException
。
Exception in thread "main" java.lang.NullPointerException
at java.lang.String.toLowerCase(String.java:2710)
at com.StringExample.main(StringExample.java:11)
2. Java 将字符串转换为小写示例
Java 程序使用默认语言环境规则将字符串转换为小写。
public class StringExample
{
public static void main(String[] args)
{
String string = "Hello World";
String lowercaseString = string.toLowerCase();
System.out.println(lowercaseString);
}
}
程序输出。
hello world
toLowerCase()
方法等于调用toLowerCase(Locale.getDefault())
。
3. Java String.toLowerCase(Locale locale)
示例
Java 程序使用默认语言环境规则将字符串转换为小写。
public class StringExample
{
public static void main(String[] args)
{
System.out.println("hello world".toLowerCase(Locale.getDefault()));
System.out.println("Γειά σου Κόσμε".toLowerCase(Locale.US));
}
}
程序输出:
hello world
γειά σου κόσμε
在此示例中,我们学会了将字符串转换为小写。
参考文献:
Java 正则表达式教程
Java 正则表达式教程
原文: https://howtodoinjava.com/java-regular-expression-tutorials/
正则表达式用作字符串的搜索模式。 使用正则表达式,我们也可以找到一个或多个匹配项。 我们可以在字符串中查找匹配的任何国王,例如简单字符,固定字符串或任何复杂的字符模式,例如电子邮件,SSN 或域名。
1. 正则表达式
正则表达式是强大,灵活和高效的文本处理的关键。 它允许您描述和解析文本。 正则表达式可以添加,删除,隔离和折叠,纺锤并破坏各种文本和数据。
1.1 元字符和字面值
完整的正则表达式由两种类型的字符组成。
- 特殊字符(类似于文件名中的
*
)称为元字符。 - 其余的称为字面值或普通文本字符。
正则表达式从其元字符提供的高级表达能力中受益。 我们可以将文本视为字面值,将元字符视为语法。 根据一组规则将单词与语法结合在一起,以创建表达思想的表达。
1.2 Java 正则表达式示例
让我们看一个使用正则表达式作为参考的 Java 快速示例。
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main
{
public static void main(String[] args)
{
Pattern pattern = Pattern.compile("Alex|Brian");
Matcher matcher = pattern.matcher("Generally, Alex and Brian share a great bonding.");
while (matcher.find()) {
System.out.print("Start index: " + matcher.start());
System.out.print(" End index: " + matcher.end() + " ");
System.out.println(" - " + matcher.group());
}
}
}
程序输出。
Start index: 11 End index: 15 - Alex
Start index: 20 End index: 25 - Brian
2. 正则表达式元字符
让我们探索常用的元字符以更好地理解它们。
2.1 行的起点和终点
起点和终点分别用'^'
(脱字符)和'$'
(美元)符号表示。 插入号和美元的特殊之处在于它们与行中的位置匹配,而不是与任何实际的文本字符本身匹配。
例如,正则表达式“cat
”在字符串中的任何位置都可以找到“cat
”,但是仅当“cat
”位于行首时,“^cat
”才匹配。 例如诸如“category
”或“catalog
”之类的词。
同样,“cat$
”仅在“cat
”位于行尾时匹配。 例如像“scat
”之类的词。
2.2 字符类
通常称为字符类的正则表达式构造"[···]"
让我们列出了比赛中该点要允许的字符。 字符类在创建拼写检查器时很有用。
例如,“e
”仅与e
匹配,而“a
”仅与a
匹配,而正则表达式[ea]
则与之匹配。 例如sep[ea]r[ea]te
将匹配所有单词“separate
”,“separate
”和“separete
”。
另一个示例可以是允许将单词的第一个字母大写,例如[Ss]mith
将同时允许使用smith
和Smith
这两个字。
同样,<[hH][123456]>
将匹配所有标题标签,即H1
,H2
,H3
,H4
,H5
和H6
。
2.2.1 字符范围
破折号"-"
表示字符范围。 <[hH][1-6]>
与<[hH][123456]>
相似。 其他有用的字符范围是[0-9]
和[a-z]
,它们匹配数字和英文小写字母。
我们可以在单个结构中指定多个范围 [0123456789abcdefABCDEF]
可以写为[0-9a-fA-F]
。 请注意,给出范围的顺序无关紧要。
请注意,破折号仅是字符类中的元字符,否则它与常规破折号匹配。 另外,如果它是范围中列出的第一个字符,则它不可能表示范围,因此在这种情况下将不是元字符。
2.2.2 否定字符类
如果我们在字符类中使用否定符号^
,则该类与未列出的任何字符匹配。 例如 [^1-6]
匹配的字符不是 1 到 6。
2.3 将任何字符与点匹配
元字符.
是与任何字符匹配的字符类的简写。 请注意,在字符类中使用点时,它们不是元字符。 在字符类中,它只是一个简单的字符。
例如,06.24.2019
将匹配06/24/2019
或06-24-2019
或06.24.2019
。 但是
06[.]24[.]2019
仅与06.24.2019
匹配。
2.4 匹配交替 - 几个子表达式中的任何一个
管道符号'|'
允许您将多个表达式组合成一个与任何单个表达式匹配的表达式。
例如,“Alex
”和“Brian
”是单独的表达式,但是Alex|Brian
是一个与两者都匹配的表达式。
与点类似,在字符类中使用管道时,管道也不是元字符。 在字符类中,它只是一个简单的字符。
例如,要匹配单词“First
”或“1st
”,我们可以编写正则表达式 – “(First|1st)
”或简写为"(Fir|1)st"
。
3. Java 正则表达式 API
Java 具有内置的 API(java.util.regex
)以使用正则表达式。 我们不需要任何第三方库就可以对 Java 中的任何字符串运行正则表达式。
Java 正则表达式 API 提供 1 个接口和 3 个类:
-
Pattern
– 必须将指定为字符串的正则表达式首先编译为此类的实例。 然后,可以使用所得的模式来创建Matcher
对象,该对象可以将任意字符序列与正则表达式进行匹配。Pattern p = Pattern.compile("abc"); Matcher m = p.matcher("abcabcabcd"); boolean b = m.matches(); //true
-
Matcher
– 此类提供执行匹配操作的方法。 -
MatchResult
(接口) – 这是匹配操作的结果。 它包含用于确定与正则表达式匹配的结果的查询方法。 -
PatternSyntaxException
– 引发非受检的异常,表示正则表达式模式中的语法错误。
详细了解这些类和重要方法。
3.1 Pattern
类
它表示正则表达式的编译表示。 要使用 Java 正则表达式 API,我们必须将正则表达式编译为此类。
编译后,其实例可用于创建Matcher
对象,该对象可以将行/字符串与正则表达式匹配。
请注意,许多匹配器可以共享同一模式。 处理期间的状态信息保存在Matcher
实例中。
此类的实例为不可变的,可以安全地由多个并发线程使用。
Predicate asPredicate()
- 创建可用于匹配字符串的 Java 8 谓词。static Pattern compile(String regex)
– 用于将给定的正则表达式编译为模式。static Pattern compile(String regex, int flags)
– 用于将给定的正则表达式编译为带有给定标志的模式。int flags()
- 用于返回此模式的匹配标志。Matcher matcher(CharSequence input)
– 用于创建匹配器,该匹配器将根据该模式匹配给定的输入。static boolean match(String regex, CharSequence input)
– 用于编译给定的正则表达式,并尝试将给定的输入与其匹配。String.pattern()
– 用于返回从中编译此模式的正则表达式。static String quote(String s)
– 用于返回指定字符串的字面模式串。String[] split(CharSequence input)
– 用于在此模式的匹配项附近拆分给定的输入序列。String[] split(CharSequence input, int limit)
– 用于在此模式的匹配项附近分割给定的输入序列。Stream splitAsStream(CharSequence input)
– 根据该模式的匹配从给定的输入序列创建流。
3.2 Matcher
类
它是通过解释Pattern
在字符串/行上执行匹配操作的主要类。 创建匹配器后,可将其用于执行各种匹配操作。
此类还定义了用新字符串替换匹配子序列的方法,如果需要,可以根据匹配结果计算其内容。
此类的实例是不是线程安全的。
boolean find()
– 主要用于搜索文本中多个出现的正则表达式。boolean find(int start)
– 用于从给定索引开始搜索文本中正则表达式的出现。int start()
– 用于获取使用find()
方法找到的匹配项的开始索引。int end()
– 用于获取使用find()
方法找到的匹配项的结束索引。 它返回最后一个匹配字符旁边的字符索引。int groupCount()
– 用于查找匹配的子序列的总数。String.group()
– 用于查找匹配的子序列。boolean matchs()
– 用于测试正则表达式是否与模式匹配。boolean lookingAt()
– 尝试从区域的开头开始,将模式与输入序列进行匹配。String quoteReplacement(String s)
– 返回指定字符串的字面替换字符串。Matcher reset()
– 重置此匹配器。MatchResult toMatchResult()
– 以MatchResult
的形式返回此匹配器的匹配状态。
4. Java 正则表达式示例
阅读下面给出的示例,以了解正则表达式在解决应用程序中这些特定问题方面的用法。
电子邮件地址的正则表达式
学习使用 Java 中的正则表达式匹配电子邮件地址
^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$
用于密码验证的正则表达式
学习使用 Java 中的正则表达式匹配密码
((?=.*[a-z])(?=.*d)(?=.*[@#$%])(?=.*[A-Z]).{6,16})
商标符号的正则表达式
学习使用 Java 中的正则表达式匹配商标符号
\u2122
任何货币符号的正则表达式
学习使用 Java 中的正则表达式匹配货币符号
\\p{Sc}
“希腊扩展”或希腊语脚本中任何字符的正则表达式
使用 Java 中的正则表达式学习在希腊扩展和希腊脚本中匹配字符
\\p{InGreek} and \\p{InGreekExtended}
北美电话号码的正则表达式
学习使用 Java 中的正则表达式匹配北美电话号码
^\\(?([0-9]{3})\\)?[-.\\s]?([0-9]{3})[-.\\s]?([0-9]{4})$
国际电话号码的正则表达式
学习使用 Java 中的正则表达式匹配国际电话号码
^\+(?:[0-9] ?){6,14}[0-9]$
日期格式的正则表达式
学习使用 Java 中的正则表达式匹配日期格式
^[0-3]?[0-9]/[0-3]?[0-9]/(?:[0-9]{2})?[0-9]{2}$
社会安全号码(SSN)的正则表达式
学习使用 Java 中的正则表达式匹配 SSN
^(?!000|666)[0-8][0-9]{2}-(?!00)[0-9]{2}-(?!0000)[0-9]{4}$
国际标准书号(ISBN)的正则表达式
学习使用 Java 中的正则表达式匹配 ISBN
^(?:ISBN(?:-1[03])?:? )?(?=[0-9X]{10}$|(?=(?:[0-9]+[- ]){3})
[- 0-9X]{13}$|97[89][0-9]{10}$|(?=(?:[0-9]+[- ]){4})[- 0-9]{17}$)
(?:97[89][- ]?)?[0-9]{1,5}[- ]?[0-9]+[- ]?[0-9]+[- ]?[0-9X]$
美国邮政编码的正则表达式
学习使用 Java 中的正则表达式匹配美国邮政编码
^[0-9]{5}(?:-[0-9]{4})?$
加拿大邮政编码的正则表达式
学习使用 Java 中的正则表达式匹配加拿大邮政编码
^(?!.*[DFIOQU])[A-VXY][0-9][A-Z] ?[0-9][A-Z][0-9]$
英国邮政编码(邮政编码)的正则表达式
学习使用 Java 中的正则表达式匹配英国邮政编码
^[A-Z]{1,2}[0-9R][0-9A-Z]? [0-9][ABD-HJLNP-UW-Z]{2}$
信用卡号码的正则表达式
学习使用 Java 中的正则表达式匹配信用卡号
^(?:(?4[0-9]{12}(?:[0-9]{3})?)|
(?5[1-5][0-9]{14})|
(?6(?:011|5[0-9]{2})[0-9]{12})|
(?3[47][0-9]{13})|
(?3(?:0[0-5]|[68][0-9])?[0-9]{11})|
(?(?:2131|1800|35[0-9]{3})[0-9]{11}))$
更多正则表达式示例
请在注释中向我发送与此 java 正则表达式教程有关的问题。
学习愉快!
参考文献:
Java 仅允许字母数字字符的正则表达式
原文: https://howtodoinjava.com/regex/regex-alphanumeric-characters/
我们可以使用给定的正则表达式来验证用户输入,使其仅允许字母数字字符。 字母数字字符都是字母和数字,即字母A–Z
,a–z
和数字0–9
。
1. 字母数字正则表达式模式
使用字母数字正则表达式,解决方案非常简单。 字符类可以设置允许的字符范围。 添加了一个可重复一次或多次重复字符类的量词,并将定位符绑定到字符串的开头和结尾的锚点,我们就可以开始了。
正则表达式:
^[a-zA-Z0-9]+$
2. 字母数字正则表达式示例
List<String> names = new ArrayList<String>();
names.add("Lokesh");
names.add("LOkesh123");
names.add("LOkesh123-"); //Incorrect
String regex = "^[a-zA-Z0-9]+$";
Pattern pattern = Pattern.compile(regex);
for (String name : names)
{
Matcher matcher = pattern.matcher(name);
System.out.println(matcher.matches());
}
程序输出。
true
true
false
了解基础知识非常容易。是吧?
学习愉快!
Java this
和super
之间的区别
this
和super
在 Java 中是保留的关键字。 this
引用一个类的当前实例,而super
引用该类的父类,其中使用了super
关键字。
1. Java this
关键字
this
关键字自动保留对类的当前实例的引用。 在我们要将方法从父类继承到子类,并要专门从子类调用方法的情况下,这非常有用。
我们也可以使用此关键字来访问类中的静态字段,但是建议的方法是使用类引用来访问静态字段,例如MyClass.STATIC_FIELD
。
2. Java super
关键字
与this
关键字相似,super
在 Java 中也是保留关键字。 它始终保存对任何给定类的父类的引用。
使用super
关键字,我们可以访问任何子类中父类的字段和方法。
3. Java this
和super
关键字示例
在此示例中,我们有两个类ParentClass
和ChildClass
,其中ChildClass
扩展ParentClass
。 我在父类中创建了一个方法showMyName()
,并覆盖了它的子类。
现在,当我们尝试使用this
和super
关键字在子类中调用showMyName()
方法时,它将分别从当前类和父类中调用方法。
public class ParentClass
{
public void showMyName()
{
System.out.println("In ParentClass");
}
}
public class ChildClass extends ParentClass
{
public void showMyName()
{
System.out.println("In ChildClass");
}
public void test()
{
this.showMyName();
super.showMyName();
}
}
public class Main
{
public static void main(String[] args)
{
ChildClass childObj = new ChildClass();
childObj.test();
}
}
程序输出。
In ChildClass
In ParentClass
在此 Java 教程中,我们学习了this
以及super
关键字。 我们还学习了在 Java 应用程序中使用这两个关键字。
学习愉快!
Java 正则表达式 – 信用卡号验证
原文: https://howtodoinjava.com/regex/java-regex-validate-credit-card-numbers/
在此 Java 正则表达式教程中,我们将学习使用正则表达式来验证信用卡号。 我们将从多个提供商(例如 VISA,Mastercard,Amex 和 Diners 等)了解号码格式和信用卡号验证。
1. 有效的信用卡号格式
在实际的信用卡上,压纹卡号的数字通常分为四组。 这使得卡号更易于人类阅读。 每个信用卡公司都使用此数字格式。
我们将利用每个公司之间的格式差异来允许用户输入数字而无需指定公司。 可以从号码中确定公司。 每个公司的格式为:
- Visa:13 或 16 位数字,以 4 开头。
- Mastercard:从 51 到 55 开头的 16 位数字。
- Discover:从 6011 或 65 开始的 16 位数字。
- American Express:15 位数字,以 34 或 37 开头。
- Diners Club :14 位数字,从 300 到 305、36 或 38 开头。
- JCB :15 位数字,以 2131 或 1800 开头,或 16 位数字,以 35 开头。
下面给出的正则表达式假设在执行有效数字检查之前,我们将明确搜索并替换所有空格和连字符。
输入中去除了空格和连字符后,下一个正则表达式将检查信用卡号是否使用六家主要信用卡公司中的任何一家的格式。 它使用命名捕获来检测客户拥有的信用卡品牌。
如果不需要确定卡的类型,则可以删除围绕每种卡类型的模式的六个捕获组,因为它们没有任何其他用途。
如果您仅接受某些品牌的信用卡,则可以从正则表达式中删除不接受的信用卡。 例如,删除 JCB 时,请确保删除最后剩余的“|
” 在正则表达式中也是如此。 如果您的正则表达式以“|
”结尾,它也将接受空字符串作为有效的卡号。
Regex : ^(?:(?<visa>4[0-9]{12}(?:[0-9]{3})?)|
(?<mastercard>5[1-5][0-9]{14})|
(?<discover>6(?:011|5[0-9]{2})[0-9]{12})|
(?<amex>3[47][0-9]{13})|
(?<diners>3(?:0[0-5]|[68][0-9])?[0-9]{11})|
(?<jcb>(?:2131|1800|35[0-9]{3})[0-9]{11}))$
在此 Wiki 页面中详细了解信用卡号码格式。
2. 信用卡号码验证示例
public static void main(String[] args)
{
List<String> cards = new ArrayList<String>();
//Valid Credit Cards
cards.add("xxxx-xxxx-xxxx-xxxx"); //Masked to avoid any inconvenience unknowingly
//Invalid Credit Card
cards.add("xxxx-xxxx-xxxx-xxxx"); //Masked to avoid any inconvenience unknowingly
String regex = "^(?:(?<visa>4[0-9]{12}(?:[0-9]{3})?)|" +
"(?<mastercard>5[1-5][0-9]{14})|" +
"(?<discover>6(?:011|5[0-9]{2})[0-9]{12})|" +
"(?<amex>3[47][0-9]{13})|" +
"(?<diners>3(?:0[0-5]|[68][0-9])?[0-9]{11})|" +
"(?<jcb>(?:2131|1800|35[0-9]{3})[0-9]{11}))$";
Pattern pattern = Pattern.compile(regex);
for (String card : cards)
{
//Strip all hyphens
card = card.replaceAll("-", "");
//Match the card
Matcher matcher = pattern.matcher(card);
System.out.println(matcher.matches());
if(matcher.matches()) {
//If card is valid then verify which group it belong
System.out.println(matcher.group("mastercard"));
}
}
3. 使用 Luhn 算法进行校验和验证
在处理顺序之前,您可以对信用卡号进行额外的验证检查。 信用卡号的最后一位数字是根据 Luhn 算法计算得出的校验和。 由于此算法需要基本的算术运算,因此无法使用正则表达式来实现。
下面是可用于使用 Luhn 算法运行校验和验证的方法。 此函数采用一个以信用卡号为参数的字符串。 卡号只能由数字组成。
实际算法在数字数组上运行,计算校验和。 如果总和模 10 为零,则卡号有效。 如果不是,则该数字无效。
我从 Google 代码中获取了 Luhn Algo 的参考实现。
public class Luhn
{
public static boolean Check(String ccNumber)
{
int sum = 0;
boolean alternate = false;
for (int i = ccNumber.length() - 1; i >= 0; i--)
{
int n = Integer.parseInt(ccNumber.substring(i, i + 1));
if (alternate)
{
n *= 2;
if (n > 9)
{
n = (n % 10) + 1;
}
}
sum += n;
alternate = !alternate;
}
return (sum % 10 == 0);
}
}
如果需要,可以随意修改上述代码示例以匹配上述正则表达式中的其他验证规则。
学习愉快!
Java 正则表达式 – 加拿大邮政编码验证
原文: https://howtodoinjava.com/regex/canada-postal-code-validation/
在此 Java 正则表达式教程中,我们将学习使用正则表达式来验证加拿大的邮政编码。 您也可以修改正则表达式以使其适合任何其他格式。
1. 什么是有效的加拿大邮政编码
加拿大邮政编码是一个六位字符的字符串,构成加拿大邮政地址的一部分。
有效的加拿大邮政编码为:
- 格式为
A1A 1A1
,其中 A 是字母,1 是数字。 - 用空格分隔第三个和第四个字符。
- 不要包含字母 D,F,I,O,Q 或 U。
- 第一个位置不使用字母 W 或 Z。
正则表达式:
^(?!.*[DFIOQU])[A-VXY][0-9][A-Z] ?[0-9][A-Z][0-9]$
在正则表达式之上,此正则表达式开头的负前瞻可防止在主题字符串中的任何位置出现 D,F,I,O,Q 或 U。[A-VXY]
字符类进一步防止 W 或 Z 作为第一个字符。
2. 加拿大邮政编码验证示例
List<String> zips = new ArrayList<String>();
//Valid ZIP codes
zips.add("K1A 0B1");
zips.add("B1Z 0B9");
//Invalid ZIP codes
zips.add("K1A 0D1");
zips.add("W1A 0B1");
zips.add("Z1A 0B1");
String regex = "^(?!.*[DFIOQU])[A-VXY][0-9][A-Z] ?[0-9][A-Z][0-9]$";
Pattern pattern = Pattern.compile(regex);
for (String zip : zips)
{
Matcher matcher = pattern.matcher(zip);
System.out.println(matcher.matches());
}
Output:
true
true
false
false
那很容易,对吗? 向我提供有关如何使用正则表达式验证加拿大邮政编码的问题。
学习愉快!
货币符号的 Java 正则表达式
原文: https://howtodoinjava.com/regex/java-regex-match-any-currency-symbol/
在本教程中,我们将学习匹配所有可用的货币符号,例如美元,欧元,日元,日元。
解决方案正则表达式:
\\p{Sc}
使用 Java 正则表达式匹配任何货币符号的示例代码
String content = "Let's find the symbols or currencies : $ Dollar, € Euro, ¥ Yen";
String regex = "\\p{Sc}";
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(content);
while (matcher.find())
{
System.out.print("Start index: " + matcher.start());
System.out.print(" End index: " + matcher.end() + " ");
System.out.println(" : " + matcher.group());
}
Output:
Start index: 39 End index: 40 : $
Start index: 49 End index: 50 : €
Start index: 57 End index: 58 : ¥
祝您学习愉快!
使用 Java 正则表达式进行日期验证
原文: https://howtodoinjava.com/regex/java-regex-date-format-validation/
在使用正则表达式进行的 Java 日期验证中,我们将学习验证简单的日期格式,例如mm/dd/yy
,mm/dd/yyyy
,dd/mm/yy
和dd mm/yyyy
。 在这里,我们想使用一个正则表达式来简单地检查输入是否为有效日期,而不尝试消除诸如 2 月 31 日这样的东西。
我们可能会认为,对于日期表达式而言,从概念上讲微不足道的事情应该是一件容易的事。 但事实并非如此。 主要问题是正则表达式不能直接处理数字。
我们不能告诉正则表达式“匹配 1 到 31 之间的数字”。 而是正则表达式逐个字符地工作。
我们使用'3[01]|[12][0-9]|0?[1-9]'
来匹配 3,后跟 0 或 1,或者匹配 1 或 2,后跟任意数字,或者匹配一个可选的 0,后跟 1 到 9。因此,您必须选择您希望您的正则表达式的简单或准确的程度。
1. Java 日期验证正则表达式 – 允许省略前导零
正则表达式:
^[0-3]?[0-9]/[0-3]?[0-9]/(?:[0-9]{2})?[0-9]{2}$
List dates = new ArrayList();
dates.add("1/1/11");
dates.add("01/01/11");
dates.add("01/01/2011");
dates.add("01/1/2011");
dates.add("1/11/2011");
dates.add("1/11/11");
dates.add("11/1/11");
String regex = "^[0-3]?[0-9]/[0-3]?[0-9]/(?:[0-9]{2})?[0-9]{2}$";
Pattern pattern = Pattern.compile(regex);
for(String date : dates)
{
Matcher matcher = pattern.matcher(date);
System.out.println(date +" : "+ matcher.matches());
}
程序输出。
1/1/11 : true
01/01/11 : true
01/01/2011 : true
01/1/2011 : true
1/11/2011 : true
1/11/11 : true
11/1/11 : true
2. Java 日期验证正则表达式 – 要求前导零
正则表达式:
^[0-3][0-9]/[0-3][0-9]/(?:[0-9][0-9])?[0-9][0-9]$
List dates = new ArrayList();
//With leading zeros
dates.add("01/01/11");
dates.add("01/01/2011");
//Missing leading zeros
dates.add("1/1/11");
dates.add("01/1/2011");
dates.add("1/11/2011");
dates.add("1/11/11");
dates.add("11/1/11");
String regex = "^[0-3][0-9]/[0-3][0-9]/(?:[0-9][0-9])?[0-9][0-9]$";
Pattern pattern = Pattern.compile(regex);
for(String date : dates)
{
Matcher matcher = pattern.matcher(date);
System.out.println(date +" : "+ matcher.matches());
}
程序输出:
01/01/11 : true
01/01/2011 : true
1/1/11 : false
01/1/2011 : false
1/11/2011 : false
1/11/11 : false
11/1/11 : false
3. Java 日期验证正则表达式 – 将mm/dd/yyyy
与所需的前导零匹配
正则表达式:
^(1[0-2]|0[1-9])/(3[01]|[12][0-9]|0[1-9])/[0-9]{4}$
验证日期格式mm/dd/yyyy
的 Java 程序。
List dates = new ArrayList();
//With leading zeros
dates.add("01/01/11");
dates.add("01/01/2011");
//Missing leading zeros
dates.add("1/1/11");
dates.add("01/1/2011");
String regex = "^(1[0-2]|0[1-9])/(3[01]|[12][0-9]|0[1-9])/[0-9]{4}$";
Pattern pattern = Pattern.compile(regex);
for(String date : dates)
{
Matcher matcher = pattern.matcher(date);
System.out.println(date +" : "+ matcher.matches());
}
程序输出:
01/01/11 : false
01/01/2011 : true
1/1/11 : false
01/1/2011 : false
4. Java 日期验证正则表达式 – 将dd/mm/yyyy
与所需的前导零匹配
用于验证日期格式dd/mm/yyyy
的正则表达式。
正则表达式:
^(3[01]|[12][0-9]|0[1-9])/(1[0-2]|0[1-9])/[0-9]{4}$
List dates = new ArrayList();
//With leading zeros
dates.add("07/13/2011");
dates.add("13/07/2011");
//Missing leading zeros
dates.add("1/1/11");
dates.add("01/1/2011");
String regex = "^(3[01]|[12][0-9]|0[1-9])/(1[0-2]|0[1-9])/[0-9]{4}$";
Pattern pattern = Pattern.compile(regex);
for(String date : dates)
{
Matcher matcher = pattern.matcher(date);
System.out.println(date +" : "+ matcher.matches());
}
程序输出:
07/13/2011 : false
13/07/2011 : true
1/1/11 : false
01/1/2011 : false
可以随意使用和编辑上述正则表达式来进行日期验证,以满足您的需求。
学习愉快!
使用 Java 正则表达式进行电子邮件验证
原文: https://howtodoinjava.com/regex/java-regex-validate-email-address/
使用正则表达式的电子邮件验证是常见的任务,在任何将电子邮件地址作为注册信息中所需信息的应用程序中都可能需要。 可能会有更多用例,但这里不再讨论。
让我们直接进入主要讨论,即使用正则表达式验证 Java 中的电子邮件。
1. 验证电子邮件的最简单的正则表达式
正则表达式:
^(.+)@(.+)$
这是最简单的,只关心@
符号。 在@
符号前后,可以有任意数量的字符。 让我们看一个简单的例子,了解我的意思。
List emails = new ArrayList();
emails.add("[email protected]");
emails.add("[email protected]");
emails.add("[email protected]");
emails.add("[email protected]");
emails.add("user#@domain.co.in");
emails.add("user@domaincom");
//Invalid emails
emails.add("user#domain.com");
emails.add("@yahoo.com");
String regex = "^(.+)@(.+)$";
Pattern pattern = Pattern.compile(regex);
for(String email : emails){
Matcher matcher = pattern.matcher(email);
System.out.println(email +" : "+ matcher.matches());
}
程序输出。
[email protected] : true
[email protected] : true
[email protected] : true
[email protected] : true
user#@domain.co.in : true
user@domaincom : true
user#domain.com : false
@yahoo.com : false
Common lang 的EmailValidator
类中提供了此模式。 因此,如果您需要它,您可以直接使用此类。
2. 对用户名添加限制部分
正则表达式:
^[A-Za-z0-9+_.-]+@(.+)$
在此正则表达式中,我们在电子邮件地址的用户名部分添加了一些限制。 上述正则表达式的限制为:
1)允许使用A-Z
字符
2)允许使用a-z
字符
3)允许使用0-9
数字
4)另外,电子邮件中只能包含点(.
),破折号(-
)和下划线(_
)
5)其余所有字符均不允许
让我们根据上述正则表达式测试一些电子邮件地址。
List emails = new ArrayList();
emails.add("[email protected]");
emails.add("[email protected]");
emails.add("[email protected]");
emails.add("[email protected]");
emails.add("[email protected]");
emails.add("[email protected]");
emails.add("user@domaincom");
//Invalid emails
emails.add("@yahoo.com");
String regex = "^[A-Za-z0-9+_.-]+@(.+)$";
Pattern pattern = Pattern.compile(regex);
for(String email : emails){
Matcher matcher = pattern.matcher(email);
System.out.println(email +" : "+ matcher.matches());
}
程序输出:
[email protected] : true
[email protected] : true
[email protected] : true
[email protected] : true
[email protected] : true
[email protected] : true
user@domaincom : true
@yahoo.com : false
请注意,类似的限制也可以适用于域名部分。 然后正则表达式将变成这样。
^[A-Z0-9+_.-]+@[A-Z0-9.-]+$
3. RFC 5322 允许的 Java 电子邮件验证
正则表达式:
^[a-zA-Z0-9_!#$%&’*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$
此正则表达式示例使用 RFC 5322 允许的所有字符,这些字符控制电子邮件格式。 如果允许的字符中有一些,如果直接从用户输入传递到 SQL 语句,则会带来安全风险,例如单引号('
)和管道字符(|
)。
在将电子邮件地址插入传递给另一个程序的字符串中时,应确保转义敏感字符,以防止诸如 SQL 注入攻击之类的安全漏洞。
List emails = new ArrayList();
emails.add("[email protected]");
emails.add("[email protected]");
emails.add("[email protected]");
emails.add("[email protected]");
emails.add("user'[email protected]");
//Invalid emails
emails.add("@yahoo.com");
String regex = "^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$";
Pattern pattern = Pattern.compile(regex);
for(String email : emails){
Matcher matcher = pattern.matcher(email);
System.out.println(email +" : "+ matcher.matches());
}
程序输出:
[email protected] : true
[email protected] : true
[email protected] : true
[email protected] : true
user'[email protected] : true
@yahoo.com : false
4. 限制电子邮件中的前导,尾随或连续点的正则表达式
正则表达式:
^[a-zA-Z0-9_!#$%&’*+/=?`{|}~^-]+(?:\\.[a-zA-Z0-9_!#$%&’*+/=?`{|}~^-]+)*@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$
本地部分和域名都可以包含一个或多个点,但是两个点之间不能紧挨出现。 此外,本地部分和域名中的第一个和最后一个字符不得为点号:
List emails = new ArrayList();
emails.add("[email protected]");
emails.add("[email protected]");
emails.add("[email protected]");
emails.add("user'[email protected]");
//Invalid emails
emails.add("[email protected]");
emails.add("[email protected].");
emails.add("[email protected]");
String regex = "^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^-]+(?:\\.[a-zA-Z0-9_!#$%&'*+/=?`{|}~^-]+)*@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$";
Pattern pattern = Pattern.compile(regex);
for(String email : emails){
Matcher matcher = pattern.matcher(email);
System.out.println(email +" : "+ matcher.matches());
}
程序输出:
[email protected] : true
[email protected] : true
[email protected] : true
user'[email protected] : true
[email protected] : false
[email protected]. : false
[email protected] : false
5. 限制顶级域中的字符数的正则表达式(推荐)
现在,让我们修改正则表达式,以使域名必须至少包含一个点,并且域名中最后一个点之后的部分只能由字母组成。
假设域名类似于secondlevel.com
或thirdlevel.secondlevel.com
。顶级域(在这些示例中为.com
)必须仅包含 2 至 6 个字母。
正则表达式:
^[\\w!#$%&’*+/=?`{|}~^-]+(?:\\.[\\w!#$%&’*+/=?`{|}~^-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$
List emails = new ArrayList();
emails.add("[email protected]");
emails.add("[email protected]");
emails.add("[email protected]");
emails.add("[email protected]");
emails.add("[email protected]");
//Invalid emails
emails.add("[email protected]");
emails.add("[email protected].");
emails.add("[email protected]");
emails.add("[email protected]");
emails.add("[email protected]");
String regex = "^[\\w!#$%&'*+/=?`{|}~^-]+(?:\\.[\\w!#$%&'*+/=?`{|}~^-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$";
Pattern pattern = Pattern.compile(regex);
for(String email : emails){
Matcher matcher = pattern.matcher(email);
System.out.println(email +" : "+ matcher.matches());
}
程序输出:
[email protected] : true
[email protected] : true
[email protected] : true
[email protected] : true
[email protected] : true
[email protected] : false
[email protected]. : false
[email protected] : false
[email protected] : false
[email protected] : false
最后一个正则表达式是我对 Java 中的简单电子邮件验证的建议。 请注意,可以在 Java 中不使用正则表达式进行电子邮件验证,但不建议这样做。 在需要处理模式的任何地方,正则表达式都是您的朋友。
请随时使用此正则表达式并根据您的应用程序的其他需求对其进行编辑。
学习愉快!
参考: http://www.rfc-editor.org/rfc/rfc5322.txt
Java 正则表达式密码验证示例
原文: https://howtodoinjava.com/regex/how-to-build-regex-based-password-validator-in-java/
密码验证是当今几乎所有应用程序的需求。 通过手动编写所有内容以使用第三方可用的 API,可以通过多种方法来验证密码。 在此 Java 正则表达式密码验证教程中,我们正在使用正则表达式构建密码验证器。
1. 用于密码验证的正则表达式
((?=.*[a-z])(?=.*d)(?=.*[@#$%])(?=.*[A-Z]).{6,16})
上面的正则表达式有以下几节:
(?=.*[a-z]) : This matches the presence of at least one lowercase letter.
(?=.*d) : This matches the presence of at least one digit i.e. 0-9.
(?=.*[@#$%]) : This matches the presence of at least one special character.
((?=.*[A-Z]) : This matches the presence of at least one capital letter.
{6,16} : This limits the length of password from minimum 6 letters to maximum 16 letters.
前 4 个部分的顺序可以更改,甚至可以从最终正则表达式中删除。 这个事实可以用来以编程方式构建我们的密码验证器。
2. 使用正则表达式验证密码的 Java 程序
我们正在使验证器可配置,以便可以根据需要设置限制。 就像我们要强制使用至少一个特殊字符而不是任何大写字母一样,我们可以相应地传递所需的参数。
package com.howtodoinjava.regex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class PasswordValidator
{
private static PasswordValidator INSTANCE = new PasswordValidator();
private static String pattern = null;
/**
* No one can make a direct instance
* */
private PasswordValidator()
{
//do nothing
}
/**
* Force the user to build a validator using this way only
* */
public static PasswordValidator buildValidator( boolean forceSpecialChar,
boolean forceCapitalLetter,
boolean forceNumber,
int minLength,
int maxLength)
{
StringBuilder patternBuilder = new StringBuilder("((?=.*[a-z])");
if (forceSpecialChar)
{
patternBuilder.append("(?=.*[@#$%])");
}
if (forceCapitalLetter)
{
patternBuilder.append("(?=.*[A-Z])");
}
if (forceNumber)
{
patternBuilder.append("(?=.*d)");
}
patternBuilder.append(".{" + minLength + "," + maxLength + "})");
pattern = patternBuilder.toString();
return INSTANCE;
}
/**
* Here we will validate the password
* */
public static boolean validatePassword(final String password)
{
Pattern p = Pattern.compile(pattern);
Matcher m = p.matcher(password);
return m.matches();
}
}
3. 密码验证的单元测试
因此,我们的密码验证程序已准备就绪。 让我们用一些 JUnit 测试用例进行测试。
package com.howtodoinjava.regex;
import junit.framework.Assert;
import org.junit.Test;
@SuppressWarnings("static-access")
public class TestPasswordValidator
{
@Test
public void testNormalPassword()
{
PasswordValidator validator = PasswordValidator.buildValidator(false, false, false, 6, 14);
Assert.assertTrue(validator.validatePassword("howtodoinjava"));
Assert.assertTrue(validator.validatePassword("howtodoin"));
//Sort on length
Assert.assertFalse(validator.validatePassword("howto"));
}
@Test
public void testForceNumeric()
{
PasswordValidator validator = PasswordValidator.buildValidator(false,false, true, 6, 16);
//Contains numeric
Assert.assertTrue(validator.validatePassword("howtodoinjava12"));
Assert.assertTrue(validator.validatePassword("34howtodoinjava"));
Assert.assertTrue(validator.validatePassword("howtodo56injava"));
//No numeric
Assert.assertFalse(validator.validatePassword("howtodoinjava"));
}
@Test
public void testForceCapitalLetter()
{
PasswordValidator validator = PasswordValidator.buildValidator(false,true, false, 6, 16);
//Contains capitals
Assert.assertTrue(validator.validatePassword("howtodoinjavA"));
Assert.assertTrue(validator.validatePassword("Howtodoinjava"));
Assert.assertTrue(validator.validatePassword("howtodOInjava"));
//No capital letter
Assert.assertFalse(validator.validatePassword("howtodoinjava"));
}
@Test
public void testForceSpecialCharacter()
{
PasswordValidator validator = PasswordValidator.buildValidator(true,false, false, 6, 16);
//Contains special char
Assert.assertTrue(validator.validatePassword("howtod@injava"));
Assert.assertTrue(validator.validatePassword("@Howtodoinjava"));
Assert.assertTrue(validator.validatePassword("howtodOInjava@"));
//No special char
Assert.assertFalse(validator.validatePassword("howtodoinjava"));
}
}
在这篇文章中,我们学习了使用 Java 正则表达式进行密码验证的方法,该规则能够验证字母数字和特殊字符,包括最大和最小密码长度。
学习愉快!
适用于希腊语扩展或希腊语脚本的 Java 正则表达式
原文: https://howtodoinjava.com/regex/java-regex-match-any-character-in-greek-extended-or-greek-script/
在本教程中,我们将学习匹配“希腊扩展” unicode 块或希腊语脚本一部分的任何字符。
解决方案正则表达式:
\\p{InGreek}
和\p{InGreekExtended}
匹配希腊文字中的任何字符
让我们看一个示例程序,该程序能够匹配字符串中希腊语脚本中的任何字符。
String content = "A math equation might be α + β = λ + γ";
String regex = "\\p{InGreek}";
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(content);
while (matcher.find())
{
System.out.print("Start index: " + matcher.start());
System.out.print(" End index: " + matcher.end() + " ");
System.out.println(" : " + matcher.group());
}
Output:
Start index: 25 End index: 26 : α
Start index: 29 End index: 30 : β
Start index: 33 End index: 34 : λ
Start index: 37 End index: 38 : γ
匹配“希腊扩展” unicode 块中的任何字符
让我们看一个示例程序,该程序能够匹配字符串中希腊语脚本中的任何字符。
String content = "Let's learn some new greek extended characters : ᾲ , ᾨ etc.";
String regex = "\\p{InGreekExtended}";
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(content);
while (matcher.find())
{
System.out.print("Start index: " + matcher.start());
System.out.print(" End index: " + matcher.end() + " ");
System.out.println(" : " + matcher.group());
}
Output:
Start index: 49 End index: 50 : ᾲ
Start index: 53 End index: 54 : ᾨ
参考:
http://en.wikipedia.org/wiki/Greek_alphabet
http://www.alanwood.net/unicode/greek_extended.html
https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html
验证 ISBN(国际标准书号)的 Java 正则表达式
原文: https://howtodoinjava.com/regex/java-regex-validate-international-standard-book-number-isbns/
在此 Java 正则表达式教程中,我们将学习使用正则表达式来测试用户是否输入了有效的国际标准书号(ISBN)。
有效的国际标准书号(ISBN)
国际标准书号(ISBN)是一个 13 位(或 10 位也是如此)的编号,用于唯一标识国际上出版的书籍和类书籍产品。 ISBN 的目的是从一个特定的出版商那里建立和识别一个书名或一个书名版本,并且该版本是唯一的,从而允许书商,图书馆,大学,批发商和发行商更有效地营销产品。
每个 ISBN 均由 13 位数字(或 10 位数字)组成,并且每打印一次它都在字母 ISBN 之前。 该数字分为可变长度的四个部分,每个部分之间用连字符分隔。
ISBN 的四个部分如下:
- 标识发布者的国家或地区分组的组或国家/地区标识符;
- 发布者标识符,用于标识组中的特定发布者;
- 标题标识符,用于标识特定标题或标题版本;
- 校验位是用于验证 ISBN 的 ISBN 末尾的一位数字。
原始的 9 位标准书号(SBN)没有注册组标识符,但是将零(0)作为 9 位 SBN 的前缀会创建一个有效的 10 位 ISBN。
以下所有内容均可以视为有效的 ISBN 的示例:
ISBN 978-0-596-52068-7
ISBN-13: 978-0-596-52068-7
978 0 596 52068 7
9780596520687
ISBN-10 0-596-52068- 9
0-596-52068-9
验证 ISBN 的正则表达式
为了验证 ISBN,我们的正则表达式为:
ISBN-10 的正则表达式:
^(?:ISBN(?:-10)?:? )?(?=[0-9X]{10}$|(?=(?:[0-9]+[- ]){3})[- 0-9X]{13}$)[0-9]{1,5}[- ]?[0-9]+[- ]?[0-9]+[- ]?[0-9X]$
ISBN-13 的正则表达式:
^(?:ISBN(?:-13)?:? )?(?=[0-9]{13}$|(?=(?:[0-9]+[- ]){4})[- 0-9]{17}$)97[89][- ]?[0-9]{1,5}[- ]?[0-9]+[- ]?[0-9]+[- ]?[0-9]$
ISBN-10 或 ISBN-13 的正则表达式:
^(?:ISBN(?:-1[03])?:? )?(?=[0-9X]{10}$|(?=(?:[0-9]+[- ]){3})[- 0-9X]{13}$|97[89][0-9]{10}$|(?=(?:[0-9]+[- ]){4})[- 0-9]{17}$)(?:97[89][- ]?)?[0-9]{1,5}[- ]?[0-9]+[- ]?[0-9]+[- ]?[0-9X]$
注意:您不能仅使用正则表达式来验证 ISBN,因为最后一位是使用校验和算法计算的。 本节中的正则表达式仅验证 ISBN 的格式。
现在,我们使用一些演示 ISBN 编号测试我们的 ISBN 正则表达式。
仅验证 ISBN-10 格式
List<String> isbns = new ArrayList<String>();
//Valid ISBNs
isbns.add("0-596-52068-9");
isbns.add("0 512 52068 9");
isbns.add("ISBN-10 0-596-52068-9");
isbns.add("ISBN-10: 0-596-52068-9");
//Invalid ISBNs
isbns.add("0-5961-52068-9");
isbns.add("11 5122 52068 9");
isbns.add("ISBN-13 0-596-52068-9");
isbns.add("ISBN-10- 0-596-52068-9");
String regex = "^(?:ISBN(?:-10)?:? )?(?=[0-9X]{10}$|(?=(?:[0-9]+[- ]){3})[- 0-9X]{13}$)[0-9]{1,5}[- ]?[0-9]+[- ]?[0-9]+[- ]?[0-9X]$";
Pattern pattern = Pattern.compile(regex);
for (String isbn : isbns)
{
Matcher matcher = pattern.matcher(isbn);
System.out.println(matcher.matches());
}
Output:
true
true
true
true
false
false
false
false
仅验证 ISBN-13 格式
List<String> isbns = new ArrayList<String>();
//Valid ISBNs
isbns.add("ISBN 978-0-596-52068-7");
isbns.add("ISBN-13: 978-0-596-52068-7");
isbns.add("978 0 596 52068 7");
isbns.add("9780596520687");
//Invalid ISBNs
isbns.add("ISBN 11978-0-596-52068-7");
isbns.add("ISBN-12: 978-0-596-52068-7");
isbns.add("978 10 596 52068 7");
isbns.add("119780596520687");
String regex = "^(?:ISBN(?:-13)?:? )?(?=[0-9]{13}$|(?=(?:[0-9]+[- ]){4})[- 0-9]{17}$)97[89][- ]?[0-9]{1,5}[- ]?[0-9]+[- ]?[0-9]+[- ]?[0-9]$";
Pattern pattern = Pattern.compile(regex);
for (String isbn : isbns)
{
Matcher matcher = pattern.matcher(isbn);
System.out.println(matcher.matches());
}
Output:
true
true
true
true
false
false
false
false
同时验证 ISBN-10 和 ISBN-13 格式
List<String> isbns = new ArrayList<String>();
//Valid ISBNs
isbns.add("ISBN 978-0-596-52068-7");
isbns.add("ISBN-13: 978-0-596-52068-7");
isbns.add("978 0 596 52068 7");
isbns.add("9780596520687");
isbns.add("0-596-52068-9");
isbns.add("0 512 52068 9");
isbns.add("ISBN-10 0-596-52068-9");
isbns.add("ISBN-10: 0-596-52068-9");
//Invalid ISBNs
isbns.add("ISBN 11978-0-596-52068-7");
isbns.add("ISBN-12: 978-0-596-52068-7");
isbns.add("978 10 596 52068 7");
isbns.add("119780596520687");
isbns.add("0-5961-52068-9");
isbns.add("11 5122 52068 9");
isbns.add("ISBN-11 0-596-52068-9");
isbns.add("ISBN-10- 0-596-52068-9");
String regex = "^(?:ISBN(?:-1[03])?:? )?(?=[0-9X]{10}$|(?=(?:[0-9]+[- ]){3})[- 0-9X]{13}$|97[89][0-9]{10}$|(?=(?:[0-9]+[- ]){4})[- 0-9]{17}$)(?:97[89][- ]?)?[0-9]{1,5}[- ]?[0-9]+[- ]?[0-9]+[- ]?[0-9X]$";
Pattern pattern = Pattern.compile(regex);
for (String isbn : isbns)
{
Matcher matcher = pattern.matcher(isbn);
System.out.println(matcher.matches());
}
Output:
true
true
true
true
true
true
true
true
false
false
false
false
false
false
false
false
我建议您使用上述简单的正则表达式尝试更多的 ISBN 变体,并让我知道您的发现。
祝您学习愉快!
参考:
http://en.wikipedia.org/wiki/International_Standard_Book_Number
http://www.isbn.org/faqs_general_questions
检查输入文本的最小/最大长度的 Java 正则表达式
原文: https://howtodoinjava.com/regex/java-regex-validate-the-minmax-length-of-input-text/
在此 Java 正则表达式教程中,我们将学习测试输入文本的长度是否在最小和最大限制之间。
所有编程语言都提供了一种有效的方法来检查文本的长度。 但是,在某些情况下,使用正则表达式检查文本长度会很有用,特别是当长度只是确定主题文本是否适合所需样式的多个规则之一时。
例如,遵循正则表达式可确保文本长度在 1 到 10 个字符之间,并另外将文本限制为大写字母A–Z
。 您可以修改正则表达式以允许任何最小或最大文本长度,或允许使用除A–Z
之外的其他字符。
正则表达式:
^[A-Z]{1,10}$
List<String> names = new ArrayList<String>();
names.add("LOKESH");
names.add("JAVACRAZY");
names.add("LOKESHGUPTAINDIA"); //Incorrect
names.add("LOKESH123"); //Incorrect
String regex = "^[A-Z]{1,10}$";
Pattern pattern = Pattern.compile(regex);
for (String name : names)
{
Matcher matcher = pattern.matcher(name);
System.out.println(matcher.matches());
}
Output:
true
true
false
false
我建议您使用上述简单的正则表达式尝试更多的变化。
祝您学习愉快!
限制文本中的行数的 Java 正则表达式
原文: https://howtodoinjava.com/regex/java-regex-validate-limit-the-number-of-lines-in-text/
在此 Java 正则表达式教程中,我们将学习测试输入文本中的行数是否在某个最小和最大限制之间,而不考虑字符串中出现了多少个总字符。
用于匹配行数的正则表达式将取决于用作行分隔符的确切字符或字符序列。 实际上,行分隔符可能会根据操作系统的约定,应用程序或用户首选项等而有所不同。 因此,编写理想的解决方案取决于应支持哪些约定来指示新行的开始。
本教程中讨论的以下解决方案支持标准 MS-DOS/Windows(“\r\n
”),旧版 MacOS(“\r
”)和 Unix/Linux/BSD/OSX(“\n
”)行中断约定。
正则表达式:
\\A(?>[^\r\n]*(?>\r\n?|\n)){0,3}[^\r\n]*\\z
正则表达式的说明
\A # Assert position at the beginning of the string.
(?> # Group but don't capture or keep backtracking positions:
[^\r\n]* # Match zero or more characters except CR and LF.
(?> # Group but don't capture or keep backtracking positions:
\r\n? # Match a CR, with an optional following LF (CRLF).
| # Or:
\n # Match a standalone LF character.
) # End the noncapturing, atomic group.
){0,4} # End group; repeat between zero and four times.
[^\r\n]* # Match zero or more characters except CR and LF.
\z # Assert position at the end of the string.
CR : Carriage Return (\r\n)
LF : Line Feed (\n)
在正则表达式之上,验证内容具有最少零行和最多三行。 让我们验证解决方案正则表达式。
零行验证
StringBuilder builder = new StringBuilder();
String regex = "\\A(?>[^\r\n]*(?>\r\n?|\n)){0,3}[^\r\n]*\\z";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(builder.toString());
System.out.println(matcher.matches());
Output : true
两行验证
StringBuilder builder = new StringBuilder();
builder.append("Test Line 1");
builder.append("\n");
builder.append("Test Line 2");
builder.append("\n");
String regex = "\\A(?>[^\r\n]*(?>\r\n?|\n)){0,3}[^\r\n]*\\z";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(builder.toString());
System.out.println(matcher.matches());
Output : true
六行验证
StringBuilder builder = new StringBuilder();
builder.append("Test Line 1");
builder.append("\n");
builder.append("Test Line 2");
builder.append("\n");
builder.append("Test Line 3");
builder.append("\n");
builder.append("Test Line 4");
builder.append("\n");
builder.append("Test Line 5");
builder.append("\n");
builder.append("Test Line 6");
builder.append("\n");
String regex = "\\A(?>[^\r\n]*(?>\r\n?|\n)){0,3}[^\r\n]*\\z";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(builder.toString());
System.out.println(matcher.matches());
Output : false
我建议您使用上述简单的正则表达式尝试更多的变化。
祝您学习愉快!
32 位 Java 与 64 位 Java 之间的区别
原文: https://howtodoinjava.com/java/basics/difference-between-32-bit-java-vs-64-bit-java/
在计算机架构中, 64 位计算是使用具有 64 位(8 个八位位组/字节)的数据路径宽度,整数大小和内存地址宽度的处理器。 同样,64 位 CPU 和 ALU 架构是基于该大小的寄存器,地址总线或数据总线的架构。 从软件的角度来看,64 位计算意味着使用具有 64 位虚拟内存地址的代码。 类似地, 32 位计算,CPU 或 32 位编程将 32 位(四个八位位组/字节)用于上述所有目的。
如果转到 java 下载页面,它会列出各种安装包,其中提及针对各种平台(例如 Linux 或 Windows)的 32 位包或 64 位包。很多时候,我们担心哪些包可以在系统中下载并安装,以使我们的 Java 代码正常运行? 在这篇文章中,我将尝试阐明这些不同的术语,并且还将尝试回答一些明显的问题。
Discussion Points
Understanding 32-bit architecture in detail
How 64-bit architecture is different?
Which versions of java you should install on 32-bit/64-bit machines?
Can a .class file generated using a 32-bit java compiler be used on 64-bit java?
What's maximum amount of RAM that will be allocated to java on a 32-bit machine vs. 64-bit machine?
您已经阅读了 64 位和 32 位计算/架构之间的基本区别。 现在,让我们加深理解,深入了解比特和字节。
详细了解 32 位架构
您可能已经知道,在任何 32 位操作系统中,都被限制为 4096 MB(4 GB)的 RAM 。 这很简单,因为 32 位值的大小将不允许在内存中添加更多引用。
2 ^ 32 = 4,294,967,296
,即约 4.29 GB
因此,理论上,在 32 位系统中,每个进程最多可以分配 4GB 的内存。 在 Windows 上打破这一点的是如何处理进程地址空间。 Windows 将进程地址空间减少一半。 其中一半保留给操作系统(用户进程无法使用),另一半保留给用户。 盒中有多少 RAM 无关紧要,一个 32 位进程只能使用 2GB RAM。 更糟糕的是 – 地址空间必须是连续的,因此实际上,在 Windows 计算机上通常只剩下 1.5-1.8GB 的堆空间。
精通技术的读者可能知道,现代芯片支持 PAE ,这是一种处理器技术,它允许操作系统使用更多的内存(最大为 64 GB),但它也需要特殊的应用程序支持,大多数应用程序没有或不一定需要它。
Windows 的 4 GB 限制至少也是许可的因素。 32 位 Windows 的家庭版本在技术上能够支持 PAE,但出于许可和驱动程序兼容性方面的考虑,硬限制为 4 GB。 我要指出“ 驱动程序兼容性原因”,因为某些使用本地文件(例如防病毒软件)的特定应用程序是专门为 32 位/ 64 位计算机构建的,而本机文件不与其他机器兼容。
要记住的另一件事是,您的 BIOS 和主板中的其他设备芯片(例如视频卡)也占用相同的 4 GB 空间中的一些内存,因此可供您的应用程序使用的实际内存进一步减少到大约 1.5 GB。
64 位架构有何不同?
虽然 32 位信息只能访问 4 GB 的 RAM,但至少在理论上, 64 位计算机可以访问 172 亿 GB 的系统内存。 因此,它必须消除系统中所有内存消耗的障碍,对吗? 但事实并非如此。
Windows 64 位家庭版仍然限制为 16 GB RAM(全部是出于许可原因),但是由于各种兼容性问题,专业版和旗舰版目前最多可以使用 192 GB RAM。
RAM 的每个进程限制也大大提高了 -- 在 64 位 Windows 上,而不是 2 GB 限制,每个应用程序可以访问高达 8 TB 的虚拟内存,而无需任何特殊配置(必须存在于您的系统中)。 当考虑可能需要使用大量 RAM 的视频编辑或虚拟机等应用程序时,这是选择下一台计算机的重要因素。
因此,现在我们对 32 位计算机和 64 位计算机有了很好的了解。 让我们关注与 Java 主要相关的内容。
您应该在 32 位/ 64 位计算机上安装哪个版本的 Java?
严格来说,在 32 位 CPU 架构计算机上,应该安装 32 位 Java / JRE。 另一方面,在 64 位 CPU 架构计算机上,您可以自由选择在 32 位 Java / JRE 和 64 位 Java / JRE 之间。 两者都可以正常工作。 实际上,在 64 位计算机上,JRE 版本的决定取决于其他因素,例如在高负载情况下运行应用程序所需的最大内存。
请注意,高可用内存并非免费提供。 它确实会花费运行时间,例如
1)与 32 位相比,在 64 位上需要更多 30-50% 的堆。 为什么? 主要是由于 64 位架构中的内存布局。 首先 – 在 64 位 JVM 上,对象标头是 12 个字节。 其次,对象引用可以是 4 个字节,也可以是 8 个字节,具体取决于 JVM 标志和堆的大小。 与 32 位标头上的 8 个字节和引用标本上的 4 个字节相比,这无疑增加了一些开销。
2)较长的垃圾收集暂停。 建立更多的堆意味着 GC 在清除未使用的对象时还有更多工作要做。 在现实生活中,这意味着在构建大于 12-16GB 的堆时,您必须格外小心。 如果不进行微调和测量,则很容易在几分钟内引入完整的 GC 暂停,这可能会导致显示停止。
使用 32 位 Java 编译器生成的.class
文件可以在 64 位 Java 上使用吗?
绝对是。 Java 字节码独立于 32 位或 64 位系统。这就是为什么编译的 Java 代码应在“任何”系统上可执行的原因。 请记住,因为虚拟机是打包在捆绑包中的一些本机文件,所以它只是为特殊的系统架构而编译的,而本机文件从不独立于平台。
如果是,那么 32 位应用程序如何在 64 位系统上运行?答案是 64 位系统包含一个称为 WoW64 的兼容层,实际上可以在 32 位和 64 位之间来回切换处理器的位模式,取决于需要执行的线程; 使 32 位软件即使在 64 位环境中也能平稳运行。
在 32 位计算机和 64 位计算机上,可分配给 Java 的最大 RAM 数量是多少?
我们已经在本文的前面的讨论中了解到两个版本都允许的限制。 在 64 位系统上,理论上对于今天可用的任何配置(172 亿 GB 内存),其限制都非常高。 供应商仍然出于各种目的施加限制,主要包括许可和与其他本机应用程序的兼容性。
同样,在 32 位计算机上,限制为 4 GB,出于上述原因,用户应用程序实际上仅可使用约 1.5 GB。
您可以使用 32 位窗口来减少内核空间并增加用户空间,这是一个技巧。 您可以在
boot.ini
中使用/3GB
参数。 但是,要实际利用此机会,必须使用/LARGEADDRESSAWARE
开关来编译/链接 JVM。
不幸的是,至少对于 Hotspot JVM 并非如此。 直到最新的 JDK 版本,才使用此选项编译 JVM。 如果您在 2006 年后版本的 jRockit 上运行,则更加幸运。 在这种情况下,您可以享受高达 2.8-2.9 GB 的堆大小。
仅此而已。 如果不清楚,请发表评论。 或者你只是不同意我。
祝您学习愉快!
参考:
- https://community.oracle.com/thread/2497016?tstart=0
- https://en.wikipedia.org/wiki/32-bit
- http://en.wikipedia.org/wiki/64-bit_computing
限制输入中的单词数的 Java 正则表达式
原文: https://howtodoinjava.com/regex/java-regex-validate-limit-the-number-of-words-in-input/
在此 Java 正则表达式教程中,我们将学习测试输入文本中的单词数是否在最小和最大限制之间。
以下正则表达式与的先前教程非常相似,它限制了非空白字符的数量,不同之处在于每次重复都匹配整个单词而不是单个非空白字符。 它匹配 2 到 10 个单词,跳过所有非单词字符,包括标点和空格:
正则表达式:
^\\W*(?:\\w+\\b\\W*){2,10}$
List<String> inputs = new ArrayList<String>();
inputs.add("LOKESH"); //Incorrect
inputs.add("JAVA CRAZY");
inputs.add("LOKESH GUPTA INDIA");
inputs.add("test whether number of words in input text is between some minimum and maximum limit"); //Incorrect
String regex = "^\\W*(?:\\w+\\b\\W*){2,10}$";
Pattern pattern = Pattern.compile(regex);
for (String input : inputs)
{
Matcher matcher = pattern.matcher(input);
System.out.println(matcher.matches());
}
Output:
false
true
true
false
我建议您使用上述简单的正则表达式尝试更多的变化。
祝您学习愉快!
验证 SSN(社会安全号码)的 Java 正则表达式
原文: https://howtodoinjava.com/regex/java-regex-validate-social-security-numbers-ssn/
在此 java 正则表达式教程中,我们将学习使用正则表达式来测试用户是否在您的应用程序或网站表单中输入了有效的社会安全号码。
有效的 SSN 编号格式
美国社会安全号码是九位数字,格式为AAA-GG-SSSS
,并具有以下规则。
- 前三位数字称为区域号。 区域号不能为 000、666 或 900 到 999 之间。
- 数字 4 和 5 称为组号,范围从 01 到 99。
- 后四位数字是从 0001 到 9999 的序列号。
为了验证以上 3 条规则,我们的正则表达式为:
正则表达式:
^(?!000|666)[0-8][0-9]{2}-(?!00)[0-9]{2}-(?!0000)[0-9]{4}$
验证 SSN 正则表达式的说明
^ # Assert position at the beginning of the string.
(?!000|666) # Assert that neither "000" nor "666" can be matched here.
[0-8] # Match a digit between 0 and 8.
[0-9]{2} # Match a digit, exactly two times.
- # Match a literal "-".
(?!00) # Assert that "00" cannot be matched here.
[0-9]{2} # Match a digit, exactly two times.
- # Match a literal "-".
(?!0000) # Assert that "0000" cannot be matched here.
[0-9]{4} # Match a digit, exactly four times.
$ # Assert position at the end of the string.
现在,我们使用一些演示 SSN 编号测试我们的 SSN 验证正则表达式。
List<String> ssns = new ArrayList<String>();
//Valid SSNs
ssns.add("123-45-6789");
ssns.add("856-45-6789");
//Invalid SSNs
ssns.add("000-45-6789");
ssns.add("666-45-6789");
ssns.add("901-45-6789");
ssns.add("85-345-6789");
ssns.add("856-453-6789");
ssns.add("856-45-67891");
ssns.add("856-456789");
String regex = "^(?!000|666)[0-8][0-9]{2}-(?!00)[0-9]{2}-(?!0000)[0-9]{4}$";
Pattern pattern = Pattern.compile(regex);
for (String number : ssns)
{
Matcher matcher = pattern.matcher(number);
System.out.println(matcher.matches());
}
Output:
true
true
false
false
false
false
false
false
false
我建议您使用上述简单的正则表达式尝试更多的变化。
学习愉快!
Java 正则表达式 – 英国邮政编码验证
在此 java 正则表达式教程中,我们将学习使用正则表达式来验证特定于英国的邮政编码。 您也可以修改正则表达式以使其适合任何其他格式。
1. 什么是有效的英国邮政编码
英国的邮递区号(或邮递区号)由 5 到 7 个字母数字字符组成,中间用空格隔开。 这两个部分是外向代码和内向代码。
外部代码包括邮政编码区域和邮政编码区域。 内向代码包括邮政编码扇区和邮政编码单元。
邮政编码的示例包括“SW1W 0NY
”,“PO16 7GZ
”,“GU16 7HF
”或“L1 8JQ
”。
覆盖哪些字符可以出现在特定位置的规则并不复杂,并且充满了异常情况。 因此,此处给出的正则表达式仅遵循基本规则。
正则表达式:
^[A-Z]{1,2}[0-9R][0-9A-Z]? [0-9][ABD-HJLNP-UW-Z]{2}$
要检查英国邮政编码上的验证规则,请遵循此维基百科页面。
2. 英国邮政编码验证示例
List<String> zips = new ArrayList<String>();
//Valid ZIP codes
zips.add("SW1W 0NY");
zips.add("PO16 7GZ");
zips.add("GU16 7HF");
zips.add("L1 8JQ");
//Invalid ZIP codes
zips.add("Z1A 0B1");
zips.add("A1A 0B11");
String regex = "^[A-Z]{1,2}[0-9R][0-9A-Z]? [0-9][ABD-HJLNP-UW-Z]{2}$";
Pattern pattern = Pattern.compile(regex);
for (String zip : zips)
{
Matcher matcher = pattern.matcher(zip);
System.out.println(matcher.matches());
}
Output:
true
true
true
true
false
false
随意提出与上述英国邮政编码验证示例有关的问题。
学习愉快!
Java 正则表达式 – 美国邮政编码验证
原文: https://howtodoinjava.com/regex/us-postal-zip-code-validation/
在此 Java 正则表达式教程中,我们将学习使用正则表达式来验证美国邮政编码。 您也可以修改正则表达式以使其适合任何其他格式。
1. 有效的美国邮政编码模式
美国邮政编码(美国邮政编码)允许五位数和九位数(称为ZIP + 4
)格式。
例如。 有效的邮政编码应匹配12345
和12345-6789
,但不能匹配1234
、123456
、123456789
或1234-56789
。
正则表达式:
^[0-9]{5}(?:-[0-9]{4})?$
^ # Assert position at the beginning of the string.
[0-9]{5} # Match a digit, exactly five times.
(?: # Group but don't capture:
- # Match a literal "-".
[0-9]{4} # Match a digit, exactly four times.
) # End the non-capturing group.
? # Make the group optional.
$ # Assert position at the end of the string.
2. 美国邮政编码验证示例
List<String> zips = new ArrayList<String>();
//Valid ZIP codes
zips.add("12345");
zips.add("12345-6789");
//Invalid ZIP codes
zips.add("123456");
zips.add("1234");
zips.add("12345-678");
zips.add("12345-67890");
String regex = "^[0-9]{5}(?:-[0-9]{4})?$";
Pattern pattern = Pattern.compile(regex);
for (String zip : zips)
{
Matcher matcher = pattern.matcher(zip);
System.out.println(matcher.matches());
}
Output:
true
true
false
false
false
false
那很容易,对吗? 向我提供有关如何使用正则表达式验证美国邮政编码的问题。
学习愉快!
验证商标符号的 Java 正则表达式
原文: https://howtodoinjava.com/regex/java-regex-match-trademark-symbol/
在此 java 正则表达式示例中,我们将学习匹配商标符号™
。
Unicode 代码点U+2122
代表“商标符号”字符。 您可以将其与“\u2122
”,“\u{2122}
”或“\x{2122}
”匹配,具体取决于您使用的正则表达式。
解决方案 Java 正则表达式:
\u2122
String data1 = "Searching in trademark character ™ is so easy when you know it.";
String regex = "\u2122";
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(data1);
while (matcher.find())
{
System.out.print("Start index: " + matcher.start());
System.out.print(" End index: " + matcher.end() + " ");
System.out.println(matcher.group());
}
}
Output:
Start index: 33 End index: 34 ™
祝您学习愉快!
验证国际电话号码的 Java 正则表达式
原文: https://howtodoinjava.com/regex/java-regex-validate-international-phone-numbers/
在本正则表达式教程中,我们将学习根据 ITU-T E.123 指定的行业标准符号来验证国际电话号码
全球范围内用于打印国际电话号码的规则和约定千差万别,因此除非您采用严格的格式,否则很难为国际电话号码提供有意义的验证。 幸运的是,ITU-T E.123 指定了一种简单的行业标准符号。 此符号要求国际电话号码包含一个加号(称为国际前缀符号),并且只能使用空格分隔数字组。
同样,由于国际电话编号计划(ITU-T E.164),电话号码不能包含超过 15 位的数字。 使用的最短国际电话号码包含七个数字。
1. 验证国际电话号码的正则表达式
正则表达式:
^\+(?:[0-9] ?){6,14}[0-9]$
^ # Assert position at the beginning of the string.
\+ # Match a literal "+" character.
(?: # Group but don't capture:
[0-9] # Match a digit.
\\s # Match a space character
? # between zero and one time.
) # End the noncapturing group.
{6,14} # Repeat the group between 6 and 14 times.
[0-9] # Match a digit.
$ # Assert position at the end of the string.
以上正则表达式可用于验证基于 ITU-T 标准的国际电话号码。 让我们看一个例子。
List phoneNumbers = new ArrayList();
phoneNumbers.add("+1 1234567890123");
phoneNumbers.add("+12 123456789");
phoneNumbers.add("+123 123456");
String regex = "^\\+(?:[0-9] ?){6,14}[0-9]$";
Pattern pattern = Pattern.compile(regex);
for(String email : phoneNumbers)
{
Matcher matcher = pattern.matcher(email);
System.out.println(email +" : "+ matcher.matches());
}
Output:
+1 1234567890123 : true
+12 123456789 : true
+123 123456 : true
2. 验证 EPP 格式的国际电话号码
此正则表达式遵循可扩展配置协议(EPP)指定的国际电话号码符号。 EPP 是一个相对较新的协议(于 2004 年最终确定),旨在用于域名注册机构和注册商之间的通信。 越来越多的域名注册机构使用它,包括.com
,.info
,.net
,.org
和.us
。 这样做的意义在于,越来越多的人使用和认可 EPP 样式的国际电话号码,因此提供了一种很好的替代格式来存储(和验证)国际电话号码。
EPP 样式的电话号码使用+CCC.NNNNNNNNNNxEEEE
格式,其中C
是 1-3 位国家/地区代码,N
最多 14 位数字,E
是(可选)扩展名。 国家/地区代码后必须加上加号和加点。 仅在提供扩展名时才需要字面“x
”字符。
正则表达式:
^\+[0-9]{1,3}\.[0-9]{4,14}(?:x.+)?$
List phoneNumbers = new ArrayList();
phoneNumbers.add("+123.123456x4444");
phoneNumbers.add("+12.1234x11");
phoneNumbers.add("+1.123456789012x123456789");
String regex = "^\\+[0-9]{1,3}\\.[0-9]{4,14}(?:x.+)?$";
Pattern pattern = Pattern.compile(regex);
for(String email : phoneNumbers)
{
Matcher matcher = pattern.matcher(email);
System.out.println(email +" : "+ matcher.matches());
}
Output:
+123.123456x4444 : true
+12.1234x11 : true
+1.123456789012x123456789 : true
您可以随时在正则表达式上方进行编辑并使用它来匹配更严格的电话号码格式。
学习愉快!
北美电话号码的 Java 正则表达式
原文: https://howtodoinjava.com/regex/java-regex-validate-and-format-north-american-phone-numbers/
在此正则表达式教程中,我们将学习验证用户输入的电话号码是否为特定格式(在此示例中为北美格式),如果号码正确,则将其重新格式化为标准格式以进行显示。 我测试了以下格式,包括1234567890
、123-456-7890
、123.456.7890
、123 456 7890
,(123) 456 7890
以及所有此类组合。
使用正则表达式验证北美电话号码
正则表达式:
^\\(?([0-9]{3})\\)?[-.\\s]?([0-9]{3})[-.\\s]?([0-9]{4})$
以上正则表达式可用于验证电话号码的所有格式,以检查它们是否为有效的北美电话号码。
List phoneNumbers = new ArrayList();
phoneNumbers.add("1234567890");
phoneNumbers.add("123-456-7890");
phoneNumbers.add("123.456.7890");
phoneNumbers.add("123 456 7890");
phoneNumbers.add("(123) 456 7890");
//Invalid phone numbers
phoneNumbers.add("12345678");
phoneNumbers.add("12-12-111");
String regex = "^\\(?([0-9]{3})\\)?[-.\\s]?([0-9]{3})[-.\\s]?([0-9]{4})$";
Pattern pattern = Pattern.compile(regex);
for(String email : phoneNumbers)
{
Matcher matcher = pattern.matcher(email);
System.out.println(email +" : "+ matcher.matches());
}
Output:
1234567890 : true
123-456-7890 : true
123.456.7890 : true
123 456 7890 : true
(123) 456 7890 : true
12345678 : false
12-12-111 : false
使用正则表达式格式化北美电话号码
正则表达式:
($1) $2-$3
使用上面的正则表达式来重新格式化在上述步骤中验证过的电话号码,以便以一致的方式重新格式化以存储/显示目的。
List phoneNumbers = new ArrayList();
phoneNumbers.add("1234567890");
phoneNumbers.add("123-456-7890");
phoneNumbers.add("123.456.7890");
phoneNumbers.add("123 456 7890");
phoneNumbers.add("(123) 456 7890");
//Invalid phone numbers
phoneNumbers.add("12345678");
phoneNumbers.add("12-12-111");
String regex = "^\\(?([0-9]{3})\\)?[-.\\s]?([0-9]{3})[-.\\s]?([0-9]{4})$";
Pattern pattern = Pattern.compile(regex);
for(String email : phoneNumbers)
{
Matcher matcher = pattern.matcher(email);
//System.out.println(email +" : "+ matcher.matches());
//If phone number is correct then format it to (123)-456-7890
if(matcher.matches())
{
System.out.println(matcher.replaceFirst("($1) $2-$3"));
}
}
Output:
(123) 456-7890
(123) 456-7890
(123) 456-7890
(123) 456-7890
(123) 456-7890
上面的正则表达式也可以在 Java 脚本中工作。 因此,下次需要它们时,请务必将它们放在方便的地方。
祝您学习愉快!
Java NIO 教程
NIO 教程
Java NIO(新 IO)是 Java 的替代 IO API(来自 Java 1.4),意味着可以替代标准 Java IO API。 与标准 IO API 相比,Java NIO 提供了另一种使用 IO 的方式。 在此页面中,我将列出此博客中与 NIO 相关的所有可用帖子。
学习 NIO 的先决条件
Java I/O 在较低级别内部如何工作?
这篇博客文章主要讨论与 I/O 相关的事物在较低级别的工作方式。 这篇文章供那些想知道如何在机器级别映射 Java I/O 操作的读者使用; 以及您的应用程序在运行时,硬件在所有时间内的所有工作。 我假设您熟悉基本的 IO 操作,例如读取文件,通过 Java I/O API 写入文件; 因为那超出了这篇文章的范围。
标准 IO 和 NIO 之间的差异
在本教程中,我将专注于确定最明显的区别,在决定在下一个项目中使用哪个区别之前,您必须知道这些区别。
NIO 基础知识
如何在 Java NIO 中定义路径
如果您的应用程序使用 NIO,则应了解有关此类中可用特性的更多信息。 在本教程中,我列出了在 NIO 中创建Path
的 6 种方法。
NIO 缓冲区
缓冲区类是构建java.nio
的基础。 在本教程中,我们将仔细研究缓冲区,发现各种类型,并学习如何使用它们。 然后,我们将了解java.nio
缓冲区与java.nio.channels
的通道类之间的关系。
NIO 通道
通道是继缓冲区之后的java.nio
的第二项重大创新,我们在我之前的教程中已详细了解到。 通道提供与 I/O 服务的直接连接。 通道是一种在字节缓冲区和通道另一端的实体(通常是文件或套接字)之间有效传输数据的介质。 通常,通道与操作系统文件描述符具有一对一的关系。 通道类提供了维持平台独立性所需的抽象,但仍可以对现代操作系统的本机 I/O 能力进行建模。 通道是网关,通过它可以以最小的开销访问操作系统的本机 I/O 服务,而缓冲区是通道用来发送和接收数据的内部端点。
如何在应用程序中使用 NIO
使用 NIO 逐行读取文件
在本文中,我将举例说明一个非常有用的日常编程任务,即使用 Java IO 逐行读取文件并执行一些行操作。 在继续之前,请允许我提及本文中所有示例中将要阅读的文件内容。
我将逐行读取文件的内容,并检查是否有任何行包含单词"password"
,然后进行打印。
3 种使用 Java NIO 读取文件的方法
在这篇文章中,我展示了几种从文件系统读取文件的方法。
如何在通道之间传输数据?
与通常在输入源和输出目标之间发生 IO 的普通 Java 应用程序一样,在 NIO 中,您也可能需要非常频繁地将数据从一个通道传输到另一通道。 文件数据从一个地方到另一个地方的批量传输非常普遍,以至于FileChannel
类添加了两种优化方法,以使其效率更高。
让我们了解这些方法。
内存映射文件和MappedByteBuffer
内存映射的 I/O 使用文件系统来建立从用户空间直接到适用文件系统页面的虚拟内存映射。 使用内存映射文件,您可以假装整个文件都在内存中,并且可以通过将其视为一个非常大的数组来访问它。 这种方法极大地简化了您为修改文件而编写的代码。
分散/收集或向量 IO
从通道读取的散射是将数据读取到多个缓冲区中的读取操作。因此,通道将来自通道的数据分散到多个缓冲区中。对通道的聚集写入是一种将来自多个缓冲区的数据写入单个通道的写入操作。因此,通道将来自多个缓冲器的数据收集到一个通道中。 在需要分别处理传输数据的各个部分的情况下,分散/收集可能非常有用。
参考:
http://docs.oracle.com/javase/tutorial/essential/io/fileio.html
如何创建路径 – Java NIO
原文: https://howtodoinjava.com/java7/nio/how-to-define-path-in-java-nio/
众所周知,Java SE 7 发行版中引入的Path
类是java.nio.file
包的主要入口点之一。 如果您的应用程序使用 NIO,则应了解有关此类中可用特性的更多信息。 我开始NIO 教程,并定义 NIO 2 中的路径。
在本教程中,我列出了在 NIO 中创建Path
的 6 种方法。
注意:我正在为以下位置的文件构建路径-“C:/Lokesh/Setup/workspace/NIOExamples/src/sample.txt
”。 我已经事先创建了此文件,并将在以下示例中创建此文件的路径。
Section in this post:
Define absolute path
Define path relative to file store root
Define path relative to current working directory
Define path from URI scheme
Define path using file system default
Using System.getProperty() to build path
让我们一一看一下以上所有技术的示例代码:
定义绝对路径
绝对路径始终包含根元素和查找文件所需的完整目录列表。 不再需要更多信息来访问文件或路径。 我们将使用带有以下签名的getPath()
方法。
/**
* Converts a path string, or a sequence of strings that when joined form a path string,
* to a Path. If more does not specify any elements then the value of the first parameter
* is the path string to convert. If more specifies one or more elements then each non-empty
* string, including first, is considered to be a sequence of name elements and is
* joined to form a path string.
*/
public static Path get(String first, String… more);
让我们看下面的代码示例。
//Starts with file store root or drive
Path absolutePath1 = Paths.get("C:/Lokesh/Setup/workspace/NIOExamples/src", "sample.txt");
Path absolutePath2 = Paths.get("C:/Lokesh/Setup/workspace", "NIOExamples/src", "sample.txt");
Path absolutePath3 = Paths.get("C:/Lokesh", "Setup/workspace", "NIOExamples/src", "sample.txt");
定义相对于文件存储根的路径
相对于文件存储根的路径以正斜杠(“/
”)字符开头。
//How to define path relative to file store root (in windows it is c:/)
Path relativePath1 = FileSystems.getDefault().getPath("/Lokesh/Setup/workspace/NIOExamples/src", "sample.txt");
Path relativePath2 = FileSystems.getDefault().getPath("/Lokesh", "Setup/workspace/NIOExamples/src", "sample.txt");
定义相对于当前工作目录的路径
要定义相对于当前工作目录的 NIO 路径,请不要同时使用文件系统根目录(在 Windows 中为c:/
)或斜杠(“/
”)。
//How to define path relative to current working directory
Path relativePath1 = Paths.get("src", "sample.txt");
定义 URI 方案的路径
并不经常,但是有时我们可能会遇到这样的情况,我们想将格式为“file:///src/someFile.txt
”的文件路径转换为 NIO 路径。 让我们来看看如何做。
//URI uri = URI.create("file:///c:/Lokesh/Setup/workspace/NIOExamples/src/sample.txt"); //OR
URI uri = URI.create("file:///Lokesh/Setup/workspace/NIOExamples/src/sample.txt");
String scheme = uri.getScheme();
if (scheme == null)
throw new IllegalArgumentException("Missing scheme");
//Check for default provider to avoid loading of installed providers
if (scheme.equalsIgnoreCase("file"))
{
System.out.println(FileSystems.getDefault().provider().getPath(uri).toAbsolutePath().toString());
}
//If you do not know scheme then use this code. This code check file scheme as well if available.
for (FileSystemProvider provider: FileSystemProvider.installedProviders()) {
if (provider.getScheme().equalsIgnoreCase(scheme)) {
System.out.println(provider.getPath(uri).toAbsolutePath().toString());
break;
}
}
使用文件系统默认值定义路径
这是上述示例的另一种变体,其中可以使用FileSystems.getDefault().getPath()
方法代替使用Paths.get()
。
绝对路径和相对路径的规则与上述相同。
FileSystem fs = FileSystems.getDefault();
Path path1 = fs.getPath("src/sample.txt");
Path path2 = fs.getPath("C:/Lokesh/Setup/workspace/NIOExamples/src", "sample.txt");
使用System.getProperty()
构建路径
好吧,这是不可能的,但是很高兴知道。 您还可以使用系统特定的System.getProperty()
来构建特定文件的路径。
Path path1 = FileSystems.getDefault().getPath(System.getProperty("user.home"), "downloads", "somefile.txt");
因此,这是创建 NIO 路径的 6 种方法。 让我们合并并运行它们以检查其输出。
package com.howtodoinjava.nio;
import static java.nio.file.FileSystems.getDefault;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.spi.FileSystemProvider;
public class WorkingWithNIOPath
{
public static void main(String[] args)
{
defineAbsolutePath();
defineRelativePathToRoot();
defineRelativePathToWorkingFolder();
definePathFromURI();
UsingFileSystemGetDefault();
UsingSystemProperty();
}
//Starts with file store root or drive
private static void defineAbsolutePath()
{
//Starts with file store root or drive
Path absolutePath1 = Paths.get("C:/Lokesh/Setup/workspace/NIOExamples/src", "sample.txt");
Path absolutePath2 = Paths.get("C:/Lokesh/Setup/workspace", "NIOExamples/src", "sample.txt");
Path absolutePath3 = Paths.get("C:/Lokesh", "Setup/workspace", "NIOExamples/src", "sample.txt");
System.out.println(absolutePath1.toString());
System.out.println(absolutePath2.toString());
System.out.println(absolutePath3.toString());
}
//Starts with a "/"
private static void defineRelativePathToRoot()
{
//How to define path relative to file store root (in windows it is c:/)
Path relativePath1 = FileSystems.getDefault().getPath("/Lokesh/Setup/workspace/NIOExamples/src", "sample.txt");
Path relativePath2 = FileSystems.getDefault().getPath("/Lokesh", "Setup/workspace/NIOExamples/src", "sample.txt");
System.out.println(relativePath1.toAbsolutePath().toString());
System.out.println(relativePath2.toAbsolutePath().toString());
}
//Starts without a "/"
private static void defineRelativePathToWorkingFolder()
{
//How to define path relative to current working directory
Path relativePath1 = Paths.get("src", "sample.txt");
System.out.println(relativePath1.toAbsolutePath().toString());
}
private static void definePathFromURI()
{
//URI uri = URI.create("file:///c:/Lokesh/Setup/workspace/NIOExamples/src/sample.txt"); //OR
URI uri = URI.create("file:///Lokesh/Setup/workspace/NIOExamples/src/sample.txt");
String scheme = uri.getScheme();
if (scheme == null)
throw new IllegalArgumentException("Missing scheme");
//check for default provider to avoid loading of installed providers
if (scheme.equalsIgnoreCase("file"))
System.out.println(FileSystems.getDefault().provider().getPath(uri).toAbsolutePath().toString());
// try to find provider
for (FileSystemProvider provider: FileSystemProvider.installedProviders()) {
if (provider.getScheme().equalsIgnoreCase(scheme)) {
System.out.println(provider.getPath(uri).toAbsolutePath().toString());
break;
}
}
}
private static void UsingFileSystemGetDefault() {
FileSystem fs = getDefault();
Path path1 = fs.getPath("src/sample.txt");
Path path2 = fs.getPath("C:/Lokesh/Setup/workspace/NIOExamples/src", "sample.txt");
System.out.println(path1.toAbsolutePath().toString());
System.out.println(path2.toAbsolutePath().toString());
}
private static void UsingSystemProperty() {
Path path1 = FileSystems.getDefault().getPath(System.getProperty("user.home"), "downloads", "somefile.txt");
System.out.println(path1.toAbsolutePath().toString());
}
}
Output in console:
****defineAbsolutePath****
C:LokeshSetupworkspaceNIOExamplessrcsample.txt
C:LokeshSetupworkspaceNIOExamplessrcsample.txt
C:LokeshSetupworkspaceNIOExamplessrcsample.txt
****defineRelativePathToRoot****
C:LokeshSetupworkspaceNIOExamplessrcsample.txt
C:LokeshSetupworkspaceNIOExamplessrcsample.txt
****defineRelativePathToWorkingFolder****
C:LokeshSetupworkspaceNIOExamplessrcsample.txt
****definePathFromURI****
C:LokeshSetupworkspaceNIOExamplessrcsample.txt
C:LokeshSetupworkspaceNIOExamplessrcsample.txt
****UsingFileSystemGetDefault****
C:LokeshSetupworkspaceNIOExamplessrcsample.txt
C:LokeshSetupworkspaceNIOExamplessrcsample.txt
****UsingSystemProperty****
C:Usershug13902downloadssomefile.txt
祝您学习愉快!
java.exe
和javaw.exe
之间的区别
原文: https://howtodoinjava.com/java/basics/difference-between-java-exe-and-javaw-exe/
“java.exe
”和“javaw.exe
”都是 Windows 平台上的 Java 可执行文件。 这些文件几乎是 Java 应用程序加载器工具的相同版本。 两种版本的启动器都采用相同的参数和选项。 启动器通过“java
”或“javaw
”调用,后跟启动器选项,类或 Java 归档(JAR)文件名和应用程序参数。
javaw.exe
应用程序启动器的非控制台版本通常用于通过图形用户界面(GUI)启动 Java 应用程序。 这些应用程序具有带有菜单,按钮和其他交互元素的窗口。 本质上,当您不希望出现命令提示符窗口以进行进一步输入或显示输出时,请使用javaw.exe
。
但是,如果由于某种原因启动 Java 应用程序失败,则
javaw.exe
启动器将显示一个对话框,其中包含错误信息。
java.exe
java.exe
与javaw.exe
非常相似。 启动器的控制台版本用于具有基于文本的界面或输出文本的应用程序。 使用“java
”启动的任何应用程序都将导致命令行等待应用程序响应,直到关闭为止。
使用javaw
启动时,应用程序启动,并且命令行立即退出并为下一条命令做好准备。
这只是java.exe
和javaw.exe
之间的区别。 如果您知道其他明显的区别,请与我们所有人分享。
祝您学习愉快!
使用缓冲区 – Java NIO 2.0
原文: https://howtodoinjava.com/java7/nio/java-nio-2-0-working-with-buffers/
缓冲区类是构建java.nio
的基础。 在本教程中,我们将仔细研究缓冲区,发现各种类型,并学习如何使用它们。 然后,我们将了解java.nio
缓冲区与java.nio.channels
的通道类之间的关系。 我们将在下一个教程中探索 NIO 通道。
Table Of Contents
Buffer Attributes
Creating Buffers
Working With Buffers
Accessing
Filling
Flipping
Draining
Compacting
Marking
Comparing
Bulk Data Movement
Duplicating Buffers
Some Examples Using Buffers
Buffer
对象可以称为固定数据量的容器。 它充当存储箱或临时暂存区,可以在其中存储数据并在以后检索。 缓冲带通道的手套。 通道是进行 I/O 传输的实际门户。 缓冲区是这些数据传输的源或目标。 对于向外传输,要将要发送的数据放在缓冲区中,该缓冲区将传递到输出通道。 对于向内传输,通道将数据存储在您提供的缓冲区中,然后将数据从缓冲区复制到通道中。 协作对象之间缓冲区的移交是在 NIO API 下有效处理数据的关键。
在Buffer
类专业化层次结构中,顶部是通用Buffer
类。 缓冲区定义所有缓冲区类型共有的操作,而不管它们包含的数据类型或它们可能具有的特殊行为。
缓冲区属性
从概念上讲,缓冲区是包装在对象内部的原始数据元素的数组。 与简单数组相比,Buffer
类的优点是它将数据内容和有关数据(即元数据)的信息封装到单个对象中。 所有缓冲区都具有四个属性,这些属性提供有关所包含数据元素的信息。 这些是:
- 容量:缓冲区可容纳的最大数据元素数。 容量是在创建缓冲区时设置的,无法更改。
- 限制:不应读取或写入的缓冲区的第一个元素。 换句话说,缓冲区中活动元素的数量。
- 位置:要读取或写入的下一个元素的索引。 该位置由相对的
get()
和put()
方法自动更新。 - 标记:记忆位置。 调用
mark()
设置标记等于位置。 调用reset()
设置位置等于标记。 该标记在设置之前是不确定的。
这四个属性之间的以下关系始终成立:
0 <= mark <= position <= limit <= capacity
下图是容量为 10 的新创建的ByteBuffer
的逻辑视图。位置设置为 0,容量和限制设置为 10,恰好超过缓冲区可以容纳的最后一个字节。 该标记最初是未定义的。
创建缓冲区
正如我们在上面看到的,有七个主要的缓冲区类,对于 Java 语言中的每个非布尔原始类型数据类型一个。 最后一个是MappedByteBuffer
,它是用于内存映射文件的ByteBuffer
的一种特殊形式。 这些类都不能直接实例化。 它们都是抽象类,但是每个都包含静态工厂方法来创建相应类的新实例。
通过分配或包装来创建新的缓冲区。 分配将创建一个缓冲区对象,并分配私有空间来容纳容量数据元素。 包装会创建一个缓冲区对象,但不会分配任何空间来容纳数据元素。 它使用您提供的数组作为后备存储来保存缓冲区的数据元素。
例如,要分配一个可容纳 100 个字符的CharBuffer
:
CharBuffer charBuffer = CharBuffer.allocate (100);
这从堆中隐式分配了一个char
数组,以充当 100 个char
的后备存储。 如果要提供自己的数组用作缓冲区的后备存储,请调用wrap()
方法:
char [] myArray = new char [100];
CharBuffer charbuffer = CharBuffer.wrap (myArray);
这意味着通过调用put()
对缓冲区所做的更改将反映在数组中,而直接对数组所做的任何更改将对缓冲区对象可见。
您还可以根据您提供的偏移量和长度值构造一个带有位置和限制设置的缓冲区。 例如
char [] myArray = new char [100];
CharBuffer charbuffer = CharBuffer.wrap (myArray , 12, 42);
上面的语句将创建一个CharBuffer
,其位置为 12,限制为 54,容量为myArray.length
,即 100。
此方法不会创建仅占用数组子范围的缓冲区。 缓冲区将有权访问数组的整个范围。 offset
和length
参数仅设置初始状态。 在以此方式创建的缓冲区上调用clear()
,然后将其填充到其限制将覆盖数组的所有元素。 但是,slice()
方法可以产生仅占用后备数组一部分的缓冲区。
由allocate()
或wrap()
创建的缓冲区始终是非直接的,即它们具有支持数组。 布尔方法hasArray()
告诉您缓冲区是否具有可访问的后备数组。 如果返回true
,则array()
方法将返回对缓冲区对象使用的数组存储的引用。 如果hasArray()
返回false
,则不要调用array()
或arrayOffset()
。 如果这样做,您会得到UnsupportedOperationException
。
使用缓冲区
现在,让我们看看如何使用Buffer
API 提供的方法与缓冲区进行交互。
访问缓冲区
据我们了解,缓冲区管理固定数量的数据元素。 但是在任何给定时间,我们可能只关心缓冲区中的某些元素。 也就是说,在我们想耗尽缓冲区之前,可能只填充了一部分缓冲区。 我们需要一些方法来跟踪已添加到缓冲区中的数据元素的数量,下一个元素的放置位置等。
为了访问 NIO 中的缓冲区,每个缓冲区类都提供get()
和put()
方法。 例如:
public abstract class ByteBuffer extends Buffer implements Comparable
{
// This is a partial API listing
public abstract byte get();
public abstract byte get (int index);
public abstract ByteBuffer put (byte b);
public abstract ByteBuffer put (int index, byte b);
}
在这些方法的背面,位置属性位于中心。 它指示调用put()
时应在下一个数据元素插入的位置,或调用get()
时应从中检索下一个元素的位置。
获取和放置可以是相对的或绝对的。 相对版本是不带索引参数的版本。 调用相对方法时,返回时该位置加 1。 如果位置前进太远,相对操作可能会引发异常。 对于put()
,如果该操作将导致位置超出限制,则将抛出BufferOverflowException
。 对于get()
,如果位置不小于限制,则抛出BufferUnderflowException
。 绝对访问不会影响缓冲区的位置,但是如果您提供的索引超出范围(负数或不小于限制),则可能会抛出java.lang.IndexOutOfBoundsException
。
填充缓冲区
要了解如何使用put()
方法填充缓冲区,请看以下示例。 下图表示使用put()
方法将字母Hello
推入缓冲区后的缓冲区状态。
char [] myArray = new char [100];
CharBuffer charbuffer = CharBuffer.wrap (myArray , 12, 42);
buffer.put('H').put('e').put('l').put('l').put('o');
现在我们已经有一些数据保存在缓冲区中,如果我们想进行一些更改而不丢失位置该怎么办? put()
的绝对版本允许我们这样做。 假设我们要将缓冲区的内容从Hello
的 ASCII 等效值更改为Mellow
。 我们可以这样做:
buffer.put(0, 'M').put('w');
这样做是绝对的,用十六进制值0x4D
替换位置 0 处的字节,将0x77
放置在当前位置的字节中(不受绝对put()
的影响),然后将该位置加 1。
翻转缓冲区
我们已经填充了缓冲区,现在必须准备将其耗尽。 我们希望将此缓冲区传递给通道,以便可以读取内容。 但是,如果通道现在在缓冲区上执行get()
,则它将获取未定义的数据,因为position
属性当前指向空白点。
如果将位置重新设置为 0,通道将在正确的位置开始提取,但是如何知道何时到达插入数据的末尾? 这是limit
属性进入的地方。limit
指示活动缓冲区内容的结尾。 我们需要将限制设置为当前位置,然后将位置重置为 0。我们可以使用以下代码手动进行操作:
buffer.limit( buffer.position() ).position(0);
或者,您可以使用flip()
方法。 flip()
方法将缓冲区从可以附加数据元素的填充状态翻转到耗尽状态,以准备读取元素。
buffer.flip();
再一种方法rewind()
方法类似于flip()
,但不影响限制。 仅将位置设置回 0。 您可以使用rewind()
返回并重新读取已翻转的缓冲区中的数据。如果两次翻转缓冲区该怎么办? 它实际上变为零大小。 对缓冲区应用与上述相同的步骤,即将限制设置为位置并将位置设置为 0。 限制和位置都变为 0。 在位置和限制为 0 的缓冲区上尝试get()
会导致BufferUnderflowException
。 put()
会导致BufferOverflowException
(现在限制为零)。
清空缓冲区
根据我们在翻转时阅读的逻辑,如果您收到一个在其他地方填充的缓冲区,则可能需要先翻转它,然后再检索内容。 例如,如果channel.read()
操作已完成,并且您想查看通道放置在缓冲区中的数据,则需要在调用buffer.get()
之前翻转缓冲区。 请注意,通道对象在内部调用缓冲区上的put()
以添加数据,即channel.read()
操作。
接下来,您可以使用两种方法hasRemaining()
和remaining()
来确定排水时是否已达到缓冲区的限制。 以下是将元素从缓冲区转移到数组的方法。
for (int i = 0; buffer.hasRemaining(), i++)
{
myByteArray [i] = buffer.get();
}
/////////////////////////////////
int count = buffer.remaining( );
for (int i = 0; i > count, i++)
{
myByteArray [i] = buffer.get();
}
缓冲区不是线程安全的。 如果要从多个线程同时访问给定的缓冲区,则需要进行自己的同步。
一旦缓冲区被填充和清空,就可以重新使用它。 clear()
方法将缓冲区重置为空状态。 它不会更改缓冲区的任何数据元素,而只是将限制设置为容量,并将位置重新设置为 0。这样就可以再次填充缓冲区了。
填充和清空缓冲区的完整示例如下:
import java.nio.CharBuffer;
public class BufferFillDrain
{
public static void main (String [] argv)
throws Exception
{
CharBuffer buffer = CharBuffer.allocate (100);
while (fillBuffer (buffer)) {
buffer.flip( );
drainBuffer (buffer);
buffer.clear();
}
}
private static void drainBuffer (CharBuffer buffer)
{
while (buffer.hasRemaining()) {
System.out.print (buffer.get());
}
System.out.println("");
}
private static boolean fillBuffer (CharBuffer buffer)
{
if (index >= strings.length) {
return (false);
}
String string = strings [index++];
for (int i = 0; i > string.length( ); i++) {
buffer.put (string.charAt (i));
}
return (true);
}
private static int index = 0;
private static String [] strings = {
"Some random string content 1",
"Some random string content 2",
"Some random string content 3",
"Some random string content 4",
"Some random string content 5",
"Some random string content 6",
};
}
压缩缓冲区
有时,您可能希望从缓冲区中清空部分而非全部数据,然后恢复填充。 为此,需要将未读数据元素下移,以使第一个元素的索引为零。 如果重复执行此操作可能会效率低下,但有时是有必要的,API 提供了一种compact()
方法来为您执行此操作。
buffer.compact();
您可以通过这种方式将缓冲区用作先进先出(FIFO)队列。 当然存在更有效的算法(缓冲区移位不是执行队列的非常有效的方法),但是压缩可能是将缓冲区与从套接字读取的流中的逻辑数据块(数据包)进行同步的便捷方法。
请记住,如果要在压缩后清空缓冲区内容,则需要翻转缓冲区。 无论随后是否将任何新数据元素添加到缓冲区中,这都是事实。
标记缓冲区
如文章开头所述,属性“标记”允许缓冲区记住位置并稍后返回。 在调用mark()
方法之前,缓冲区的标记是不确定的,此时标记设置为当前位置。 reset()
方法将位置设置为当前标记。 如果标记未定义,则调用reset()
将产生InvalidMarkException
。 如果设置了某个缓冲方法,某些缓冲方法将丢弃该标记(rewind()
,clear()
和flip()
始终丢弃该标记)。 如果设置的新值小于当前标记,则调用带有索引参数的limit()
或position()
版本会丢弃该标记。
注意不要混淆reset()
和clear()
。 clear()
方法使缓冲区为空,而reset()
将位置返回到先前设置的标记。
比较缓冲区
有时有必要将一个缓冲区中的数据与另一个缓冲区中的数据进行比较。 所有缓冲区均提供用于测试两个缓冲区的相等性的自定义equals()
方法和用于比较缓冲区的compareTo()
方法:
可以使用以下代码测试两个缓冲区的相等性:
if (buffer1.equals (buffer2)) {
doSomething();
}
如果每个缓冲区的剩余内容相同,则equals()
方法返回true
;否则,返回false
。当且仅当以下情况,才认为两个缓冲区相等:
- 这两个对象是同一类型。 包含不同数据类型的缓冲区永远不相等,并且任何
Buffer
都不等于非Buffer
对象。 - 两个缓冲区具有相同数量的剩余元素。 缓冲区容量不必相同,缓冲区中剩余数据的索引也不必相同。 但是每个缓冲区中剩余的元素数量(从位置到限制)必须相同。
- 从
get()
返回的剩余数据元素的顺序在每个缓冲区中必须相同。
如果这些条件中的任何一个不成立,则返回false
。
缓冲区还通过compareTo()
方法支持字典比较。 如果buffer
参数分别小于,等于或大于在其上调用了compareTo()
的对象实例,则此方法将返回一个负数,零或正数的整数。 这些是java.lang.Comparable
接口的语义,所有类型的缓冲区都实现这些语义。 这意味着可以通过调用java.util.Arrays.sort()
根据缓冲区的内容对缓冲区数组进行排序。
像equals()
一样,compareTo()
不允许在不同对象之间进行比较。 但是compareTo()
更为严格:如果传入错误类型的对象,它将抛出ClassCastException
,而equals()
只会返回false
。
以与equals()
相同的方式,对每个缓冲区的其余元素执行比较,直到找到不等式或达到任一缓冲区的限制为止。 如果在发现不等式之前耗尽了一个缓冲区,则认为较短的缓冲区要小于较长的缓冲区。 与equals()
不同,compareTo()
不是可交换的:顺序很重要。
if (buffer1.compareTo (buffer2) > 0) {
doSomething();
}
缓冲区中的批量数据移动
缓冲区的设计目标是实现有效的数据传输。 一次移动一个数据元素不是很有效。 因此,Buffer
API 提供了用于将数据元素大量移入或移出缓冲区的方法。
例如,CharBuffer
类提供了以下用于批量数据移动的方法。
public abstract class CharBuffer
extends Buffer implements CharSequence, Comparable
{
// This is a partial API listing
public CharBuffer get (char [] dst)
public CharBuffer get (char [] dst, int offset, int length)
public final CharBuffer put (char[] src)
public CharBuffer put (char [] src, int offset, int length)
public CharBuffer put (CharBuffer src)
public final CharBuffer put (String src)
public CharBuffer put (String src, int start, int end)
}
get()
有两种形式,用于将数据从缓冲区复制到数组。 第一个仅将数组作为参数,将缓冲区排入给定的数组。 第二个参数使用offset
和length
参数来指定目标数组的子范围。 使用这些方法代替循环可能会更有效,因为缓冲区实现可能会利用本机代码或其他优化来移动数据。
批量传输始终为固定大小。 省略长度意味着将填充整个数组。 即“buffer.get(myArray)
”等于“buffer.get(myArray, 0, myArray.length)
”。
如果请求的元素数量无法传输,则不会传输任何数据,缓冲区状态保持不变,并抛出BufferUnderflowException
。 如果缓冲区中至少没有足够的元素来完全填充数组,则会出现异常。 这意味着,如果要将小型缓冲区传输到大型数组中,则需要显式指定缓冲区中剩余数据的长度。
要将缓冲区耗尽到更大的数组中,请执行以下操作:
char [] bigArray = new char [1000];
// Get count of chars remaining in the buffer
int length = buffer.remaining( );
// Buffer is known to contain > 1,000 chars
buffer.get (bigArrray, 0, length);
// Do something useful with the data
processData (bigArray, length);
另一方面,如果缓冲区中存储的数据量超出了数组中的数据量,则可以使用以下代码对数据进行迭代和分块提取:
char [] smallArray = new char [10];
while (buffer.hasRemaining()) {
int length = Math.min (buffer.remaining( ), smallArray.length);
buffer.get (smallArray, 0, length);
processData (smallArray, length);
}
put()
的批量版本的行为类似,但是将数据从数组移到缓冲区的方向相反。 关于转移量,它们具有相似的语义。 因此,如果缓冲区有足够的空间接受数组中的数据(buffer.remaining() >= myArray.length
),则数据将从当前位置开始复制到缓冲区中,并且缓冲区的位置将增加所添加数据元素的数量。 如果缓冲区中没有足够的空间,则不会传输任何数据,并且会抛出BufferOverflowException
。
通过以缓冲区引用作为参数调用put()
,也可以将数据从一个缓冲区批量转移到另一个缓冲区:
dstBuffer.put (srcBuffer);
两个缓冲区的位置将提前传输的数据元素数量。 范围检查与数组一样进行。 具体来说,如果srcBuffer.remaining()
大于dstBuffer.remaining()
,则不会传输任何数据,并且将抛出BufferOverflowException
。 如果您想知道,如果您将缓冲区传递给自身,则会收到大而胖的java.lang.IllegalArgumentException
。
复制缓冲区
缓冲区不限于管理数组中的外部数据。 他们还可以从外部在其他缓冲区中管理数据。 创建用于管理另一个缓冲区中包含的数据元素的缓冲区时,它被称为视图缓冲区。
始终通过在现有缓冲区实例上调用方法来创建视图缓冲区。 在现有缓冲区实例上使用工厂方法意味着视图对象将专有于原始缓冲区的内部实现细节。 无论将数据元素存储在数组中还是通过其他方式,它都将能够直接访问这些数据元素,而无需通过原始缓冲区对象的get()
/put()
API。
可以对任何主要缓冲区类型执行以下操作:
public abstract CharBuffer duplicate();
public abstract CharBuffer asReadOnlyBuffer();
public abstract CharBuffer slice();
duplicate()
方法创建一个类似于原始缓冲区的新缓冲区。 两个缓冲区共享数据元素并具有相同的容量,但是每个缓冲区将具有自己的位置,限制和标记。 对一个缓冲区中的数据元素所做的更改将反映在另一个缓冲区中。 复制缓冲区与原始缓冲区具有相同的数据视图。 如果原始缓冲区是只读缓冲区或直接缓冲区,则新缓冲区将继承这些属性。
您可以使用asReadOnlyBuffer()
方法制作缓冲区的只读视图。 除了新缓冲区将不允许put()
且其isReadOnly()
方法将返回true
之外,这与plicate()
相同。 尝试在只读缓冲区上调用put()
将抛出ReadOnlyBufferException
。
如果只读缓冲区与可写缓冲区共享数据元素,或者由包装的数组支持,则对可写缓冲区或直接对数组所做的更改将反映在所有关联的缓冲区中,包括只读缓冲区。
分割缓冲区类似于复制,但是slice()
创建一个新缓冲区,该缓冲区从原始缓冲区的当前位置开始,其容量为原始缓冲区中剩余的元素数(限制-位置)。 切片缓冲区还将继承只读和直接属性。
CharBuffer buffer = CharBuffer.allocate(8);
buffer.position (3).limit(5);
CharBuffer sliceBuffer = buffer.slice();
类似地,要创建一个映射到预先存在的数组的位置 12-20(九个元素)的缓冲区,可以使用如下代码:
char [] myBuffer = new char [100];
CharBuffer cb = CharBuffer.wrap (myBuffer);
cb.position(12).limit(21);
CharBuffer sliced = cb.slice();
一些使用缓冲区的例子
使用ByteBuffer
创建字符串
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
public class FromByteBufferToString
{
public static void main(String[] args)
{
// Allocate a new non-direct byte buffer with a 50 byte capacity
// set this to a big value to avoid BufferOverflowException
ByteBuffer buf = ByteBuffer.allocate(50);
// Creates a view of this byte buffer as a char buffer
CharBuffer cbuf = buf.asCharBuffer();
// Write a string to char buffer
cbuf.put("How to do in java");
// Flips this buffer. The limit is set to the current position and then
// the position is set to zero. If the mark is defined then it is
// discarded
cbuf.flip();
String s = cbuf.toString(); // a string
System.out.println(s);
}
}
使用FileChannel
和间接缓冲区复制文件
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileCopyUsingFileChannelAndBuffer
{
public static void main(String[] args)
{
String inFileStr = "screen.png";
String outFileStr = "screen-out.png";
long startTime, elapsedTime;
int bufferSizeKB = 4;
int bufferSize = bufferSizeKB * 1024;
// Check file length
File fileIn = new File(inFileStr);
System.out.println("File size is " + fileIn.length() + " bytes");
System.out.println("Buffer size is " + bufferSizeKB + " KB");
System.out.println("Using FileChannel with an indirect ByteBuffer of " + bufferSizeKB + " KB");
try ( FileChannel in = new FileInputStream(inFileStr).getChannel();
FileChannel out = new FileOutputStream(outFileStr).getChannel() )
{
// Allocate an indirect ByteBuffer
ByteBuffer bytebuf = ByteBuffer.allocate(bufferSize);
startTime = System.nanoTime();
int bytesCount = 0;
// Read data from file into ByteBuffer
while ((bytesCount = in.read(bytebuf)) > 0) {
// flip the buffer which set the limit to current position, and position to 0.
bytebuf.flip();
out.write(bytebuf); // Write data from ByteBuffer to file
bytebuf.clear(); // For the next read
}
elapsedTime = System.nanoTime() - startTime;
System.out.println("Elapsed Time is " + (elapsedTime / 1000000.0) + " msec");
}
catch (IOException ex) {
ex.printStackTrace();
}
}
}
如果您在文章中有不清楚的地方或错误的地方,请随时发表您的看法。
祝您学习愉快!
Java 通道教程 – NIO 2.0
原文: https://howtodoinjava.com/java7/nio/java-nio-2-0-channels/
通道是继缓冲区之后的java.nio
的第二项重大创新,我们在我之前的教程中已详细了解到。 通道提供与 I/O 服务的直接连接。 通道是一种在字节缓冲区和通道另一端的实体(通常是文件或套接字)之间有效传输数据的介质。 通常,通道与操作系统文件描述符具有一对一的关系。 通道类提供了维持平台独立性所需的抽象,但仍可以对现代操作系统的本机 I/O 能力进行建模。 通道是网关,通过它可以以最小的开销访问操作系统的本机 I/O 服务,而缓冲区是通道用来发送和接收数据的内部端点。
NIO 通道基础
在层次结构的顶部,有Channel
接口,如下所示:
package java.nio.channels;
public interface Channel
{
public boolean isOpen();
public void close() throws IOException;
}
由于依赖底层平台的各种因素,Channel
实现在操作系统之间存在根本性的差异,因此通道 API(或接口)仅描述了可以完成的工作。 Channel
实现通常使用本机代码执行实际工作。 通过这种方式,通道接口使您能够以受控且可移植的方式访问低级 I/O 服务。
从顶级Channel
接口可以看到,所有通道只有两个共同的操作:检查通道是否打开(isOpen()
)和关闭打开的通道(close()
)。
打开通道
众所周知,I/O 分为两大类:文件 I/O 和流 I/O 。 因此,通道有两种类型也就不足为奇了:文件和套接字。 FileChannel
类和SocketChannel
类用于处理这两个类别。
仅可以通过在打开的RandomAccessFile
,FileInputStream
或FileOutputStream
对象上调用getChannel()
方法来获得FileChannel
对象。 您不能直接创建FileChannel
对象。
RandomAccessFile raf = new RandomAccessFile ("somefile", "r");
FileChannel fc = raf.getChannel();
与FileChannel
相反,套接字通道具有工厂方法来直接创建新的套接字通道。 例如
//How to open SocketChannel
SocketChannel sc = SocketChannel.open();
sc.connect(new InetSocketAddress("somehost", someport));
//How to open ServerSocketChannel
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind (new InetSocketAddress (somelocalport));
//How to open DatagramChannel
DatagramChannel dc = DatagramChannel.open();
上面的方法返回一个相应的套接字通道对象。 它们不是RandomAccessFile.getChannel()
的新通道的来源。 如果已经存在,则它们返回与套接字关联的通道。 他们从不创造新的渠道。
使用通道
正如我们已经学习到缓冲区的教程一样,该教程介绍了通道与ByteBuffer
对象之间的数据传输。 大多数读/写操作由从下面的接口实现的方法执行。
public interface ReadableByteChannel extends Channel
{
public int read (ByteBuffer dst) throws IOException;
}
public interface WritableByteChannel extends Channel
{
public int write (ByteBuffer src) throws IOException;
}
public interface ByteChannel extends ReadableByteChannel, WritableByteChannel
{
}
通道可以是单向或双向。 给定的通道类可能实现ReadableByteChannel
,它定义了read()
方法。 另一个可能会实现WritableByteChannel
以提供write()
。 实现这些接口中的一个或另一个的类是单向的:它只能在一个方向上传输数据。 如果一个类实现两个接口(或扩展两个接口的ByteChannel
),则它是双向的,可以双向传输数据。
如果您查看通道类,则会发现每个文件和套接字通道都实现了这三个接口。 就类定义而言,这意味着所有文件和套接字通道对象都是双向的。 对于套接字来说,这不是问题,因为它们始终是双向的,但是对于文件来说,这是一个问题。 从FileInputStream
对象的getChannel()
方法获得的FileChannel
对象是只读的,但是在接口声明方面是双向的,因为 FileChannel
实现了ByteChannel
。 在这样的通道上调用write()
会抛出未选中的NonWritableChannelException
,因为FileInputStream
总是打开具有只读权限的文件。 因此,请记住,当通道连接到特定的 I/O 服务时,通道实例的特性将受到与其连接的服务的特性的限制。 连接到只读文件的Channel
实例无法写入,即使该Channel
实例所属的类可能具有write()
方法。 程序员要知道如何打开通道,而不要尝试执行底层 I/O 服务不允许的操作。
FileInputStream input = new FileInputStream ("readOnlyFile.txt");
FileChannel channel = input.getChannel();
// This will compile but will throw an IOException
// because the underlying file is read-only
channel.write (buffer);
ByteChannel
的read()
和write()
方法将ByteBuffer
对象作为参数。 每个都返回传输的字节数,该字节数可以小于缓冲区中的字节数,甚至为零。 缓冲区的位置将增加相同的量。 如果执行了部分传输,则可以将缓冲区重新提交给通道以继续传输数据,并从该处停止传输。 重复直到缓冲区的hasRemaining()
方法返回false
。
在下面的示例中,我们将数据从一个通道复制到另一个通道(或从一个文件复制到另一个文件):
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
public class ChannelCopyExample
{
public static void main(String args[]) throws IOException
{
FileInputStream input = new FileInputStream ("testIn.txt");
ReadableByteChannel source = input.getChannel();
FileOutputStream output = new FileOutputStream ("testOut.txt");
WritableByteChannel dest = output.getChannel();
copyData(source, dest);
source.close();
dest.close();
}
private static void copyData(ReadableByteChannel src, WritableByteChannel dest) throws IOException
{
ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
while (src.read(buffer) != -1)
{
// Prepare the buffer to be drained
buffer.flip();
// Make sure that the buffer was fully drained
while (buffer.hasRemaining())
{
dest.write(buffer);
}
// Make the buffer empty, ready for filling
buffer.clear();
}
}
}
通道可以在阻塞或非阻塞模式下运行。 处于非阻塞模式的通道永远不会使调用线程进入睡眠状态。 所请求的操作要么立即完成,要么返回结果表明未完成任何操作。 只能将面向流的通道(例如套接字和管道)置于非阻塞模式。
关闭通道
要关闭通道,请使用其close()
方法。 与缓冲区不同,通道在关闭后不能重新使用。 开放通道表示与特定 I/O 服务的特定连接,并封装了该连接的状态。 关闭通道后,该连接将丢失,并且该通道不再连接任何东西。
在通道上多次调用close()
是没有害处的。 随后在关闭通道上对close()
的调用无济于事,并立即返回。
可以想到,套接字通道可能需要大量时间才能关闭,具体取决于系统的网络实现。 某些网络协议栈可能会在输出耗尽时阻止关闭。
通道的打开状态可以使用isOpen()
方法进行测试。 如果返回true
,则可以使用该通道。 如果为false
,则该通道已关闭,无法再使用。 尝试读取,写入或执行任何其他需要通道处于打开状态的操作将导致ClosedChannelException
。
祝您学习愉快!
3 种读取文件的方法 – Java NIO
原文: https://howtodoinjava.com/java7/nio/3-ways-to-read-files-using-java-nio/
新的 I/O,通常称为 NIO ,是一组 API,它们为密集的 I/O 操作提供附加能力。 它是由 Sun 微系统的 Java 1.4 发行版引入的,以补充现有的标准 I/O。 随 Java SE 7(“海豚”)一起发布的扩展 NIO 提供了进一步的新文件系统 API,称为 NIO2。
在 java 面试 中,与 NIO 相关的问题非常流行。
NIO2 提供了两种主要的读取文件的方法:
- 使用缓冲区和通道类
- 使用路径和文件类
在这篇文章中,我展示了几种从文件系统读取文件的方法。 因此,让我们从首先展示旧的著名方法入手,以便我们可以看到真正的变化。
古老的著名 I/O 方式
此示例说明我们如何使用旧的 I/O 库 API 读取文本文件。 它使用BufferedReader
对象进行读取。 另一种方法可以使用InputStream
实现。
package com.howtodoinjava.test.nio;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class WithoutNIOExample
{
public static void main(String[] args)
{
BufferedReader br = null;
String sCurrentLine = null;
try
{
br = new BufferedReader(
new FileReader("test.txt"));
while ((sCurrentLine = br.readLine()) != null)
{
System.out.println(sCurrentLine);
}
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
try
{
if (br != null)
br.close();
} catch (IOException ex)
{
ex.printStackTrace();
}
}
}
}
1)在文件大小的缓冲区中读取一个小文件
package com.howtodoinjava.test.nio;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class ReadFileWithFileSizeBuffer
{
public static void main(String args[])
{
try
{
RandomAccessFile aFile = new RandomAccessFile(
"test.txt","r");
FileChannel inChannel = aFile.getChannel();
long fileSize = inChannel.size();
ByteBuffer buffer = ByteBuffer.allocate((int) fileSize);
inChannel.read(buffer);
//buffer.rewind();
buffer.flip();
for (int i = 0; i < fileSize; i++)
{
System.out.print((char) buffer.get());
}
inChannel.close();
aFile.close();
}
catch (IOException exc)
{
System.out.println(exc);
System.exit(1);
}
}
}
2)使用固定大小的缓冲区分块读取大文件
package com.howtodoinjava.test.nio;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class ReadFileWithFixedSizeBuffer
{
public static void main(String[] args) throws IOException
{
RandomAccessFile aFile = new RandomAccessFile
("test.txt", "r");
FileChannel inChannel = aFile.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
while(inChannel.read(buffer) > 0)
{
buffer.flip();
for (int i = 0; i < buffer.limit(); i++)
{
System.out.print((char) buffer.get());
}
buffer.clear(); // do something with the data and clear/compact it.
}
inChannel.close();
aFile.close();
}
}
3)使用映射的字节缓冲区的更快的文件复制
package com.howtodoinjava.test.nio;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class ReadFileWithMappedByteBuffer
{
public static void main(String[] args) throws IOException
{
RandomAccessFile aFile = new RandomAccessFile
("test.txt", "r");
FileChannel inChannel = aFile.getChannel();
MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
buffer.load();
for (int i = 0; i < buffer.limit(); i++)
{
System.out.print((char) buffer.get());
}
buffer.clear(); // do something with the data and clear/compact it.
inChannel.close();
aFile.close();
}
}
所有上述技术将读取文件的内容并将其打印到控制台。 阅读后,您可以做任何您想做的事情。
祝您学习愉快!
Java 8 – 逐行读取文件
在本 Java 8 教程中,学习使用流 API 逐行读取文件。 另外,还要学习遍历行并根据某些条件过滤文件内容。
1. Java 8 读取文件 – 逐行
在此示例中,我将读取文件内容为stream
,并一次读取每一行,并检查其中是否包含单词"password"
。
Path filePath = Paths.get("c:/temp", "data.txt");
//try-with-resources
try (Stream<String> lines = Files.lines( filePath ))
{
lines.forEach(System.out::println);
}
catch (IOException e)
{
e.printStackTrace();
}
上面的程序输出将在控制台中逐行打印文件的内容。
Never
store
password
except
in mind.
2. Java 8 读取文件 – 过滤行流
在此示例中,我们将文件内容读取为行流。 然后,我们将过滤掉所有带有单词"password"
的行。
Path filePath = Paths.get("c:/temp", "data.txt");
try (Stream<String> lines = Files.lines(filePath))
{
List<String> filteredLines = lines
.filter(s -> s.contains("password"))
.collect(Collectors.toList());
filteredLines.forEach(System.out::println);
}
catch (IOException e) {
e.printStackTrace();
}
程序输出。
password
我们将读取给定文件的内容,并检查是否有任何一行包含单词"password"
,然后将其打印出来。
3. Java 7 – 使用FileReader
读取文件
到 Java 7 为止,我们可以通过FileReader
以各种方式读取文件。
private static void readLinesUsingFileReader() throws IOException
{
File file = new File("c:/temp/data.txt");
FileReader fr = new FileReader(file);
BufferedReader br = new BufferedReader(fr);
String line;
while((line = br.readLine()) != null)
{
if(line.contains("password")){
System.out.println(line);
}
}
br.close();
fr.close();
}
这就是 Java 逐行读取文件示例的全部。 请在评论部分提出您的问题。
学习愉快!
Java 内存映射文件 – Java MappedByteBuffer
原文: https://howtodoinjava.com/java7/nio/memory-mapped-files-mappedbytebuffer/
了解 Java 内存映射文件,并借助RandomAccessFile
和MemoryMappedBuffer
从内存映射文件中读取和写入内容。
1. 内存映射的 IO
如果您知道 Java IO 在较低级别的工作方式,那么您将了解缓冲区处理,内存分页和其他此类概念。 对于传统的文件 I/O,用户进程在其中发出read()
和write()
系统调用来传输数据,几乎总是存在一个或多个复制操作,以在内核空间中的这些文件系统页面和内存中的内存区域之间移动数据。 用户空间。 这是因为文件系统页面和用户缓冲区之间通常没有一对一的对齐方式。
但是,大多数操作系统都支持一种特殊类型的 I/O 操作,它允许用户进程最大程度地利用系统 I/O 的面向页面的特性,并完全避免缓冲区复制。 这称为内存映射的 I/O ,我们将在此处学习有关内存映射文件的一些知识。
2. Java 内存映射文件
内存映射的 I/O 使用文件系统来建立从用户空间直接到适用文件系统页面的虚拟内存映射。 使用内存映射文件,我们可以假装整个文件都在内存中,并且可以通过简单地将其视为非常大的数组来访问它。 这种方法极大地简化了我们为修改文件而编写的代码。
阅读更多信息:使用缓冲区
为了在内存映射文件中进行写入和读取,我们从RandomAccessFile
开始,获取该文件的通道。 存储器映射的字节缓冲区是通过FileChannel.map()
方法创建的。 此类通过特定于内存映射文件区域的操作扩展了ByteBuffer
类。
映射的字节缓冲区及其表示的文件映射将保持有效,直到缓冲区本身被垃圾回收为止。 请注意,必须指定文件中要映射的区域的起点和长度。 这意味着您可以选择映射大文件的较小区域。
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class MemoryMappedFileExample
{
static int length = 0x8FFFFFF;
public static void main(String[] args) throws Exception
{
try(RandomAccessFile file = new RandomAccessFile("howtodoinjava.dat", "rw"))
{
MappedByteBuffer out = file.getChannel()
.map(FileChannel.MapMode.READ_WRITE, 0, length);
for (int i = 0; i < length; i++)
{
out.put((byte) 'x');
}
System.out.println("Finished writing");
}
}
}
使用上述程序创建的文件的长度为 128 MB,可能大于操作系统允许的空间。 该文件似乎可以一次访问,因为只有部分文件被带入内存,而其他部分被换出。 这样,可以轻松地修改非常大的文件(最大 2 GB)。
文件模式
像常规文件句柄一样,文件映射可以是可写的或只读的。
- 前两个映射模式
MapMode.READ_ONLY** 和 **MapMode.READ_WRITE
相当明显。 它们指示您是要映射为只读映射还是允许修改映射文件。 - 第三种模式
MapMode.PRIVATE
表示您需要写时复制映射。 这意味着您通过put()
进行的任何修改都将导致只有MappedByteBuffer
实例可以看到的数据的私有副本。 不会对基础文件进行任何更改,并且对缓冲区进行垃圾回收时所做的任何更改都将丢失。 即使写时复制映射阻止了对基础文件的任何更改,您也必须已打开文件以进行读/写以设置MapMode.PRIVATE
映射。 这对于返回的MappedByteBuffer
对象允许put()
是必需的。
您会注意到,没有unmap()
方法。 建立后,映射将一直有效,直到MappedByteBuffer
对象被垃圾回收为止。 同样,映射缓冲区不绑定到创建它们的通道。 关闭关联的FileChannel
不会破坏映射。 仅处理缓冲区对象本身会破坏映射。
MemoryMappedBuffer
具有固定大小,但映射到的文件是弹性的。 具体来说,如果在映射生效时文件大小发生变化,则部分或全部缓冲区可能变得不可访问,可能会返回未定义的数据,或者会引发未经检查的异常。 内存映射文件时,请注意其他线程或外部进程如何处理文件。
3. 内存映射文件的好处
与常规 I/O 相比,内存映射 IO 具有以下优点:
- 用户进程将文件数据视为内存,因此无需发出
read()
或write()
系统调用。 - 当用户进程触摸映射的内存空间时,将自动生成页面错误,以从磁盘引入文件数据。 如果用户修改了映射的内存空间,则受影响的页面会自动标记为脏页面,随后将刷新到磁盘以更新文件。
- 操作系统的虚拟内存子系统将执行页面的智能缓存,并根据系统负载自动管理内存。
- 数据总是页面对齐的,不需要缓冲区复制。
- 可以映射非常大的文件,而无需消耗大量内存来复制数据。
4. 如何读取内存映射文件
要使用内存映射的 IO 读取文件,请使用以下代码模板:
import java.io.File;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class MemoryMappedFileReadExample
{
private static String bigExcelFile = "bigFile.xls";
public static void main(String[] args) throws Exception
{
try (RandomAccessFile file = new RandomAccessFile(new File(bigExcelFile), "r"))
{
//Get file channel in read-only mode
FileChannel fileChannel = file.getChannel();
//Get direct byte buffer access using channel.map() operation
MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size());
// the buffer now reads the file as if it were loaded in memory.
System.out.println(buffer.isLoaded()); //prints false
System.out.println(buffer.capacity()); //Get the size based on content size of file
//You can read the file from this buffer the way you like.
for (int i = 0; i < buffer.limit(); i++)
{
System.out.print((char) buffer.get()); //Print the content of file
}
}
}
}
5. 如何写入内存映射文件
要使用内存映射的 IO 将数据写入文件,请使用以下代码模板:
import java.io.File;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class MemoryMappedFileWriteExample {
private static String bigTextFile = "test.txt";
public static void main(String[] args) throws Exception
{
// Create file object
File file = new File(bigTextFile);
//Delete the file; we will create a new file
file.delete();
try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"))
{
// Get file channel in read-write mode
FileChannel fileChannel = randomAccessFile.getChannel();
// Get direct byte buffer access using channel.map() operation
MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 4096 * 8 * 8);
//Write the content using put methods
buffer.put("howtodoinjava.com".getBytes());
}
}
}
在评论部分中将您的评论和想法留给我。
学习愉快!
Java NIO – 分散/聚集或向量 IO
原文: https://howtodoinjava.com/java7/nio/java-nio-2-0-scatter-gather-or-vectored-io/
通道提供了一项重要的新特性,称为分散/聚集(在某些圈子中称为向量 I/O)。 分散/聚集是一个简单而强大的概念。 分散/聚集是一种技术,通过该技术,可以通过一次read()
调用将字节从流中读取到一组缓冲区(向量)中,并且可以通过一次write()
调用,将字节从一组缓冲区中写入到流中。大多数现代操作系统都支持本机向量 I/O。 当您请求通道上的分散/聚集操作时,该请求将转换为适当的本机调用以直接填充或耗尽缓冲区。 这是一个巨大的胜利,因为减少了或消除了缓冲区副本和系统调用。 直到最近,Java 还没有执行向量 I/O 操作的能力。 因此,我们习惯于将字节直接读取到单个字节数组中,或者进行多次读取以获取数据。
分散/聚集 API
从通道读取的散射是将数据读取到多个缓冲区中的读取操作。因此,通道将来自通道的数据分散到多个缓冲区中。对通道的聚集写入是一种将来自多个缓冲区的数据写入单个通道的写入操作。因此,通道将来自多个缓冲器的数据聚集到一个通道中。在需要分别处理传输数据的各个部分的情况下,分散/聚集可能非常有用。
public interface ScatteringByteChannel extends ReadableByteChannel
{
public long read (ByteBuffer [] dsts) throws IOException;
public long read (ByteBuffer [] dsts, int offset, int length) throws IOException;
}
public interface GatheringByteChannel extends WritableByteChannel
{
public long write(ByteBuffer[] srcs) throws IOException;
public long write(ByteBuffer[] srcs, int offset, int length) throws IOException;
}
您可以看到每个接口都添加了两个新方法,这些新方法将缓冲区数组作为参数。
现在,让我们写一个简单的例子来了解如何使用此特性。
分散/聚集 IO 示例
在此示例中,我创建了两个缓冲区。 一个缓冲区将存储一个随机数,另一个缓冲区将存储一个随机字符串。 我将使用GatheringByteChannel
读写文件通道中两个缓冲区中存储的数据。 然后,我将使用ScatteringByteChannel
将文件中的数据读回到两个单独的缓冲区中,并在控制台中打印内容以验证存储和检索的数据是否匹配。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.GatheringByteChannel;
import java.nio.channels.ScatteringByteChannel;
public class ScatteringAndGatheringIOExample
{
public static void main(String params[])
{
String data = "Scattering and Gathering example shown in howtodoinjava.com";
gatherBytes(data);
scatterBytes();
}
/*
* gatherBytes() reads bytes from different buffers and writes to file
* channel. Note that it uses a single write for both the buffers.
*/
public static void gatherBytes(String data)
{
//First Buffer holds a random number
ByteBuffer bufferOne = ByteBuffer.allocate(4);
//Second Buffer holds data we want to write
ByteBuffer buffer2 = ByteBuffer.allocate(200);
//Writing Data sets to Buffer
bufferOne.asIntBuffer().put(13);
buffer2.asCharBuffer().put(data);
//Calls FileOutputStream(file).getChannel()
GatheringByteChannel gatherer = createChannelInstance("test.txt", true);
//Write data to file
try
{
gatherer.write(new ByteBuffer[] { bufferOne, buffer2 });
}
catch (Exception e)
{
e.printStackTrace();
}
}
/*
* scatterBytes() read bytes from a file channel into a set of buffers. Note that
* it uses a single read for both the buffers.
*/
public static void scatterBytes()
{
//First Buffer holds a random number
ByteBuffer bufferOne = ByteBuffer.allocate(4);
//Second Buffer holds data we want to write
ByteBuffer bufferTwo = ByteBuffer.allocate(200);
//Calls FileInputStream(file).getChannel()
ScatteringByteChannel scatterer = createChannelInstance("test.txt", false);
try
{
//Reading from the channel
scatterer.read(new ByteBuffer[] { bufferOne, bufferTwo });
}
catch (Exception e)
{
e.printStackTrace();
}
//Read the buffers seperately
bufferOne.rewind();
bufferTwo.rewind();
int bufferOneContent = bufferOne.asIntBuffer().get();
String bufferTwoContent = bufferTwo.asCharBuffer().toString();
//Verify the content
System.out.println(bufferOneContent);
System.out.println(bufferTwoContent);
}
public static FileChannel createChannelInstance(String file, boolean isOutput)
{
FileChannel fc = null;
try
{
if (isOutput) {
fc = new FileOutputStream(file).getChannel();
} else {
fc = new FileInputStream(file).getChannel();
}
}
catch (Exception e) {
e.printStackTrace();
}
return fc;
}
}
总结
正确使用分散/聚集工具可能会非常强大。 它使您可以将将读取的数据分离到多个存储桶中或将不同的数据块组装成一个整体的繁琐工作委托给操作系统。 这是一个巨大的胜利,因为操作系统已针对此类事物进行了高度优化。 它节省了您四处移动的工作,从而避免了缓冲区复制,并减少了编写和调试所需的代码量。 由于基本上是通过提供对数据容器的引用来组装数据,因此可以通过以不同组合构建多个缓冲区引用数组来以不同的方式来组装各种块。
祝您学习愉快!
通道之间的数据传输 – Java NIO
原文: https://howtodoinjava.com/java7/nio/java-nio-2-0-how-to-transfer-copy-data-between-channels/
与通常在输入源和输出目标之间发生 IO 的普通 Java 应用程序一样,在 NIO 中,您也可能需要非常频繁地将数据从一个通道传输到另一通道。 文件数据从一个地方到另一个地方的批量传输非常普遍,以至于FileChannel
类添加了两种优化方法,以使其效率更高。
FileChannel.transferTo()
和FileChannel.transferFrom()
方法
transferTo()
和transferFrom()
方法使您可以将一个通道交叉连接到另一个通道,而无需通过中间缓冲区传递数据。 这些方法仅在FileChannel
类上存在,因此,在通道间传输中涉及的通道之一必须是FileChannel
。
public abstract class FileChannel
extends AbstractChannel
implements ByteChannel, GatheringByteChannel, ScatteringByteChannel
{
// There are more other methods
public abstract long transferTo (long position, long count, WritableByteChannel target);
public abstract long transferFrom (ReadableByteChannel src, long position, long count);
}
您无法在套接字通道之间进行直接传输,但是套接字通道实现了WritableByteChannel
和ReadableByteChannel
,因此可以使用transferTo()
将文件的内容传输到套接字,或者可以使用transferFrom()
从套接字直接读取数据到文件中。
另外,请记住,如果在传输过程中遇到问题,这些方法可能会抛出java.io.IOException
。
阅读更多: NIO 通道
通道之间的传输可能非常快,尤其是在底层操作系统提供本机支持的情况下。 某些操作系统可以执行直接传输,而无需在用户空间中传递数据。 对于大量数据传输而言,这可能是一个巨大的胜利。
通道间数据传输示例
在此示例中,我从 3 个不同的文件中读取文件内容,并将它们的合并输出写入第四个文件。
package com.howtodoinjava.nio;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
public class ChannelTransferExample
{
public static void main(String[] argv) throws Exception
{
//Input files
String[] inputFiles = new String[]{"inputFile1.txt","inputFile2.txt","inputFile3.txt"};
//Files contents will be written in these files
String outputFile = "outputFile.txt";
//Get channel for output file
FileOutputStream fos = new FileOutputStream(new File(outputFile));
WritableByteChannel targetChannel = fos.getChannel();
for (int i = 0; i < inputFiles.length; i++)
{
//Get channel for input files
FileInputStream fis = new FileInputStream(inputFiles[i]);
FileChannel inputChannel = fis.getChannel();
//Transfer data from input channel to output channel
inputChannel.transferTo(0, inputChannel.size(), targetChannel);
//close the input channel
inputChannel.close();
fis.close();
}
//finally close the target channel
targetChannel.close();
fos.close();
}
}
将您的评论和建议放在评论部分。
祝您学习愉快!
Java 查看/生成类文件的字节码
原文: https://howtodoinjava.com/java/basics/how-to-view-generate-bytecode-for-a-java-class-file/
很多时候,我们需要了解编译器在后台执行的操作。 我们正在编写的 Java 语句将如何重新排序和执行。 另外,我们也需要查看字节码以用于学习目的,我很少这样做。 在本教程中,我将给出一个示例,说明如何在 Java 中为类文件生成字节码。
为了演示该示例,我使用了为我的其他教程创建的 java 文件,该教程与 java 7 中的自动资源管理有关。
步骤 1)使用命令javac
(可选)编译文件ResourceManagementInJava7.java
这是可选的,因为您可能已经具有.class
文件。
prompt > javac C:tempjavatestResourceManagementInJava7.java
这将生成.class
文件ResourceManagementInJava7.class
。
步骤 2)执行javap
命令并将输出重定向到.bc
文件
C:>javap -c C:tempjavatestResourceManagementInJava7.class > C:tempjavatestbytecode.bc
资料夹检视
让我们看一下在命令提示符下运行的命令。
命令窗口视图
文件bytecode.bc
文件将在给定位置生成。 将会是这样的:
Compiled from "ResourceManagementInJava7.java"
public class com.howtodoinjava.java7.tryCatch.ResourceManagementInJava7 {
public com.howtodoinjava.java7.tryCatch.ResourceManagementInJava7();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/io/BufferedReader
3: dup
4: new #3 // class java/io/FileReader
7: dup
8: ldc #4 // String C:/temp/test1.txt
10: invokespecial #5 // Method java/io/FileReader."<init>":(Ljava/lang/String;)V
13: invokespecial #6 // Method java/io/BufferedReader."<init>":(Ljava/io/Reader;)V
16: astore_1
17: aconst_null
18: astore_2
19: new #2 // class java/io/BufferedReader
22: dup
23: new #3 // class java/io/FileReader
26: dup
27: ldc #7 // String C:/temp/test2.txt
29: invokespecial #5 // Method java/io/FileReader."<init>":(Ljava/lang/String;)V
32: invokespecial #6 // Method java/io/BufferedReader."<init>":(Ljava/io/Reader;)V
35: astore_3
36: aconst_null
37: astore 4
39: new #2 // class java/io/BufferedReader
42: dup
43: new #3 // class java/io/FileReader
46: dup
47: ldc #8 // String C:/temp/test3.txt
49: invokespecial #5 // Method java/io/FileReader."<init>":(Ljava/lang/String;)V
52: invokespecial #6 // Method java/io/BufferedReader."<init>":(Ljava/io/Reader;)V
55: astore 5
57: aconst_null
58: astore 6
60: aload 5
62: ifnull 138
65: aload 6
67: ifnull 90
70: aload 5
72: invokevirtual #9 // Method java/io/BufferedReader.close:()V
75: goto 138
78: astore 7
80: aload 6
82: aload 7
84: invokevirtual #11 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
87: goto 138
90: aload 5
92: invokevirtual #9 // Method java/io/BufferedReader.close:()V
95: goto 138
98: astore 8
100: aload 5
102: ifnull 135
105: aload 6
107: ifnull 130
110: aload 5
112: invokevirtual #9 // Method java/io/BufferedReader.close:()V
115: goto 135
118: astore 9
120: aload 6
122: aload 9
124: invokevirtual #11 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
127: goto 135
130: aload 5
132: invokevirtual #9 // Method java/io/BufferedReader.close:()V
135: aload 8
137: athrow
138: aload_3
139: ifnull 219
142: aload 4
144: ifnull 166
147: aload_3
148: invokevirtual #9 // Method java/io/BufferedReader.close:()V
151: goto 219
154: astore 5
156: aload 4
158: aload 5
160: invokevirtual #11 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
163: goto 219
166: aload_3
167: invokevirtual #9 // Method java/io/BufferedReader.close:()V
170: goto 219
173: astore 5
175: aload 5
177: astore 4
179: aload 5
181: athrow
182: astore 10
184: aload_3
185: ifnull 216
188: aload 4
190: ifnull 212
193: aload_3
194: invokevirtual #9 // Method java/io/BufferedReader.close:()V
197: goto 216
200: astore 11
202: aload 4
204: aload 11
206: invokevirtual #11 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
209: goto 216
212: aload_3
213: invokevirtual #9 // Method java/io/BufferedReader.close:()V
216: aload 10
218: athrow
219: aload_1
220: ifnull 290
223: aload_2
224: ifnull 243
227: aload_1
228: invokevirtual #9 // Method java/io/BufferedReader.close:()V
231: goto 290
234: astore_3
235: aload_2
236: aload_3
237: invokevirtual #11 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
240: goto 290
243: aload_1
244: invokevirtual #9 // Method java/io/BufferedReader.close:()V
247: goto 290
250: astore_3
251: aload_3
252: astore_2
253: aload_3
254: athrow
255: astore 12
257: aload_1
258: ifnull 287
261: aload_2
262: ifnull 283
265: aload_1
266: invokevirtual #9 // Method java/io/BufferedReader.close:()V
269: goto 287
272: astore 13
274: aload_2
275: aload 13
277: invokevirtual #11 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
280: goto 287
283: aload_1
284: invokevirtual #9 // Method java/io/BufferedReader.close:()V
287: aload 12
289: athrow
290: goto 298
293: astore_1
294: aload_1
295: invokevirtual #13 // Method java/io/IOException.printStackTrace:()V
298: return
Exception table:
from to target type
70 75 78 Class java/lang/Throwable
110 115 118 Class java/lang/Throwable
98 100 98 any
147 151 154 Class java/lang/Throwable
39 138 173 Class java/lang/Throwable
39 138 182 any
193 197 200 Class java/lang/Throwable
173 184 182 any
227 231 234 Class java/lang/Throwable
19 219 250 Class java/lang/Throwable
19 219 255 any
265 269 272 Class java/lang/Throwable
250 257 255 any
0 290 293 Class java/io/IOException
}
学习愉快!
什么是 Java 编程语言?
原文: https://howtodoinjava.com/java/basics/what-is-java-programming-language/
Java 是通用计算机编程语言,它是并发,基于类,面向对象,并且经过专门设计,以尽可能减少实现依赖 。 旨在让应用程序开发人员“编写一次,随处运行”(WORA),这意味着已编译的 Java 代码可以在支持 Java 的所有平台上运行,而无需重新编译。
例如,您可以在 UNIX 上编写和编译 Java 程序,然后在 Microsoft Windows,Macintosh 或 UNIX 计算机上运行它,而无需对源代码进行任何修改。 通过将 Java 程序编译为称为字节码的中间语言来实现WORA
。 字节码的格式是与平台无关的。 称为 Java 虚拟机(JVM)的虚拟机用于在每个平台上运行字节码。
JDK vs JRE vs JVM
Java 的历史
Java 最初由 James Gosling 在 Sun Microsystems (已由 Oracle Corporation 收购)开发,并于 1995 年作为 Sun Microsystems Java 平台的核心组件发布。 该语言的大部分语法均来自 C 和 C++,但与任何一种相比,它的低级特性都更少。
Oracle 公司是 Java SE 平台的正式实现的当前所有者,此后于 2010 年 1 月 27 日收购了 Sun Microsystems。该实现基于 Sun 最初的 Java 实现。 Oracle 实现可用于 Microsoft Windows,Mac OS X,Linux 和 Solaris。
Oracle 实现打包为两个不同的发行版:
- Java 运行时环境(JRE)包含运行 Java 程序所需的 Java SE 平台部分,供最终用户使用。
- Java 开发工具包(JDK)供软件开发人员使用,包括 Java 编译器,Javadoc,Jar 和调试器之类的开发工具。
垃圾收集
Java 使用自动垃圾收集器来管理对象生命周期中的内存。 程序员确定何时创建对象,一旦不再使用对象,Java 运行时将负责恢复内存。 一旦没有对对象的引用,则垃圾回收器将有资格自动释放无法访问的内存。
如果程序员的代码持有对不再需要的对象的引用,则通常仍会发生类似于内存泄漏的情况,通常是将不再需要的对象存储在仍在使用的容器中时。 如果调用了不存在的对象的方法,则会引发NullPointerException
。
垃圾收集可能随时发生。 理想情况下,它将在程序空闲时发生。 如果堆上的可用内存不足以分配新对象,则可以保证触发该事件。 这可能会导致程序暂时停止。 在 Java 中无法进行显式内存管理。
Java Hello World 程序
传统的“你好,世界!” 程序可以用 Java 编写为:
public class HelloWorldApplication {
public static void main(String[] args) {
System.out.println("Hello World!"); // Prints Hello World! to the console.
}
}
Java 类文件
- Java 源文件必须以它们包含的公共类命名,并在其后附加
.java
后缀,例如HelloWorldApplication.java
。 - 必须首先使用 Java 编译器将其编译为字节码,然后生成一个名为
HelloWorldApplication.class
的文件。 只有这样才能执行或“启动”。 - Java 源文件只能包含一个公共类,但是可以包含多个类,除了公共访问权限和任何数量的公共内部类之外。
- 当源文件包含多个类时,将一个类设为
public
,然后使用该公共类名命名源文件。
在下一组教程中,我们将了解有关其他语言特性的更多信息。
学习愉快!
Java 中的小端和大端
原文: https://howtodoinjava.com/java/basics/little-endian-and-big-endian-in-java/
您必须在工程课程中多次听到这些术语 小端和大端。 让我们快速回顾一下这些词背后的概念。
这两个术语与 CPU 架构中一个字的字节的方向有关。 计算机内存由正整数的地址引用。 将数字的最低有效字节存储在计算机内存中的最高有效字节之前是“自然”的。 有时,计算机设计人员喜欢使用这种表示形式的逆序版本。
“自然”顺序在内存中的低有效字节先于高有效字节,被称为小端。 像 IBM,CRAY 和 Sun 这样的许多供应商都喜欢相反的顺序,当然,这种顺序被称为大端。
例如,将 32 位十六进制值0x45679812
如下存储在内存中:
Address 00 01 02 03
-------------------------------
Little-endian 12 98 67 45
Big-endian 45 67 98 12
在两台机器之间传输数据时,字节序差异可能是一个问题。
Java 二进制格式文件中的所有内容均按大端顺序存储。 有时也称为网络顺序。 这意味着,如果仅使用 Java,则在所有平台(Mac,PC,UNIX 等)上,所有文件的处理方式都相同。您可以自由地电子交换二进制数据,而无需担心字节顺序。
问题是当您必须与某些不是用 Java 编写的使用低端顺序的程序交换数据文件时,该程序通常使用 C 编写。某些平台在内部使用了大端顺序(Mac,IBM 390)。 有些使用小端序(Intel)。
Java 对您隐藏了内部字节序。
祝您学习愉快!
Java 命令行参数
原文: https://howtodoinjava.com/java/basics/command-line-args/
启动 Java 程序时传递的程序参数称为命令行参数。
可以从控制台或从编辑器启动 Java 程序。 要启动程序,我们必须使用命令提示符或系统控制台中的java className
命令。 启动程序时,我们可以使用以下语法传递附加参数(无参数数量限制)。
1. 命令行参数语法
在下面给出的语法中,我们将 5 个参数传递给启动类MyClass
。MyClass
具有main()
方法,该方法以字符串数组的形式接受这些参数。
$ java MyClass arg1 arg2 arg3 arg4 arg5
2. Java 命令行参数示例
让我们创建一个示例,以了解命令行程序参数如何在 Java 中工作。 此类简单地接受参数并将其打印在控制台中。
作为程序员,我们可以将这些参数用作启动参数来自定义应用程序在运行时的行为。
package app;
public class Main
{
public static void main(String[] args)
{
for(int i = 0; i< args.length; i++)
{
System.out.println( args[i] );
}
}
}
现在从控制台运行此类。
$ java Main 1 2 3 4
#prints
1
2
3
4
3. 总结
- 命令行参数可用于在启动应用程序时指定配置信息。
- 对参数的最大数量没有限制。 我们可以指定任意数量的参数。
- 参数作为字符串传递。
- 传递的参数将作为
main()
方法参数中的字符串数组检索。
学习愉快!
在 Java 中比较浮点数或双精度数的正确方法
原文: https://howtodoinjava.com/java/basics/correctly-compare-float-double/
正确地比较浮点或比较双不仅是 Java 特定的问题。 当今几乎所有编程语言都可以观察到它。 使用 IEEE 754 标准格式存储浮点数和双精度数。 实际的存储和转换如何工作,不在本文的讨论范围之内。
现在,只需了解在计算和转换期间,可以在这些数字中引入较小的舍入误差。 这就是为什么不建议仅依赖相等运算符(==
)比较浮点数的原因。
让我们学习如何比较 Java 中的浮点值。
Table of Contents
1\. Simple comparison [Not recommended]
2\. Threshold based comparison [Recommended]
3\. Compare with BigDecimal [Recommended]
1. 双精度比较 -- 简单比较(不推荐)
首先看一下简单比较,以了解用==
运算符比较double
到底有什么问题。 在给定的程序中,我使用两种方法创建相同的浮点数(即1.1
):
- 添加
.1
11 次。 - 将
.1
乘以 11。
理论上,两个操作都应产生数字1.1
。 当我们比较这两种方法的结果时,它应该匹配。
private static void simpleFloatsComparison()
{
//Method 1
double f1 = .0;
for (int i = 1; i <= 11; i++) {
f1 += .1;
}
//Method 2
double f2 = .1 * 11;
System.out.println("f1 = " + f1);
System.out.println("f2 = " + f2);
if (f1 == f2)
System.out.println("f1 and f2 are equal\n");
else
System.out.println("f1 and f2 are not equal\n");
}
程序输出。
f1 = 1.0999999999999999
f2 = 1.1
f1 and f2 are not equal
查看控制台中打印的两个值。 将f1
计算为1.0999999999999999
。 其舍入问题恰恰是内部引起的。 因此,不建议将浮点数用'=='
运算符进行比较。
2. 双精度比较 -- 基于阈值的比较[推荐]
现在,当我们知道相等运算符的问题时,让我们解决它。 使用编程,我们无法更改存储或计算这些浮点数的方式。 因此,我们必须采用一种解决方案,在该解决方案中,我们同意确定两个可以容忍的值的差异,并且仍然认为数字相等。 该商定的值差被称为阈值或ε
。
因此,现在要使用“基于阈值的浮点比较”,我们可以使用Math.abs()
方法计算两个数字之间的差并将其与阈值进行比较。
private static void thresholdBasedFloatsComparison()
{
final double THRESHOLD = .0001;
//Method 1
double f1 = .0;
for (int i = 1; i <= 11; i++) {
f1 += .1;
}
//Method 2
double f2 = .1 * 11;
System.out.println("f1 = " + f1);
System.out.println("f2 = " + f2);
if (Math.abs(f1 - f2) < THRESHOLD)
System.out.println("f1 and f2 are equal using threshold\n");
else
System.out.println("f1 and f2 are not equal using threshold\n");
}
程序输出:
f1 = 1.0999999999999999
f2 = 1.1
f1 and f2 are equal using threshold
3. 比较double
与BigDecimal
(推荐)
在BigDecimal
类中,可以指定要使用的舍入模式和精确度。 使用精确的精度限制,可以解决舍入误差。
最好的部分是BigDecimal
数字是不可变的,即,如果创建具有"1.23"
值的BigDecimal
,则该对象将保持"1.23"
且不能更改。 此类提供了许多方法,可用于对其值进行数值运算。
您可以使用compareTo()
方法与BigDecimal
数字进行比较。 比较时会忽略比例。
a.compareTo(b);
方法返回:
-1 – 如果是
a < b
0 – 如果
a == b
1 – 如果是
a > b
切勿使用equals()
方法比较BigDecimal
实例。这是因为此equals
函数将比较比例。 如果比例不同,即使在数学上相同,equals()
也将返回false
。
Java 程序将BigDecimal
类与double
进行比较。
private static void testBdEquality()
{
BigDecimal a = new BigDecimal("2.00");
BigDecimal b = new BigDecimal("2.0");
System.out.println(a.equals(b)); // false
System.out.println(a.compareTo(b) == 0); // true
}
现在,为了验证,让我们使用BigDecimal
类解决原始问题。
private static void bigDecimalComparison()
{
//Method 1
BigDecimal f1 = new BigDecimal("0.0");
BigDecimal pointOne = new BigDecimal("0.1");
for (int i = 1; i <= 11; i++) {
f1 = f1.add(pointOne);
}
//Method 2
BigDecimal f2 = new BigDecimal("0.1");
BigDecimal eleven = new BigDecimal("11");
f2 = f2.multiply(eleven);
System.out.println("f1 = " + f1);
System.out.println("f2 = " + f2);
if (f1.compareTo(f2) == 0)
System.out.println("f1 and f2 are equal using BigDecimal\n");
else
System.out.println("f1 and f2 are not equal using BigDecimal\n");
}
程序输出:
f1 = 1.1
f2 = 1.1
f1 and f2 are equal using BigDecimal
这就是比较 Java 中的浮点数的全部。 在评论部分分享您的想法。
学习愉快!
Java 递归指南
递归是一种编程样式,其中方法重复调用其自身,直到满足某个预定条件为止。 这种方法调用也称为递归方法。
1. 递归方法的语法
基于递归的方法必须具有两个基本组成部分才能正确解决问题。
- 递归方法调用
- 打破递归的前提
请记住,如果无法达到或未定义前提条件,则会发生栈溢出问题。
method(T parameters...)
{
if(precondition == true) //precondition
{
return result;
}
return method(T parameters...); //recursive call
}
2. 递归类型
根据何时进行递归方法调用,递归有两种类型。
1. 尾递归
当递归方法调用是该方法内部执行的最后一条语句时(通常与return
语句一起使用),则递归方法为尾递归。
method(T parameters...)
{
if(precondition == true)
{
return result;
}
return method(T parameters...);
}
2. 头递归
不是尾递归的任何递归都可以称为头递归。
method(T parameters...)
{
if(some condition)
{
return method(T parameters...);
}
return result;
}
3. Java 递归示例
3.1 斐波那契序列
斐波那契数列是一个数字序列,其中每个数字都定义为两个数字之和。
例如 -1、1、2、3、5、8、13、21、34,依此类推…
此函数为我们提供从 1 开始的第 n 个位置的斐波那契数。
public int fibonacci(int n)
{
if (n <= 1) {
return n;
}
return fibonacci(n-1) + fibonacci(n-2);
}
让我们测试一下此函数以打印n = 10
的斐波那契数列;
public static void main(String[] args)
{
int number = 10;
for (int i = 1; i <= number; i++)
{
System.out.print(fibonacci(i) + " ");
}
}
程序输出。
1 1 2 3 5 8 13 21 34 55
3.2 最大公约数
两个正整数的最大公约数(gcd)是最大的整数,该整数平均分为两个整数。
public static int gcd(int p, int q) {
if (q == 0) return p;
else return gcd(q, p % q);
}
让我们测试一下此函数以打印n = 10
的斐波那契数列:
public static void main(String[] args)
{
int number1 = 40;
int number2 = 500;
System.out.print( gcd(number1, number2) );
}
程序输出:
20
让我知道您有关 Java 中递归的问题。
学习愉快!
Java 偶对
使用偶对类,例如javafx.util.Pair
,ImmutablePair
,MmutablePair
(通用语言)和io.vavr.Tuple2
类,在 Java 中学习如何使用键值对
阅读更多: Java 中的元组
1. 为什么需要偶对?
偶对提供了一种将简单键值关联的便捷方法。 在 Java 中,映射用于存储键值对。 映射存储成对的集合并作为整体进行操作。
有时,我们需要处理一些要求,其中键值对应独立存在。 例如
- 偶对需要在方法中作为参数传递
- 方法需要以偶对形式返回两个值
2. javafx.util.Pair
类
Java 核心 API 具有javafx.util.Pair
作为最接近的匹配,用于具有两个值作为名称-值对的目的。 单击此链接以了解在 Eclipse 中添加 JavaFx 支持。
Pair
类提供以下方法。
boolean equals(Object o)
– 测试此对与另一个对象的相等性。K getKey()
– 获取该对的键。V getValue()
– 获取该对的值。int hashCode()
– 为此对生成哈希码。String.toString()
– 此对的字符串表示形式。
让我们看一个 Java 程序来创建和使用对。
Pair<Integer, String> pair = new Pair<>(100, "howtodoinjava.com");
Integer key = pair.getKey(); //100
String value = pair.getValue(); //howtodoinjava.com
pair.equals(new Pair<>(100, "howtodoinjava.com")); //true - same name and value
pair.equals(new Pair<>(222, "howtodoinjava.com")); //false - different name
pair.equals(new Pair<>(100, "example.com")); //false - different value
3. Pair
,ImmutablePair
和MutablePair
– Apache 公共语言
Commons lang 库有一个有用的类,可以用于偶对,即org.apache.commons.lang3.tuple.Pair
。 它有两个子类,也可以用于相同目的,即ImmutablePair
和MutablePair
。
Pair
类是由两个元素组成的偶对。Pair
将元素称为“左”和“右”。Pair
还实现了Map.Entry
接口,其中键为“左”,值为“右”。ImmutablePair
是Pair
上的不可变表示。 如果将可变的对象存储在该对中,那么该对本身将有效地变为可变的。 该类也不是final
,因此子类可能会添加不良行为。- 如果存储的对象是线程安全的,则
ImmutablePair
是线程安全的。
ImmutablePair<Integer, String> pair = ImmutablePair.of(100, "howtodoinjava.com");
Integer key = pair.getKey(); //100
String value = pair.getValue(); //howtodoinjava.com
//Integer key = pair.getLeft(); //100
//String value = pair.getRight(); //howtodoinjava.com
pair.equals(ImmutablePair.of(100, "howtodoinjava.com")); //true - same name and value
pair.equals(ImmutablePair.of(222, "howtodoinjava.com")); //false - different name
pair.equals(ImmutablePair.of(100, "example.com")); //false - different value
不要忘记将库导入应用程序类路径。
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
4. io.vavr.Tuple2
- Vavr
用于存储键值对的另一个有用的类是Tuple2
。
Tuple2
提供了许多有用的方法来处理其中存储的数据。 例如:
T1 _1()
– 此元组的第一个元素的获取器。T2 _2()
– 此元组的第二个元素的获取器。Tuple2 update1(T1 value)
– 将此元组的第一个元素设置为给定值。Tuple2 update2(T2 value)
– 将此元组的第二个元素设置为给定值。Map.Entry toEntry()
– 将元组转换为java.util.Map.Entry
元组。Tuple2 swap()
– 交换此元组的元素。Tuple2 map(BiFunction mapper)
– 使用映射器函数映射该元组的组件。int compareTo(Tuple2 that)
– 比较两个Tuple2
实例。
Tuple2<Integer, String> pair = new Tuple2<>(100, "howtodoinjava.com");
Integer key = pair._1(); //100
String value = pair._2(); //howtodoinjava.com
pair.equals(new Tuple2<>(100, "howtodoinjava.com")); //true - same name and value
pair.equals(new Tuple2<>(222, "howtodoinjava.com")); //false - different name
pair.equals(new Tuple2<>(100, "example.com")); //false - different value
不要忘记将库导入应用程序类路径。
<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
<version>0.10.2</version>
</dependency>
向我提供有关 Java 中的名称/值对的问题。
学习愉快!
Java 元组 – 使用 Java 中的元组
在本 Java 教程中,我们将学习 Java 元组 – 通用数据结构,以及如何在 Java 程序中使用元组。 默认情况下,元组在 Java 编程语言中不作为数据结构出现,因此我们将使用一个不错的第三方库javatuples
。
Table of Contents
1\. What is tuple
2\. Tuple in Java
3\. Introduction of Javatuples
4\. Common Operations on Javatuples
5\. Conclusion
1. 什么是元组?
元组可以看作是有序的不同类型的对象的集合。 这些对象不一定以任何方式相互关联,但是它们总的来说将具有一定的意义。
例如,["Sajal Chakraborty", "IT Professional", 32]
可以是一个元组,其中该元组中的每个值都没有任何关系,但是整个值集在应用程序中可能具有某些意义。 例如,给定元组可以代表具有姓名,部门和年龄的员工数据。
让我们来看更多 java 元组示例。
["Java", 1.8, "Windows"]
["Alex", 32, "New York", true]
[3, "Alexa", "howtodoinjava.com", 37000]
2. Java 中的元组
Java 没有任何这样的内置数据结构来支持元组。 无论何时需要,我们显然都可以创建一个类似于元组的类。
同样,在 Java 中,可以使用列表或数组来编写元组特性的一部分,但这些不允许我们通过设计保存不同类型的数据类型。 因此,可以说在 Java 中使用标准数据结构的异构元组是不可能的。
2.1 元组与列表/数组的比较
通常将元组与列表进行比较,因为它看起来非常像一个列表。 但是它们在某些方面有所不同。
- 元组是可以包含异构数据的对象。 列表旨在存储单一类型的元素。
- 在所有数据结构中,元组被认为是最快的,并且它们消耗的内存量最少。
- 虽然数组和列表是可变的,这意味着您可以更改其数据值并修改其结构,但元组是不可变的。
- 像数组一样,元组的大小也是固定的。 这就是为什么元组旨在完全替换数组的原因,因为它们在所有参数上都更有效率。
- 如果您有一个在生命周期内仅分配一次的数据集,并且其值不应再次更改,则需要一个元组。
3. javatuples
库
3.1 javatuples
maven 依赖
javatuples
库存在于 Maven 中央存储库中,我们可以添加此依赖项以使用该库。
<dependency>
<groupId>org.javatuples</groupId>
<artifactId>javatuples</artifactId>
<version>1.2</version>
</dependency>
3.2 javatuples
- 类
Java 元组支持最大为10
的元组,并且对于每种大小,它都提供了如下的元组实现。
Unit
(一个元素)Pair
(两个元素)Triplet
(三个元素)Quartet
(四个元素)Quintet
(五个元素)Sextet
(六个元素)Septet
(七个元素)Octet
(八个元素)Ennead
(九个元素)Decade
(十个元素)
除了上述类之外,它还提供了另外两个类来方便表示对。 大部分与Pair
相同,但语法更详细。
KeyValue
LabelValue
3.3 javatuples
– 特性
不同类型的 Java 元组是:
- 输入安全
- 不可变
- 可迭代的
- 可序列化
- 可比(实现
Comparable
) - 实现
equals()
和hashCode()
- 实现
toString()
4. javatuples
的常见操作
4.1 创建元组
4.1.1 工厂方法
元组对象是通过每个元组类提供的工厂方法with()
构造的。 例如,我们可以使用创建Pair
的元组。
Pair<String, Integer> pair = Pair.with("Sajal", 12);
4.1.2 构造器
我们还可以使用Pair
的构造器。
Pair<String, Integer> person = new Pair<String, Integer>("Sajal", 12);
4.1.3 集合或可迭代对象
我们可以从Collection
或Iterable
创建元组,前提是该集合具有确切数量的对象。 在这种情况下,请记住,集合中的项目数应与我们要创建的元组的类型相匹配。
//Collection of 4 elements will create Quartet
List<String> listOf4Names = Arrays.asList("A1","A2","A3","A4");
Quartet<String, String, String, String> quartet = Quartet.fromCollection(listOf4Names);
System.out.println(quartet);
//Create pair with items starting from the specified index.
List<String> listOf4Names = Arrays.asList("A1","A2","A3","A4");
Pair<String, String> pair1 = Pair.fromIterable(listOf4Names, 2);
System.out.println(pair1);
程序输出。
[A1, A2, A3, A4]
[A3, A4]
同样,我们可以根据需要创建任何元组类的对象。
4.2 检索值
4.2.1 getValueX()
方法
我们可以使用其索引的getValueX()
方法从元组中获取值,其中'X'
表示元组内部的元素位置。 例如getValue0()
,getValue1()
等。
Pair<String, Integer> pair = Pair.with("Sajal", 12);
System.out.println("Name : " + pair.getValue0());
System.out.println("Exp : " + pair.getValue1());
程序输出:
Name : Sajal
Exp : 12
请注意,这些获取方法是类型安全的。 这意味着编译器已经基于用于初始化元组的元素值知道方法的返回类型。
4.2.2 getValue(int index)
方法
元组还有另一种类型不安全的方法getValue(int index)
。 因此,在分配变量时,需要将值转换为期望的类型。
Pair<String, Integer> pair = Pair.with("Sajal", 12);
System.out.println("Name : " + pair.getValue(0));
System.out.println("Exp : " + pair.getValue(1));
程序输出:
Name : Sajal
Exp : 12
类KeyValue
和LabelValue
的方法分别为getKey()
/getValue()
和getLabel()
/getValue()
。
4.3 设定值
创建元组后,我们可以设置值。 我们可以通过setAtX()
方法执行此操作,其中'X'
是我们要设置值的索引位置。
Pair<String, Integer> pair = Pair.with("Sajal", 12);
//Modify the value
Pair<String, Integer> modifiedPair = pair.setAt0("Kajal");
System.out.println(pair);
System.out.println(modifiedPair);
程序输出:
[Sajal, 12]
[Kajal, 12]
请注意,元组是不可变的。 因此,
setAt()
方法返回具有修改后值的相同类型的元组。 原始元组不变。
4.4 添加或删除元素
4.4.1 add()
方法
我们还可以在元组中添加元素,这将返回与元素数量匹配的新元组类型。 例如,如果我们将元素值添加到Pair
,那么我们将获得一个Triplet
对象作为回报。
元组的末尾添加了新元素。
Pair<String, Integer> pair = Pair.with("Sajal", 12);
Triplet<String, Integer, String> triplet = pair.add("IT Professional");
System.out.println(pair);
System.out.println(triplet);
程序输出:
[Sajal, 12]
[Sajal, 12, IT Professional]
我们也可以将一个元组对象添加到另一个元组中。 它将根据添加后存在的元素数返回元组的类型。
Triplet<String, String, String> triplet = Triplet.with("Java", "C", "C++");
Quartet<String, String, String, String> quartet = triplet.addAt1("Python");
Septet septet = quartet.add(triplet); //3 + 4 = 7
System.out.println(triplet);
System.out.println(quartet);
System.out.println(septet);
程序输出:
[Java, C, C++]
[Java, Python, C, C++]
[Java, Python, C, C++, Java, C, C++]
4.4.2 join()
方法
默认情况下,新元素添加到元组的末尾。 但是我们也可以使用addAtX()
方法在元组的其他位置添加元素。
Triplet<String, String, String> triplet = Triplet.with("Java", "C", "C++");
Quartet<String, String, String, String> quartet = triplet.addAt1("Python");
System.out.println(triplet);
System.out.println(quartet);
程序输出:
[Java, C, C++]
[Java, Python, C, C++]
4.5 将元组转换为集合或数组
每个元组类提供asList()
和toArray()
方法,它们分别返回List
和Array
。
//Convert to list
Quartet<String, Integer, String, Double> quartet1 = Quartet.with("A1",1,"A3",2.3);
List<Object> quartletList = quartet1.toList();
System.out.println(quartletList);
//Convert to array
Object[] quartletArr = quartet1.toArray();
System.out.println(Arrays.toString(quartletArr));
程序输出:
[A1, 1, A3, 2.3]
[A1, 1, A3, 2.3]
请注意,元组可以包含异构类型,因此相应的结果类型将为
List<Object>
或Object[]
。
4.6 迭代
javatuples
中的所有元组类都实现Iterable
接口,因此可以与集合或数组相同的方式对其进行迭代。
Quartet<String, Integer, String, Double> quartet1 = Quartet.with("A1",1,"A3",2.3);
for(Object obj : quartet1) {
System.out.println(obj);
}
程序输出:
A1
1
A3
2.3
4.7 Java 元组中的更多操作
所有元组类都有以下实用方法,例如集合,我们可以根据需要使用它们。
contains()
– 如果该元组包含指定的元素,则返回true
。containsAll()
– 如果该元组包含所有指定的元素,则返回true
。indexOf()
– 返回指定元素首次出现的索引。lastIndexOf()
– 返回指定元素最后一次出现的索引。
元组还提供hashCode()
,equals()
和compareTo()
方法的通用实现,这些方法可以很好地处理包装程序和字符串类。
5. Java 元组 - 总结
在此 Java 元组教程中,我们了解了如何通过javatuple
库在 Java 中使用元组。 因此,如果您对存储固定数量的异构元素的数据结构有任何要求,则可以使用此库。 它非常简单,易于使用并提供良好的性能。
学习愉快!
参考文献:
sun.misc.Unsafe
类的用法
原文: https://howtodoinjava.com/java/basics/usage-of-class-sun-misc-unsafe/
这篇文章是有关 Java 鲜为人知的特性的讨论顺序的下一个更新。 请通过电子邮件订阅,以在下一次讨论进行时进行更新。 并且不要忘记在评论部分表达您的观点。
Java 是一种安全的编程语言,可以防止程序员犯很多愚蠢的错误,这些错误大多数是基于内存管理的。 但是,如果您决定将其弄乱,则可以使用Unsafe
类。 此类是sun.*
API,其中并不是 J2SE 的真正组成部分,因此您可能找不到任何正式文档。 可悲的是,它也没有任何好的代码文档。
sun.misc.Unsafe
的实例化
如果尝试创建Unsafe
类的实例,则由于两个原因,将不允许您这样做。
1)不安全类具有私有构造器。
2)它也具有静态的getUnsafe()
方法,但是如果您朴素地尝试调用Unsafe.getUnsafe()
,则可能会得到SecurityException
。 此类只能从受信任的代码实例化。
但是总有一些解决方法。 创建实例的类似的简单方法是使用反射:
Field f = Unsafe.class.getDeclaredField("theUnsafe"); //Internal reference
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
注意:您的 IDE,例如 eclipse 可能显示与访问限制有关的错误。 不用担心,继续运行程序。 它将运行。
现在到主要部分。 使用此对象,您可以执行“有趣的”任务。
sun.misc.Unsafe
的使用
1)创建不受限制的实例
使用allocateInstance()
方法,您可以创建类的实例,而无需调用其构造器代码,初始化代码,各种 JVM 安全检查以及所有其他低级内容。 即使类具有私有构造器,也可以使用此方法创建新实例。
所有单例爱好者的噩梦。 伙计们,您只是无法轻松应对这种威胁。
举个例子:
public class UnsafeDemo
{
public static void main(String[] args) throws NoSuchFieldException, SecurityException,
IllegalArgumentException, IllegalAccessException, InstantiationException
{
Field f = Unsafe.class.getDeclaredField("theUnsafe"); //Internal reference
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
//This creates an instance of player class without any initialization
Player p = (Player) unsafe.allocateInstance(Player.class);
System.out.println(p.getAge()); //Print 0
p.setAge(45); //Let's now set age 45 to un-initialized object
System.out.println(p.getAge()); //Print 45
System.out.println(new Player().getAge()); //This the normal way to get fully initialized object; Prints 50
}
}
class Player{
private int age = 12;
public Player(){ //Even if you create this constructor private;
//You can initialize using Unsafe.allocateInstance()
this.age = 50;
}
public int getAge(){
return this.age;
}
public void setAge(int age){
this.age = age;
}
}
Output:
0
45
50
2)使用直接内存访问的浅克隆
通常如何进行浅克隆? 在clone(){..}
方法中调用super.clone()
吧? 这里的问题是,您必须实现Cloneable
接口,然后在要实现浅层克隆的所有类中覆盖clone()
方法。 懒惰的开发人员要付出太多努力。
我不建议这样做,但是使用不安全的方法,我们可以在几行中创建浅表克隆,最好的部分是它可以与任何类一起使用,就像某些工具方法一样。
诀窍是将对象的字节复制到内存中的另一个位置,然后将该对象类型转换为克隆的对象类型。
3)黑客的密码安全性
这看起来很有趣吗? 是的,是的。 开发人员创建密码或将密码存储在字符串中,然后在应用程序代码中使用它们。 使用密码后,更聪明的开发人员将字符串引用设置为NULL
,以便不再对其进行引用,并且可以轻松地对其进行垃圾回收。
但是从那时起,您对垃圾回收器启动时的引用为null
,该字符串实例位于字符串池中。 对您的系统进行的复杂攻击将能够读取您的内存区域,从而也可以访问密码。 机会很低,但他们在这里。
因此,建议使用char []
存储密码,以便在使用后可以遍历数组并使每个字符变脏/变空。
另一种方法是使用我们的魔术类“不安全”。 在这里,您将创建另一个长度与密码相同的临时字符串,并存储“?
”或为临时密码中的每个字符输入“*
”(或任何字母)。 完成密码逻辑操作后,您只需将临时密码(例如????????
)的字节复制到原始密码上即可。 这意味着用临时密码覆盖原始密码。
示例代码如下所示。
String password = new String("l00k@myHor$e");
String fake = new String(password.replaceAll(".", "?"));
System.out.println(password); // l00k@myHor$e
System.out.println(fake); // ????????????
getUnsafe().copyMemory(fake, 0L, null, toAddress(password), sizeOf(password));
System.out.println(password); // ????????????
System.out.println(fake); // ????????????
在运行时动态创建类
我们可以在运行时创建类,例如从已编译的.class
文件中。 要将读取的类内容执行到字节数组,然后将其传递给defineClass
方法。
//Sample code to craeet classes
byte[] classContents = getClassContent();
Class c = getUnsafe().defineClass(null, classContents, 0, classContents.length);
c.getMethod("a").invoke(c.newInstance(), null);
//Method to read .class file
private static byte[] getClassContent() throws Exception {
File f = new File("/home/mishadoff/tmp/A.class");
FileInputStream input = new FileInputStream(f);
byte[] content = new byte[(int)f.length()];
input.read(content);
input.close();
return content;
}
4)超大数组
如您所知,Integer.MAX_VALUE
常量是 java 数组的最大大小。 如果要构建真正的大数组(尽管在常规应用程序中没有实际需要),则可以为此使用直接内存分配。
以此类的示例为例,该类创建的顺序存储器(数组)的大小是允许的大小的两倍。
class SuperArray {
private final static int BYTE = 1;
private long size;
private long address;
public SuperArray(long size) {
this.size = size;
address = getUnsafe().allocateMemory(size * BYTE);
}
public void set(long i, byte value) {
getUnsafe().putByte(address + i * BYTE, value);
}
public int get(long idx) {
return getUnsafe().getByte(address + idx * BYTE);
}
public long size() {
return size;
}
}
用法示例:
long SUPER_SIZE = (long)Integer.MAX_VALUE * 2;
SuperArray array = new SuperArray(SUPER_SIZE);
System.out.println("Array size:" + array.size()); // 4294967294
for (int i = 0; i < 100; i++) {
array.set((long)Integer.MAX_VALUE + i, (byte)3);
sum += array.get((long)Integer.MAX_VALUE + i);
}
System.out.println("Sum of 100 elements:" + sum); // 300
请注意,这会导致 JVM 崩溃。
总结
sun.misc.Unsafe
提供了几乎无限的能力来探索和修改 VM 的运行时数据结构。 尽管事实上这些特性几乎不能在 Java 开发本身中使用,但是对于希望在不进行 C++代码调试的情况下研究 HotSpot VM 或需要创建临时分析工具的任何人来说,Unsafe
都是一个不错的工具。
参考:
http://mishadoff.github.io/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/
祝您学习愉快!
Java UUID 生成器示例
了解什么是 UUID 及其版本和变体。 学习使用UUID.randomUUID()
API 在 Java 中生成 UUID。 另请学习用 Java 生成版本 5 UUID。
1. 什么是 UUID?
UUID (通用唯一标识符),也称为 GUID (全局唯一标识符)是128 bits
长标识符,相对于所有其他 UUID 的空间而言。 它不需要中央注册过程。 结果,按需生成可以完全自动化,并用于多种目的。
要了解 UUID 的独特性,您应该知道 UUID 生成算法支持非常高的分配速率,每台机器每秒可支持高达 1000 万的速度,因此甚至可以用作事务 ID。
我们可以应用排序,排序并将它们存储在数据库中。 通常,它在编程中很有用。
由于 UUID 是唯一且持久的,因此与其他替代方法相比,它们具有出色的统一资源名称(URN),挖掘成本最低。
空的 UUID(UHTID)是 UUID 的一种特殊形式,它指定将所有 128 位都设置为零。
不要以为 UUID 很难猜测; 它们不应用作安全特性。 可预测的随机数源将加剧这种情况。 人类没有能力仅仅看一眼就能轻易地检查 UUID 的完整性。
2. UUID 类型
典型的 UUID 以8-4-4-4-12
的形式显示在 5 组中,由连字符分隔,总共 36 个字符(32 个字母数字字符和 4 个连字符)。
123e4567-e89b-12d3-a456-426655440000
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
在此'M'
表示 UUID 版本,'N'
表示 UUID 变体。
- 变体字段包含一个值,该值标识 UUID 的布局。
- 版本字段包含一个描述此 UUID 类型的值。 UUID 有五种不同的基本类型。
3. Java UUID 示例
3.1 UUID.randomUUID()
– 版本 4
默认 API randomUUID()
是一个静态工厂,用于检索类型 4(伪随机生成的)UUID。 对于大多数用例来说已经足够了。
UUID uuid = UUID.randomUUID();
System.out.println(uuid);
System.out.println(uuid.variant()); //2
System.out.println(uuid.version()); //4
程序输出。
17e3338d-344b-403c-8a87-f7d8006d6e33
2
4
3.2 生成版本 5 UUID
Java 没有提供内置 API 来生成版本 5 UUID,因此我们必须创建自己的实现。 以下是一种这样的实现。 (参考)。
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;
public class UUID5
{
public static UUID fromUTF8(String name) {
return fromBytes(name.getBytes(Charset.forName("UTF-8")));
}
private static UUID fromBytes(byte[] name) {
if (name == null) {
throw new NullPointerException("name == null");
}
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
return makeUUID(md.digest(name), 5);
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
private static UUID makeUUID(byte[] hash, int version) {
long msb = peekLong(hash, 0, ByteOrder.BIG_ENDIAN);
long lsb = peekLong(hash, 8, ByteOrder.BIG_ENDIAN);
// Set the version field
msb &= ~(0xfL << 12);
msb |= ((long) version) << 12;
// Set the variant field to 2
lsb &= ~(0x3L << 62);
lsb |= 2L << 62;
return new UUID(msb, lsb);
}
private static long peekLong(final byte[] src, final int offset, final ByteOrder order) {
long ans = 0;
if (order == ByteOrder.BIG_ENDIAN) {
for (int i = offset; i < offset + 8; i += 1) {
ans <<= 8;
ans |= src[i] & 0xffL;
}
} else {
for (int i = offset + 7; i >= offset; i -= 1) {
ans <<= 8;
ans |= src[i] & 0xffL;
}
}
return ans;
}
}
UUID uuid = UUID5.fromUTF8("954aac7d-47b2-5975-9a80-37eeed186527");
System.out.println(uuid);
System.out.println(uuid.variant());
System.out.println(uuid.version());
d1d16b54-9757-5743-86fa-9ffe3b937d78
2
5
向我提供有关在 Java 中生成 UUID 的问题。
学习愉快!
参考: Rfc 4122
Java 12 教程
Java 12 – 新特性和增强特性
原文: https://howtodoinjava.com/java12/new-features-enhancements/
Java 12(于 2019 年 3 月 19 日发布)是 JDK 的最新版本。 让我们看看它为开发人员和建筑师带来的新特性和改进。
1. 流 API 中的Collectors.teeing()
teeing
收集器已作为静态方法公开Collectors::teeing
。 在将其结果与函数合并之前,此收集器将其输入转发给其他两个收集器。
teeing(Collector, Collector, BiFunction)
接受两个收集器和一个函数来合并其结果。 传递给结果收集器的每个元素都由两个下游收集器处理,然后使用指定的合并函数将其结果合并为最终结果。
例如,在给定的雇员列表中,如果我们想找出最高薪水和最低薪水的雇员,则可以使用teeing
收集器在单个语句中完成。
SalaryRange salaryRange = Stream
.of(56700, 67600, 45200, 120000, 77600, 85000)
.collect(teeing(
minBy(Integer::compareTo),
maxBy(Integer::compareTo),
SalaryRange::fromOptional));
阅读更多:Collectors.teeing()
2. 字符串 API 的更改
2.1 String.indent()
缩进方法有助于更改字符串的缩进。 我们可以传递一个正值或一个负值,具体取决于我们是要添加更多的空白还是要删除现有的空白。
String result = "foo\nbar\nbar2".indent(4);
System.out.println(result);
// foo
// bar
// bar2
请注意,indent()
方法会自动添加换行符(如果尚未提供)。 这是预料之中的,并且是新方法的特性。
每个空格字符都被视为一个字符。 特别是,制表符
"\t" (U+0009)
被视为单个字符; 它不会扩展。
2.2 String.transform()
transform()
方法采用String
并借助Function
将其转换为新的String
。
在给定的示例中,我们有一个名称列表。 我们正在使用transform()
方法执行两个操作(修剪空白并使所有名称都大写)。
List<String> names = List.of(
" Alex",
"brian");
List<String> transformedNames = new ArrayList<>();
for (String name : names)
{
String transformedName = name.transform(String::strip)
.transform(StringUtils::toCamelCase);
transformedNames.add(transformedName);
}
2.3 字符串常量
从 Java 12 开始,String
类实现了两个附加接口java.lang.constant.Constable
和java.lang.constant.ConstantDesc
。
String
类还引入了两个附加的低级方法describeConstable()
和resolveConstantDesc(MethodHandles.Lookup)
。
它们是低级 API,适用于提供字节码解析和生成特性的库和工具,例如 ByteBuddy。
请注意,Constable
类型是一种值,该值是可以在 Java 类文件的常量池中表示的常量,如 JVMS 4.4 中所述,并且其实例可以名义上将自己描述为ConstantDesc
。
resolveConstantDesc()
与describeConstable()
相似,不同之处在于此方法改为返回ConstantDesc
的实例。
3. Files.mismatch(Path, Path)
有时,我们想确定两个文件是否具有相同的内容。 该 API 有助于比较文件的内容。
mismatch()
方法比较两个文件路径并返回long
值。 long
表示两个文件内容中第一个不匹配字节的位置。 如果文件“相等”,则返回值为 – 1
。
Path helloworld1 = tempDir.resolve("helloworld1.txt");
Path helloworld2 = tempDir.resolve("helloworld2.txt");
long diff = Files.mismatch(helloworld1, helloworld2); //returns long value
4. 精简数字格式
用户界面或命令行工具提供的大量数字始终很难解析。 使用数字的缩写形式更为常见。 精简数字表示形式更易于阅读,并且在屏幕上占用的空间更少,而不会失去其原始含义。
例如。 3.6 M 比 3,600,000 更容易阅读。
Java 12 引入了一种方便的方法,称为NumberFormat.getCompactNumberInstance(Locale,NumberFormat.Style)
,用于创建精简数字表示形式。
NumberFormat formatter = NumberFormat.getCompactNumberInstance(Locale.US,
NumberFormat.Style.SHORT);
String formattedString = formatter.format(25000L); //25K
5. 支持 Unicode 11
在表情符号在社交媒体渠道交流中发挥关键作用的时代,支持最新的 Unicode 规范比以往任何时候都更为重要。 Java 12 保持同步并支持 Unicode 11。
Unicode 11 增加了 684 个字符,总计 137,374 个字符 – 以及七个新脚本,总共 146 个脚本。
6. switch
表达式(预览)
此更改扩展了 switch
语句 ,以便可以将其用作语句或表达式。
不必为每个case
块定义break
语句,我们只需使用箭头语法即可。 箭头语法在语义上看起来像 lambda,并且将case
标签与表达式分开。
使用新的switch
表达式,我们可以将switch
语句直接分配给变量。
boolean isWeekend = switch (day)
{
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> false;
case SATURDAY, SUNDAY -> true;
default -> throw new IllegalStateException("Illegal day entry :: " + day);
};
System.out.println(isWeekend); //true or false - based on current day
要使用此预览特性,请记住,我们必须在应用程序启动期间使用
– enable-preview
标志明确指示 JVM。
向我提供有关 Java 12 中这些新 API 更改的问题。
学习愉快!
什么是 Java JDK,JRE 和 JVM – 深入分析
了解 JDK,JRE 和 JVM 之间的区别。 JVM 是如何工作的? 什么是类加载器,解释器和 JIT 编译器。 还要签出一些面试问题。
Table of Contents
1\. Execution of a Java Program
2\. What is JVM?
3\. What is JRE?
4\. What is JDK?
5\. Differences between JDK, JRE and JVM
6\. Interview questions related to JDK, JRE and JVM
7\. JDK and JRE downloads
1. 执行 Java 程序
在深入了解 Java 内部之前,让我们了解如何执行 Java 源文件。
- 我们使用编辑器或 IDE(集成开发环境)在
Simple.Java
文件中编写 Java 源代码。 Eclipse 或 IntelliJ Idea 。 - 程序必须编译成字节码。 Java 编译器(
javac
)将源代码编译为Simple.class
文件。 - JVM(Java 虚拟机)可以在任何平台/操作系统中执行此类文件。
- JVM 将字节码转换为机器可执行的本机机器代码。
Java 执行流程
2. 什么是 JVM?
Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。 您可以通过将.java
文件编译为.class
文件来获得此字节码。 .class
文件包含 JVM 可以理解的字节码。
在现实世界中,JVM 是提供可在其中执行 Java 字节码的运行时环境的规范。 不同的供应商提供此规范的不同实现。 例如,此 Wiki 页面列出了不同的 JVM 实现。
JVM 最流行的实现是 Hotspot ,它由 Oracle Corporation 拥有和提供。 (先前由 Sun Microsystems,Inc. )。
JVM 使用许多先进技术为 Java 应用程序提供最佳性能,这些技术结合了最新的内存模型,垃圾收集器和自适应优化器。
JVM 具有两种不同的风格-客户端和服务器。 尽管服务器 VM 和客户端 VM 相似,但已经对服务器 VM 进行了特殊调整,以最大程度地提高峰值运行速度。 它用于执行长时间运行的服务器应用程序,这些应用程序需要比快速启动时间或较小的运行时内存占用更多的最快的运行速度。 开发人员可以通过指定-client
或-server
选择他们想要的系统。
JVM 之所以称为虚拟,是因为它提供的机器接口不依赖于底层操作系统和机器硬件架构。 这种与硬件和操作系统的独立性是 Java 程序一次写入,随处运行的价值的基石。
2.1 JVM 架构
JVM 架构
2.1.1 类加载器
类加载器是用于加载类文件的子系统。 它执行三个主要功能,即类加载,链接和初始化。
-
载入
- 为了加载类,JVM 有 3 种类加载器。 引导程序,扩展名和应用程序类加载器。
- 加载类文件时,JVM 会发现某个任意类
XYZ.class
的依赖项。 - 第一个引导程序类加载器尝试查找该类。 它将扫描 JRE
lib
文件夹中的rt.jar
文件。 - 如果找不到类,那么扩展类加载器会在
jre\lib\ext
文件夹中搜索类文件。 - 同样,如果未找到类,则应用程序类加载器将在系统的
CLASSPATH
环境变量中搜索所有 Jar 文件和类。 - 如果任何加载程序找到了类,则由类加载程序加载类; 否则抛出
ClassNotFoundException
。
-
链接
由类加载器加载类后,将执行链接。 字节码验证器将验证生成的字节码是否正确,如果验证失败,我们将收到验证错误。 它还对类中的静态变量和方法执行内存分配。
-
初始化
这是类加载的最后阶段,此处将为所有静态变量分配原始值,并执行静态块。
2.1.2 JVM 内存区域
JVM 中的内存区域分为多个部分,以存储应用程序数据的特定部分。
- 方法区,用于存储类结构,如元数据,常量运行时池和方法代码。
- 堆存储在应用程序执行期间创建的所有对象。
- 栈存储局部变量和中间结果。 所有这些变量对于创建它们的线程都是本地的。 每个线程都有自己的 JVM 栈,并在创建线程时同时创建。 因此,所有此类局部变量都称为线程局部变量。
- PC 寄存器存储当前正在执行的语句的物理内存地址。 在 Java 中,每个线程都有其单独的 PC 寄存器。
- Java 也支持并使用本机代码。 许多底层代码都是用 C 和 C++ 等语言编写的。 本机方法栈保存本机代码的指令。
2.2 JVM 执行引擎
分配给 JVM 的所有代码均由执行引擎执行。 执行引擎读取字节码并一一执行。 它使用两个内置的解释器和 JIT 编译器 将字节码转换为机器代码并执行。
平台特定的解释器
使用 JVM,解释器和编译器均会生成本机代码。 不同之处在于它们如何生成本机代码,其优化程度以及优化的代价。
2.2.1 解释器
JVM 解释器通过查找预定义的 JVM 指令到机器指令的映射,几乎将每个字节码指令转换为相应的本机指令。 它直接执行字节码,并且不执行任何优化。
2.2.2 JIT 编译器
为了提高性能,JIT 编译器在运行时与 JVM 交互,并将适当的字节码序列编译为本地机器代码。 通常,JIT 编译器采用一段代码(每次一次都没有一个语句作为解释器),优化代码,然后将其转换为优化的机器代码。
默认情况下启用 JIT 编译器。 您可以禁用 JIT 编译器,在这种情况下,将解释整个 Java 程序。 除了诊断或解决 JIT 编译问题外,不建议禁用 JIT 编译器。
3. 什么是 JRE?
Java 运行时环境(JRE)是一个包,它将库(jar)和 Java 虚拟机以及其他组件捆绑在一起,以运行用 Java 编写的应用程序。 JVM 只是 JRE 发行版的一部分。
要执行任何 Java 应用程序,您需要在计算机中安装 JRE。 在任何计算机上执行 Java 应用程序都是最低要求。
JRE 捆绑了以下组件:
- Java HotSpot 客户端虚拟机使用的 DLL 文件。
- Java HotSpot 服务器虚拟机使用的 DLL 文件。
- Java 运行时环境使用的代码库,属性设置和资源文件。 例如
rt.jar
和charsets.jar
。 - Java 扩展文件,例如
localedata.jar
。 - 包含用于安全管理的文件。 这些文件包括安全策略(
java.policy
)和安全属性(java.security
)文件。 - 包含小程序的支持类的 Jar 文件。
- 包含 TrueType 字体文件供平台使用。
JRE 可以作为 JDK 的一部分下载,也可以单独下载。 JRE 与平台有关。 这意味着您必须根据计算机的类型(操作系统和架构)选择要导入和安装的 JRE 包。
例如,您不能在32-bit
计算机上安装64-bit
JRE 发行版。 同样, Windows 的 JRE 分发在 Linux 中将不起作用; 反之亦然。
4. 什么是 JDK?
JDK 是 JRE 的超集。 JDK 包含 JRE 拥有的所有内容以及用于开发,调试和监视 Java 应用程序的开发工具。 需要开发 Java 应用程序时就需要 JDK。
JDK 附带的几个重要组件如下:
- appletviewer – 此工具可用于在没有 Web 浏览器的情况下运行和调试 Java applet
- apt – 注释处理工具
- extcheck – 一种检测 JAR 文件冲突的工具
- javadoc – 文档生成器,可从源代码注释自动生成文档
- jar – 归档程序,它将相关的类库打包到单个 JAR 文件中。 该工具还有助于管理 JAR 文件
- jarsigner – jar 签名和验证工具
- javap – 类文件反汇编程序
- javaws -用于 JNLP 应用程序的 Java Web Start 启动器
- JConsole -Java 监视和管理控制台
- jhat – Java 堆分析工具
- jrunscript – Java 命令行脚本外壳
- jstack – 打印 Java 线程的 Java 栈跟踪的工具
- keytool – 用于操作密钥库的工具
- policytool – 策略创建和管理工具
- xjc – XML 绑定 Java API(JAXB)API 的一部分。 它接受 XML 模式并生成 Java 类
与 JRE 一样,JDK 也依赖于平台。 因此,在为您的计算机下载 JDK 包时请多加注意。
5. JDK,JRE 和 JVM 之间的区别
基于以上讨论,我们可以得出以下三个方面的关系:
JRE = JVM + 运行 Java 应用程序的库。
JDK = JRE + 开发 Java 应用程序的工具。
JDK vs JRE vs JVM
简而言之,如果您是编写代码的 Java 应用程序开发人员,则需要在计算机中安装 JDK。 但是,如果只想运行用 Java 内置的应用程序,则只需要在计算机上安装 JRE。
6. 与 JDK,JRE 和 JVM 有关的面试问题
如果您了解我们到目前为止在本文中讨论的内容,那么面对任何面试问题都将很困难。 尽管如此,请准备回答以下问题:
-
什么是 JVM 架构?
已经详细解释了。
-
Java 中有几种类型的类加载器?
有 3 种装载机。 引导程序,扩展程序和应用程序类加载器。
-
Java 中的类加载器如何工作?
类加载器会在其预定义位置扫描 jar 文件和类。 他们扫描路径中的所有那些类文件,并查找所需的类。 如果找到它们,请加载,链接并初始化类文件。
-
JRE 和 JVM 之间的区别?
JVM 是用于运行 Java 应用程序的运行时环境的规范。 热点 JVM 是规范的这样一种实现。 它加载类文件,并使用解释器和 JIT 编译器将字节码转换为机器代码并执行。
-
解释器和 JIT 编译器之间的区别?
解释器逐行解释字节码并顺序执行。 这会导致性能下降。 JIT 编译器通过分析块中的代码来为该过程添加优化,然后准备更多优化的机器代码。
7. JDK 和 JRE 下载
您可以在 Oracle 的 Java 发行页面中找到特定于平台的 JDK 和 JRE 包。
例如,此页列出了 Java 8 的所有可用 JDK 发行版。
JDK 8 发行版
类似地,此页中提供了 JRE 8 发行版。
JRE 8 发行版
学习愉快!
进一步阅读:
收集器teeing()
方法示例
原文: https://howtodoinjava.com/java12/collectors-teeing-example/
了解Collectors.teeing()
方法(在 Java 12 中添加),方法语法以及如何在 Java 的各种用例中应用teeing()
方法。
1. teeing()
的目的
这是java.util.stream.Collectors
接口的新静态方法,它允许使用两个独立的收集器进行收集,然后使用提供的BiFunction
合并其结果。
传递给结果收集器的每个元素都由两个下游收集器处理,然后使用指定的合并函数将其结果合并为最终结果。
请注意,此函数有助于一步完成特定任务。 如果不使用teeing()
函数,我们已经可以分两步执行给定的任务。 它只是一个帮助函数,可以帮助减少冗长程度。
2. 语法
/**
* downstream1 - the first downstream collector
* downstream2 - the second downstream collector
* merger - the function which merges two results into the single one
* returns - a Collector which aggregates the results of two supplied collectors.
*/
public static Collector teeing (Collector downstream1,
Collector downstream2,
BiFunction merger);
3. 使用teeing()
查找薪水最高和最低的员工
在此Collectors.teeing()
示例中,我们有一个雇员列表。 我们要一步找到最高薪的员工和最低薪的员工。
以下 Java 程序执行查找最大和最小操作,然后将两个项目收集到Map
中。
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.HashMap;
import java.util.Optional;
import java.util.stream.Collectors;
public class Main
{
public static void main(String[] args)
{
List<Employee> employeeList = Arrays.asList(
new Employee(1, "A", 100),
new Employee(2, "B", 200),
new Employee(3, "C", 300),
new Employee(4, "D", 400));
HashMap<String, Employee> result = employeeList.stream().collect(
Collectors.teeing(
Collectors.maxBy(Comparator.comparing(Employee::getSalary)),
Collectors.minBy(Comparator.comparing(Employee::getSalary)),
(e1, e2) -> {
HashMap<String, Employee> map = new HashMap();
map.put("MAX", e1.get());
map.put("MIN", e2.get());
return map;
}
));
System.out.println(result);
}
}
程序输出。
C:\BAML\DFCCUI\installs\jdk-12.0.1\bin>java Main.java
{
MIN=Employee [id=1, name=A, salary=100.0],
MAX=Employee [id=4, name=D, salary=400.0]
}
这里的Employee
类是这样的。
class Employee
{
private long id;
private String name;
private double salary;
public Employee(long id, String name, double salary) {
super();
this.id = id;
this.name = name;
this.salary = salary;
}
//Getters and setters
@Override
public String toString() {
return "Employee [id=" + id + ", name=" + name + ", salary=" + salary + "]";
}
}
4. 使用teeing()
过滤项目并计数
在此示例中,我们将使用同一组员工。 在这里,我们将找到所有薪水高于 200 的员工,然后我们还将计算这些员工的数量。
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.HashMap;
import java.util.Optional;
import java.util.stream.Collectors;
public class Main
{
public static void main(String[] args)
{
List<Employee> employeeList = Arrays.asList(
new Employee(1, "A", 100),
new Employee(2, "B", 200),
new Employee(3, "C", 300),
new Employee(4, "D", 400));
HashMap<String, Object> result = employeeList.stream().collect(
Collectors.teeing(
Collectors.filtering(e -> e.getSalary() > 200, Collectors.toList()),
Collectors.filtering(e -> e.getSalary() > 200, Collectors.counting()),
(list, count) -> {
HashMap<String, Object> map = new HashMap();
map.put("list", list);
map.put("count", count);
return map;
}
));
System.out.println(result);
}
}
程序输出:
C:\BAML\DFCCUI\installs\jdk-12.0.1\bin>java Main.java
{
count=2,
list=[Employee [id=3, name=C, salary=300.0], Employee [id=4, name=D, salary=400.0]]
}
5. 总结
上面的Collectors.teeing()
方法示例非常简单,为便于基本理解而编写。 您需要使用非常适合您自己需要的函数。
只需记住,当您需要执行两次流操作并在两个不同的收集器中收集结果时,请考虑使用teeing()
方法。 它并不总是适合用例,但适合时可能会有用。
学习愉快!
参考: Java 文档
字符串indent(count)
– Java 中的行左缩进
原文: https://howtodoinjava.com/java12/string-left-indent-lines/
学习使用String.indent()
API 在 Java 中缩进(左缩进)字符串。 此 API 已在 Java 12 中引入。
1. String.indent(count)
API
此方法根据count
的值调整给定字符串的每一行的缩进,并标准化行终止符。
/**
* count - number of leading white space characters to add or remove
* returns - string with indentation adjusted and line endings normalized
*/
public String indent(int count)
注意,count
的值可以是正数或负数。
- 正数 – 如果
count > 0
,则在每行的开头插入的空格。 - 负数 – 如果
count < 0
,则在每行的开头将删除空格。 - 负数 – 如果
count > available white spaces
,则删除所有前导空格。
每个空格字符都被视为一个字符。 特别是,制表符
\t
被视为单个字符; 它不会扩展。
2. String.indent()
示例
Java 程序将空白字符串转换成缩进 8 个字符的文件。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.stream.Stream;
public class Main
{
public static void main(String[] args)
{
try
{
Path file = Files.createTempFile("testOne", ".txt");
//Write strings to file indented to 8 leading spaces
Files.writeString(file, "ABC".indent(8), StandardOpenOption.APPEND);
Files.writeString(file, "123".indent(8), StandardOpenOption.APPEND);
Files.writeString(file, "XYZ".indent(8), StandardOpenOption.APPEND);
//Verify the content
Stream<String> lines = Files.lines(file);
lines.forEach(System.out::println);
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
程序输出。
ABC
123
XYZ
请把关于将文件读入流的行中的问题提供给我。
学习愉快!
参考: Java 文档
精简数字格式
了解如何将语言环境敏感的精简/较短格式应用于通用编号,例如小数,货币和百分比。 它是在 Java 12 中的CompactNumberFormat
类中添加的。
例如,可以将数字(例如 1000)格式化为“ 1K”(短样式)或“ 1000”(长样式)。
1. CompactNumberFormat
类
CompactNumberFormat
是NumberFormat
的具体子类,它以精简形式格式化十进制数。 精简数字格式设计用于空间受限的环境,并且格式化的字符串可以在该有限的空间中显示。
精简数字格式是指基于为给定语言环境提供的模式,以较短的形式表示数字。
1.1 创建新的CompactNumberFormat
实例
要获取语言环境的CompactNumberFormat
,请使用NumberFormat
给出的工厂方法之一。
NumberFormat fmt = NumberFormat.getCompactNumberInstance(
new Locale("hi", "IN"), NumberFormat.Style.SHORT);
NumberFormat fmt = NumberFormat.getCompactNumberInstance(
Locale.US, NumberFormat.Style.LONG);
1.2 自定义CompactNumberFormat
实例
我们还可以创建自定义的数字格式,在其中可以定义如何使用CompactNumberFormat(String, DecimalFormatSymbols, String[])
构造器以较短的形式表示数字。
final String[] compactPatterns
= {"", "", "", "0k", "00k", "000k", "0m", "00m", "000m",
"0b", "00b", "000b", "0t", "00t", "000t"};
final DecimalFormat decimalFormat = (DecimalFormat)
NumberFormat.getNumberInstance(Locale.GERMANY);
final CompactNumberFormat customCompactNumberFormat
= new CompactNumberFormat( decimalFormat.toPattern(),
decimalFormat.getDecimalFormatSymbols(),
compactPatterns);
- 精简数字
compactPatterns
以一系列模式表示,其中每个模式用于格式化一系列数字。 - 数组中最多可以提供 15 个样式,但是第一个提供的样式始终对应于
10 ^ 0
。 - 基于数组元素的数量,这些值的范围为
10 ^ 0
至10 ^ 14
。
2. 精简数字格式示例
2.1 简单格式化
Java 程序以精简数字格式格式化数字。
import java.text.NumberFormat;
import java.util.Locale;
public class Main
{
public static void main(String[] args)
{
NumberFormat fmt = NumberFormat
.getCompactNumberInstance(Locale.US, NumberFormat.Style.LONG);
System.out.println( fmt.format(100) );
System.out.println( fmt.format(1000) );
System.out.println( fmt.format(10000) );
System.out.println( fmt.format(100000) );
NumberFormat fmtShort = NumberFormat
.getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);
System.out.println( fmtShort.format(100) );
System.out.println( fmtShort.format(1000) );
System.out.println( fmtShort.format(10000) );
System.out.println( fmtShort.format(100000) );
}
}
程序输出。
100
1 thousand
10 thousand
100 thousand
100
1K
10K
100K
2.2 设置小数
设置数字的小数部分中允许的最小位数。 默认情况下,小数部分设置为0
个数字。
import java.text.NumberFormat;
import java.util.Locale;
public class Main
{
public static void main(String[] args)
{
NumberFormat fmt = NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);
fmt.setMinimumFractionDigits(3);
System.out.println( fmt.format(10000) );
System.out.println( fmt.format(10012) );
System.out.println( fmt.format(100201) );
System.out.println( fmt.format(1111111) );
}
}
程序输出:
10.000K
10.012K
100.201K
1.111M
3. 精简数字解析示例
Java 程序将精简数字解析为长模式。
import java.text.NumberFormat;
import java.util.Locale;
public class Main
{
public static void main(String[] args) throws Exception
{
NumberFormat fmt = NumberFormat
.getCompactNumberInstance(Locale.US, NumberFormat.Style.LONG);
System.out.println( fmt.parse("100") );
System.out.println( fmt.parse("1 thousand") );
System.out.println( fmt.parse("10 thousand") );
System.out.println( fmt.parse("100 thousand") );
}
}
程序输出:
100
1000
10000
100000
向我提供有关 Java 12 中精简数字格式的问题。
学习愉快!
Java 11 教程
Java 11 的新特性和增强特性
Java 11(于 2018 年 9 月发布)包含许多重要且有用的更新。 让我们看看它为开发人员和建筑师带来的新特性和改进。
1. HTTP 客户端 API
Java 长时间使用HttpURLConnection
类进行 HTTP 通信。 但是随着时间的流逝,要求变得越来越复杂,对应用程序的要求也越来越高。 在 Java 11 之前,开发人员不得不诉诸特性丰富的库,例如 Apache HttpComponents 或 OkHttp 等。
我们看到 Java 9 版本包含HttpClient
实现作为实验特性。 随着时间的流逝,它已经成为 Java 11 的最终特性。现在,Java 应用程序可以进行 HTTP 通信,而无需任何外部依赖。
1.1 如何使用HttpClient
与java.net.http
模块的典型 HTTP 交互看起来像:
- 创建
HttpClient
的实例,并根据需要对其进行配置。 - 创建
HttpRequest
的实例并填充信息。 - 将请求传递给客户端,执行请求,并检索
HttpResponse
的实例。 - 处理
HttpResponse
中包含的信息。
HTTP API 可以处理同步和异步通信。 让我们来看一个简单的例子。
1.2 同步请求示例
请注意,http 客户端 API 如何使用构建器模式创建复杂的对象。
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
try
{
String urlEndpoint = "https://postman-echo.com/get";
URI uri = URI.create(urlEndpoint + "?foo1=bar1&foo2=bar2");
HttpRequest request = HttpRequest.newBuilder()
.uri(uri)
.build();
HttpResponse<String> response = httpClient.send(request,
HttpResponse.BodyHandlers.ofString());
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Status code: " + response.statusCode());
System.out.println("Headers: " + response.headers().allValues("content-type"));
System.out.println("Body: " + response.body());
1.2 异步请求示例
如果我们不想等待响应,那么异步通信很有用。 我们提供了回调处理程序,可在响应可用时执行。
注意使用sendAsync()
方法发送异步请求。
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toList;
final List<URI> uris = Stream.of(
"https://www.google.com/",
"https://www.github.com/",
"https://www.yahoo.com/"
).map(URI::create).collect(toList());
HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.ALWAYS)
.build();
CompletableFuture[] futures = uris.stream()
.map(uri -> verifyUri(httpClient, uri))
.toArray(CompletableFuture[]::new);
CompletableFuture.allOf(futures).join();
private CompletableFuture<Void> verifyUri(HttpClient httpClient,
URI uri)
{
HttpRequest request = HttpRequest.newBuilder()
.timeout(Duration.ofSeconds(5))
.uri(uri)
.build();
return httpClient.sendAsync(request,HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::statusCode)
.thenApply(statusCode -> statusCode == 200)
.exceptionally(ex -> false)
.thenAccept(valid ->
{
if (valid) {
System.out.println("[SUCCESS] Verified " + uri);
} else {
System.out.println("[FAILURE] Could not " + "verify " + uri);
}
});
}
2. 启动不编译的单文件程序
传统上,对于我们要执行的每个程序,我们都需要先对其进行编译。 出于测试目的,小型程序似乎不必要地耗时。
Java 11 对其进行了更改,现在我们可以执行单个文件中包含的 Java 源代码,而无需先对其进行编译。
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
要执行上述类,请直接使用java
命令运行它。
$ java HelloWorld.java
Hello World!
注意,程序不能使用
java.base module
之外的任何外部依赖项。 并且该程序只能是单文件程序。
3. 字符串 API 的更改
3.1 String.repeat(int)
此方法仅重复字符串n
次。 它返回一个字符串,其值是重复 N 次给定字符串的连接。
如果此字符串为空或count
为零,则返回空字符串。
public class HelloWorld
{
public static void main(String[] args)
{
String str = "1".repeat(5);
System.out.println(str); //11111
}
}
3.2 String.isBlank()
此方法指示字符串是空还是仅包含空格。 以前,我们一直在 Apache 的StringUtils.java
中使用它。
public class HelloWorld
{
public static void main(String[] args)
{
"1".isBlank(); //false
"".isBlank(); //true
" ".isBlank(); //true
}
}
3.3 String.strip()
此方法需要除去前导和尾随空白。 通过使用String.stripLeading()
仅删除开头字符,或者使用String.stripTrailing()
仅删除结尾字符,我们甚至可以更加具体。
public class HelloWorld
{
public static void main(String[] args)
{
" hi ".strip(); //"hi"
" hi ".stripLeading(); //"hi "
" hi ".stripTrailing(); //" hi"
}
}
3.4 String.lines()
此方法有助于将多行文本作为流来处理。
public class HelloWorld
{
public static void main(String[] args)
{
String testString = "hello\nworld\nis\nexecuted";
List<String> lines = new ArrayList<>();
testString.lines().forEach(line -> lines.add(line));
assertEquals(List.of("hello", "world", "is", "executed"), lines);
}
}
4. Collection.toArray(IntFunction)
在 Java 11 之前,将集合转换为数组并不容易。 Java 11 使转换更加方便。
public class HelloWorld
{
public static void main(String[] args)
{
List<String> names = new ArrayList<>();
names.add("alex");
names.add("brian");
names.add("charles");
String[] namesArr1 = names.toArray(new String[names.size()]); //Before Java 11
String[] namesArr2 = names.toArray(String[]::new); //Since Java 11
}
}
5. Files.readString()
和Files.writeString()
使用这些重载方法,Java 11 的目标是减少大量样板代码,从而使文件读写更加容易。
public class HelloWorld
{
public static void main(String[] args)
{
//Read file as string
URI txtFileUri = getClass().getClassLoader().getResource("helloworld.txt").toURI();
String content = Files.readString(Path.of(txtFileUri),Charset.defaultCharset());
//Write string to file
Path tmpFilePath = Path.of(File.createTempFile("tempFile", ".tmp").toURI());
Path returnedFilePath = Files.writeString(tmpFilePath,"Hello World!",
Charset.defaultCharset(), StandardOpenOption.WRITE);
}
}
6. Optional.isEmpty()
Optional
是一个容器对象,可能包含也可能不包含非null
值。 如果不存在任何值,则该对象被认为是空的。
如果存在值,则先前存在的方法isPresent()
返回true
,否则返回false
。 有时,它迫使我们编写不可读的负面条件。
isEmpty()
方法与isPresent()
方法相反,如果存在值,则返回false
,否则返回true
。
因此,在任何情况下我们都不会写负面条件。 适当时使用这两种方法中的任何一种。
public class HelloWorld
{
public static void main(String[] args)
{
String currentTime = null;
assertTrue(!Optional.ofNullable(currentTime).isPresent()); //It's negative condition
assertTrue(Optional.ofNullable(currentTime).isEmpty()); //Write it like this
currentTime = "12:00 PM";
assertFalse(!Optional.ofNullable(currentTime).isPresent()); //It's negative condition
assertFalse(Optional.ofNullable(currentTime).isEmpty()); //Write it like this
}
}
向我提供有关 Java 11 中这些新 API 更改的问题。
学习愉快!
参考: Java 11 发行文档
String.isBlank()
– 在 Java 中检查空白或空字符串
学会使用String.isBlank()
方法确定给定的字符串是空白还是空,或者仅包含空格。isBlank()
方法已添加到 Java 11 中。
要检查给定的字符串是否没有偶数空格,请使用String.isEmpty()
方法。
1. String.isBlank()
方法
如果给定的字符串为空或仅包含空格代码点,则此方法返回true
,否则返回false
。
它使用Character.isWhitespace(char)
方法确定空白字符。
/**
* returns - true if the string is empty or contains only white space codepoints
* - otherwise false
*/
public boolean isBlank()
2. String.isBlank()
示例
检查给定字符串是否为空的 Java 程序。
public class Main
{
public static void main(String[] args)
{
System.out.println( "ABC".isBlank() ); //false
System.out.println( " ABC ".isBlank() ); //false
System.out.println( " ".isBlank() ); //true
System.out.println( "".isBlank() ); //true
}
}
程序输出。
false
false
true
true
3. isBlank()
与isEmpty()
两种方法都用于检查 Java 中的空白或空字符串。 两种方法之间的区别在于,当且仅当字符串长度为 0 时,isEmpty()
方法返回true
。
isBlank()
方法仅检查非空白字符。 它不检查字符串长度。
public class Main
{
public static void main(String[] args)
{
System.out.println( "ABC".isBlank() ); //false
System.out.println( " ".isBlank() ); //true
System.out.println( "ABC".isEmpty() ); //false
System.out.println( " ".isEmpty() ); //false
}
}
程序输出:
false
true
false
false
学习愉快!
String.lines()
– 获取行流 – Java 11
原文: https://howtodoinjava.com/java11/string-to-stream-of-lines/
学习 Java 11 中的使用 String.lines()
方法将多行字符串转换为行。
当我们要从文件中读取内容并分别处理每个字符串时,此方法很有用。
1. String.lines()
API
lines()
方法是静态方法。 它返回从行终止符分隔的给定多行字符串中提取的行流。
/**
* returns - the stream of lines extracted from given string
*/
public Stream<String> lines()
行终止符是以下之一:
- 换行符(
\n
) - 回车符(
\r
) - 回车符后紧跟换行符(
\r\n
)
根据定义,行是零个或多个字符,后接行终止符。 一行不包括行终止符。
lines()
方法返回的流包含此字符串中的行,其顺序与多行中出现的顺序相同。
2. Java 程序获取行流
Java 程序读取文件,并以行流的形式获取内容。
import java.io.IOException;
import java.util.stream.Stream;
public class Main
{
public static void main(String[] args)
{
try
{
String str = "A \n B \n C \n D";
Stream<String> lines = str.lines();
lines.forEach(System.out::println);
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
程序输出。
A
B
C
D
将有关将字符串读入流的行中的问题,提供给我。
学习愉快!
String.repeat()
– 在 Java 中重复字符串 N 次
通过简单的 Java 程序,学习将给定的字符串重复 N 次,以产生包含所有重复的新字符串。 我们将使用方法Sting.repeat(N)
(自 Java 11 起),并使用可用于 Java 10 的正则表达式。
1. String.repeat()
API(自 Java 11 起)
此方法返回一个字符串,该字符串的值是重复count
次的给定字符串的连接。 如果字符串为空或count
为零,则返回空字符串。
1.1 语法
/**
* Parameters:
* count - number of times to repeat
*
* Returns:
* A string composed of this string repeated count times or the empty string if this string is empty or count is zero
*
* Throws:
* IllegalArgumentException - if the count is negative.
*/
public String repeat(int count)
1.2 示例
Java 程序将字符串"Abc"
重复 3 次。
public class Main
{
public static void main(String[] args)
{
String str = "Abc";
System.out.println( str.repeat(3) );
}
}
程序输出。
AbcAbcAbc
2. 使用正则表达式重复字符串(直到 Java 10)
如果您正在使用 JDK < = 10,则可以考虑使用正则表达式将字符串重复 N 次。
Java program to repeat string ‘Abc’ to 3 times.
public class Main
{
public static void main(String[] args)
{
String str = "Abc";
String repeated = new String(new char[3]).replace("\0", str);
System.out.println(repeated);
}
}
程序输出:
AbcAbcAbc
3. Apache Common 的StringUtils
类
如果不是正则表达式,则可以使用StringUtils
类及其方法repeat(times)
。
import org.apache.commons.lang3.StringUtils;
public class Main
{
public static void main(String[] args)
{
String str = "Abc";
String repeated = StringUtils.repeat(str, 3);
System.out.println(repeated);
}
}
程序输出:
AbcAbcAbc
向我提供有关如何在 Java 中将字符串重复 N 次的问题。
学习愉快!
String.strip()
– 删除开头和结尾的空格
原文: https://howtodoinjava.com/java11/strip-remove-white-spaces/
在 Java 11 中,学习如何使用String
类的strip()
,stripLeading()
和stripTrailing()
方法从给定的字符串中删除不需要的空格。
1. String.strip()
API
从 Java 11 开始,String
类包含 3 个以上的方法,这些方法有助于消除多余的空白。 这些方法使用Character.isWhitespace(char)
方法确定空白字符。
String.strip()
– 返回其值为字符串的字符串,其中删除了所有前导和尾随空白。 请注意,String.trim()
方法也会产生相同的结果。String.stripLeading()
– 返回其值为字符串的字符串,其中删除了所有前导空白。String.stripTrailing()
– 返回其值为字符串的字符串,其中删除了所有尾随空白。
public class Main
{
public static void main(String[] args)
{
String str = " Hello World !! ";
System.out.println( str.strip() ); //"Hello World !!"
System.out.println( str.stripLeading() ); //"Hello World !! "
System.out.println( str.stripTrailing() ); //" Hello World !!"
}
}
2. 使用正则表达式修剪空白(包括制表符)
在不使用 Java 11 的情况下,可以使用正则表达式来修剪字符串周围的空白。
正则表达式 | 描述 |
---|---|
[1]+|[\t]+$ |
删除开头和结尾的空格 |
^[\t]+ |
仅删除开头的空格 |
[\t]+$ |
仅删除结尾的空格 |
public class Main
{
public static void main(String[] args)
{
String str = " Hello World !! ";
System.out.println( str.replaceAll("^[ \t]+|[ \t]+$", "") ); //"Hello World !!"
System.out.println( str.replaceAll("^[ \t]+", "") ); //"Hello World !! "
System.out.println( str.replaceAll("[ \t]+$", "") ); //" Hello World !!"
}
}
向我提供有关如何在 Java 中从字符串中删除空格和制表符的问题。
学习愉快!
参考:
文件readString()
API – 将文件读取为 Java 中的字符串
原文: https://howtodoinjava.com/java11/files-readstring-read-file-to-string/
学习使用Files.readString(path)
方法将文件读取为 Java 字符串。 该 API 已在 Java 11 中引入。
1. 文件readString()
方法
java.nio.file.Files
类具有两个重载方法。
public static String readString(Path path) throws IOException
public static String readString(Path path, Charset cs) throws IOException
-
第一种方法将文件中的所有内容读取为字符串,然后使用 UTF-8 字符集将其从字节解码为字符。
该方法可确保在读取所有内容或引发 I/O 错误或其他运行时异常时关闭文件。
-
第一种方法等效于
readString(path, StandardCharsets.UTF_8)
。 -
第二种方法与仅使用指定字符集的方法相同。
-
请注意,这些方法不适用于读取非常大的文件。 否则,如果文件过大(例如,文件大小大于 2GB),它们可能会抛出
OutOfMemoryError
。
2. 文件readString()
示例
Java 程序使用Files.readString()
方法将文件读取为字符串。
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.Files;
import java.io.IOException;
public class Main
{
public static void main(String[] args)
{
Path filePath = Paths.get("C:/", "temp", "test.txt");
try
{
String content = Files.readString(filePath);
System.out.println(content);
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
程序输出。
Hello Java Learner !!
文件c:/temp/test.txt
的位置在这里。
Hello Java Learner !!
将我的问题放在评论部分。
学习愉快!
Java 命名约定
原文: https://howtodoinjava.com/java/basics/java-naming-conventions/
Java 命名约定是应用程序程序员应遵循的准则,以在整个应用程序中产生一致且可读的代码。 如果团队不遵循这些约定,他们可能会集体编写难以阅读和理解的应用程序代码。
Java 大量使用 Camel Case 表示法来命名方法,变量等,并为类和接口使用 TitleCase 表示法。
让我们通过示例详细了解这些命名约定。
1. 包命名约定
套件名称必须是一组以所有小写域名开头的字词(例如com
,org
,net
等)。 根据组织自己的内部命名约定,包名称的后续部分可能会有所不同。
package com.howtodoinjava.webapp.controller;
package com.company.myapplication.web.controller;
package com.google.search.common;
2. 类命名约定
在 Java 中,类名通常应为名词,在标题情况下,每个单独单词的首字母大写。 例如
public class ArrayList {}
public class Employee {}
public class Record {}
public class Identity {}
3. 接口命名约定
在 Java 中,接口名称通常应为形容词。 接口应使用大写字母,每个单独单词的首字母大写。 在相同情况下,当接口提供一类类别时,接口也可以是名词。 List
和Map
。
public interface Serializable {}
public interface Clonable {}
public interface Iterable {}
public interface List {}
4. 方法命名约定
方法始终应为动词。 它们代表一个动作,方法名称应清楚说明它们执行的动作。 为了清楚地表示操作,方法名称可以是单个单词,也可以是 2-3 个单词。 单词应使用驼峰式大写。
public Long getId() {}
public void remove(Object o) {}
public Object update(Object o) {}
public Report getReportById(Long id) {}
public Report getReportByName(String name) {}
5. 变量命名约定
所有实例,静态和方法参数变量名称均应使用驼峰表示法。 它们应该简短,足以描述其目的。 临时变量可以是单个字符,例如循环中的计数器。
public Long id;
public EmployeeDao employeeDao;
private Properties properties;
for (int i = 0; i < list.size(); i++) {
}
6. 常量命名约定
Java 常量应全部为大写,其中单词用下划线字符(_
)分隔。 确保将final
修饰符与常量变量一起使用。
public final String SECURITY_TOKEN = "...";
public final int INITIAL_SIZE = 16;
public final Integer MAX_SIZE = Integer.MAX;
7. 泛型类型命名约定
泛型类型参数名称应为大写单字母。 通常建议使用字母'T'
作为类型。 在 JDK 类中,E
用于收集元素,S
用于服务加载程序,K and V
用于映射键和值。
public interface Map <K,V> {}
public interface List<E> extends Collection<E> {}
Iterator<E> iterator() {}
8. 枚举命名约定
类似于类常量,枚举名称应全部为大写字母。
enum Direction {NORTH, EAST, SOUTH, WEST}
9. 注解命名约定
注解名称遵循标题大小写。 根据要求,它们可以是形容词,动词或名词。
public @interface FunctionalInterface {}
public @interface Deprecated {}
public @interface Documented {}
public @Async Documented {
public @Test Documented {
在本文中,我们讨论了一致的代码编写所遵循的 Java 命名约定,这使代码更具可读性和可维护性。
在使用任何编程语言编写简洁的代码时,命名约定可能是遵循的第一个最佳实践。
学习愉快!
文件writeString()
API – 用 Java 将字符串写入文件
学习使用Files.writeString(path, string, options)
方法将字符串写入 Java 文件中。 该 API 已在 Java 11 中引入。
1. 文件writeString()
方法
java.nio.file.Files
类具有两个重载的静态方法,用于将内容写入文件。
public static Path writeString(Path path, CharSequence csq,
OpenOption... options) throws IOException
public static Path writeString(Path path, CharSequence csq,
Charset cs, OpenOption... options) throws IOException
- 第一种方法使用 UTF-8 字符集将所有内容写入文件。
- 第一种方法等效于
writeString(path, string, StandardCharsets.UTF_8, options)
。 - 第二种方法与仅使用指定字符集的方法相同。
options
指定如何打开文件。
2. 文件writeString()
示例
使用Files.writeString()
方法将String
写入文件的 Java 程序。
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.Files;
import java.io.IOException;
import java.nio.file.StandardOpenOption;
public class Main
{
public static void main(String[] args)
{
Path filePath = Paths.get("C:/", "temp", "test.txt");
try
{
//Write content to file
Files.writeString(filePath, "Hello World !!", StandardOpenOption.APPEND);
//Verify file content
String content = Files.readString(filePath);
System.out.println(content);
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
程序输出。
Hello World !!
文件c:/temp/test.txt
最初为空。
将我的问题放在评论部分。
学习愉快!
Java 10 教程
Java 10 特性和增强特性
在 Java 9 发布之后,Java 10 很快问世。 与以前的版本不同,Java 10 并没有那么多令人兴奋的特性,但仍然没有几个重要的更新会改变您的编码方式以及其他将来的 Java 版本。
Table of Contents
JEP 286: Local Variable Type Inference
JEP 322: Time-Based Release Versioning
JEP 304: Garbage-Collector Interface
JEP 307: Parallel Full GC for G1
JEP 316: Heap Allocation on Alternative Memory Devices
JEP 296: Consolidate the JDK Forest into a Single Repository
JEP 310: Application Class-Data Sharing
JEP 314: Additional Unicode Language-Tag Extensions
JEP 319: Root Certificates
JEP 317: Experimental Java-Based JIT Compiler
JEP 312: Thread-Local Handshakes
JEP 313: Remove the Native-Header Generation Tool
New Added APIs and Options
Removed APIs and Options
JEP 286:局部变量类型推断
Java 现在具有var
样式声明。 它允许您声明局部变量而无需指定其类型。 变量的类型将从创建的实际对象的类型推断出来。 它声称是 JDK 10 中开发人员唯一真正的特性。
var str = "Hello world";
//or
String str = "Hello world";
在上面的示例中,两个语句都是等效的。 在第一句话中,str
的类型由分配类型String
的类型确定。
阅读更多: Java
var
– 局部变量类型推断
JEP 322:基于时间的发行版本控制
从 Java 10 开始,Oracle 调整了基于时间的版本字符串方案。 版本号的新格式为:
$FEATURE.$INTERIM.$UPDATE.$PATCH
与旧版本不同,新的基于时间的版本不会延迟,并且特性将每六个月发布一次,并且对发布中的哪些特性没有限制。
也有长期发行(LTS)。 它主要针对企业客户。 LTS 版本的产品将提供 Oracle 的首要和持续的支持,目标是每三年一次。 此外,这些版本的更新将至少提供三年。
阅读更多: Java 版本 – 基于时间的发行版本控制
JEP 304:垃圾收集器接口
在早期的 JDK 结构中,构成垃圾收集器(GC)实现的组件分散在代码库的各个部分。 它已在 Java 10 中进行了更改。现在,它是 JVM 源代码中的一个干净接口,可以快速轻松地集成替代收集器。 它将改善不同垃圾收集器的源代码隔离。
这纯粹是重构。 之前工作的所有内容都需要事后进行工作,并且性能不应降低。
JEP 307:用于 G1 的并行全 GC
Java 9 引入了 G1(垃圾优先)垃圾收集器。 G1 垃圾收集器的设计避免了完整的收集,但是当并发收集不能足够快地回收内存时。 进行此更改后,将发生后备完整 GC。
G1 的完整 GC 的当前实现使用单线程的 mark-sweep-compact 算法。 此更改将使 mark-sweep-compact 算法并行化,并使用相同数量的线程。 当用于收集的并发线程无法足够快地恢复内存时,将触发该事件。
线程数可以通过-XX:ParallelGCThreads
选项控制。
JEP 316:备用存储设备上的堆分配
进行此更改的目的是使 HotSpot VM 能够在用户指定的备用存储设备(例如 NV-DIMM)上分配 Java 对象堆。
要在此类内存中分配堆,我们可以添加一个新选项-XX:AllocateHeapAt=<path>
。 该选项将采用文件系统的路径,并使用内存映射来实现在存储设备上分配对象堆的预期结果。 现有的与堆相关的标志,例如-Xmx
,-Xms
等,以及与垃圾回收相关的标志将继续像以前一样工作。
JEP 296:将 JDK 森林整合到单个存储库中
作为此更改的一部分,为了简化和简化开发,将 JDK 森林的许多存储库合并到一个存储库中。
在 JDK 9 中,有八个存储库:root
,corba
,hotspot
,jaxp
,jaxws
,jdk
,langtools
和nashorn
。 在统一森林中,通常将 Java 模块的代码合并在单个顶级src
目录下。 例如,今天在 JDK 森林中,存在基于模块的目录,例如
$ROOT/jdk/src/java.base
...
$ROOT/langtools/src/java.compiler
...
在合并林中,此代码改为组织为:
$ROOT/src/java.base
$ROOT/src/java.compiler
...
JEP 310:应用程序类 - 数据共享
此特性的目标是改善启动范围,扩展现有的类数据共享(“ CDS”)特性,以允许将应用程序类放置在共享档案中。
JDK 5 中引入的类数据共享允许将一组类预处理为共享的存档文件,然后可以在运行时对其进行内存映射以减少启动时间。 当多个 JVM 共享同一个存档文件时,它还可以减少动态内存占用。
当前,CDS 仅允许引导类加载器加载归档的类。 应用程序 CDS 允许内置系统类加载器,内置平台类加载器和自定义类加载器加载归档的类。
指定-XX:+UseAppCDS
命令行选项可为系统类加载器,平台类加载器和其他用户定义的类加载器启用类数据共享。
JEP 314:其他 Unicode 语言标签扩展
目的是增强java.util.Locale
和相关 API,以实现 BCP 47 语言标签的其他 Unicode 扩展。 最初在 Java SE 7 中添加了对 BCP 47 语言标签的支持,并且对 Unicode 语言环境扩展的支持仅限于日历和数字。 该 JEP 将在相关的 JDK 类中实现最新的 LDML 规范中指定的更多扩展。
该 JEP 将增加对以下附加扩展的支持:
- cu(货币类型)
- fw(一周的第一天)
- rg(区域替代)
- tz(时区)
修改后的相关 API 为:
java.text.DateFormat::get*Instance
java.text.DateFormatSymbols::getInstance
java.text.DecimalFormatSymbols::getInstance
java.text.NumberFormat::get*Instance
java.time.format.DateTimeFormatter::localizedBy
java.time.format.DateTimeFormatterBuilder::getLocalizedDateTimePattern
java.time.format.DecimalStyle::of
java.time.temporal.WeekFields::of
java.util.Calendar::{getFirstDayOfWeek,getMinimalDaysInWeek}
java.util.Currency::getInstance
java.util.Locale::getDisplayName
java.util.spi.LocaleNameProvider
JEP 319:根证书
cacerts 密钥库是 JDK 的一部分,旨在包含一组根证书,这些根证书可用于建立对各种安全协议中使用的证书链的信任。 但是,JDK 源代码中的 cacerts 密钥库当前为空。
cacerts 密钥库将填充由 Oracle Java SE Root CA Program 的 CA 颁发的一组根证书。 许多供应商已经签署了所需的协议,并且每个供应商都将包含一份根证书列表。 那些没有签署协议的人目前不会包括在内。 下一个版本将包含处理时间较长的内容。
这也意味着两个 Oracle & Open JDK 二进制文件在特性上都相同。 以后,默认的 TLS 等关键安全组件将在 OpenJDK 构建中正常工作。
JEP 317:基于 Java 的实验性 JIT 编译器
此特性使基于 Java 的 JIT 编译器 Graal 可用作 Linux / x64 平台上的实验性 JIT 编译器。 Graal 将使用 JDK 9 中引入的 JVM 编译器接口(JVMCI)。Graal 已经存在于 JDK 中,因此将其作为实验性 JIT 启用将主要是一项测试和调试工作。
要将 Graal 用作 JIT 编译器,请在 Java 命令行上使用以下选项:
-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler
Graal 是从头开始完全覆盖 Java 中的 JIT 编译器。 以前的 JIT 编译器是用 C++编写的。
JEP 312:线程本地握手
通过使无需执行全局 VM 安全点就可以在应用程序线程上执行回调成为可能,该 JEP 为提高 VM 性能奠定了基础。 这意味着 JVM 可以停止单个线程,而不仅仅是所有线程。
线程本地握手将首先在 x64 和 SPARC 上实现。 其他平台将恢复到正常的安全点。 新的产品选项-XX:ThreadLocalHandshakes
(默认值true
)使用户可以在受支持的平台上选择常规安全点。
JEP 313:删除本机头生成工具
它将从 JDK 中删除javah
工具,这是一个单独的工具,可在编译 JNI 代码时生成头文件,因为可以通过javac
来完成。
这是 Java 10 的另一项着重于内务处理的特性。
新增的 API 和选项
Java 10 中添加了 73 个新 API。让我们来看看其中的几个:
API | 描述 |
---|---|
Optional.orElseThrow() |
新方法orElseThrow 已添加到Optional 类。 它与现有的get 方法同义,并且现在是它的首选替代方法。 |
List.copyOf ,Set.copyOf 和Map.copyOf |
这些方法从现有实例创建新的集合实例。 |
Collectors.toUnmodifiableList ,Collectors.toUnmodifiableSet ,Collectors.toUnmodifiableMap |
这些方法允许将Stream 的元素收集到不可修改的集合中 |
--jdk.disableLastUsageTracking |
要为正在运行的 VM 禁用 JRE 上次使用情况跟踪。 |
--add-stylesheet |
在生成的文档中提供对使用多个样式表的支持。 |
--main-stylesheet |
为了帮助将主要样式表与任何其他样式表区分开。 |
@summary |
添加以明确指定用作 API 描述摘要的文本。 默认情况下,从第一句话推断出 API 描述的摘要。 |
删除的 API 和选项
API | 描述 |
---|---|
LookAndFeels |
|
Runtime.getLocalizedInputStream ,Runtime.getLocalizedOutputStream |
已过时的国际化机制的一部分,并且没有已知用途。 |
RMI 服务器端多路复用协议支持 | 它已在 JDK 9 中禁用,现在已被删除。 |
常见的 DOM API | com.sun.java.browser.plugin2.DOM 和sun.plugin.dom.DOMObject API 已被删除。 应用程序可以使用netscape.javascript.JSObject 来操作 DOM。 |
FlatProfiler |
在 JDK 9 中已弃用,已通过删除实现代码来作废。 |
-Xoss ,-Xsqnopause ,-Xoptimize ,-Xboundthreads 和-Xusealtsigs |
选项已删除。 |
policytool |
策略工具安全工具已从 JDK 中删除。 |
com.sun.security.auth.** 中不推荐使用的类 |
下面的类现在已移除:com.sun.security.auth.PolicyFile 、com.sun.security.auth.SolarisNumericGroupPrincipal 、com.sun.security.auth.SolarisNumericUserPrincipal 、com.sun.security.auth.SolarisPrincipal 、com.sun.security.auth.X500Principal 、com.sun.security.auth.module.SolarisLoginModule 、com.sun.security.auth.module.SolarisSystem |
总体而言,Java 10 具有许多我们日常编程中可能不会使用的特性,但是它仍然具有许多在幕后起作用的特性,从而使其成为重要的里程碑。
学习愉快!
Java 版本 – 基于时间的发行版本控制
从 Java 10 开始,Oracle 调整了基于时间的版本字符串方案(JEP 322)。 新的基于时间的模型已取代了过去基于特性的多年发布模型。 与旧版本不同,新的基于时间的版本不会延迟,并且特性将每六个月发布一次,而对于该版本中可以发布的特性没有任何限制。
更新版本将在每个季度(1 月,4 月,7 月,10 月)发布。 更新版本将严格限于安全性问题,回归和较新特性中的错误的修复。 按照进度计划,可以说每个特性版本在下一个特性版本发布之前都会收到两个更新。
Java 版本格式
如果在逗号和提示/终端中运行命令java -version
,您将获得以下输出版本信息:
C:\Users\Lokesh>java -version
java version "10.0.1" 2018-04-17
Java(TM) SE Runtime Environment 18.3 (build 10.0.1+10)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.1+10, mixed mode)
版本号的新格式为:
$FEATURE.$INTERIM.$UPDATE.$PATCH
名称 | 描述 |
---|---|
$FEATURE | 它会每 6 个月增加一次,具体取决于特性发布版本,例如:JDK 10,JDK11。(以前为$MAJOR 。) |
|
$INTERIM | 通常为零,因为六个月内不会有任何中期发布。 对于包含兼容的错误修复和增强特性但不兼容的更改,不删除的特性以及对标准 API 的更改的非特性版本,它将增加。 (以前为$MINOR 。) |
|
$UPDATE | 对于解决安全问题,回归和较新特性中的错误的兼容更新版本,它将递增。 (以前为$SECURITY 。) |
|
$PATCH |
仅在需要紧急发布以解决关键问题时才会增加。 |
版本号中的数字序列与其他此类序列以数字逐点方式进行比较; 例如10.0.4
小于10.1.2
。 如果一个序列比另一个序列短,则认为较短序列的缺失元素少于较长序列的对应元素; 例如10.0.2
小于10.0.2.1
。
Java 版本 API
Runtime.version()
可用于以编程方式获取版本计数器值。 例如
Version version = Runtime.version();
version.feature();
version.interim();
version.update();
version.patch();
Output:
10
0
1
0
解析现有版本
Version version = Runtime.Version.parse("10.0.1");
version.feature();
version.interim();
version.update();
version.patch();
长期发行(LTS)
它主要针对企业客户。 LTS 版本的产品将提供 Oracle 的首要和持续的支持,目标是每三年一次。 此外,这些版本的更新将至少提供三年。
这将导致“LTS”在java –versions
的输出中突出显示。 例如11.0.2+13-LTS
学习愉快!
参考: http://openjdk.java.net/jeps/322
Java var
– 局部变量类型推断
原文: https://howtodoinjava.com/java10/var-local-variable-type-inference/
Java 一直在努力减少语法的冗长性。 首先是菱形运算符,现在是var
(局部变量类型 – JEP 286)在 Java 中声明变量。 当您使用var
声明变量时,基本上,而不是声明变量类型,而是根据设置的类型假定其类型。 例如
var str = "Hello world";
//or
String str = "Hello world";
在上面的示例中,在第一个语句中,您将String
设置为变量str
,因此隐式假定其为String
类型。 在上面的示例中,第一条语句基本上等同于第二条语句。
var
同时声明和初始化
使用var
时,必须在同一位置初始化变量。 您不能将声明和初始化放在不同的位置。 如果未在适当位置初始化变量,则会出现编译错误 – Cannot use 'var' on variable without initializer
。
var i; //Invalid Declaration - - Cannot use 'var' on variable without initializer
var j = 10; //Valid Declaration
System.out.println(i);
var
不是关键字
虽然看起来像var
并不是保留的 Java 关键字。 因此,您可以创建名称为var
的变量,允许使用。
var var = 10; //Valid Declaration
int var = 10; //Also valid Declaration
var
用法
使用var
仅限于具有初始化器的局部变量,增强的for
循环中的索引以及在传统的for
循环中声明的局部变量; 它不适用于方法形式,构造器形式,方法返回类型,字段,catch
形式或任何其他类型的变量声明。
用法如下:
- 带有初始化器的局部变量
- 增强的
for
循环中的索引 - 在传统的
for
循环中声明的局部变量
var blogName = "howtodoinjava.com";
for ( var object : dataList){
System.out.println( object );
}
for ( var i = 0 ; i < dataList.size(); i++ ){
System.out.println( dataList.get(i) );
}
不允许的用法如下:
- 方法参数
- 构造器参数
- 方法返回类型
- 类字段
- 捕获形式(或任何其他类型的变量声明)
public class Application {
//var firstName; //Not allowed as class fields
//public Application(var param){ //Not allowed as parameter
//}
/*try{
} catch(var ex){ //Not allowed as catch formal
}*/
/*public var demoMethod(){ //Not allowed in method return type
return null;
}*/
/*public Integer demoMethod2( var input ){ //Not allowed in method parameters
return null;
}*/
}
var
不向后兼容
由于这是新的语言特性,使用var
编写的代码将不会在较低的 JDK 版本(小于 10)中编译。 因此,只有在确定时才使用此特性。
var
不会影响性能
请记住,在 Java 中,类型不是在运行时推断的,而是在编译时推断的。 这意味着生成的字节码与显式类型声明相同 – 它确实包含有关类型的信息。 这意味着在运行时无需额外的处理。
学习愉快!
Java 9 教程
Java 9 特性和增强特性
原文: https://howtodoinjava.com/java9/java9-new-features-enhancements/
Java 9 带来了许多新的增强特性,它们将在很大程度上影响您的编程风格和习惯。 最大的变化是 Java 的模块化。 这是 Java 8 中的 Lambdas 之后的又一重大变化。 在本文中,我列出了将作为 JDK 9 版本的一部分的更改。
What is new in Java 9
Java platform module system
Interface Private Methods
HTTP 2 Client
JShell - REPL Tool
Platform and JVM Logging
Process API Updates
Collection API Updates
Stream API Improvements
Multi-Release JAR Files
@Deprecated Tag Changes
Stack Walking
Java Docs Updates
Miscellaneous Other Features
Java 平台模块系统
JPMS(Java 平台模块系统)是新 Java 9 版本的核心亮点。 它也被称为 Jigshaw 项目。 模块是新的结构,就像我们已经有了包一样。 使用新的模块化编程开发的应用程序可以看作是交互模块的集合,这些模块之间具有明确定义的边界和依赖性。
JPMS 包括为编写模块化应用程序以及模块化 JDK 源代码提供支持。 JDK 9 随附约 92 个模块(GA 版本中可能会进行更改)。 Java 9 模块系统具有一个“java.base
”模块。 它被称为基本模块。 这是一个独立模块,不依赖于任何其他模块。 默认情况下,所有其他模块都依赖于“java.base
”。
在 Java 模块化编程中:
- 模块通常只是一个 jar 文件,其根目录具有
module-info.class
文件。 - 要使用模块,请将 jar 文件而不是
classpath
包含在modulepath
中。 添加到类路径的模块化 jar 文件是普通的 jar 文件,module-info.class
文件将被忽略。
典型的module-info.java
类如下所示:
module helloworld {
exports com.howtodoinjava.demo;
}
module test {
requires helloworld;
}
阅读更多: Java 9 模块教程
接口私有方法
Java 8 允许您在接口中编写默认方法,这是广受赞赏的特性。 因此,在此之后,接口仅缺少一些东西,只有非私有方法是其中之一。 从 Java 9 开始,您可以在接口中包含私有方法。
这些私有方法将改善接口内部的代码可重用性。 举例来说,如果需要两个默认方法来共享代码,则可以使用私有接口方法来共享代码,但不必将该私有方法暴露给实现类。
在接口中使用私有方法有四个规则:
- 接口私有方法不能是抽象的。
- 私有方法只能在接口内部使用。
- 私有静态方法可以在其他静态和非静态接口方法中使用。
- 私有非静态方法不能在私有静态方法内部使用。
在接口中使用私有方法的示例:
public interface CustomCalculator
{
default int addEvenNumbers(int... nums) {
return add(n -> n % 2 == 0, nums);
}
default int addOddNumbers(int... nums) {
return add(n -> n % 2 != 0, nums);
}
private int add(IntPredicate predicate, int... nums) {
return IntStream.of(nums)
.filter(predicate)
.sum();
}
}
Java 9 – 接口中的私有方法
HTTP/2 客户端
HTTP/1.1 客户端于 1997 年发布。此后发生了很多变化。 因此,对于 Java 9,引入了新的 API,该 API 使用起来更加简洁明了,并且还增加了对 HTTP/2 的支持。 新 API 使用 3 个主要类,即HttpClient
,HttpRequest
和HttpResponse
。
要发出请求,就像获取客户,建立请求并发送它一样简单,如下所示。
HttpClient httpClient = HttpClient.newHttpClient();
HttpRequest httpRequest = HttpRequest.newBuilder().uri(new URI("//howtodoinjava.com/")).GET().build();
HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandler.asString());
System.out.println( httpResponse.body() );
上面的代码看起来更简洁易读。
新 API 还使用httpClient.sendAsync()
方法支持异步 HTTP 请求。 它返回CompletableFuture
对象,该对象可用于确定请求是否已完成。 请求完成后,它还使您可以访问HttpResponse
。 最好的部分是,如果您愿意,甚至可以在请求完成之前取消它。 例如:
if(httpResponse.isDone()) {
System.out.println(httpResponse.get().statusCode());
System.out.println(httpResponse.get().body());
} else {
httpResponse.cancel(true);
}
JShell – REPL 工具
JShell 是 JDK 9 发行版(JEP 222)附带的新命令行交互工具,用于求值用 Java 编写的声明,语句和表达式。 JShell 允许我们执行 Java 代码段并获得即时结果,而无需创建解决方案或项目。
Jshell 非常类似于 linux OS 中的命令窗口。 区别在于 JShell 是 Java 特定的。 除了执行简单的代码片段外,它还有许多其他特性。 例如
- 在单独的窗口中启动内置代码编辑器
- 在单独的窗口中启动您选择的代码编辑器
- 在这些外部编辑器中发生“保存”操作时执行代码
- 从文件系统加载预编写的类
平台和 JVM 日志记录
JDK 9 通过新的日志记录 API 改进了平台类(JDK 类)和 JVM 组件中的日志记录。 它使您可以指定所选的日志记录框架(例如 Log4J2)作为日志记录后端,用于记录来自 JDK 类的消息。 关于此 API,您应该了解几件事:
- 该 API 是由 JDK 中的类而非应用程序类使用的。
- 对于您的应用程序代码,您将像以前一样继续使用其他日志记录 API。
- 该 API 不允许您以编程方式配置记录器。
该 API 包含以下内容:
- 服务接口
java.lang.System.LoggerFinder
,它是抽象的静态类 - 接口
java.lang.System.Logger
,它提供日志记录 API java.lang.System
类中的重载方法getLogger()
,它返回记录器实例。
JDK 9 还添加了一个新的命令行选项-Xlog
,使您可以单点访问从 JVM 所有类记录的所有消息。 以下是使用-Xlog
选项的语法:
-Xlog[:][:[
<output>][:[<decorators>][:<output-options>]]]</output-options></decorators></output>
所有选项都是可选的。 如果缺少-Xlog
中的前一部分,则必须对该部分使用冒号。 例如,-Xlog::stderr
表示所有部件均为默认设置,输出设置为stderr
。
我将在单独的文章中深入讨论该主题。
进程 API 更新
在 Java 5 之前,产生新进程的唯一方法是使用Runtime.getRuntime().exec()
方法。 然后在 Java 5 中,引入了ProcessBuilder
API,该 API 支持一种更干净的方式产生新进程。 现在,Java 9 添加了一种获取有关当前进程和任何衍生进程的信息的新方法。
要获取任何进程的信息,现在应该使用java.lang.ProcessHandle.Info
接口。 此接口在获取大量信息方面很有用,例如
- 用于启动过程的命令
- 命令的参数
- 进程开始的瞬间
- 它和创建它的用户所花费的总时间
ProcessHandle processHandle = ProcessHandle.current();
ProcessHandle.Info processInfo = processHandle.info();
System.out.println( processHandle.getPid() );
System.out.println( processInfo.arguments().isPresent() );
System.out.println( pprocessInfo.command().isPresent() );
System.out.println( processInfo.command().get().contains("java") );
System.out.println( processInfo.startInstant().isPresent() );
要获取新生成的进程的信息,请使用process.toHandle()
方法获取ProcessHandle
实例。 其余的一切都如上所述。
String javaPrompt = ProcessUtils.getJavaCmd().getAbsolutePath();
ProcessBuilder processBuilder = new ProcessBuilder(javaPrompt, "-version");
Process process = processBuilder.inheritIO().start();
ProcessHandle processHandle = process.toHandle();
也可以使用ProcessHandle.allProcesses()
获取系统中所有可用进程的ProcessHandle
流。
要获取所有子进程的列表(直接和深层次的子进程),请使用children()
和descendants()
方法。
Stream<ProcessHandle> children = ProcessHandle.current().children();
Stream<ProcessHandle> descendants = ProcessHandle.current().descendants();
集合 API 更新
从 Java 9 开始,您可以使用新的工厂方法创建不可变集合,例如不可变列表,不可变集合和不可变映射。 例如
import java.util.List;
public class ImmutableCollections
{
public static void main(String[] args)
{
List<String> namesList = List.of("Lokesh", "Amit", "John");
Set<String> namesSet = Set.of("Lokesh", "Amit", "John");
Map<String, String> namesMap = Map.ofEntries(
Map.entry("1", "Lokesh"),
Map.entry("2", "Amit"),
Map.entry("3", "Brian"));
}
}
流 API 的改进
Java 9 引入了两种与流进行交互的新方法,即takeWhile
/dropWhile
方法。 此外,它还添加了两个重载方法,即ofNullable
和iterate
方法。
新方法takeWhile
和dropWhile
允许您基于谓词获取流的一部分。
- 在有序流上,
takeWhile
返回流的“最长前缀”,其元素匹配给定谓词。dropWhile
返回匹配给定谓词的“最长前缀”之后的其余项。 - 在无序流上,
takeWhile
返回流的子集,从流的开头开始,其元素匹配给定谓词(但不是全部)。dropWhile
返回不匹配给定谓词的剩余的流元素。
同样,在 Java 8 之前,流中不能具有null
值。 会导致NullPointerException
。 从 Java 9 开始,Stream.ofNullable()
方法使您可以创建一个单元素流,如果不包含null
,则包装一个值,否则将为空流。 从技术上讲,Stream.ofNullable()
与流条件上下文中的空条件检查非常相似。
多版本 JAR 文件
此增强与将应用程序类打包到 jar 文件中的方式有关。 以前,您必须将所有类打包到一个 jar 文件中,并放到另一个要使用它的应用程序的类路径中。
现在,通过使用多发行版特性,一个 jar 可以包含一个类的不同版本 – 与不同的 JDK 版本兼容。 有关类的不同版本以及通过加载的类应选择哪个类的 JDK 版本的信息存储在MANIFEST.MF
文件中。 在这种情况下,MANIFEST.MF
文件在其主要部分中包含条目Multi-Release: true
。
此外,META-INF
包含一个versions
子目录,该目录的整数子目录(从 Java 9 开始)存储特定于版本的类和资源文件。 例如:
JAR content root
A.class
B.class
C.class
D.class
META-INF
MANIFEST.MF
versions
9
A.class
B.class
假设在 JDK 10 中,A.class
已更新为利用 Java 10 的某些特性,则可以如下所示更新此 Jar 文件:
JAR content root
A.class
B.class
C.class
D.class
META-INF
MANIFEST.MF
versions
9
A.class
B.class
10
A.class
解决具有多种版本的 jar 彼此不兼容的大型应用程序中经常出现的依赖地狱,这看起来确实是很有前途的一步。 此特性可能对解决这些情况有很大帮助。
@Deprecated
的标签更改
从 Java 9 开始,@Deprecated
注解将具有两个属性,即forRemoval
和since
。
forRemoval
– 指示带注释的元素是否在将来的版本中会被删除。since
– 它返回已弃用带注释元素的版本。
强烈建议在文档中使用@deprecated
javadoc 标记说明不赞成使用程序元素的原因。 该文档还应该建议并链接到建议的替代 API(如果适用)。 替换 API 的语义通常会有所不同,因此也应讨论此类问题。
栈的遍历
栈是后进先出(LIFO)数据结构。 在 JVM 级别,栈存储帧。 每次调用方法时,都会创建一个新框架并将其推入栈的顶部。 方法调用完成后,框架将被破坏(从栈中弹出)。 栈上的每个帧都包含其自己的局部变量数组,其自己的操作数栈,返回值以及对当前方法类的运行时常量池的引用。
在给定线程中,任何时候只有一帧处于活动状态。 活动帧称为当前帧,其方法称为当前方法。(阅读更多)
直到 Java 8,StackTraceElement
代表一个栈帧。 要获得完整的栈,您必须使用Thread.getStackTrace()
和Throwable.getStackTrace()
。 它返回了StackTraceElement
数组,您可以对其进行迭代以获取所需的信息。
在 Java 9 中,引入了新类StackWalker
。 该类使用当前线程的栈帧顺序流提供简单有效的栈遍历。 StackWalker
类非常有效,因为它懒惰地求值栈帧。
// Prints the details of all stack frames of the current thread
StackWalker.getInstance().forEach(System.out::println);
您可以使用此信息流执行其他许多操作,我们将在其他一些专用文章中介绍这些内容。
Java 文档更新
Java 9 增强了javadoc
工具以生成 HTML5 标记。 当前,它以 HTML 4.01 生成页面。
为了生成 HTML5 Javadoc,需要在命令行参数中加入参数-html5
。 要在命令行上生成文档,可以运行:
javadoc [options] [packagenames] [sourcefiles] [@files]
使用 HTML5 可以带来更轻松的 HTML5 结构的好处。 它还实现了 WAI-ARIA 标准的可访问性。 这样做的目的是使身体或视觉障碍的人更容易使用屏幕阅读器等工具访问 javadocs 页面。
JEP 225 提供了在 Javadoc 中搜索程序元素以及标记的单词和短语的特性。
以下内容将被索引并可以搜索:
- 声明的模块名称
- 配套
- 类型和成员
- 方法参数类型的简单名称
这是通过新的search.js
Javascript 文件以及在生成 Javadoc 时生成的索引在客户端实现的。 在生成的 HTML5 API 页面上有一个搜索框。
请注意,默认情况下将添加“搜索”选项,但可以使用参数-noindex
将其关闭。
其他特性
Java 9 中还有其他特性,我在这里列出以供快速参考。 我们将在以后的文章中讨论所有这些特性。
- 反应式流 API
- GC(垃圾收集器)改进
- 筛选传入的序列化数据
- 弃用 Applet API
- 字符串化连接
- 增强的方法句柄
- 精简字符串
- Nashorn 的解析器 API
Java 9 计划于 2017/09/21 发行。 对于最新更改,请遵循此链接。
学习愉快!
Java 9 – 精简字符串改进 [JEP 254]
直到 Java 8,Java 中的字符串内部都由char[]
表示。 每个char
都以 2 个字节存储在内存中。 oracle 的 JDK 开发人员分析了许多客户端的应用程序堆转储,他们注意到大多数字符串只能使用 Latin-1 字符集表示。 拉丁 1 个字符可以存储在一个字节中,比char
数据类型存储少 50%(1 个字节)。
因此,JDK 开发人员将String
类的内部存储从char[]
缺省设置为byte[]
。 通常,这导致节省了堆内存的大量空间,因为字符串对象实际上占据了堆内存的很大一部分。(来源)
您可以使用java
命令的-XX:-CompactStrings
参数来控制应用程序中此特性的使用。
Java 9 之前的字符串类
在 Java 9 之前,字符串数据存储为char
数组。 每个字符需要 16 位。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
//The value is used for character storage.
private final char value[];
}
Java 9 之后的字符串类
从 Java 9 开始,现在使用字节数组以及用于编码引用的标志字段在内部表示字符串。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
@Stable
private final byte[] value;
/**
* The identifier of the encoding used to encode the bytes in
* {@code value}. The supported values in this implementation are
*
* LATIN1
* UTF16
*
* @implNote This field is trusted by the VM, and is a subject to
* constant folding if String instance is constant. Overwriting this
* field after construction will cause problems.
*/
private final byte coder;
}
java
命令参考
众所周知,java
命令用于启动 Java 应用程序。 它可以具有许多参数来定制应用程序运行时。 下面是一个这样的命令:
-XX:-CompactStrings
禁用精简字符串特性。 默认情况下,启用此选项。 启用此选项后,内部仅包含单字节字符的 Java 字符串将使用 ISO-8859-1/Latin-1 编码在内部表示并存储为每个字符的单字节字符串。 这将只包含单字节字符的字符串减少了 50% 的空间。 对于包含至少一个多字节字符的 Java 字符串:这些字符串使用 UTF-16 编码表示并存储为每个字符 2 个字节。 禁用精简字符串特性将强制使用 UTF-16 编码作为所有 Java 字符串的内部表示。
禁用精简字符串可能有益的情况包括:
- 当知道应用程序将大量分配多字节字符字符串时
- 在从 Java SE 8 迁移到 Java SE 9 的过程中观察到性能下降的意外事件中,分析表明精简字符串引入了回归。
在这两种情况下,禁用精简字符串都是有意义的。
这纯粹是实现更改,不更改现有的公共接口。
将我的问题放在评论部分。
学习愉快!
Java 模块教程
原文: https://howtodoinjava.com/java9/java-9-modules-tutorial/
JPMS(Java 平台模块系统)是 Java 9 的主要增强特性。它也被称为 Jigsaw 项目。 在此 Java 9 模块示例中,我们将学习有关模块的一般知识以及将来开始编写模块化代码时编程风格的变化。
Table of Contents
What is a Module
Introduction to Java 9 Modules
How to Write Modular Code
Summary
什么是一般模块
在任何编程语言中,模块都是包含代码的(类似包的)工件,元数据描述了模块及其与其他模块的关系。 理想情况下,从编译时一直到运行时都可以识别这些工件。 通常,任何应用程序都是多个模块的组合,这些模块可以一起执行业务目标。
就应用程序架构而言,模块应代表特定的业务能力。 它应具有该特性的自足能力,并且应仅公开使用模块特性的接口。 要完成其任务,它可能依赖于其他模块,应明确声明其他模块。
因此,简而言之,一个模块应遵循三个核心原则:
-
强封装
封装意味着隐藏实现细节,这些细节对于正确使用模块不是必需的。 目的是封装的代码可以自由更改,而不会影响模块的用户。
-
稳定的抽象
抽象有助于使用接口(即公共 API)公开模块特性。 任何时候,您想更改模块代码中的业务逻辑或实现,更改对模块用户都是透明的。
-
显式依赖
模块也可以依赖于其他模块。 这些外部依赖项必须是模块定义本身的一部分。 模块之间的这些依赖关系通常表示为图形。 在应用程序级别查看该图后,您将更好地了解应用程序的架构。
Java 9 模块简介
在 Java 9 之前,您已经拥有“包”来根据业务特性对相关类进行分组。 与包一起,您还具有“访问修饰符”,以控制其他类或包的可见内容和隐藏内容。 到目前为止,它一直运行良好。 Java 对封装和抽象有强大的支持。
但是,显式依赖关系是事情开始崩溃的地方。 在 Java 中,依存关系用import
语句声明; 但严格来说,它们是“编译时”结构。 编译代码后,没有任何机制可以清楚地说明其运行时依赖项。 实际上,Java 运行时相关性解析是一个问题很大的领域,以至于已经创建了专门的工具来解决此问题,例如 gradle 或 maven 。 同样,很少有框架开始捆绑其完整的运行时依赖关系,例如 SpringBoot 项目。
使用新的 Java 9 模块,我们将具有更好的特性来编写结构良好的应用程序。 此增强特性分为两个区域:
- 模块化 JDK 本身。
- 提供一个模块系统供其他应用程序使用。
Java 9 模块系统具有一个“java.base
”模块。 称为“基本模块”。 这是一个独立模块,不依赖于任何其他模块。 默认情况下,所有其他模块都依赖于“java.base
”。
在 Java 9 中,模块可帮助您封装包和管理依赖项。 所以通常
- 类是字段和方法的容器
- 包是类和接口的容器
- 模块是包的容器
如果您不知道要寻找的特定内容,您将不会感觉到普通代码和模块化代码之间的主要区别。 例如:
- 模块通常只是一个 jar 文件,其根目录具有
module-info.class
文件。 - 要使用模块,请将 jar 文件而不是
classpath
包含在modulepath
中。 添加到类路径的模块化 jar 文件是普通的 jar 文件,module-info.class
文件将被忽略。
如何编写模块化代码
阅读以上所有概念后,让我们看看如何在现实中编写模块化代码。 我正在使用 Netbeans IDE,因为它具有对 Java 9 的良好的早期支持(截至今天)。
创建 Java 模块化项目
创建新的模块化项目。 我已经创建了名称JavaAppOne
。
创建 Java 模块化项目
创建 Java 模块化项目 – 步骤 2
创建 Java 模块
现在,在此项目中添加一个或两个模块。
创建新模块
我添加了两个模块helloworld
和test
。 让我们看看他们的代码和项目结构。
Java 9 模块项目结构
/helloworld/module-info.java
module helloworld {
}
HelloWorldApp.java
package com.howtodoinjava.demo;
public class HelloWorldApp {
public static void sayHello() {
System.out.println("Hello from HelloWorldApp");
}
}
/test/module-info.java
module test {
}
TestApp.java
package com.test;
public class TestApp {
public static void main(String[] args) {
//some code
}
}
到目前为止,模块是独立的。 现在假设,我们想在TestApp
类中使用HelloWorldApp.sayHello()
方法。 如果尝试使用该类而不导入模块,则会出现编译时错误“包com.howtodoinjava.demo
包不可见”。
导出包和导入模块
为了能够导入HelloWorldApp
,您必须首先从helloworld
模块中导出“com.howtodoinjava.demo
”包,然后在test
模块中包含helloworld
模块。
module helloworld {
exports com.howtodoinjava.demo;
}
module test {
requires helloworld;
}
在上面的代码中,requires
关键字表示依赖性,exports
关键字标识可导出到其他模块的包。 仅当显式导出包时,才能从其他模块访问它。 默认情况下,无法从其他模块访问模块中未导出的包。
现在您将可以在TestApp
类中使用HelloWorldApp
类。
package com.test;
import com.howtodoinjava.demo.HelloWorldApp;
public class TestApp {
public static void main(String[] args) {
HelloWorldApp.sayHello();
}
}
Output:
Hello from HelloWorldApp
让我们来看一下模块图。
模块图
从 Java 9 开始,“public
”仅对该模块内部的所有其他包表示“public
”。 仅当导出包含“public
”类型的包时,其他模块才能使用它。
总结
模块化应用程序具有许多优点,当您遇到具有非模块化代码库的应用程序时,您会更加欣赏。 您必须听说过诸如“意大利面条架构”或“凌乱的整体”之类的术语。 模块化不是灵丹妙药,但它是一种架构原理,如果正确应用,可以在很大程度上防止这些问题。
借助 JPMS,Java 已迈出了一大步,成为了模块化语言。 决定是对还是错,只有时间会证明一切。 第三方库和框架如何适应和使用模块系统将会很有趣。 以及它如何影响开发工作,我们每天都在做。
学习愉快!
Java 9 – JShell
原文: https://howtodoinjava.com/java9/complete-jshell-tutorial-examples/
JShell 是新的命令行交互式 REPL (读取-求值-打印循环)控制台,随附 JDK 9 发行版(JEP 222)来求值用 Java 编写的声明,语句和表达式。 JShell 允许我们执行 Java 代码段并获得即时结果,而无需创建解决方案或项目。
在本教程中,我们将通过示例学习可以在 JShell 中完成的各种任务。
Table of Contents
1\. Launch JShell
2\. Write and Execute Java Code
3\. Edit Code in JShell Edit Pad
4\. Launch Code in External Editor
5\. Load Code from External Files
1. 启动 JShell
首先是将 JDK 9 安装到您的计算机中。 从此链接下载 JDK 9 并进行安装。
转到安装位置并查看/jdk-9/bin
文件夹。 您可以在这里找到jshell.exe
文件。
JDK 9 中的 JShell 位置
现在启动一个新的命令窗口并检查 Java 版本。
>> java -version
它应该指向 JDK 9 版本。 如果不是,则使用相应的值更新环境属性JAVA_HOME
和PATH
。
JAVA_HOME=C:\Program Files\Java\jdk-9
PATH=C:\Program Files\Java\jdk-9\bin //Path till bin folder
现在再次启动新的命令提示符窗口,然后键入命令jshell
。 它将光标更改为jshell
。
Jshell 启动窗口
恭喜,您已准备好在 JShell REPL (读取-求值-打印循环)中玩游戏。
2. 在 REPL 中编写和执行 Java 代码
Jshell 允许创建小的代码段并对其进行测试,而无需创建和构建复杂的项目。 这就是应该使用它的方式。 在 JShell 上进行操作使其易于使用和快速。 让我们看看如何?
2.1 变量
您可以像在实际编程中一样,定义变量。 唯一的区别是您不必编写类或main
方法即可开始。
jshell> int i = 10;
i ==> 10
要打印变量的值,只需键入变量名称并按ENTER
。 它将打印变量的值。
jshell> i
i ==> 10
要将重新分配给新值,只需按常规方法即可。
jshell> i=20;
i ==> 20
要列出所有声明的变量,请使用命令/vars
。
jshell> /vars
| int i = 20
| int j = 30
在 JShell 中使用变量
2.2 方法
与变量非常相似,方法也很简单。
要在 jshell 中创建方法,定义带有返回类型,方法名称,参数和方法主体的方法。 不需要访问修饰符。
jshell> int sum (int a, int b) {
...> return a+b;
...> }
| created method sum(int,int)
要列出所有定义的方法,请使用命令/methods
。
jshell> /methods
| int sum(int,int)
要调用该方法,请像普通编程一样调用它。
jshell> sum(2,2)
$6 ==> 4
如果要查看方法代码,请使用/list
命令。 它将显示当前方法的源代码。
jshell> /list sum
1 : int sum (int a, int b) {
return a+b;
}
要更改方法代码,您将需要使用相同的方法名称覆盖新的修改后的代码。
jshell> int sum (int a, int b) {
...> int c = a+b;
...> return c;
...> }
| modified method sum(int,int)
jshell> /list sum
3 : int sum (int a, int b) {
int c = a+b;
return c;
}
在 JShell 中使用方法
请记住方法重载规则。 如果您更改了方法参数数量或它们的数据类型,那么它将是一个新方法,并且在 jshell 中将注册两个方法。
3. 在 JShell 编辑板上编辑代码
到那时,您正在处理几行代码,JShell 内联编辑器已经足够了。 但是,当代码开始变大时,则可能需要文件编辑器来修改代码。
在这里您可以使用 JShell 编辑板。 要启动编辑板,请使用/edit
命令和方法名称。
JShell 编辑板
在这里根据需要更改方法代码,然后单击“接受”按钮。 修改后的代码将在 Jshell 中更新,您将在提示符下收到确认消息。 您可以根据需要多次更改代码,保存代码然后退出窗口。
在 Jshell 编辑板中保存操作
4. 在外部编辑器中启动代码
实际上,编辑板足以满足大多数需求,即使您想在任何特定的编辑器上进行编码,也可以使用它。 JShell 允许轻松配置任何外部编辑器来编辑代码段。 您只需要获取我们要使用的编辑器的完整路径,并在 JShell 中运行/set editor
命令来配置编辑器。
/set editor "C:\\Program Files\\Sublime Text 3\\sublime_text.exe"
现在再次执行/edit
命令。 现在它将在崇高编辑器中打开代码。
从 JShell 启动 Sublime 编辑器
随时编辑代码并保存在编辑板中。
5. 将代码从外部 Java 文件加载到 REPL 中
很多时候,您已经在任何 Java 文件中编写了一些代码,并且希望将其执行到 JShell 中。 要在 JShell 中加载文件,请使用/open
命令。
假设我在c://temp
文件夹中有一个文件Demo.java
。 内容是:
int i1 = 10;
int i2 = 20;
int i3 = 30;
int i4 = 40;
int sum(int a, int b) {
return a+b;
}
int sum(int a, int b, int c) {
return a+b;
}
现在,将文件加载到 JShell 中。
/open c:\\temp\\demo.java
验证在 Jshell 中加载的变量和方法。
JShell 中加载的 Java 代码
使用 Java 9 中的 REPL 工具时,您必须了解的一切。
将您的问题放在评论部分中。
学习愉快!
Java 类路径
了解如何将类路径设置为环境变量并作为命令行参数传递。 在任何 Java 应用程序的运行期间,CLASSPATH
是一个告诉 JVM 在何处查找类和包的参数,可以使用环境变量或命令行参数进行设置。
类路径分隔符:
Windows –
;
(分号)Linux / Unix –
:
(冒号)
1. 将 Java 类路径设置为环境变量
当您设置了在应用程序运行期间始终需要的 jar 文件集时,最好将它们添加到计算机的环境变量'CLASSPATH'
中。 在应用程序运行时,应用程序类加载器将始终在此变量的指定路径下扫描 jar 文件和类。
要设置类路径环境变量,请在您的计算机中查找用户变量的位置,并添加存储 Jar 文件的所有路径。 在两个不同的文件夹,jar 文件或类之间使用分隔符。
例如,您可以通过以下方式找到环境变量:
- 在桌面上,右键单击计算机图标。
- 从上下文菜单中选择属性。
- 单击高级系统设置链接。
- 单击环境变量。 在系统变量部分中,找到
CLASSPATH
环境变量并将其选中。 点击编辑。 如果CLASSPATH
环境变量不存在,请单击New
。 - 添加所有用分隔符分隔的文件夹。 单击 OK。 通过单击 OK 关闭所有剩余的窗口。
系统属性
如果是第一次创建CLASSPATH
,则需要在窗口中指定变量名的名称。 使用'.'
(点)表示当前目录。
2. 从命令行设置 Java 类路径
使用-classpath
参数从命令提示符/控制台设置类路径。 使用以下给定的命令来设置不同需求的类路径。 假设我们有一个名为dependency
的文件夹,用于放置 JAR 文件和其他类。
2.1 在类路径中添加单个 jar 文件
下面的语法示例将在类路径中添加单个 jar 文件。
//WINDOWS
$ set CLASSPATH=.;C:\dependency\framework.jar
//Linux/Unix
$ export CLASSPATH=.:/dependency/framework.jar
2.2 在类路径中添加多个 jar 文件
以下语法示例将在类路径中添加多个 jar 文件。 为此,只需将操作系统的分隔符(;
或:
)用作为CLASSPATH
指定的位置之间的分隔符。
要向添加目录中存在的所有 JAR 文件,请使用通配符('*'
)。
//WINDOWS
$ set CLASSPATH=C:\dependency\framework.jar;C:\location\otherFramework.jar
$ set CLASSPATH=C:\dependency\framework.jar;C:\location\*.jar
//Linux/Unix
$ export CLASSPATH=/dependency/framework.jar:/location/otherFramework.jar
$ export CLASSPATH=/dependency\framework.jar:/location/*.jar
2.3 将类添加到类路径
很多时候,您可能还需要在classpath
中添加单个类。 为此,只需添加存在类文件的文件夹。 例如假设location
文件夹中存在五个.class
文件,您希望将它们包括在类路径中。
//WINDOWS
$ set CLASSPATH=C:\dependency\*;C:\location
//Linux/Unix
$ export CLASSPATH=/dependency/*:/location
最佳做法是,始终将所有 JAR 文件和应用程序类组织在一个根文件夹中。 这可能是应用程序的工作空间。
请注意,CLASSPATH
中包含的子目录不会被加载。 为了加载子目录中包含的文件,必须在CLASSPATH
中显式列出这些目录和/或文件。
3. 使用-classpath
参数执行 Java 程序
除了将classpath
设置为环境变量之外,您还可以在使用 – classpath
参数启动应用程序时将其他classpath
传递给 Java 运行时。
$ javac – classpath C:\dependency\framework.jar MyApp.Java
$ java – classpath C:\dependency\framework.jar MyApp
4. 如何检查类路径
每当您希望验证CLASSPATH
变量中的所有路径条目时,都可以使用echo
命令进行验证。
//Windows
c:/> echo %CLASSPATH%
//Linux/Unix
$ echo $CLASSPATH
如果未设置CLASSPATH
,则控制台将显示“CLASSPATH
:未定义的变量”错误(Solaris 或 Linux),或仅在 Windows 命令提示符中打印%CLASSPATH%
。
学习愉快!
阅读更多:
Java – 日期流
原文: https://howtodoinjava.com/java9/stream-dates-datesuntil/
对于 Java 开发人员而言,日期和时间处理一直是一个痛苦的领域。 Java 8 中添加的新的 Date-Time API 更改了在 Java 中与日期进行交互的方式。 这是一项非常强大且急需的改进。 唯一缺少的是获得日期的流,在两个后续日期之间存在一些共同的差异(尽管有可能,但没有简便的方法)。
Java 9 引入了一种新方法LocalDate.datesUntil()
,该方法可以提供日期流。 使用datesUntil()
可以轻松创建具有固定偏移量的日期流。
1. LocalDate.datesUntil()
的语法
此方法有两种重载形式:
Stream<LocalDate> datesUntil(LocalDate end)
Stream<LocalDate> datesUntil(LocalDate end, Period step)
第一个版本(即不带Period
)内部调用带有Period.ofDays(1)
的第二个方法,并生成日期流,两者之间相差 1 天。
使用LocalDate.datesUntil()
的日期流示例
创建日期流非常简单明了。
import java.time.LocalDate;
import java.time.Period;
import java.util.List;
import java.util.stream.Collectors;
public class Java9StreamExamples {
public static void main(String[] args) {
System.out.println( getDaysInJava9(LocalDate.now(), LocalDate.now().plusDays(10)) );
System.out.println( getDaysInJava9Weeks(LocalDate.now(), LocalDate.now().plusWeeks(10)) );
}
//Stream of dates with 1 day difference
public static List<LocalDate> getDaysInJava9(LocalDate start, LocalDate end) {
return start.datesUntil(end).collect(Collectors.toList());
}
//Stream of dates with 1 week difference
public static List<LocalDate> getDaysInJava9Weeks(LocalDate start, LocalDate end) {
return start.datesUntil(end, Period.ofWeeks(1)).collect(Collectors.toList());
}
}
Output:
[2017-07-31, 2017-08-01, 2017-08-02, 2017-08-03, 2017-08-04,
2017-08-05, 2017-08-06, 2017-08-07, 2017-08-08, 2017-08-09]
[2017-07-31, 2017-08-07, 2017-08-14, 2017-08-21, 2017-08-28,
2017-09-04, 2017-09-11, 2017-09-18, 2017-09-25, 2017-10-02]
2. Java 8 中的日期流
如果您仍未使用 Java 9,则可以使用以下给定的方法生成 Date 流。 该代码与 Java 8 兼容。
import java.time.LocalDate;
import java.time.Period;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Java9StreamExamples {
public static void main(String[] args)
{
System.out.println( getDaysInJava8(LocalDate.now(), 10) );
}
//Stream of dates with 1 day difference
public static List<LocalDate> getDaysInJava8(LocalDate start, int days)
{
return Stream.iterate(start, date -> date.plusDays(1))
.limit(days)
.collect(Collectors.toList());
}
}
Output:
[2017-07-31, 2017-08-01, 2017-08-02, 2017-08-03, 2017-08-04,
2017-08-05, 2017-08-06, 2017-08-07, 2017-08-08, 2017-08-09]
将我的问题放在评论部分。
学习愉快!
Java 9 Stream
API 的改进
原文: https://howtodoinjava.com/java9/stream-api-improvements/
通过示例了解流 API(即takeWhile
/dropWhile
方法,ofNullable
和iterate
方法)中 Java 9 的新改进。
Table of Contents
Limiting Stream with takeWhile() and dropWhile() methods
Overloaded Stream iterate method
New Stream ofNullable() method
使用takeWhile()
和dropWhile()
方法限制Stream
新方法takeWhile
和dropWhile
允许您基于谓词获取流的一部分。 这里的流可以是有序的也可以是无序的,所以:
- 在有序流上,
takeWhile
返回流的“最长前缀”,其元素匹配给定谓词。 - 在无序流上,
takeWhile
返回流的子集,从流的开头开始,其元素匹配给定谓词(但不是全部)。
dropWhile
方法与takeWhile
方法相反。
- 在有序流上,
dropWhile
返回匹配给定谓词的“最长前缀”之后的其余项。 - 在无序流上,
dropWhile
返回不匹配给定谓词的剩余的流元素。
takeWhile
和dropWhile
示例
在此示例中,我们具有从a
到i
的字符列表。 我希望所有可能在迭代中出现在字符d
之前的字符。
List<String> alphabets = List.of("a", "b", "c", "d", "e", "f", "g", "h", "i");
List<String> subset1 = alphabets
.stream()
.takeWhile(s -> !s.equals("d"))
.collect(Collectors.toList());
System.out.println(subset1);
Output:
[a, b, c]
如前所述,dropWhile
的作用与takeWhile
方法相反,因此在上面的示例中,如果使用dropWhile
,它将返回takeWhile
谓词留下的所有字符。
List<String> alphabets = List.of("a", "b", "c", "d", "e", "f", "g", "h", "i");
List<String> subset2 = alphabets
.stream()
.dropWhile(s -> !s.equals("d"))
.collect(Collectors.toList());
System.out.println(subset2);
Output:
[d, e, f, g, h, i]
重载Stream
迭代方法
iterate()
方法用于创建以单个元素(种子)开头的流,并通过依次应用一元运算符来生成后续元素。 结果是无限的流。 为了终止流,使用限制或其他一些短路函数,例如findFirst
或findAny
。
Java 8 中的iterate
方法具有签名:
static Stream iterate(final T seed, final UnaryOperator f)
在 Java 9 中,新的iterate
重载版本将谓词作为第二个参数:
static Stream iterate(T seed, Predicate super T> hasNext, UnaryOperator next)
让我们看看从 Java 8 到 Java 9 的iterate
方法的使用不同。
Java 8 中的迭代方法
List<Integer> numbers = Stream.iterate(1, i -> i+1)
.limit(10)
.collect(Collectors.toList());
System.out.println(numbers);
Output:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Java 9 中的迭代方法
List<Integer> numbers = Stream.iterate(1, i -> i <= 10 ,i -> i+1)
.collect(Collectors.toList());
System.out.println(numbers);
Output:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
在上面的示例中,第一个流是 Java 8 使用限制迭代的方式。 第二个使用谓词作为第二个参数。
新的Stream ofNullable()
方法
在 Java 8 之前,流中不能具有null
值。 会导致NullPointerException
。
在 Java 9 中,ofNullable
方法使您可以创建单元素流,该流将包装一个值(如果不为null
),否则为空流。
Stream<String> stream = Stream.ofNullable("123");
System.out.println(stream.count());
stream = Stream.ofNullable(null);
System.out.println(stream.count());
Output:
1
0
此处,count
方法返回流中非空元素的数量。
从技术上说,Stream.ofNullable()
与流条件上下文中的空条件检查非常相似。
将我的问题放在评论部分。
学习愉快!
Java 9 中的不可变集合和工厂方法
原文: https://howtodoinjava.com/java9/create-immutable-collections-factory-methods/
学习使用新的创建不可变集合,例如不可变列表,不可变集和不可变映射 Java 9 中的工厂方法。
Table of Contents
Create Immutable List
Create Immutable Set
Create Immutable Map
创建不可变列表
使用List.of()
静态工厂方法创建不可变列表。 它具有以下不同的重载版本:
static <E> List<E> of()
static <E> List<E> of(E e1)
static <E> List<E> of(E e1, E e2)
static <E> List<E> of(E e1, E e2, E e3)
static <E> List<E> of(E e1, E e2, E e3, E e4)
static <E> List<E> of(E e1, E e2, E e3, E e4, E e5)
static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6)
static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7)
static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8)
static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9)
static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10)
//varargs
static <E> List<E> of(E... elements)
这些方法创建的List
实例具有以下特征:
- 这些列表是不可变的。 不能在这些列表中添加,删除或替换元素。 调用任何可变方法(即
add
,addAll
,clear
,remove
,removeAll
,replaceAll
)将始终导致抛出UnsupportedOperationException
。 - 它们不允许
null
元素。 尝试添加null
元素将导致NullPointerException
。 - 如果所有元素都是可序列化的,它们可以可序列化的。
- 列表中元素的顺序与提供的参数或提供的数组中的元素的顺序相同。
让我们看一些使用不可变列表的例子。
package com.howtodoinjava;
import java.util.List;
public class ImmutableCollections
{
public static void main(String[] args)
{
List<String> names = List.of("Lokesh", "Amit", "John");
//Preserve the elements order
System.out.println(names);
//names.add("Brian"); //UnsupportedOperationException occured
//java.lang.NullPointerException
//List<String> names2 = List.of("Lokesh", "Amit", "John", null);
}
}
Output:
[Lokesh, Amit, John]
创建不可变集
Set
的行为与List
非常相似,只是差异很小。 例如:
Set
也不允许重复元素。 传递的任何重复元素将导致IllegalArgumentException
。- 集合元素的迭代顺序未指定,可能会发生变化。
所有Set
工厂方法都具有与List
相同的签名。
static <E> Set<E> of()
static <E> Set<E> of(E e1)
static <E> Set<E> of(E e1, E e2)
static <E> Set<E> of(E e1, E e2, E e3)
static <E> Set<E> of(E e1, E e2, E e3, E e4)
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5)
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6)
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7)
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8)
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9)
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10)
//varargs
static <E> Set<E> of(E... elements)
我们来看几个不可变集的例子。
import java.util.Set;
public class ImmutableCollections {
public static void main(String[] args) {
Set<String> names = Set.of("Lokesh", "Amit", "John");
//Elements order not fixed
System.out.println(names);
//names.add("Brian"); //UnsupportedOperationException occured
//java.lang.NullPointerException
//Set<String> names2 = Set.of("Lokesh", "Amit", "John", null);
//java.lang.IllegalArgumentException
//Set<String> names3 = Set.of("Lokesh", "Amit", "John", "Amit");
}
}
创建不可变映射
Map
工厂方法与List
或Set
重载工厂方法相同。 唯一的区别是方法的签名采用交替的键和值作为参数。 例如:
static <K,V> Map<K,V> of()
static <K,V> Map<K,V> of(K k1, V v1)
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2)
...
...
static <K,V> Map<K,V> ofEntries(Map.Entry<? extends K,? extends V>... entries)
Java 9 还提供了一种特殊的方法来创建 Map 条目实例。
static <K,V> Map.Entry<K,V> entry(K k, V v)
让我们举一个在 Java 9 中创建不可变Map
的示例。
import java.util.Map;
public class ImmutableCollections {
public static void main(String[] args) {
Map<String, String> names = Map.ofEntries(
Map.entry("1", "Lokesh"),
Map.entry("2", "Amit"),
Map.entry("3", "Brian"));
System.out.println(names);
//UnsupportedOperationException
//names.put("2", "Ravi");
}
}
Output:
{1=Lokesh, 2=Amit, 3=Brian}
显然,在 Java 9 中创建不可变集合的新工厂方法具有很高的可读性和易用性。
请在评论部分中将您对这些不可变集合的观点放下。
学习愉快!
接口中的私有方法 – Java 9
原文: https://howtodoinjava.com/java9/java9-private-interface-methods/
从 Java 9 开始,您可以在接口中包含私有方法。 使用私有方法,现在也可以在接口中实现封装。
在此 Java 9 教程中,我们将详细了解接口私有方法。
Table of Contents
Interfaces till Java 7
Static and defaults methods since Java 8
Private methods since java 9
Java 9 Private Interface Method Example
Summary
Java 7 接口
在 Java 7 和所有早期版本中,接口非常简单。 它们只能包含公共抽象方法。 这些接口方法必须由选择实现接口的类实现。
public interface CustomInterface {
public abstract void method();
}
public class CustomClass implements CustomInterface {
@Override
public void method() {
System.out.println("Hello World");
}
public static void main(String[] args){
CustomInterface instance = new CustomClass();
instance.method();
}
}
Output:
Hello World
自 Java 8 以来的静态方法和默认方法
在 Java 8 中,除了公共抽象方法之外,您还可以使用公共静态方法和公共默认方法。
public interface CustomInterface {
public abstract void method1();
public default void method2() {
System.out.println("default method");
}
public static void method3() {
System.out.println("static method");
}
}
public class CustomClass implements CustomInterface {
@Override
public void method1() {
System.out.println("abstract method");
}
public static void main(String[] args){
CustomInterface instance = new CustomClass();
instance.method1();
instance.method2();
CustomInterface.method3();
}
}
Output:
abstract method
default method
static method
访问修饰符public
在以上所有接口方法声明中都是可选的。 我添加它们只是为了提高可读性。
自 Java 9 以来的私有方法
从 Java 9 开始,您将可以在接口中添加私有方法和私有静态方法。
这些私有方法将改善接口内部的代码可重用性。 举例来说,如果需要两个默认方法来共享代码,则可以使用私有接口方法来共享代码,但不必将该私有方法暴露给实现类。
在接口中使用私有方法有四个规则:
- 接口私有方法不能是抽象的。
- 私有方法只能在接口内部使用。
- 私有静态方法可以在其他静态和非静态接口方法中使用。
- 私有非静态方法不能在私有静态方法内部使用。
public interface CustomInterface {
public abstract void method1();
public default void method2() {
method4(); //private method inside default method
method5(); //static method inside other non-static method
System.out.println("default method");
}
public static void method3() {
method5(); //static method inside other static method
System.out.println("static method");
}
private void method4(){
System.out.println("private method");
}
private static void method5(){
System.out.println("private static method");
}
}
public class CustomClass implements CustomInterface {
@Override
public void method1() {
System.out.println("abstract method");
}
public static void main(String[] args){
CustomInterface instance = new CustomClass();
instance.method1();
instance.method2();
CustomInterface.method3();
}
}
Output:
abstract method
private method
private static method
default method
private static method
static method
Java 9 接口私有方法示例
让我们看一个演示,以了解接口私有方法的用法。
我正在创建具有两个函数的计算器类。 第一个函数将接受一些整数并将所有偶数相加。 第二个函数将接受一些整数并将所有奇数相加。
CustomCalculator.java
– 接口
import java.util.function.IntPredicate;
import java.util.stream.IntStream;
public interface CustomCalculator
{
default int addEvenNumbers(int... nums) {
return add(n -> n % 2 == 0, nums);
}
default int addOddNumbers(int... nums) {
return add(n -> n % 2 != 0, nums);
}
private int add(IntPredicate predicate, int... nums) {
return IntStream.of(nums)
.filter(predicate)
.sum();
}
}
Main.java
– 类
public class Main implements CustomCalculator {
public static void main(String[] args) {
CustomCalculator demo = new Main();
int sumOfEvens = demo.addEvenNumbers(1,2,3,4,5,6,7,8,9);
System.out.println(sumOfEvens);
int sumOfOdds = demo.addOddNumbers(1,2,3,4,5,6,7,8,9);
System.out.println(sumOfOdds);
}
}
Output:
20
25
总结
简而言之, java 9 私有接口方法可以是静态的也可以是实例的。 在这两种情况下,私有方法都不会被子接口或实现继承。 它们主要是为了提高代码仅在接口内的可重用性,从而改善封装。
让我们回顾一下 Java 9 中所有允许的方法类型。
方法类型 | 从何时起 |
---|---|
公共抽象 | Java 7 |
公共默认 | Java 8 |
公共静态 | Java 8 |
私有 | Java 9 |
私有静态 | Java 9 |
将您的问题放在评论部分中。
学习愉快!
参考: JEP 213
Java 8 教程
Java 8 教程
此 Java 8 教程列出了重要的 Java 8 特性,并提供了此发行版中引入的示例。 所有特性均具有指向详细教程的链接,例如 lambda 表达式,Java 流,函数式接口和日期时间 API 更改。
Java SE 8 是于 2014 年初发布的。 在 Java 8 中,最受关注的特性是 lambda 表达式。 它还具有许多其他重要特性,例如默认方法,流 API 和新的日期/时间 API。 让我们通过示例了解 Java 8 中的这些新特性。
Table of Contents
1\. Lambda Expression
2\. Functional Interface
3\. Default Methods
4\. Streams
5\. Date/Time API Changes
1. Lambda 表达式
Lambda 表达式对于使用 Scala 等其他流行编程语言的我们很多人并不陌生。 在 Java 编程语言中,Lambda 表达式(或函数)只是匿名函数,即不带名称且不受标识符限制的函数。 它们准确地写在需要的地方,通常以作为其他函数的参数。
Lambda 表达式的基本语法为:
either
(parameters) -> expression
or
(parameters) -> { statements; }
or
() -> expression
典型的 lambda 表达式示例如下所示:
(x, y) -> x + y //This function takes two parameters and return their sum.
请注意,根据x
和y
的类型,方法可能会在多个地方使用。 参数可以匹配int
或Integer
或简单地也可以匹配String
。 根据上下文,它将添加两个整数或连接两个字符串。
编写 Lambda 表达式的规则
- Lambda 表达式可以具有零个,一个或多个参数。
- 参数的类型可以显式声明,也可以从上下文中推断出来。
- 多个参数用强制括号括起来,并用逗号分隔。 空括号用于表示空参数集。
- 当有单个参数时,如果推断出其类型,则不强制使用括号。 例如
a -> a * a
。 - Lambda 表达式的主体可以包含零个,一个或多个语句。
- 如果 lambda 表达式的主体具有单个语句,则不必使用大括号,并且匿名函数的返回类型与主体表达式的返回类型相同。 如果正文中的语句多于一个,则这些语句必须用大括号括起来。
阅读更多: Java 8 Lambda 表达式教程
2. 函数式接口
函数式接口也称为单一抽象方法接口(SAM 接口)。 顾名思义,它们允许其中恰好是一种抽象方法。 Java 8 引入了一个注解,即@FunctionalInterface
,当您注解的接口违反函数式接口的约定时,该注解可用于编译器级错误。
典型的函数式接口示例:
@FunctionalInterface
public interface MyFirstFunctionalInterface {
public void firstWork();
}
请注意,即使省略@FunctionalInterface
注释,函数式接口也有效。 它仅用于通知编译器在接口内部强制执行单个抽象方法。
另外,由于默认方法不是抽象的,因此可以随意向函数式接口添加任意数量的默认方法。
要记住的另一个重要点是,如果接口声明了一个覆盖java.lang.Object
的公共方法之一的抽象方法,那么该接口的任何实现都将具有java.lang.Object
的实现,因此这也不会计入接口的抽象方法数量。 例如,下面是完全有效的函数式接口。
@FunctionalInterface
public interface MyFirstFunctionalInterface
{
public void firstWork();
@Override
public String toString(); //Overridden from Object class
@Override
public boolean equals(Object obj); //Overridden from Object class
}
阅读更多: Java 8 函数式接口教程
3. 默认方法
Java 8 允许您在接口中添加非抽象方法。 这些方法必须声明为默认方法。 Java 8 中引入了默认方法以启用 lambda 表达式的特性。
默认方法使您可以向库的接口添加新特性,并确保与为这些接口的较早版本编写的代码二进制兼容。
让我们看一个例子:
public interface Moveable {
default void move(){
System.out.println("I am moving");
}
}
Moveable
接口定义了一种方法move()
,并提供了默认实现。 如果有任何类实现了此接口,则无需实现其自己的move()
方法版本。 它可以直接调用instance.move()
。 例如:
public class Animal implements Moveable{
public static void main(String[] args){
Animal tiger = new Animal();
tiger.move();
}
}
Output: I am moving
如果类愿意自定义move()
方法的行为,则它可以提供自己的自定义实现并覆盖该方法。
重做更多: Java 8 默认方法教程
4. Java 8 流
引入的另一项重大更改是 Java 8 流 API ,它提供了一种以各种方式处理一组数据的机制,这些方式可以包括过滤,转换或任何其他可能对应用程序有用的方式。
Java 8 中的 流 API 支持不同类型的迭代,您可以在其中简单地定义要处理的项目集,对每个项目执行的操作,以及存储这些操作的输出。
流 API 的示例。 在此示例中,items
是String
值的集合,您想删除以某些前缀文本开头的条目。
List<String> items;
String prefix;
List<String> filteredList = items.stream().filter(e -> (!e.startsWith(prefix))).collect(Collectors.toList());
这里items.stream()
表示我们希望使用 流 API 处理items
集合中的数据。
阅读更多: Java 8 内部和外部迭代
5. Java 8 日期/时间 API 更改
新的日期和时间 API /类(JSR-310),也称为 ThreeTen ,它们仅改变了您在 Java 应用程序中处理日期的方式。
日期
Date
类甚至已过时。 打算替换Date
类的新类是LocalDate
,LocalTime
和LocalDateTime
。
LocalDate
类表示日期。 没有时间或时区的表示。LocalTime
类表示时间。 没有日期或时区的表示。LocalDateTime
类表示日期时间。 没有时区的表示。
如果您想将日期特性与区域信息一起使用,则 Lambda 会为您提供额外的 3 类,类似于上面的一种,即OffsetDate
,OffsetTime
和OffsetDateTime
。 时区偏移可以以“+05:30
”或“Europe/Paris
”格式表示。 这是通过使用另一个类即ZoneId
完成的。
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.of(12, 20);
LocalDateTime localDateTime = LocalDateTime.now();
OffsetDateTime offsetDateTime = OffsetDateTime.now();
ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of("Europe/Paris"));
时间戳和持续时间
为了随时表示特定的时间戳,需要使用的类是Instant
。 Instant
类表示精确到纳秒的时间瞬间。 即时操作包括与另一个Instant
进行比较,以及增加或减少持续时间。
Instant instant = Instant.now();
Instant instant1 = instant.plus(Duration.ofMillis(5000));
Instant instant2 = instant.minus(Duration.ofMillis(5000));
Instant instant3 = instant.minusSeconds(10);
Duration
类是用 Java 语言首次带来的全新概念。 它表示两个时间戳之间的差异。
Duration duration = Duration.ofMillis(5000);
duration = Duration.ofSeconds(60);
duration = Duration.ofMinutes(10);
Duration
处理较小的时间单位,例如毫秒,秒,分钟和小时。 它们更适合与应用程序代码进行交互。 要与人互动,您需要使持续时间更长,该类与Period
类一起提供。
Period period = Period.ofDays(6);
period = Period.ofMonths(6);
period = Period.between(LocalDate.now(), LocalDate.now().plusDays(60));
阅读更多: Java 8 日期和时间 API 更改
在注释部分中,将您对此 Java 8 教程的问题留给我。
学习愉快!
Java 8 forEach
Java forEach
是一种工具方法,可迭代诸如(列表,集合或映射)和流之类的集合,并对它的每个元素执行特定操作。
1. Java 8 forEach
方法
1.1 Iterable.forEach()
下面的代码片段显示了Iterable
接口中forEach
的默认实现。 它使Iterable.forEach()
方法可用于除Map
之外的所有集合类。 下一节将讨论Map
中的 forEach()
方法。
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
上面的方法对Iterable
的每个元素执行给定的操作,直到所有元素都已处理或该操作引发异常。
action
表示接受单个输入参数且不返回结果的操作。 它是Consumer
接口的实例。
List<String> names = Arrays.asList("Alex", "Brian", "Charles");
names.forEach(System.out::println);
//Console output
Alex
Brian
Charles
可以使用此简单语法创建自定义消费者操作。 这里Object
类型应替换为集合或流中元素的类型。
List<String> names = Arrays.asList("Alex", "Brian", "Charles");
Consumer<String> makeUpperCase = new Consumer<String>()
{
@Override
public void accept(String t)
{
System.out.println(t.toUpperCase());
}
};
names.forEach(makeUpperCase);
//Console output
ALEX
BRIAN
CHARLES
1.2 Map.forEach()
此方法对此映射中的每个条目执行给定的 BiConsumer
操作,直到已处理所有条目或该操作引发异常。
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
Map<String, String> map = new HashMap<String, String>();
map.put("A", "Alex");
map.put("B", "Brian");
map.put("C", "Charles");
map.forEach((k, v) ->
System.out.println("Key = " + k + ", Value = " + v));
//Console Output
Key = A, Value = Alex
Key = B, Value = Brian
Key = C, Value = Charles
与List
示例类似,我们可以创建自定义双消费者操作,该操作从映射中获取键值对,然后一次处理每个条目。
BiConsumer<String, Integer> action = (a, b) ->
{
System.out.println("Key is : " + a);
System.out.println("Value is : " + b);
};
Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);
map.forEach(action);
程序输出。
Key is : A
Value is : 1
Key is : B
Value is : 2
Key is : C
Value is : 3
2. 使用List
的forEach
示例
Java 程序迭代流的所有元素并执行操作。 在此示例中,我们将打印所有偶数。
List<Integer> numberList = Arrays.asList(1,2,3,4,5);
Consumer<Integer> action = System.out::println;
numberList.stream()
.filter(n -> n%2 == 0)
.forEach( action );
程序输出:
2
4
3. 使用Map
的forEach
示例
我们已经看到上面的程序遍历HashMap
的所有条目并执行操作。
我们还可以遍历映射键和值,并对所有元素执行任何操作。
HashMap<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);
//1\. Map entries
Consumer<Map.Entry<String, Integer>> action = System.out::println;
map.entrySet().forEach(action);
//2\. Map keys
Consumer<String> actionOnKeys = System.out::println;
map.keySet().forEach(actionOnKeys);
//3\. Map values
Consumer<Integer> actionOnValues = System.out::println;
map.values().forEach(actionOnValues);
程序输出:
A=1
B=2
C=3
A
B
C
1
2
3
学习愉快!
Java 8 流 API
原文: https://howtodoinjava.com/java8/java-streams-by-examples/
可以将 Java 中的流定义为源中支持其聚合操作的元素序列。 此处的来源是指向流提供数据的集合或数组。
流保持数据在源中的顺序。 聚合操作或批量操作是使我们能够轻松,清晰地表达对流元素的常用操作的操作。
Table of Contents
1\. Streams vs. Collections
2\. Different ways to create streams
3\. Converting streams to collections
4\. Core stream operations
4.1\. Intermediate operations
4.2\. Terminal operations
5\. Short-circuit operations
6\. Parallelism
在继续之前,重要的是要了解 Java 8 流的设计方式是大多数流操作仅返回流。 这有助于我们创建流操作链。 这称为管道内衬。 在这篇文章中,我将多次使用该术语,因此请牢记。
1. Java 流与集合
我们所有人都已经在 youtube 或其他此类网站上观看了在线视频。 当您开始观看视频时,文件的一小部分首先会加载到计算机中并开始播放。 开始播放之前,您无需下载完整的视频。 这称为流式传输。 我将尝试将此概念与集合相关联,并通过流进行区分。
在基本级别上,集合和流之间的差异与计算事物时有关。 集合是内存中的数据结构,它保存该数据结构当前具有的所有值-集合中的每个元素必须先计算,然后才能添加到集合中。 流是概念上固定的数据结构,其中元素按需计算。 这带来了显着的编程优势。 想法是,用户将仅从流中提取他们需要的值,并且仅在需要时才对用户无形地生成这些元素。 这是生产者-消费者关系的一种形式。
在 java 中,java.util.Stream
表示可以在其上执行一个或多个操作的流。 流操作是中间或终止。 尽管终止操作返回某个类型的结果,但是中间操作返回流本身,因此您可以连续链接多个方法调用。 流是在源上创建的,例如类似于列表或集合的java.util.Collection
(不支持映射)。 流操作可以顺序执行,也可以并行执行。
基于以上几点,如果我们列出Stream
的各种特征,它们将如下所示:
- 不是数据结构
- 专为 lambdas 设计
- 不支持索引访问
- 可以轻松输出为数组或列表
- 支持延迟访问
- 可并行化
2. 创建流的不同方法
以下是从集合构建流的最流行的不同方法。
2.1 Stream.of(val1, val2, val3 ....)
public class StreamBuilders
{
public static void main(String[] args)
{
Stream<Integer> stream = Stream.of(1,2,3,4,5,6,7,8,9);
stream.forEach(p -> System.out.println(p));
}
}
2.2 Stream.of(arrayOfElements)
public class StreamBuilders
{
public static void main(String[] args)
{
Stream<Integer> stream = Stream.of( new Integer[]{1,2,3,4,5,6,7,8,9} );
stream.forEach(p -> System.out.println(p));
}
}
2.3. List.stream()
public class StreamBuilders
{
public static void main(String[] args)
{
List<Integer> list = new ArrayList<Integer>();
for(int i = 1; i< 10; i++){
list.add(i);
}
Stream<Integer> stream = list.stream();
stream.forEach(p -> System.out.println(p));
}
}
2.4 Stream.generate()
或Stream.iterate()
public class StreamBuilders
{
public static void main(String[] args)
{
Stream<Date> stream = Stream.generate(() -> { return new Date(); });
stream.forEach(p -> System.out.println(p));
}
}
2.5 字符串字符或字符串标记
public class StreamBuilders
{
public static void main(String[] args)
{
IntStream stream = "12345_abcdefg".chars();
stream.forEach(p -> System.out.println(p));
//OR
Stream<String> stream = Stream.of("A$B$C".split("\\$"));
stream.forEach(p -> System.out.println(p));
}
}
除了上述列表以外,还有其他一些方法,例如使用Stream.Buider
或使用中间操作。 我们将不时在单独的文章中了解它们。
3. 将流转换为集合
我应该说将流转换为其他数据结构。
请注意,这不是真正的转换。 它只是将流中的元素收集到集合或数组中。
3.1 将流转换为列表 – Stream.collect(Collectors.toList())
public class StreamBuilders {
public static void main(String[] args){
List<Integer> list = new ArrayList<Integer>();
for(int i = 1; i< 10; i++){
list.add(i);
}
Stream<Integer> stream = list.stream();
List<Integer> evenNumbersList = stream.filter(i -> i%2 == 0).collect(Collectors.toList());
System.out.print(evenNumbersList);
}
}
3.2 将流转换为数组 – Stream.toArray(EntryType[]::new)
public class StreamBuilders {
public static void main(String[] args){
List<Integer> list = new ArrayList<Integer>();
for(int i = 1; i< 10; i++){
list.add(i);
}
Stream<Integer> stream = list.stream();
Integer[] evenNumbersArr = stream.filter(i -> i%2 == 0).toArray(Integer[]::new);
System.out.print(evenNumbersArr);
}
}
还有很多其他方式可以将流收集到Set
,Map
或多种方式中。 只需通过收集器类并尝试牢记它们。
4. 核心流操作
流抽象为您提供了一长串有用的函数。 我不会覆盖所有内容,但是我打算在这里列出所有最重要的内容,您必须第一手知道。
在继续之前,让我们预先构建String
的集合。 我们将在此列表上构建示例,以便易于联系和理解。
List<String> memberNames = new ArrayList<>();
memberNames.add("Amitabh");
memberNames.add("Shekhar");
memberNames.add("Aman");
memberNames.add("Rahul");
memberNames.add("Shahrukh");
memberNames.add("Salman");
memberNames.add("Yana");
memberNames.add("Lokesh");
这些核心方法分为以下两个部分:
4.1 中间操作
中间操作返回流本身,因此您可以连续链接多个方法调用。 让我们学习重要的东西。
4.1.1 Stream.filter()
Filter
接受谓词以过滤流中的所有元素。 此操作是中间操作,使我们能够对结果调用另一个流操作(例如forEach
)。
memberNames.stream().filter((s) -> s.startsWith("A"))
.forEach(System.out::println);
Output:
Amitabh
Aman
4.1.2. Stream.map()
中间操作映射通过给定的函数将每个元素转换为另一个对象。 下面的示例将每个字符串转换为大写字符串。 但是您也可以使用map()
将每个对象转换为另一种类型。
memberNames.stream().filter((s) -> s.startsWith("A"))
.map(String::toUpperCase)
.forEach(System.out::println);
Output:
AMITABH
AMAN
4.1.2 Stream.sorted()
Sorted
是一个中间操作,它返回流的排序视图。 除非您通过自定义比较器,否则元素将以自然顺序排序。
memberNames.stream().sorted()
.map(String::toUpperCase)
.forEach(System.out::println);
Output:
AMAN
AMITABH
LOKESH
RAHUL
SALMAN
SHAHRUKH
SHEKHAR
YANA
请记住,sorted
只会创建流的排序视图,而不会操纵支持的集合的顺序。 memberNames
的顺序保持不变。
4.2 终止操作
终止操作返回某个类型的结果,而不是流。
4.2.1 Stream.forEach()
此方法有助于迭代流的所有元素,并对每个元素执行一些操作。 该操作作为 lambda 表达式参数传递。
memberNames.forEach(System.out::println);
4.2.2 Stream.collect()
collect()
方法用于从流中接收元素并将其存储在集合中,并在参数函数中提到。
List<String> memNamesInUppercase = memberNames.stream().sorted()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.print(memNamesInUppercase);
Output: [AMAN, AMITABH, LOKESH, RAHUL, SALMAN, SHAHRUKH, SHEKHAR, YANA]
4.2.3 Stream.match()
各种匹配操作可用于检查某个谓词是否与流匹配。 所有这些操作都是终止操作,并返回布尔结果。
boolean matchedResult = memberNames.stream()
.anyMatch((s) -> s.startsWith("A"));
System.out.println(matchedResult);
matchedResult = memberNames.stream()
.allMatch((s) -> s.startsWith("A"));
System.out.println(matchedResult);
matchedResult = memberNames.stream()
.noneMatch((s) -> s.startsWith("A"));
System.out.println(matchedResult);
Output:
true
false
false
4.2.4 Stream.count()
Count 是一个终止操作,将流中的元素数作为long
值返回。
long totalMatched = memberNames.stream()
.filter((s) -> s.startsWith("A"))
.count();
System.out.println(totalMatched);
Output: 2
4.2.5 Stream.reduce()
此终止操作使用给定函数对流的元素进行归约。 结果是一个保留归约的可选值。
Optional<String> reduced = memberNames.stream()
.reduce((s1,s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);
Output: Amitabh#Shekhar#Aman#Rahul#Shahrukh#Salman#Yana#Lokesh
5. 流短路操作
尽管对满足谓词的集合内的所有元素执行流操作,但通常需要在迭代过程中遇到匹配元素时中断操作。 在外部迭代中,将使用if-else
块。 在内部迭代中,可以使用某些方法来实现此目的。 让我们看一下两种方法的示例:
5.1 Stream.anyMatch()
一旦作为谓词传递的条件满足,此方法将返回true
。 它不会再处理任何元素。
boolean matched = memberNames.stream()
.anyMatch((s) -> s.startsWith("A"));
System.out.println(matched);
Output: true
5.2 Stream.findFirst()
它将从流中返回第一个元素,然后不再处理任何其他元素。
String firstMatchedName = memberNames.stream()
.filter((s) -> s.startsWith("L"))
.findFirst().get();
System.out.println(firstMatchedName);
Output: Lokesh
6. Java 流中的并行性
借助 Java SE 7 中添加的派生/连接框架,我们有了有效的机制来在应用程序中实现并行操作。 但是,实现此框架本身是一项复杂的任务,如果没有正确执行,那么这是一项艰巨的任务。 它是可能导致应用程序崩溃的复杂多线程错误的来源。 通过引入内部迭代,我们可以并行执行操作。
要启用并行性,您要做的就是创建并行流,而不是顺序流。 令您惊讶的是,这确实非常容易。 在上面列出的任何流示例中,只要您想在并行内核中使用多个线程来完成特定作业,您都必须调用方法parallelStream()
方法而不是stream()
方法。
public class StreamBuilders {
public static void main(String[] args){
List<Integer> list = new ArrayList<Integer>();
for(int i = 1; i< 10; i++){
list.add(i);
}
//Here creating a parallel stream
Stream<Integer> stream = list.parallelStream();
Integer[] evenNumbersArr = stream.filter(i -> i%2 == 0).toArray(Integer[]::new);
System.out.print(evenNumbersArr);
}
}
这项工作的主要推动力是使并行性更易于开发人员使用。 尽管 Java 平台已经为并发和并行性提供了强大的支持,但是开发人员在根据需要将其代码从顺序迁移到并行时会遇到不必要的障碍。 因此,重要的是要鼓励顺序友好和并行友好的习语。 通过将重点转移到描述应该执行什么计算,而不是应该如何执行计算,可以方便地进行操作。
同样重要的是要在使并行性变得更容易但不至于使其变得不可见之间取得平衡。 使并行性透明将引入不确定性,并可能在用户可能不期望的情况下进行数据竞争。
关于 Java 8 中引入的流抽象的基础知识,这就是我要分享的全部内容。 我将在以后的文章中讨论与流相关的其他各种事情。
学习愉快!
阅读更多:
流操作
中间操作
终止操作
forEach()
forEachOrdered()
toArray()
reduce()
collect()
min()
max()
count()
anyMatch()
allMatch()
noneMatch()
findFirst()
findAny()
其他例子
Java 8 – 将Iterable
或Iterator
转换为流
Java 流装箱示例
在 Java 8 中,如果要将对象流转换为集合,则可以使用Collector
类中的静态方法之一。
//It works perfect !!
List<String> strings = Stream.of("how", "to", "do", "in", "java")
.collect(Collectors.toList());
但是,相同的过程不适用于原始类型流。
//Compilation Error !!
IntStream.of(1,2,3,4,5)
.collect(Collectors.toList());
要转换原始流,必须首先将包装类中的元素装箱,然后收集它们。 这种类型的流称为装箱流。
1. IntStream
– 整数流
示例将int
流转换为Integer
列表。
//Get the collection and later convert to stream to process elements
List<Integer> ints = IntStream.of(1,2,3,4,5)
.boxed()
.collect(Collectors.toList());
System.out.println(ints);
//Stream operations directly
Optional<Integer> max = IntStream.of(1,2,3,4,5)
.boxed()
.max(Integer::compareTo);
System.out.println(max);
程序输出:
[1, 2, 3, 4, 5]
5
2. LongStream
– 长整数流
示例将long
流转换为Long
列表。
List<Long> longs = LongStream.of(1l,2l,3l,4l,5l)
.boxed()
.collect(Collectors.toList());
System.out.println(longs);
Output:
[1, 2, 3, 4, 5]
3. DoubleStream
– 双精度流
示例将double
流转换为Double
列表。
List<Double> doubles = DoubleStream.of(1d,2d,3d,4d,5d)
.boxed()
.collect(Collectors.toList());
System.out.println(doubles);
Output:
[1.0, 2.0, 3.0, 4.0, 5.0]
在 Java 流 API 中与装箱流或原始类型有关的评论部分中,向我提出您的问题。
学习愉快!
Lambda 表达式
java lambda 表达式是 Java 8 附带的一个非常令人兴奋的新特性。 对于我们许多使用 scala 之类的高级语言工作的人来说,它们并不陌生。
实际上,如果您回顾历史并尝试找出过去 20 年中 Java 语言的任何改进,您将无法回忆起许多令人兴奋的事情。 在过去的十年中,java 中只有很少的并发类,泛型,如果您也同意,那么注释也是如此。 Lambda 表情打破了这种干旱,感觉就像是一件令人愉快的礼物。
在本文中,我将分三部分介绍 lambda 表达式及其概念。 以下是继续进行的计划。
Table of Contents
1\. What is a lambda expression in Java?
2\. Java 8 functional interface
3\. Lambda expression examples
让我们坚持计划并逐步进行。
1. Java 中的 lambda 表达式是什么?
在编程中, Lambda 表达式(或函数)只是一个匿名函数,即不带名称且不受标识符约束的函数。
换句话说,lambda 表达式是无名称的函数,以常量值形式给出,并精确地写在需要的地方,通常作为其他函数的参数。
Lambda 表达式的最重要特征是它们在其出现的上下文中执行。 因此,类似的 lambda 表达式可以在其他一些上下文中以不同的方式执行(即逻辑是相同的,但基于传递给函数的不同参数,结果将有所不同)。
上面的定义充满了关键字,只有当您已经深入了解什么是 lambda 表达式时才能理解。 因此,一旦您对下一部分中的 lambda 表达式有了更好的了解,我建议您重新阅读以上段落。
因此,很明显 lambda 是某种没有名称和标识符的函数。 好吧,有什么大不了的? 为什么每个人都这么兴奋?
答案在于,与面向对象编程(OOP)相比,函数式编程所带来的好处。 大多数 OOP 语言围绕对象和实例发展,并且仅将它们作为头等公民对待。 另一个重要的实体,即职能倒退。 在 Java 中尤其如此,在 Java 中,函数不能存在于对象外部。 函数本身在 Java 中没有任何意义,直到它与某个对象或实例相关为止。
但是在函数式编程中,您可以定义函数,为它们提供引用变量并将其作为方法参数传递等等。 JavaScript 是一个很好的例子,您可以在其中传递回调方法,例如到 Ajax 调用。 这是非常有用的特性,并且从一开始就在 Java 中缺少。 现在使用 Java 8,我们也可以使用这些 lambda 表达式。
1.1 Lambda 语法
典型的 lambda 表达式语法如下所示:
(x, y) -> x + y //This function takes two parameters
//and return their sum.
现在基于x
和y
的类型,可以在多个地方使用方法。 参数可以匹配int
或Integer
或简单地也可以匹配String
。 根据上下文,它将添加两个整数或连接两个字符串。
语法:
lambda 表达式的其他可能语法为:
either
(parameters) -> expression //1
or
(parameters) -> { statements; } //2
or
() -> expression //3
1.2 Lambda 示例
我们还来看一些 lambda 表达式的示例:
(int a, int b) -> a * b // takes two integers and returns their multiplication
(a, b) -> a - b // takes two numbers and returns their difference
() -> 99 // takes no values and returns 99
(String a) -> System.out.println(a) // takes a string, prints its value to the console, and returns nothing
a -> 2 * a // takes a number and returns the result of doubling it
c -> { //some complex statements } // takes a collection and do some procesing
1.3 Lambda 表达式的特性
让我们确定 lambda 表达式的一些特性:
- Lambda 表达式可以具有零个,一个或多个参数。
- 参数的类型可以显式声明,也可以从上下文中推断出来。
- 多个参数用强制括号括起来,并用逗号分隔。 空括号用于表示空参数集。
- 当有单个参数时,如果推断出其类型,则不强制使用括号。 例如
a -> a * a
。 - Lambda 表达式的主体可以包含零个,一个或多个语句。
- 如果 lambda 表达式的主体具有单个语句,则不必使用大括号,并且匿名函数的返回类型与主体表达式的返回类型相同。 如果正文中的语句多于一个,则这些语句必须用大括号括起来。
因此,我们简要了解了什么是 lambda 表达式。 如果您感到迷茫且无法联系,请耐心等待,如何在 Java 编程语言中使用它。 我们将在接下来的 30 分钟内解决所有问题。 让我们开始吧。
在深入研究 lambda 表达式和 Java 编程之间的关系之前,您还必须了解函数式接口。 这太重要了。
2. Java 8 函数式接口
单一抽象方法接口(SAM 接口)不是一个新概念。 这意味着仅使用一种方法进行接口。 在 Java 中,我们已经有许多此类 SAM 接口的示例。 从 Java 8 开始,它们也将也称为函数式接口。 Java 8 通过使用新的注释(即@FunctionalInterface
)标记这些接口来实现单一职责规则。
例如,Runnable
接口的新定义是这样的:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
如果尝试在任何函数式接口中添加新方法,则编译器将不允许您执行此操作,并且会引发编译时错误。
到目前为止,一切都很好。 但是,它们与 Lambda 表达式有何关系? 让我们找出答案。
我们知道 Lambda 表达式是不带名称的匿名函数,它们(大多数)作为参数传递给其他函数。 好吧,在 Java 方法中,参数总是具有类型,并且在确定方法重载甚至是简单方法调用的情况下,都会寻找此类型信息以确定需要调用哪个方法。 因此,基本上每个 lambda 表达式也必须都可以转换为某种类型才能被接受为方法参数。 好吧,用于转换 lambda 表达式的类型始终是函数式接口类型。
让我们通过一个例子来理解它。 如果我们必须编写一个在控制台上打印"howtodoinjava"
的线程,那么最简单的代码将是:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("howtodoinjava");
}
}).start();
如果我们使用 lambda 表达式执行此任务,则代码将为:
new Thread(
() -> {
System.out.println("My Runnable");
}
).start();
我们还看到Runnable
是具有单个方法run()
的函数式接口。 因此,当您将 lambda 表达式传递给Thread
类的构造器时,编译器将尝试将该表达式转换为等效的Runnable
代码,如第一个代码示例所示。 如果编译器成功,则一切运行良好,如果编译器无法将表达式转换为等效的实现代码,它将抱怨。 在此,在上面的示例中,lambda 表达式被转换为Runnable
类型。
简单来说,lambda 表达式是函数式接口的实例。 但是,lambda 表达式本身并不包含有关它正在实现哪个函数接口的信息。 该信息是从使用它的上下文中推导出来的。
3. Java 8 lambda 表达式示例
我列出了一些代码示例,您可以阅读并分析如何在日常编程中使用 lambda 表达式。
1)遍历一个列表并执行一些操作
List<String> pointList = new ArrayList();
pointList.add("1");
pointList.add("2");
pointList.forEach(p -> {
System.out.println(p);
//Do more work
}
);
2)创建一个新的可运行对象,并将其传递给线程
new Thread(
() -> System.out.println("My Runnable");
).start();
3)按员工名称对其排序
public class LambdaIntroduction {
public static void main (String[] ar){
Employee[] employees = {
new Employee("David"),
new Employee("Naveen"),
new Employee("Alex"),
new Employee("Richard")};
System.out.println("Before Sorting Names: "+Arrays.toString(employees));
Arrays.sort(employees, Employee::nameCompare);
System.out.println("After Sorting Names "+Arrays.toString(employees));
}
}
class Employee {
String name;
Employee(String name) {
this.name = name;
}
public static int nameCompare(Employee a1, Employee a2) {
return a1.name.compareTo(a2.name);
}
public String toString() {
return name;
}
}
Output:
Before Sorting Names: [David, Naveen, Alex, Richard]
After Sorting Names [Alex, David, Naveen, Richard]
4)将事件监听器添加到 GUI 组件
JButton button = new JButton("Submit");
button.addActionListener((e) -> {
System.out.println("Click event triggered !!");
});
上面是 Java 8 中 lambda 表达式的非常基本的示例。我将不时提出一些更有用的示例和代码示例。
学习愉快!
Java 变量
了解 Java 中的 Java 变量,类型的变量,如何声明变量的示例以及有关变量命名约定的最佳做法的示例。
Java 编程语言使用“ 字段”和“变量”作为其术语的一部分。 字段引用在方法外部声明的变量,而变量引用在方法内部的声明,包括方法参数。
1. 什么是变量
顾名思义,变量的值在运行时可以变化。 在 Java 中,变量是对存储变量值的存储区的命名引用。
变量如何工作
1.1 变量声明语法
变量声明具有以下语法:
[data_type] [variable_name] = [variable_value];
data_type
– 指存储在存储区中的信息类型。variable_name
– 引用变量的名称。variable_value
– 引用要存储在存储区中的值。
例如,以下语句是 Java 中有效的变量声明。
int i = 10; //Variable of int type
String str = "howtodoinjava.com"; //Variable of string type
Object obj = new Object(); //Variable of object type
int[] scores = [1,2,3,4,5,6,7,8,9]; //Variable of int type
1.2 Java 变量示例
int i = 10;
int j = 10;
int sum = i + j;
System.out.println( sum );
程序输出。
20
2. 扩大和缩小
2.1 加宽
当较小的原始类型值自动容纳在较大/较宽的原始数据类型中时,这称为变量的扩展。 在给定的示例中,将int
类型变量分配给long
类型变量,而没有任何数据丢失或错误。
int i = 10;
long j = i;
System.out.println( i );
System.out.println( j );
程序输出:
10
10
2.2 缩小
如果在较小的原始数据类型中分配了较大的原始类型值,则称为变量变窄。 由于可用于存储数据的位数较少,可能会导致某些数据丢失。 它需要显式类型广播到所需的数据类型。
在给定的示例中,将int
类型变量分配给具有数据丢失的byte
类型变量。
int i=198;
byte j=(byte)i;
System.out.println( i );
System.out.println( j );
程序输出:
198
-58
3. 变量类型
在 Java 中,有四种类型的变量。 这些变量可以是基本类型,类类型或数组类型之一。
根据变量的范围划分所有变量,可以在其中访问它们。
-
实例变量
没有
static
关键字声明的变量(在类中)。 非静态字段也称为实例变量,因为它们的值对于类的每个实例都是唯一的。它们也称为状态变量。public class VariableExample { int counter = 20; //1 - Instance variable }
-
静态变量
也称为类变量。 它是使用
static
修饰符声明的任何字段。 这意味着无论实例已被实例化多少次,该变量的确切副本只有一个。public class VariableExample { static float PI = 3.14f; //2 - Class variable }
在 Java 中,可以将声明为
public static
的变量视为全局变量。 -
局部变量
这些在方法内部使用,因为在方法执行期间存在临时变量。 声明局部变量的语法类似于声明字段。 局部变量只对声明它们的方法可见; 在班上的其他同学中无法访问它们。
public class VariableExample { public static void main( String[] args ) { int age = 30; //3 - Local variable (inside method body) } }
-
方法参数
参数是在调用方法时传递给方法的变量。 尽管在调用方法时会为其分配值,但参数也只能在声明它们的方法内部访问。
public class VariableExample { public static void main( String[] args ) { print( 40 ); } public static void print ( int param ) { //4 - Method Argument System.out.println ( param ); } }
4. 实例变量与类变量
-
实例变量(非静态字段)对于类的每个实例都是唯一的。
-
类变量(静态字段)是使用
static
修饰符声明的字段; 无论实例已被实例化多少次,类变量都只有一个副本。 -
要访问实例变量,必须创建一个新的类实例。 类变量可通过类引用进行访问,并且不需要创建对象实例。
举个例子。 我们有一个
Data
类,它具有一个实例变量和一个类变量。public class Data { int counter = 20; static float PI = 3.14f; }
我们可以以给定的方式访问两个变量。
public class Main { public static void main(String[] args) { Data dataInstance = new Data(); //Need new instance System.out.println( dataInstance.counter ); //20 //Can access using class reference System.out.println( Data.PI ); //3.14 } }
5. Java 变量命名约定
有一些规则和约定与有关如何定义变量名。
- Java 变量名称是区分大小写。 变量名称
employee
与Employee
或EMPLOYEE
不同。 - Java 变量名称必须以字母或
$
或_
字符开头。 - Java 变量名称中的第一个字符之后,名称也可以包含数字,
$
或_
字符。 - 变量名在 Java 中不能为保留关键字。 例如,单词
break
或continue
是 Java 中的保留字。 因此,您不能为它们命名变量。 - 变量名称应以小写编写。 例如,
variable
或apple
。 - 如果变量名由多个单词组成,请遵循驼峰表示法。 例如,
deptName
或firstName
。 - 静态最终字段(常量)应在所有大写字母中命名,通常使用
_
分隔名称中的单词。 例如LOGGER
或INTEREST_RATE
。
学习愉快!
Java 8 – 函数式接口
原文: https://howtodoinjava.com/java8/functional-interface-tutorial/
了解 Java 8 函数式接口以及围绕一个接口允许的一种抽象方法的规则。 了解如何通过函数式接口中的默认方法添加更多方法。
Table of Contents
1\. What is functional interface
2\. Do's and Don't's in functional interfaces
1. 什么是函数式接口
函数式接口是 java 8 中的新增特性,其中恰好允许其中的一种抽象方法。 这些接口也称为单一抽象方法接口(SAM 接口)。
在 Java 8 中,函数式接口也可以使用 lambda 表达式,方法引用和构造器引用表示。
Java 8 也引入了一个注解,即@FunctionalInterface
,当您注解的接口违反了一种抽象方法的约定时,该注解也可用于编译器级错误。
让我们构建第一个函数式接口:
@FunctionalInterface
public interface MyFirstFunctionalInterface
{
public void firstWork();
}
让我们尝试添加另一个抽象方法:
@FunctionalInterface
public interface MyFirstFunctionalInterface
{
public void firstWork();
public void doSomeMoreWork(); //error
}
以上将导致编译器错误,如下所示:
Unexpected @FunctionalInterface annotation
@FunctionalInterface ^ MyFirstFunctionalInterface is not a functional interface
multiple non-overriding abstract methods found in interface MyFirstFunctionalInterface
阅读更多:通用函数式接口
2. 函数式接口中的“是”与“否”
以下是函数式接口中允许和不允许的事物的列表。
-
如上所述, 在任何函数式接口中仅允许使用一种抽象方法 。 在函数式接口中不允许使用第二种抽象方法。 如果删除
@FunctionInterface
注解,则可以添加另一个抽象方法,但是它将使该接口成为非函数式接口。 -
即使
@FunctionalInterface
注释将被省略 ,函数式接口也有效。 它仅用于通知编译器在接口内部强制使用单个抽象方法。 -
从概念上讲,函数式接口仅具有一种抽象方法。 由于默认方法具有实现,因此它们不是抽象的。 由于默认方法不是抽象的,因此您可以自由地向函数式接口添加任意数量的默认方法。
以下是有效的函数式接口:
@FunctionalInterface public interface MyFirstFunctionalInterface { public void firstWork(); default void doSomeMoreWork1(){ //Method body } default void doSomeMoreWork2(){ //Method body } }
-
如果接口声明了覆盖
java.lang.Object
的公共方法之一的抽象方法,则该方法也不计入接口的抽象方法数量,因为该接口的任何实现都有来自java.lang.Object
或其他地方的实现。 例如Comparator
是函数式接口,即使它声明了两个抽象方法。 为什么? 因为这些抽象方法之一equals()
具有与Object
类中的public
方法相同的签名。例如下方的接口是有效的函数式接口。
@FunctionalInterface public interface MyFirstFunctionalInterface { public void firstWork(); @Override public String toString(); //Overridden from Object class @Override public boolean equals(Object obj); //Overridden from Object class }
这就是 Java 8 中函数式接口的全部内容。
学习愉快!
Java 8 方法引用示例
原文: https://howtodoinjava.com/java8/lambda-method-references-example/
在 Java 8 中,我们可以使用class::methodName
类型的语法从类或对象中引用方法。 让我们学习一下 Java 8 中可用的方法引用的不同类型。
Table of Contents
1\. Types of Method References
2\. Reference to static method - Class::staticMethodName
3\. Reference to instance method from instance - ClassInstance::instanceMethodName
4\. Reference to instance method from class type - Class::instanceMethodName
5\. Reference to constructor - Class::new
1. 方法引用的类型
Java 8 允许四种类型的方法引用。
方法引用 | 描述 | 方法引用示例 |
---|---|---|
静态方法的引用 | 用于从类中引用静态方法 | Math::max 等同于(x, y) -> Math.max(x,y) |
来自实例的实例方法的引用 | 使用所提供对象的引用来引用实例方法 | System.out::println 等同于x -> System.out.println(x) |
来自类类型的实例方法的引用 | 在上下文提供的对象的引用上调用实例方法 | String::length 等同于str -> str.length() |
构造器的引用 | 引用构造器 | ArrayList::new 等同于() -> new ArrayList() |
2. 静态方法的引用 – Class::staticMethodName
使用Math.max()
是静态方法的示例。
List<Integer> integers = Arrays.asList(1,12,433,5);
Optional<Integer> max = integers.stream().reduce( Math::max );
max.ifPresent(value -> System.out.println(value));
输出:
433
3. 来自实例的实例方法的引用 – ClassInstance::instanceMethodName
在上面的示例中,我们使用System.out.println(value)
打印找到的最大值。 我们可以使用System.out::println
打印该值。
List<Integer> integers = Arrays.asList(1,12,433,5);
Optional<Integer> max = integers.stream().reduce( Math::max );
max.ifPresent( System.out::println );
Output:
433
4. 来自类类型的实例方法的引用 – Class::instanceMethodName
在此示例中,s1.compareTo(s2)
称为String::compareTo
。
List<String> strings = Arrays
.asList("how", "to", "do", "in", "java", "dot", "com");
List<String> sorted = strings
.stream()
.sorted((s1, s2) -> s1.compareTo(s2))
.collect(Collectors.toList());
System.out.println(sorted);
List<String> sortedAlt = strings
.stream()
.sorted(String::compareTo)
.collect(Collectors.toList());
System.out.println(sortedAlt);
输出:
[com, do, dot, how, in, java, to]
[com, do, dot, how, in, java, to]
5. 构造器的引用 – Class::new
可以更新第一种方法,以创建一个从 1 到 100 的整数列表。使用 lambda 表达式非常简单。 要创建ArrayList
的新实例,我们已经使用ArrayList::new
。
List<Integer> integers = IntStream
.range(1, 100)
.boxed()
.collect(Collectors.toCollection( ArrayList::new ));
Optional<Integer> max = integers.stream().reduce(Math::max);
max.ifPresent(System.out::println);
输出:
99
这是 Java 8 lambda 增强特性中的方法引用的 4 种类型。
学习愉快!
Java 默认方法教程
原文: https://howtodoinjava.com/java8/default-methods-in-java-8/
在上一篇文章中,我们了解了 Lambda 表达式和函数式接口 。 现在,让我们继续进行讨论,并讨论另一个相关特性,即默认方法。 好吧,这对于 Java 开发人员来说确实是革命性的。 到 Java 7 为止,我们已经学到了很多关于接口的知识,而在编写代码或设计应用程序时,所有这些事情都已经牢记在心。 在引入默认方法之后,其中一些概念将从 Java 8 发生巨大变化。
I will discuss following points in this post:
What are default methods in java 8?
Why default methods were needed in java 8?
How conflicts are resolved while calling default methods?
Java 8 中的默认方法是什么?
默认方法使您可以向库的接口添加新功能,并确保与为这些接口的较早版本编写的代码二进制兼容。
顾名思义,java 8 中的默认方法就是默认的。 如果不覆盖它们,则它们是调用者类将调用的方法。 它们在接口中定义。
让我们看一个例子:
public interface Moveable {
default void move(){
System.out.println("I am moving");
}
}
可移动接口定义方法move()
; 并提供了默认实现。 如果有任何类实现此接口,则无需实现其自己的move()
方法版本。 它可以直接调用instance.move()
;
public class Animal implements Moveable{
public static void main(String[] args){
Animal tiger = new Animal();
tiger.move();
}
}
Output: I am moving
而且,如果类愿意定制行为,那么它可以提供自己的自定义实现并覆盖该方法。 现在将调用它自己的自定义方法。
public class Animal implements Moveable{
public void move(){
System.out.println("I am running");
}
public static void main(String[] args){
Animal tiger = new Animal();
tiger.move();
}
}
Output: I am running
这还没有全部完成。 最好的部分是以下好处:
- 静态默认方法:您可以在接口中定义静态默认方法,这些方法将对实现该接口的所有类实例可用。 这使您可以更轻松地在库中组织帮助程序方法。 您可以将特定于接口的静态方法保留在同一接口中,而不是在单独的类中。 这使您可以在类之外定义方法,但仍可与所有子类共享。
- 它们为您提供了一种非常理想的特性,可以在不触及其代码的情况下为多个类添加功能。 只需在它们都实现的接口中添加默认方法即可。
为什么 Java 8 需要默认方法?
这是您下一个面试问题的不错的选择。 最简单的答案是在 Java 中启用 lambda 表达式的特性。 Lambda 表达本质上是函数式接口的类型。 为了无缝支持 lambda 表达式,必须修改所有核心类。 但是,这些核心类(例如java.util.List
)不仅在 JDK 类中实现,而且在数千个客户端代码中也实现。 核心类中任何不兼容的更改都将肯定会产生反作用,并且根本不会被接受。
默认方法打破了这种僵局,并允许在核心类中添加对函数式接口的支持。 让我们来看一个例子。 以下是已添加到java.lang.Iterable
的方法。
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
在 Java 8 之前,如果必须迭代 Java 集合,则将获得一个迭代器实例,并调用它的下一个方法,直到hasNext()
返回false
。 这是常见的代码,我们在日常编程中已经使用了数千次。 语法也总是相同的。 因此,我们可以使其精简吗,使其仅占用一行代码,仍然像以前一样为我们完成工作。 上面的函数可以做到这一点。
现在要迭代并对列表中的每个项目执行一些简单的操作,您需要做的是:
import java.util.ArrayList;
import java.util.List;
public class Animal implements Moveable{
public static void main(String[] args){
List<Animal> list = new ArrayList();
list.add(new Animal());
list.add(new Animal());
list.add(new Animal());
//Iterator code reduced to one line
list.forEach((Moveable p) -> p.move());
}
}
因此,这里,在不破坏列表的任何自定义实现的情况下,已将其他方法添加到List
中。 长期以来,它一直是 Java 中非常需要的特性。 现在就在我们身边。
调用默认方法时如何解决冲突?
到目前为止,一切都很好。 我们的所有基础知识都很好。 现在转到复杂的事情。 在 Java 中,一个类可以实现 N 个接口。 另外,一个接口也可以扩展另一个接口。 如果在两个这样的接口中声明了默认方法,则该接口由单个类实现。 那么很明显,类会混淆调用哪个方法。
解决此冲突的规则如下:
1)最优选的是类中的覆盖方法。 如果在匹配任何内容之前找到它们,它们将被匹配并被调用。
2)选择在“最特定的默认提供接口”中具有相同签名的方法。 这意味着如果Animal
类实现了两个接口,即Moveable
和Walkable
,从而Walkable
扩展了Moveable
。 那么Walkable
是最具体的接口,如果方法签名匹配,则将从此处选择默认方法。
3)如果Moveable
和Walkable
是独立的接口,则会发生严重的冲突情况,并且编译器将抱怨并无法确定。 您必须通过提供额外的信息来帮助编译器,该信息应从哪个接口调用默认方法。 例如:
Walkable.super.move();
//or
Moveable.super.move();
这就是这里的全部内容。 下次,当我想到一些有趣的事情时,我将继续讨论。 不要忘记发表您的评论/想法或问题。
祝您学习愉快!
Java 8 Optional
:完整参考
原文: https://howtodoinjava.com/java8/java-8-Optional-complete-reference/
我们所有人都必须在我们的应用程序中遇到NullPointerException
。 当您尝试利用尚未初始化的对象引用,使用null
初始化或根本不指向任何实例的对象引用时,会发生此异常。NULL
仅表示“不存在值”。 罗马人很可能只是一个人,没有遇到这个空缺的问题,而是从 I,II,III 开始计数(不为零)。 也许,他们无法模拟市场上苹果的缺乏。