Java编程实践与技巧解析


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

替换为实际的文件路径。

© 版权声明

相关文章

暂无评论

none
暂无评论...