58、编写一个对 reduce 方法的调用,用于计算 Stream
的平均值。为什么不能简单地计算总和并除以 count() 呢?
要计算
Stream<Double>
的平均值,可使用
reduce
方法结合自定义逻辑。示例代码如下:
Optional<Double> average = stream.reduce((sum, num) -> sum + num).map(sum -> sum / stream.count());
不能简单计算总和并除以
count()
的原因在于,
count()
操作会消耗流,之后再进行
reduce
计算总和时流已被使用,会抛出异常。并且在并行流中,
count()
和
reduce
操作可能会相互影响,导致结果不准确。
59、如何从流中消除相邻重复项?如果流是并行的,你的方法是否有效?
可使用
Stream.distinct
方法消除相邻重复项。若流是有序的,
distinct
会保留所有相等元素中的第一个,这会阻碍并行化,因为处理某一段的线程在处理前一段之前无法知道要丢弃哪些元素;若不关心保留哪些唯一元素,可调用
Stream.unordered
方法表示不关心顺序,此时所有段可以并行处理(使用共享集来跟踪重复项),从而使
distinct
操作受益于并行化。
60、编写一个程序,读取一个文本文件,并生成一个扩展名为 .toc 的同名文件,该文件包含输入文件中所有单词的按字母顺序排列的列表,以及每个单词出现的行号列表。假设文件的编码为 UTF – 8。
以下是一个 Java 程序示例,用于实现读取文本文件并生成
.toc
文件的功能:
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
public class WordIndexer {
public static void main(String[] args) {
String inputFilePath = "your_input_file.txt";
Path inputPath = Paths.get(inputFilePath);
Path outputPath = Paths.get(inputFilePath.replaceFirst("(?s)(.*)\.[^.]+$", "$1.toc"));
try {
// 读取文件的所有行
List<String> lines = Files.readAllLines(inputPath, StandardCharsets.UTF_8);
// 用于存储单词及其出现的行号
Map<String, Set<Integer>> wordIndex = new TreeMap<>();
// 遍历每一行
for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
String line = lines.get(lineNumber);
// 将行拆分为单词
String[] words = line.split("\W+");
for (String word : words) {
if (!word.isEmpty()) {
word = word.toLowerCase();
// 如果单词不在索引中,创建一个新的行号集合
wordIndex.computeIfAbsent(word, k -> new TreeSet<>()).add(lineNumber + 1);
}
}
}
// 将结果写入 .toc 文件
try (BufferedWriter writer = Files.newBufferedWriter(outputPath, StandardCharsets.UTF_8)) {
for (Map.Entry<String, Set<Integer>> entry : wordIndex.entrySet()) {
String word = entry.getKey();
Set<Integer> lineNumbers = entry.getValue();
// 写入单词
writer.write(word + ": ");
// 写入行号
boolean first = true;
for (int lineNumber : lineNumbers) {
if (!first) {
writer.write(", ");
}
writer.write(String.valueOf(lineNumber));
first = false;
}
writer.newLine();
}
}
System.out.println("TOC 文件已生成:" + outputPath);
} catch (IOException e) {
e.printStackTrace();
}
}
}
代码说明:
读取文件
:使用
Files.readAllLines
方法读取输入文件的所有行。
构建单词索引
:遍历每一行,将行拆分为单词,并将每个单词及其出现的行号存储在
TreeMap
中。
写入 .toc 文件
:将单词及其行号按字母顺序写入
.toc
文件。
使用方法:
将
inputFilePath
替换为实际的输入文件路径,运行程序即可生成
.toc
文件。
61、使用Scanner很方便,但比使用BufferedReader稍慢。逐行读取一个长文件,统计输入行数,分别使用(a)Scanner搭配hasNextLine/nextLine方法,(b)BufferedReader搭配readLine方法,(c)BufferedReader搭配lines方法。哪种方式最快?哪种最方便?
最快的方式是(c)
BufferedReader
搭配
lines
方法,因为
BufferedReader
以块为单位读取输入,而
lines
方法能以流的形式处理行,效率较高。
最方便的方式是(a)
Scanner
搭配
hasNextLine
/
nextLine
方法,因为
Scanner
类提供了方便的方法来检查和读取不同类型的输入,代码编写更直观简单。
62、使用 URLConnection 类,从需要“基本”身份验证的受密码保护的网页读取数据。具体步骤为:先将用户名、冒号和密码连接起来,再计算其 Base64 编码,代码示例如下:String input = username + “:” + password; String encoding = Base64.getEncoder().encodeToString(input.getBytes(StandardCharsets.UTF_8)); 然后将 HTTP 头 Authorization 设置为值 “Basic ” + encoding。最后读取并打印页面内容。
以下是实现该功能的 Java 代码示例:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class PasswordProtectedPageReader {
public static void main(String[] args) {
try {
// 网页 URL
URL url = new URL("https://example.com/password-protected-page");
// 用户名和密码
String username = "your_username";
String password = "your_password";
// 拼接用户名和密码并进行 Base64 编码
String input = username + ":" + password;
String encoding = Base64.getEncoder().encodeToString(input.getBytes(StandardCharsets.UTF_8));
// 打开连接
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 设置请求方法
connection.setRequestMethod("GET");
// 设置 Authorization 头
connection.setRequestProperty("Authorization", "Basic " + encoding);
// 获取响应码
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
// 读取响应内容
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
// 打印响应内容
System.out.println(response.toString());
} else {
System.out.println("请求失败,响应码: " + responseCode);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
请将
https://example.com/password-protected-page
替换为实际的受密码保护的网页 URL,将
your_username
和
your_password
替换为实际的用户名和密码。
63、提出一个在Matcher.replaceAll中使用组引用的现实用例并实现它。
用例:将时间格式从“小时:分钟”替换为“小时小时和分钟分钟”。实现代码如下:
String result = "3:45".replaceAll("(\d{1,2}):(?<minutes>\d{2})", "$1小时和${minutes}分钟");
// 结果为 "3小时和45分钟"
64、实现一个可序列化的类Point,该类包含用于表示x和y的实例变量。编写一个程序将Point对象数组序列化到文件中,另一个程序从文件中读取该数组。
以下是实现该功能的 Java 代码示例:
import java.io.*;
// 可序列化的 Point 类
class Point implements Serializable {
private double x;
private double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
@Override
public String toString() {
return "Point{x=" + x + ", y=" + y + "}";
}
}
// 序列化 Point 对象数组到文件的程序
class SerializePoints {
public static void main(String[] args) {
Point[] points = {new Point(1, 2), new Point(3, 4), new Point(5, 6)};
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("points.ser"))) {
oos.writeObject(points);
System.out.println("Points serialized successfully.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 从文件中读取 Point 对象数组的程序
class DeserializePoints {
public static void main(String[] args) {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("points.ser"))) {
Point[] points = (Point[]) ois.readObject();
for (Point point : points) {
System.out.println(point);
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
上述代码中,首先定义了可序列化的
Point
类,包含
x
和
y
实例变量。
SerializePoints
类将
Point
对象数组序列化到
points.ser
文件中,
DeserializePoints
类从该文件中读取
Point
对象数组并打印。
65、继续前面的练习,但更改Point的数据表示方式,使其将坐标存储在一个数组中。当新版本尝试读取旧版本生成的文件时会发生什么?修复serialVersionUID后会发生什么?假设你的生命取决于让新版本与旧版本兼容,你能做什么?
当新版本尝试读取旧版本生成的文件时,若未指定
serialVersionUID
,由于类的结构改变,自动生成的
serialVersionUID
可能不同,
readObject
方法会抛出
InvalidClassException
。
若指定了相同的
serialVersionUID
,只要名称和类型匹配:
非瞬态实例变量会被设置为序列化状态中的值
其他实例变量设为默认值
序列化状态中不存在于要读取对象的内容会被忽略
若要让新版本与旧版本兼容,可采取以下方法:
使用
serialver
工具
:对旧版本的类运行
serialver
工具,将结果添加到新版本中。
重写
readObject
方法
:调用
readFields
方法而非
defaultReadObject
方法,自行处理流中的所有字段。
66、在本次练习中,你将使用 parallelPrefix 方法来并行计算斐波那契数。利用这样一个事实:第 n 个斐波那契数是 Fn 的左上角系数。创建一个填充有 2×2 矩阵的数组。定义一个带有乘法方法的 Matrix 类,使用 parallelSetAll 方法创建一个矩阵数组,并使用 parallelPrefix 方法将它们相乘。
编程练习说明
本题是一个编程练习,需要完成以下步骤:
创建一个填充 2×2 矩阵的数组;
定义一个
Matrix
类,该类包含乘法方法;
使用
parallelSetAll
方法创建矩阵数组;
使用
parallelPrefix
方法将矩阵数组中的矩阵相乘,以并行计算斐波那契数。
67、编写一个应用程序,其中多个线程从一组文件中读取所有单词。使用 ConcurrentHashMap
> 来跟踪每个单词出现在哪些文件中。使用 merge 方法来更新该映射。
以下是一个 Java 示例代码,展示了如何实现这个应用程序:
import java.io.File;
import java.io.FileNotFoundException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class WordFileTracker {
private static final ConcurrentHashMap<String, Set<File>> wordFileMap = new ConcurrentHashMap<>();
public static void main(String[] args) {
// 假设这里有一组文件
List<File> files = Arrays.asList(
new File("file1.txt"),
new File("file2.txt")
);
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
// 为每个文件创建一个任务
for (File file : files) {
executor.submit(() -> processFile(file));
}
// 关闭线程池并等待所有任务完成
executor.shutdown();
try {
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输出结果
for (Map.Entry<String, Set<File>> entry : wordFileMap.entrySet()) {
System.out.println(entry.getKey() + " 出现在以下文件中: ");
for (File file : entry.getValue()) {
System.out.println(" - " + file.getName());
}
}
}
private static void processFile(File file) {
try (Scanner scanner = new Scanner(file)) {
while (scanner.hasNext()) {
String word = scanner.next();
// 使用 merge 方法更新映射
wordFileMap.merge(word, new HashSet<>(Collections.singletonList(file)), (existingSet, newSet) -> {
existingSet.addAll(newSet);
return existingSet;
});
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
代码解释:
ConcurrentHashMap<String, Set<File>>
:用于存储每个单词及其出现的文件集合。
线程池
:使用
ExecutorService
创建一个固定大小的线程池,每个文件对应一个任务。
processFile
方法
:读取文件中的每个单词,并使用
merge
方法更新
ConcurrentHashMap
。如果单词不存在,则创建一个新的文件集合;如果单词已存在,则将当前文件添加到现有的文件集合中。
输出结果
:遍历
ConcurrentHashMap
并输出每个单词及其出现的文件。
请确保将
file1.txt
和
file2.txt
替换为实际的文件路径。




