JDK17 |java17学习 第 4 章 异常处理
Technical requirements
为了能够执行本章提供的代码示例,您将需要以下内容:
- 装有 Microsoft Windows、Apple macOS 或 Linux 操作系统的计算机
- Java SE 版本 17 或更高版本
- 您喜欢的 IDE 或代码编辑器
第 1 章,Java 17 入门,在本书中。本章的代码示例文件可在 GitHub 存储库中获得,地址为 https:// /github.com/PacktPublishing/Learn-Java-17-Programming.git 在 examples/src/main/java/com/packt/learnjava/ch05_stringsIoStreams 文件夹。
String processing
在主流的编程中,String
大概是最流行的类了。在第 1 章中,< em class="italic">Java 17 入门,我们了解了这个类、它的字面量以及它的特定特性,即字符串不变性。在 本节中,我们将解释如何使用标准库中的 String
类方法和实用程序类来处理字符串,特别是 org.apache.commons.lang3
包中的 StringUtils
类。
Methods of the String class
String
类具有 70 多个方法,可以分析、修改和比较字符串,并将数字文字转换为相应的字符串文字。要查看 String
类的所有方法,请在 https://docs.oracle.com/en/java/javase。
String analysis
length()
方法 返回 字符串中的字符数,如以下代码所示:
后面的 isEmpty()
方法返回 true
当字符串的长度(字符数)为 0
:
indexOf()
和 lastIndexOf()
方法 返回 < a id="_idIndexMarker531">此代码片段中显示的字符串中的指定子字符串:
可以看到,字符串中第一个字符的位置(索引)为0
,没有指定的子字符串导致索引-1
。
matches()
方法将 正则表达式(作为参数传递)应用于字符串,如下所示:
正则 表达式超出了本书的范围。您可以在 https://www.regular-expressions 了解 .info。在前面的示例中,[a-z]+
表达式仅匹配一个或多个字母。
String comparison
在 第 3 章中,< em class="italic">Java Fundamentals,我们谈到了 仅当两个
方法。下面的代码片段演示了它是如何工作的:String
对象或文字的拼写方式完全相同时才返回 true
的 equals()
另一个 String
类,equalsIgnoreCase()
方法做了类似的工作,但忽略了字符大小写的差异,如下所示:
contentEquals()
方法的作用类似于 equals()
方法,如下所示:
区别 是 equals()
方法检查两个值是否由 String
类表示,而 contentEquals()
只比较字符序列的字符(内容)。字符序列可以用String
、StringBuilder
、StringBuffer
、< code class="literal">CharBuffer,或任何其他实现 CharSequence
接口的类。然而,如果两个序列包含相同的字符,contentEquals()
方法将返回 true
,而 String
类创建的,">equals() 方法将返回 false
。
如果 string
包含某个子字符串,则 contains()
方法返回 true
, 如下:
startsWith()
和 endsWith()
方法执行类似的检查,但仅在字符串的开头或结尾字符串值,如下代码所示:
compareTo()
和 compareToIgnoreCase()
方法按字典顺序比较字符串 - 基于字符串中每个字符的 Unicode 值。如果字符串相等,则返回 值0
,如果第一个字符串为 按字典顺序小于第二个字符串(具有较小的 Unicode 值),如果第一个字符串按字典顺序大于第二个字符串(具有较大的 Unicode 值),则为正整数值,如下所示:
从这个代码片段中,您可以看到 compareTo()
和 compareToIgnoreCase()
方法是基于组成字符串的字符。 s4
字符串比 s5
字符串大 32
的原因是因为 a
字符 (97
) 的代码点大于 A
字符(65
),由 32
编写。
给定的示例还表明 codePointAt()
方法返回位于字符串中指定位置的 字符的代码点. Integral types 部分中描述了代码点book/programming/9781803241432/2" linkend="ch02lvl1sec02">第 1 章,Java 17 入门 .
String transformation
substring()
方法 返回子字符串,从指定位置开始 (索引),如下:
format()
方法 使用传入的第一个参数作为模板,并将其他参数插入到模板顺序。下面的代码示例打印句子 Hey, Nick!请给我 2 个苹果!
三遍:
%s
和 %d
符号称为 格式说明符。有许多说明符和各种标志允许程序员对结果进行精细控制。您可以在 java.util.Formatter
类的 API 中了解它们。
concat()
方法 与 算术运算符 ( +
),如下所示:
下面一组replace()
、replaceFirst()
和replaceAll()< /code> methods 用提供的字符替换字符串中的某些字符:
上述代码的第一行将 "bc"
的所有实例替换为 "42"
。第二个仅用 "42"
替换 "bc"
的第一个实例,最后一个替换所有子字符串 将提供的正则表达式与 "42"
匹配。
toLowerCase()
和 toUpperCase()
方法 改变大小写整个 字符串,如下所示:
split()
方法将字符串分解为子字符串,使用提供的字符作为分隔符,如下所示:
有几个 valueOf()
方法可以将原始类型的值转换为 String
类型,例如 如下:
还有 和 ()
和 getChars()
方法可以转换一个string 到对应类型的数组,而 chars()
方法 创建一个 IntStream
个字符(它们的代码 点)。我们将在 第 14 章中讨论流/a>,Java 标准流。
Methods added with Java 11
repeat()
方法允许您基于同一字符串的多个连接创建一个新的 String
值,如以下代码:
isBlank()
方法返回 true
如果字符串的长度为 0
或仅由空格组成,例如:
stripLeading()
方法从字符串中删除前导空格,stripTrailing()
方法删除尾随空格,< code class="literal">strip() 方法将两者都删除,如下所示:
最后,lines()
方法按行终止符分隔字符串并返回结果行的 Stream<String>
。行终止符是转义序列换行符 (\n
) (\u000a
)、回车符 (\r
) (\u000d
),或回车后紧跟换行符 (\r\ n
) (\u000d\u000a
),例如:
上述代码的输出如下:
我们将讨论 第 14 章,Java 标准流。
String utilities
除了 到 String
类之外,还有许多其他类具有处理 的方法字符串
值。其中最有用的是来自项目 org.apache.commons.lang3 包的 StringUtils
类_idIndexMarker563">称为Apache Commons,由程序员的开源社区维护,称为Apache 软件基金会。我们将在 第 7 章中详细讨论这个项目及其库,Java 标准和外部库。要在您的项目中使用它,请在 pom.xml
文件中添加以下依赖项:
StringUtils
类是许多程序员的最爱。它通过提供以下 null 安全操作来补充 String
类的方法(当方法以这种方式实现时 - 例如通过检查 null 的值 - 它不抛出 NullPointerException
):
isBlank(CharSequence cs)
:如果输入值为空格,则返回true
,为空(""
),或null
isNotBlank(CharSequence cs)
:当上述方法返回true
时返回false
isEmpty(CharSequence cs)
:如果输入值为空(""< /code>) 或
null
isNotEmpty(CharSequence cs)
:当上述方法返回true
时返回false
trim(String str)
:从输入值中删除前导和尾随空格并处理null
、空(""
)和空格,如下:trimToNull(String str)
:从输入值中删除前导和尾随空格并处理null
,空(""
) 和空格,如下:trimToEmpty(String str)
:从 输入值中删除前导和尾随空格并处理null
、空(""
)和空格,如下:strip(String str)
、stripToNull(String str)
和stripToEmpty(String str)
:产生与前面的trim(String str)
、trimToNull(String str)
和< code class="literal">trimToEmpty(String str) 方法,但使用更广泛的空白定义(基于Character.isWhitespace(int codepoint)
)和因此删除与trim(String str)
、trimToNull(String str)
和trimToEmpty(String str)
方法,等等strip(String str, String stripChars)
,stripAccents(String input)
,stripAll(String ... strs)
,stripAll(String[] strs, String stripChars)
,stripEnd(String str, String stripChars)< /code> 和
stripStart(String str, String stripChars)
:从String
或String[]
数组元素startsWith(CharSequence str, CharSequence prefix)
,startsWithAny(CharSequence string, CharSequence... searchStrings)
,startsWithIgnoreCase(CharSequence str, CharSequence prefix)
和类似的endsWith*()
方法:检查String value 以某个前缀(或后缀)开始(或结束)
indexOf
,lastIndexOf
,contains
:检查空安全中的索引方式indexOfAny
,lastIndexOfAny
,indexOfAnyBut
,lastIndexOfAnyBut
:返回一个索引containsOnly
、containsNone
和containsAny
:检查值是否包含某些字符或不substring
、left
、right
和mid
:以空安全方式返回子字符串substringBefore
,substringAfter
,substringBetween
:从相对位置返回一个子字符串split
或join
:拆分或连接一个值(分别)remove
和delete
:消除子串replace
和overlay
:替换一个值chomp
和chop
:去掉结尾appendIfMissing
:如果不存在则添加一个值prependIfMissing
:如果不存在,则在String
值的开头添加前缀leftPad
、rightPad
、center
和repeat
:添加填充upperCase
,lowerCase
,swapCase
,大写
和uncapitalize
:改变大小写countMatches
:返回子串出现的次数isWhitespace
,isAsciiPrintable
,isNumeric
,isNumericSpace
、isAlpha
、isAlphaNumeric
、isAlphaSpace
和< code class="literal">isAlphaNumericSpace:检查某类字符是否存在isAllLowerCase
和isAllUpperCase
:检查大小写defaultString
、defaultIfBlank
和defaultIfEmpty
:如果null
rotate
:使用循环移位旋转字符reverse
和reverseDelimited
:反转字符或分隔字符组abbreviate
和abbreviateMiddle
:使用省略号或其他值缩写一个值difference
:返回值的差异getLevenshteinDistance
:返回将一个值转换为另一个值所需的更改次数
如您所见,StringUtils
类有一组非常丰富的(我们没有列出所有内容)用于字符串分析、比较和转换的方法,这些方法补充了 String
类的方法。
I/O streams
任何软件 系统都必须接收和生成某种数据,这些数据可以组织为一组隔离的输入/输出或数据流。流可以是有限的或无穷无尽的。程序可以从流(称为输入流)中读取或写入流(称为输出流)。 Java I/O 流是基于字节或基于字符的,这意味着它的数据被解释为原始字节或字符。
java.io
包包含支持许多(但不是全部)可能的数据源的类。它主要围绕文件、网络流和内部内存缓冲区的输入和输入而构建。它不包含网络通信所需的许多类。它们属于 java.net
、javax.net
和其他 Java 网络 API 包。只有在建立网络源或目标(例如网络套接字)之后,程序才能使用 InputStream
和 OutputStream<
java.io
包的 /code> 类。
java.nio
包的类与 java.io
包的类具有几乎相同的功能。但是,此外,它们可以在 非阻塞 模式下工作,这可以在某些情况下显着提高性能。我们将在 第 15 章
,反应式编程。
Stream data
输入 数据必须是二进制的——用 0 和 1 表示——至少因为这是计算机可以读取的格式。数据可以一次读取或写入一个字节,也可以一次读取或写入多个字节的数组。这些字节可以保持二进制,也可以解释为字符。
在第一种情况下,它们可以被 InputStream
和 OutputStream
类的后代读取为字节或字节数组,例如(如果类属于 java.io
包,则省略包名) ByteArrayInputStream
, ByteArrayOutputStream
, FileInputStream
, FileOutputStream
, ObjectInputStream
, ObjectOutputStream
、javax.sound.sampled.AudioInputStream
和 org.omg.CORBA.portable.OutputStream
;您使用哪一种取决于数据的来源或目的地。 InputStream
和 OutputStream
类本身是抽象的,无法实例化。
第二种情况,可以解释为字符的数据称为文本数据,基于Reader有面向字符的读写类
和 Writer
也是抽象类。它们的子类的示例是 CharArrayReader
和 CharArrayWriter
、InputStreamReader
和 < code class="literal">OutputStreamWriter、PipedReader
和 PipedWriter
和 StringReader
和 StringWriter
。
您可能已经注意到我们成对列出了这些类。但并不是每个输入类都有匹配的输出特化——例如,有 PrintStream
和 PrintWriter
类支持输出到打印设备,但没有对应的输入伙伴,至少没有名称。但是,有一个 java.util.Scanner
类可以解析已知格式的输入文本。
还有一组配备缓冲区的类,通过一次读取或写入更大的数据块来帮助提高性能,尤其是在访问源或 目标的情况下需要很长的时间。
在本节的其余部分,我们将回顾 java.io
包的类和其他包中一些流行的相关类。
The InputStream class and its subclasses
在 Java 类库中,InputStream
抽象 类有以下直接实现:ByteArrayInputStream
, FileInputStream
, ObjectInputStream
、PipedInputStream
、SequenceInputStream
、FilterInputStream
和<代码类="literal">javax.sound.sampled.AudioInputStream。
所有这些都可以按原样使用或覆盖 InputStream
类的以下方法:
int available()
:返回可供读取的字节数void close()
:关闭流并释放资源void mark(int readlimit)
:标记流中的一个位置,定义可以读取多少字节boolean markSupported()
:如果支持标记,则返回true
static InputStream nullInputStream()
:创建一个空流abstract int read()
:读取流中的下一个字节int read(byte[] b)
:从流中读取数据到b
缓冲区int read(byte[] b, int off, int len)
:从流中读取len
或更少字节到 < code class="literal">b 缓冲区byte[] readAllBytes()
:从流中读取所有剩余的字节int readNBytes(byte[] b, int off, int len)
:将len
或更少字节读入off
偏移处的 "literal">b 缓冲区byte[] readNBytes(int len)
:将len
或更少的字节读入b< /代码>缓冲区
void reset()
:将读取位置重置为上次调用mark()
方法的位置long skip(long n)
:跳过n
或更少字节的流;返回实际跳过的字节数long transferTo(OutputStream out)
:从输入流中读取,逐字节写入提供的输出流;返回实际传输的字节数
abstract int read()
是唯一必须实现的方法,但是这个类的大多数后代 覆盖了许多其他方法 也是。
ByteArrayInputStream
ByteArrayInputStream
类允许 读取字节数组作为输入流。它具有以下两个构造函数,它们创建类的对象并定义用于读取字节输入流的缓冲区:
ByteArrayInputStream(byte[] buffer)
ByteArrayInputStream(byte[] buffer, int offset, int length)
除了缓冲区之外,第二个构造函数还允许您设置缓冲区的偏移量和长度。让我们看一个例子,看看如何使用这个类。我们假设有一个包含数据的 byte[]
数组的来源:
然后,我们可以编写以下内容:
bytesSource()
方法生成填充缓冲区的字节数组,该数组作为 ByteArrayInputStream
类的构造函数传递给范围。然后使用 read()
方法逐字节读取结果流,直到到达流的末尾(并且 read()< /code> 方法返回
-1
)。每个新字节都被打印出来(没有换行符,后面有空格,所以所有读取的字节都显示在一行中,由空格分隔)。
不仅可以打印字节,还可以以任何其他必要的方式处理它们,包括将它们解释为字符,例如:
但是,在这种情况下,最好使用专门用于字符 处理的 Reader
类之一。我们将在 Reader 和 writer 类及其子类 部分中讨论。
FileInputStream
FileInputStream
类 从文件系统中的文件中获取数据 - 原始例如,图像的字节数。它具有以下三个构造函数:
FileInputStream(文件文件)
FileInputStream(String name)
FileInputStream(FileDescriptor fdObj)
每个构造函数都会打开指定为参数的文件。第一个构造函数接受 File
对象;第二,文件系统中文件的路径;第三,文件描述符对象,表示与文件系统中实际文件的现有连接。让我们看下面的例子:
在 src/main/resources
文件夹中,我们创建了 hello.txt
文件,其中只有一行 - <代码类="literal">你好!。上述示例的输出如下所示:
从 hello.txt
文件中读取字节后,出于演示目的,我们决定将每个 byte
转换为 char
这样你就可以看到代码确实是从指定的文件中读取的,但是 FileReader
类是文本文件处理的更好选择(我们稍后会讨论)。如果没有演员表,结果将如下:
顺便说一句,因为 src/main/resources
文件夹是由 IDE(使用 Maven)放置在类路径上的,所以放置在其中的文件也可以通过类加载器访问使用其 自己的 InputStream
实现创建一个 流:
前面示例中的 InputOutputStream
类不是某个库中的类。它只是我们用来运行示例的主类。 InputOutputStream.class.getResourceAsStream()
构造允许您使用已加载 InputOutputStream
类的相同类加载器< a id="_idIndexMarker592"> 在类路径 上查找文件并创建包含其内容的流。在文件管理部分,我们还将介绍其他读取文件的方法。
ObjectInputStream
ObjectInputStream
类 的方法集合比集合大很多任何其他 InputStream
实现的方法。原因是它是围绕读取可以是各种类型的对象字段的值而构建的。为了使 ObjectInputStream
能够从输入的数据流构造一个对象,该对象必须是 deserializable,这意味着它首先必须是serializable的——也就是说,可以转换成字节流。通常,这样做是为了通过网络传输对象。在目的地,序列化的对象被反序列化,并恢复原始对象的值。
原始类型和大多数 Java 类,包括 String
类和原始类型包装器,都是可序列化的。如果一个类具有自定义类型的字段,则必须通过实现 java.io.Serizalizable
使其可序列化。如何做到这一点超出了本书的范围。现在,我们将只使用可序列化的类型。让我们看看这个类:
我们必须告诉编译器它是可序列化的。否则,编译将失败。这样做是为了确保在声明类是可序列化的之前,程序员要么检查所有字段并确保它们是可序列化的,要么已经实现了序列化所需的方法。
在我们可以创建输入流并使用 ObjectInputStream
进行反序列化之前,我们需要先序列化对象。这就是为什么我们首先使用 ObjectOutputStream
和 FileOutputStream
来序列化一个对象并将其写入 someClass.bin
文件。我们将在 OutputStream 类及其子类 部分详细讨论它们。然后,我们使用 FileInputStream
从文件中读取并使用 ObjectInputStream
反序列化文件内容:
请注意,必须先创建 文件,然后才能运行上述代码。我们将在创建文件和目录部分展示它是如何完成的。并且,提醒一下,我们使用了 try-with-resources
语句,因为 InputStream
和 OutputStream
都实现了 Closeable
接口。
PipedInputStream
管道输入 流具有非常特殊的 特化;它被用作线程之间的通信机制之一。一个线程从 PipedInputStream
对象读取数据并将数据传递给另一个线程,该线程将数据写入 PipedOutputStream
对象。这是一个例子:
或者,当一个线程从 PipedOutputStream
对象读取数据而另一个线程写入 PipedInputStream
对象时,数据可以向相反的方向移动如下:
在这方面工作的人都熟悉消息,Broken pipe
,这意味着提供数据管道流已停止工作。
管道 流也可以在没有任何连接的情况下创建并稍后连接,如下所示:
例如,这里有两个类将由不同的线程执行——首先是 PipedOutputWorker
类,如下所示:
PipedOutputWorker
类具有 run()
方法(因为它实现了 Runnable
interface) 写入 流三个数字 1
, < code class="literal">2 和 3
,然后关闭。现在,让我们看看 PipedInputWorker
类,如下所示:
它还有一个 run()
方法(因为它实现了一个 Runnable
接口)从流中读取并打印出每个字节直到流结束(由 -1
指示)。现在,让我们连接这些 管道并 执行一个 run()
方法这些类:
如您所见,worker 的对象被传递到 Thread
类的构造函数中。 Thread
对象的 start()
方法执行 run()
Runnable
传入的方法。我们看到了预期的结果——PipedInputWorker
打印出由 PipedOutputWorker
写入管道流的所有字节。我们将在 第 8 章
,多线程和并发处理。
SequenceInputStream
SequenceInputStream
类 连接 输入流作为以下构造函数之一参数:
SequenceInputStream(InputStream s1, InputStream s2)
SequenceInputStream(Enumeration
e)
枚举是集合尖括号中指示的类型的对象,称为泛型,意思是类型为T。 SequenceInputStream
类从第一个输入字符串读取直到它结束,然后它从第二个输入字符串读取,依此类推,直到最后一个流结束。例如,让我们在 howAreYou.txt
文件(带有文本,How are you?
) hello.txt
文件旁边的 ="literal">resources 文件夹。 SequenceInputStream
类可以如下使用:
类似地,当传入输入流的枚举时,会读取每个流(在我们的例子中,打印)直到结束。
FilterInputStream
FilterInputStream
类 是 包装器">InputStream
对象作为构造函数中的参数传递。下面是 FilterInputStream
类的构造函数和两个 read()
方法:
InputStream
类的所有其他方法都被类似地覆盖;该函数被委托给分配给 in
属性的对象。
如您所见,构造函数是受保护的,这意味着只有孩子可以访问它。这样的设计对客户端隐藏了流的实际来源,并迫使程序员使用 FilterInputStream
类扩展之一:BufferedInputStream
, CheckedInputStream
, DataInputStream
, PushbackInputStream
, javax.crypto.CipherInputStream
, java.util.zip.DeflaterInputStream
, java.util.zip.InflaterInputStream
、java.security.DigestInputStream
或 javax.swing.ProgressMonitorInputStream
。或者,您可以创建自定义扩展。但是,在创建自己的扩展之前,请查看列出的类,看看其中一个是否符合您的需求。下面是一个使用 BufferedInputStream
类的例子:
BufferedInputStream
类使用缓冲区来提高性能。当流中的字节被跳过或读取时,内部缓冲区会自动从包含的输入流中自动重新填充所需的字节数。
CheckedInputStream
类添加正在读取的数据的校验和,允许使用 getChecksum()
方法。
DataInputStream
类以与机器无关的方式读取输入数据并将其解释为原始 Java 数据类型。
PushbackInputStream
类 添加了使用unread()
方法。它在代码具有分析刚刚读取的数据并决定不读取它的逻辑的情况下很有用,因此可以在下一步重新读取它。
javax.crypto.CipherInputStream
类将 Cipher
添加到 read()
方法。如果 Cipher
被初始化用于解密,javax.crypto.CipherInputStream
将在返回之前尝试解密数据。
java.util.zip.DeflaterInputStream
类以 deflate 压缩格式压缩数据。
java.util.zip.InflaterInputStream
类以 deflate 压缩格式解压缩数据。
java.security.DigestInputStream
类使用通过流的位更新关联的消息摘要。 on (boolean on)
方法打开或关闭 digest
函数。可以使用 getMessageDigest()
方法检索计算出的摘要。
javax.swing.ProgressMonitorInputStream
类提供从 InputStream< 读取的进度 的监视器/代码>。 监控对象可以使用
getProgressMonitor()
方法访问。
javax.sound.sampled.AudioInputStream
AudioInputStream
类代表 具有指定音频格式和长度的输入流.它有以下两个构造函数:
AudioInputStream (InputStream stream, AudioFormat format, long length)
:接受音频数据的流,请求的格式,以样本帧为单位的长度AudioInputStream (TargetDataLine line)
:接受指示的目标数据行
javax.sound.sampled.AudioFormat
类描述音频格式属性,例如通道、编码、帧速率等。 javax.sound.sampled.TargetDataLine
类有 open()
方法以指定格式打开行,< code class="literal">read() 方法从数据线的输入缓冲区读取音频数据。
还有 javax.sound.sampled.AudioSystem
类,其方法处理 AudioInputStream
对象。它们可用于读取音频文件、流或 URL,并写入音频文件。它们还可用于将音频流转换为另一种音频格式。
The OutputStream class and its subclasses
OutputStream
类是 InputStream
类的对等点 表示 写入数据而不是读取数据。它是一个抽象类,在 Java 类库 (JCL ): ByteArrayOutputStream
, FilterOutputStream
, ObjectOutputStream
, PipedOutputStream
和 FileOutputStream
。
FileOutputStream
类具有以下直接扩展:BufferedOutputStream
、CheckedOutputStream
、< code class="literal">DataOutputStream, PrintStream
, javax.crypto.CipherOutputStream
, java.util.zip.DeflaterOutputStream
、java.security.DigestOutputStream
和 java.util.zip.InflaterOutputStream< /代码>。
所有这些都可以按原样使用或覆盖 OutputStream
类的以下方法:
void close()
:关闭流并释放资源void flush()
:强制写出剩余字节static OutputStream nullOutputStream()
:创建一个不写任何内容的新OutputStream
void write(byte[] b)
:将提供的字节数组写入输出流void write(byte[] b, int off, int len)
:写入提供的字节数组的len
个字节,从off
偏移量,到输出流abstract void write(int b)
:将提供的字节写入输出流
唯一需要实现的方法是abstract void write(int b)
,但大部分是OutputStream
类的后代也覆盖许多其他方法。
在InputStream类及其子类部分了解了输入流之后,所有OutputStream
的实现,除了PrintStream
类, 你应该直观地熟悉。所以,我们将在这里只讨论PrintStream
类。
PrintStream
PrintStream
类 向另一个输出 流添加了将数据打印为的能力人物。我们实际上已经使用过很多次了。 System
类有一个 PrintStream
类的对象设置为 System.out
公共静态属性。这意味着每次我们使用
System.out
打印一些东西时,我们都在使用 PrintStream
类:
让我们看另一个 PrintStream
类用法的例子:
如您所见,PrintStream
类采用 FileOutputStream
对象并打印由它生成的字符。在这种情况下,它会打印出 FileOutputStream
写入文件的所有字节。顺便说一句,不需要显式创建目标文件。如果不存在,它将在 FileOutputStream
构造函数中自动创建。如果我们在前面的代码运行后打开文件,我们会在其中看到一行——"Hi there!"
。
或者,可以使用另一个采用 File
对象的 PrintStream
构造函数来实现相同的结果,如下所示:
使用将文件名作为参数的 PrintStream
构造函数的第三个变体可以创建更简单的解决方案:
前面的 两个例子是可能的,因为 PrintStream
构造函数使用 FileOutputStream
类在幕后,就像我们在 PrintStream
类用法的第一个示例中所做的那样。因此,PrintStream
类有几个构造函数只是为了方便,但它们本质上都具有相同的功能:
PrintStream(文件文件)
PrintStream(File file, String csn)
PrintStream(File file, Charset charset)
PrintStream(String fileName)
PrintStream(String fileName, String csn)
PrintStream(String fileName, Charset charset)
PrintStream(OutputStream out)
PrintStream(OutputStream out, boolean autoFlush)
PrintStream(OutputStream out, boolean autoFlush, String encoding)
PrintStream(OutputStream out, boolean autoFlush, Charset charset)
一些构造函数还采用 Charset
实例或只是其名称 (String csn
),这允许您应用不同的映射在 16 位 Unicode 代码单元序列和字节序列之间。您只需将它们打印出来即可查看所有可用的字符集,如下所示:
其他构造函数将 boolean autoFlush
作为参数。此参数表示(当 true
时)当数组被 写入 或遇到行尾符号。
一旦创建了 PrintStream
的对象,它就会提供各种方法,如下所示:
void print(T value)
:打印传入的任何T
原始类型的值,而不移动到另一行void print(Object obj)
:在传入的对象上调用toString()
方法并打印结果而不移动到另一个线;如果传入的对象为null
并打印null
,则不会生成NullPointerException
代码>改为void println(T value)
:打印传入的任何T
原始类型的值并移至另一行void println(Object obj)
:对传入的对象调用toString()
方法,打印结果,移动到另一条线;如果传入的对象为null
并打印null
,则不会生成NullPointerException
代码>改为void println()
:只是移动到另一行PrintStream printf(String format, Object... values)
:将提供的format
字符串中的占位符替换为提供的values
并将结果写入流PrintStream printf(Locale l, String format, Object... args)
:与前面的方法相同,但使用提供的Local< /代码>对象;如果提供的
Local
对象为null
,则不应用本地化,并且 this 方法的行为与前面的完全一样PrintStream format(String format, Object... args)
和PrintStream format(Locale l, String format, Object... args)
code>:与PrintStream printf(String format, Object... values)
和PrintStream printf(Locale l, String format, Object...args)
(已经在列表中描述过),例如:
在前面的示例中,(%
) 表示格式规则。以下符号 (s
) 表示 String
值。此位置的其他可能符号可以是 (d
)(十进制)、(f
)(浮点)等上。符号 (n
) 表示换行符(与 (\n
) 转义字符相同)。有许多格式规则。所有这些都在 java.util.Formatter
类的文档中进行了描述。
PrintStream append(char c)
、PrintStream append(CharSequence c)
和PrintStream append( CharSequence c, int start, int end)
:将提供的字符追加到流中,例如:
至此,我们结束对OutputStream
子类的讨论,现在转向我们注意另一个类层次结构 - Reader
和 Writer
类及其来自 JCL 的子类。
The Reader and Writer classes and their subclasses
正如我们提到几次次< /a>Reader
和 Writer
类的功能已经与 非常相似InputStream
和 OutputStream
类,但专门处理文本。它们将流字节解释为字符,并有自己独立的 InputStream
和 OutputStream
类层次结构。可以在没有 Reader
和 Writer
或其任何子类的情况下将流字节作为字符处理。我们已经在前面描述 InputStream
和 OutputStream
类的部分中看到了这样的示例。但是,使用 Reader
和 Writer
类 会使文本处理 更简单,代码更易阅读。
Reader and its subclasses
Reader
类是一个将流作为字符读取的抽象类。它是 InputStream
的模拟,具有以下方法:
abstract void close()
:关闭流和其他使用的资源void mark(int readAheadLimit)
:标记流中的当前位置boolean markSupported()
:如果流支持mark()
,则返回true
> 操作static Reader nullReader()
:创建一个不读取字符的空Reader
int read()
:读取一个字符int read(char[] buf)
:将字符读入提供的buf
数组并返回读取字符数abstract int read(char[] buf, int off, int len)
:将len
个字符读入数组,从off
索引int read(CharBuffer target)
:尝试将字符读入提供的target
缓冲区boolean ready()
:当流准备好被读取时返回true
void reset()
:重置标记;但是,并非所有流都支持此操作,有些流支持它但不支持首先设置标记long skip(long n)
:尝试跳过n
个字符;返回跳过字符的计数long transferTo(Writer out)
:从这个 reader 中读取所有字符,并将字符写入提供的Writer
对象
如您所见,唯一需要实现的方法是两个抽象的 read()
和 close()
方法。尽管如此,这个类的许多子类也覆盖了其他方法,有时是为了更好的性能或不同的功能。 JCL 中的 Reader
子类是 CharArrayReader
, InputStreamReader
, PipedReader
、StringReader
、BufferedReader
和 FilterReader< /代码>。
BufferedReader
类有一个 LineNumberReader
子类,以及 FilterReader
类有一个 PushbackReader
子类。
Writer and its subclasses
抽象的 Writer
类将 写入字符流。它是 OutputStream
的模拟,具有以下方法:
Writer append(char c)
:将提供的字符追加到流中Writer append(CharSequence c)
:将提供的字符序列追加到流中Writer append(CharSequence c, int start, int end)
:将提供的字符序列的子序列追加到流中abstract void close()
:刷新和关闭流及相关系统资源abstract void flush()
:刷新流static Writer nullWriter()
:创建一个新的Writer
对象,丢弃所有字符void write(char[] c)
:写入c
个字符的数组abstract void write(char[] c, int off, int len)
:写入len元素class="literal">c 个字符,从
off
索引开始void write(int c)
:写入一个字符void write(String str)
:写入提供的字符串void write(String str, int off, int len)
:从提供的len
长度的子串="literal">str 字符串,从off
索引开始
可以看到,三个抽象方法,write(char[], int, int)
、flush()
和close()
,必须由这个类的孩子实现。它们通常也覆盖其他方法。
JCL中的Writer
子类是CharArrayWriter
, OutputStreamWriter
, PipedWriter
, StringWriter
, BufferedWriter
、FilterWriter
和 PrintWriter
。 OutputStreamWriter
类有一个 FileWriter
子类。
Other classes of the java.io package
Console
:允许与基于字符的控制台设备进行交互,与当前 Java 虚拟机实例相关联StreamTokenizer
:获取输入流并将其解析为tokens
ObjectStreamClass
:类的序列化描述符ObjectStreamField
:来自可序列化类的可序列化字段的描述RandomAccessFile
:允许随机访问以读取和写入文件,但它的讨论超出了本书的范围File
:允许创建和管理文件和目录;在文件管理部分中描述
Console
有 几种 方法可以创建和运行 Java 虚拟机(< strong class="bold">JVM) 执行应用程序的实例。如果 JVM 从命令行启动,则会自动打开一个控制台窗口。它允许您从键盘在显示屏上打字;但是,JVM 也可以由后台进程启动。在这种情况下,不会创建控制台。
要以编程方式检查控制台是否存在,您可以调用 System.console()
静态方法。如果没有可用的控制台设备,则调用该方法将返回 null
。否则,它将返回允许与控制台设备和应用程序用户交互的 Console
类的对象。
如果我们像往常一样从 IDE 运行它,结果将如下所示:
那是因为 JVM 不是从命令行启动的。为了做到这一点,让我们编译我们的应用程序并通过执行 mvn clean package
Maven 命令创建一个 .jar
文件项目的根目录。 (我们假设您的计算机上安装了 Maven。)它将删除 target
文件夹,然后重新创建它,编译所有 .java< /code> 文件复制到
target
文件夹中对应的 .class
文件,然后将它们归档到 .jar
文件,learnjava-1.0-SNAPSHOT.jar
。
现在,我们可以使用以下命令从同一项目根目录启动 ConsoleDemo
应用程序:
前面的 –cp
命令选项描述了一个类路径,所以在我们的例子中,我们告诉 JVM 在 .jar
文件夹目标中的文件。该命令显示为两行,因为页面宽度无法容纳它。但是,如果您想运行它,请确保将其作为一行执行。结果如下:
这告诉我们现在有了 Console
类对象。让我们看看我们能用它做什么。该类具有以下方法:
String readLine()
:等到用户按下 Enter 键并从控制台读取该行文本String readLine(String format, Object... args)
:显示提示( 消息在提供的格式具有占位符用提供的参数替换 ),等到用户点击 Enter 键,然后从控制台读取文本行;如果没有提供任何参数(args
),则将格式显示为提示char[] readPassword()
:执行与readLine()
函数相同的功能,但不回显键入的字符char[] readPassword(String format, Object... args)
:执行与readLine(String format, Object... args) 相同的功能)
但不回显输入的字符
要单独运行以下每个代码部分,您需要注释掉 main
方法中的 console1()
调用并取消注释console2()
或 console3()
,使用 mvn package
重新编译,以及然后重新运行之前显示的 java
命令。
让我们用下面的例子来演示前面的方法(console2()
方法):
某些 IDE 无法运行这些示例并抛出 NullPointerException
。如果是这种情况,请从命令行运行与控制台相关的示例,如前所述。每次更改代码时不要忘记运行 maven package
命令。
另一组 Console
类方法可以与前面演示的方法结合使用:
Console format(String format, Object... args)
:将提供的format
字符串中的占位符替换为提供的args
值并显示结果Console printf(String format, Object... args)
:行为方式与format()
方法相同
它产生与此行相同的结果:
最后,Console
类的最后三个方法如下:
PrintWriter writer()
:创建一个与此控制台关联的PrintWriter
对象,可用于生成字符输出流Reader reader()
:创建一个与此控制台关联的Reader
对象,可用于将输入作为字符流读取void flush()
:刷新控制台并强制立即写入任何缓冲的输出
以下是它们的用法示例(console3()
方法):
上述代码的结果如下所示:
Reader
和 PrintWriter
也可以用于创建其他 Input
和 Output
流,我们在本节中讨论过。
StreamTokenizer
StreamTokenizer
类解析 输入 流并生成令牌。它的 StreamTokenizer(Reader r)
构造函数接受一个作为标记源的 Reader
对象。每次在 StreamTokenizer
对象上调用 int nextToken()
方法时,都会发生以下情况:
- 解析下一个令牌。
StreamTokenizer
实例字段ttype
由指示令牌类型的值填充:ttype
值可以是以下整数常量之一:TT_WORD
、TT_NUMBER
、TT_EOL
(行尾)或TT_EOF
(流尾)。李>- 如果
ttype
值为TT_WORD
,则StreamTokenizer
实例,sval
字段由令牌的String
值填充。 - 如果
ttype
值为TT_NUMBER
,则StreamTokenizer
实例字段,nval
,由令牌的double
值填充。
StreamTokenizer
实例的lineno()
方法返回当前行号。
在说 StreamTokenizer
类的其他方法之前,我们先看一个例子 .假设在项目 resources
文件夹中,有一个 tokens.txt
文件,其中包含以下四行文本:
以下代码将读取文件并标记其内容(InputOutputStream
类的 streamTokenizer()
方法):
我们使用了 BufferedReader
类,这是提高效率的好习惯,但在我们的例子中,我们可以轻松避免它,如下所示:
void eolIsSignificant(boolean flag)
:表示是否应将行尾视为标记void commentChar(int ch)
:表示注释是由哪个字符开始的,所以该行的其余部分被忽略int lineno()
:返回当前行号
可以使用 StreamTokenizer
对象调用以下方法:
void lowerCaseMode(boolean fl)
:指示单词标记是否应为小写void normalChar(int ch)
,void normalChars(int low, int hi)
:表示特定字符或字符范围必须被视为普通(而不是注释字符、单词组件、字符串分隔符、空格或数字字符)void parseNumbers()
:表示必须将具有双精度浮点数格式的单词标记解释为数字,而不是单词void pushBack()
:强制nextToken()
方法返回的当前值ttype
字段void quoteChar(int ch)
:表示必须将提供的字符解释为必须按原样获取的字符串值的开头和结尾(作为引号)void resetSyntax()
:重置此分词器的语法表,使所有字符普通void slashSlashComments(boolean flag)
:表示必须识别C++风格的注释void slashStarComments(boolean flag)
:表示C风格的注释有被认可String toString()
:返回token的字符串表示和行号void whitespaceChars(int low, int hi)
:表示必须解释为空格的字符范围void wordChars(int low, int hi)
:表示必须解释为单词的字符范围
如您所见,使用上述丰富的方法可以微调文本解释。
ObjectStreamClass and ObjectStreamField
ObjectStreamClass
和 ObjectStreamField
类 提供 访问在JVM中加载的类的序列化数据。 ObjectStreamClass
对象可以使用以下查找方法之一找到/创建:
static ObjectStreamClass lookup(Class cl)
:查找可序列化类的描述符static ObjectStreamClass lookupAny(Class cl)
:查找任何类的描述符,无论是否可序列化
找到ObjectStreamClass
且类可序列化后(实现Serializable
接口),即可访问
ObjectStreamField 对象,每个对象都包含一个序列化字段的信息。如果类不可序列化,则没有与任何字段关联的
ObjectStreamField
对象。
让我们看看 一个 的例子。这是显示从 ObjectStreamClass
和 ObjectStreamField
对象获得的信息的方法 :
为了演示它是如何工作的,我们将创建一个可序列化的 Person1
类:
我们没有添加方法,因为只有对象状态是可序列化的,而不是方法。现在,让我们运行 代码:
结果如下:
如您所见,有关于类名和所有字段名和类型的信息。还有两个其他方法可以使用 ObjectStreamField
对象调用:
boolean isPrimitive()
:如果此字段具有原始类型,则返回true
boolean isUnshared()
:如果此字段未共享(私有或只能从同一个包访问),则返回true
现在,让我们创建一个不可序列化的 Person2
类:
正如预期的那样,使用 lookup()
方法找不到不可序列化的对象。为了找到一个不可序列化的对象,我们需要使用 lookupAny()
方法:
如果我们运行前面的示例,结果将如下所示:
从不可序列化的 对象中,我们能够提取有关类的信息,但不能提取 关于 字段。
The java.util.Scanner class
java.util.Scanner
类是 通常用于 从键盘,但也可以从实现 Readable
接口的任何对象读取文本(此接口只有 int read(CharBuffer buffer)
方法)。它通过分隔符(空格是默认分隔符)将输入值分解为使用不同方法处理的标记。
例如,我们可以从 System.in
读取输入——标准输入流,通常表示键盘输入:
它接受多行(每行在按下 Enter 键后结束),直到输入 end
行,如下所示:
或者,Scanner
可以从文件中读取行:
如您所见,我们再次使用了 tokens.txt
文件。结果如下:
为了演示Scanner
通过分隔符打破输入,让我们运行以下命令代码:
结果如下:
要使用另一个分隔符,可以设置如下:
结果保持不变:
也可以使用正则表达式来提取标记,但该主题超出了本书的范围。
Scanner
类有许多其他方法,使其适用于各种来源和所需结果。 findInLine()
、findWithinHorizon()
、skip()
和findAll()
方法 不使用分隔符;他们只是尝试匹配提供的模式。如需 更多信息,请参阅扫描仪文档 (https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Scanner.html )。
File management
我们已经 已经使用了一些方法来使用 JCL 类查找、创建、读取和写入文件。为了支持输入/输出流的演示代码,我们必须这样做。在本节中,我们将更详细地讨论使用 JCL 进行文件管理。
java.io
包中的 File
类表示底层文件系统。 File
类的对象可以使用以下构造函数之一创建:
File(String pathname)
:根据提供的路径名创建一个新的File
实例File(String parent, String child)
:根据提供的父路径名和子路径名创建一个新的File
实例File(File parent, String child)
:根据提供的父File
实例>File 对象和子路径名File(URI uri)
:根据提供的URI
创建一个新的File
实例code> 表示路径名的对象
我们现在将看到 在讨论创建和删除文件时使用构造函数的一些示例。
Creating and deleting files and directories
要在文件系统中创建 文件或 目录,首先需要构造一个新的文件
对象使用 文件管理 部分中列出的构造函数之一。例如,假设文件名是 FileName.txt
,则 File
对象可以创建为 新文件(“文件名.txt”)
。如果必须在目录中创建文件,则必须在文件名前面添加路径(当它传递给构造函数时)或必须使用其他三个构造函数之一,例如以下 (请参阅 Files
类中的 createFile2()
方法):
请注意使用 File.separator
而不是斜杠符号 (/
) 或 ( \
)。这是因为 File.separator
返回特定于平台的斜杠符号。这是另一个 File
构造函数用法的示例:
可以使用另一个构造函数,如下所示:
但是,如果您更喜欢或必须使用通用资源标识符( URI),你可以像这样构造一个File
对象:
然后,必须在新创建的 File
对象上调用以下方法之一:
boolean createNewFile()
:如果同名文件尚不存在,则创建一个新文件并返回true
;否则,返回false
static File createTempFile(String prefix, String suffix)
:在临时文件目录下创建文件static File createTempFile(String prefix, String suffix, File directory)
:创建目录;提供的前缀和后缀用于生成目录名称
如果您要创建的文件必须放在尚不存在的目录中,则必须首先使用以下方法之一,在 File
对象上调用表示文件的文件系统路径:
boolean mkdir()
:使用提供的名称创建目录boolean mkdirs()
:使用提供的名称创建目录,包括任何必要但不存在的父目录
在我们看一个代码示例之前,我们需要解释一下delete()< /code> 方法有效:
boolean delete()
:删除文件或空目录,表示可以删除文件,但不能删除所有目录,如下:
让我们看看如何在以下示例中克服此限制:
此示例创建和删除文件和所有相关目录。请注意我们对 org.apache.commons.lang3.StringUtils
类的使用,我们在 String utility 部分中讨论了该类。它允许我们从路径中删除刚刚删除的目录并 继续这样做,直到所有嵌套的 目录都被删除,最后删除顶层目录。
Listing files and directories
String[] list()
:返回目录中文件和目录的名称File[] listFiles()
:返回代表目录中文件和目录的File
对象static File[] listRoots()
:列出可用的文件系统根
为了演示上述方法,假设我们已经创建了目录和其中的两个文件,如下所示:
结果应如下所示:
演示的 方法可以通过向它们添加以下过滤器来增强,以便它们仅列出与过滤器匹配的文件和目录:
String[] list(FilenameFilter 过滤器)
File[] listFiles(FileFilter filter)
File[] listFiles(FilenameFilter filter)
然而,文件过滤器的讨论超出了本书的范围。
Apache Commons’ FileUtils and IOUtils utilities
JCL 的一个受欢迎的 伙伴是 Apache Commons 项目 (https://commons.apache.org),它提供了许多补充JCL功能的库。 org.apache.commons.io
包的类包含在以下根包和子包中:
org.apache.commons.io
根包包含具有用于常见任务的静态方法的实用程序类,例如流行的FileUtils
和IOUtils
类,分别在 FileUtils class 和 Class IOUtils class 部分中描述.org.apache.commons.io.input
包包含支持基于InputStream
和Reader
实现,例如XmlStreamReader
或ReversedLinesFileReader
。org.apache.commons.io.output
包包含支持基于OutputStream
和Writer
实现,例如XmlStreamWriter
或StringBuilderWriter
。org.apache.commons.io.filefilter
包包含用作文件过滤器的类,例如DirectoryFileFilter
或正则表达式文件过滤器
。org.apache.commons.io.comparator
包包含java.util.Comparator
文件的各种实现,例如NameFileComparator
。org.apache.commons.io.serialization
包提供了一个控制类反序列化的框架。org.apache.commons.io.monitor
包允许监视文件系统、检查目录以及创建、更新或删除文件。您可以将FileAlterationMonitor
对象作为线程启动,并创建一个FileAlterationObserver
对象来检查 文件系统在指定的时间间隔。
有关详细信息,请参阅 Apache Commons 项目文档 (https://commons.apache.org/)。
The FileUtils class
流行的 org.apache.commons.io.FileUtils
类允许您对文件执行所有可能的操作,如下所示:
- 写入文件
- 从文件中读取
- 制作目录,包括父目录
- 复制文件和目录
- 删除文件和目录
- 与 URL 相互转换
- 按过滤器和扩展名列出文件和目录
- 比较文件内容
- 获取文件的最后更改日期
- 计算校验和
如果您计划以编程方式管理文件和目录,则必须在 Apache Commons 项目网站 (https://commons.apache.org/proper/commons-io /javadocs/api-2.7/org/apache/commons/io/FileUtils.html)。
The IOUtils class
org.apache.commons.io.IOUtils
是另一个非常有用的实用程序类,它提供了 以下通用 I/O 流操作方法:
closeQuietly
方法关闭流,忽略空值和异常- 从流中读取数据的
toXxx/read
方法 - 将数据写入流的
write
方法 copy
方法将所有数据从一个流复制到另一个- 比较两个流的内容的
contentEquals
方法
该类中读取流的所有方法都在内部缓冲,因此无需使用 BufferedInputStream
或 BufferedReader
类. copy
方法都在后台使用 copyLarge
方法,大大提高了它们的性能和效率。
这个类对于管理 I/O 流是必不可少的。在 Apache Commons 项目网站 (https://commons.apache.org/proper/commons-io /javadocs/api-2.7/org/apache/commons/io/IOUtils.html)。
Summary
在本章中,我们讨论了允许分析、比较和转换字符串的 String
类方法。我们还讨论了来自 JCL 和 Apache Commons 项目的流行字符串实用程序。本章的两个主要部分专门介绍了 JCL 和 Apache Commons 项目中的输入/输出流和支持类。文件管理类及其方法也在特定的代码示例中进行了讨论和演示。现在,您应该能够使用标准 Java API 和 Apache Commons 实用程序编写处理字符串和文件的代码。
在下一章中,我们将介绍 Java Collections 框架及其三个主要接口 List
、Set
和 Map
,包括泛型的讨论和演示。我们还将讨论用于管理数组、对象和 time
/date
值的实用程序类。
Quiz
- 下面的代码打印什么?
3
2
1
0
- 下面的代码打印什么?
错误
异常
真
假
- 下面的代码打印什么?
wx
x6
%w
异常
- 下面的代码打印什么?
ab4242
ab42ab42
ab422
错误
- 下面的代码打印什么?
false false
false true
真真
真假
- 选择所有正确的陈述:
- 一个流可以代表一个数据源。
- 输入流可以写入文件。
- 一个流可以代表一个数据目的地。
- 输出流可以在屏幕上显示数据。
- 选择关于
java.io
包的类的所有正确陈述:- 阅读器扩展了
InputStream
。 - 阅读器扩展
OutputStream
。 - 阅读器扩展了
java.lang.Object
。 - 阅读器扩展了
java.lang.Input
。
- 阅读器扩展了
- 选择关于
java.io
包的类的所有正确陈述:- Writer 扩展了
FilterOutputStream
。 - Writer 扩展了
OutputStream
。 - Writer 扩展了
java.lang.Output
。 - Writer 扩展了
java.lang.Object
。
- Writer 扩展了
- 选择关于
java.io
包的类的所有正确陈述:PrintStream
扩展了FilterOutputStream
。PrintStream
扩展了OutputStream
。PrintStream
扩展了java.lang.Object
。PrintStream
扩展了java.lang.Output
。
- 下面的代码有什么作用?
- 在
demo2
目录下创建两个目录和一个文件 - 在其中创建一个目录和一个文件
- 不创建任何目录
- 例外
- 在