Java作业-10

数据结构相关

描述并比较散列映射表HashMap、链式散列映射表LinkedHashMap和树形映射表TreeMap

特性 HashMap LinkedHashMap TreeMap
内部数据结构 基于散列 (哈希表) 哈希表 + 双向链表 红黑树
顺序 无序 插入顺序或访问顺序 自然排序或自定义排序
Key-Value对的唯一性 唯一的Key,可以有null的Key和Value 唯一的Key,可以有null的Key和Value 唯一的Key,不允许null的Key,可以有null的Value
性能 插入、删除、定位速度快 略慢于HashMap (因为维护了链表) 由于红黑树的特性,插入、删除、搜索的时间复杂度为 O(log n)
适用场景 需要快速查找且不关心顺序的场景 需要保持映射的插入顺序或访问顺序 需要一个按照自然顺序或自定义顺序排序的映射表
迭代器行为 效率较高,但在迭代过程中修改映射可能导致迭代器快速失败 效率略低,但在迭代过程中修改映射不会影响迭代器 效率取决于树的平衡程度,迭代器在修改映射时快速失败
注意 适用于对插入和查找操作有高性能要求的场景。注意在多线程环境下,HashMap 不是线程安全的,需要额外的同步措施。 当需要保持映射的插入顺序或访问顺序时使用。性能略低于 HashMap,但提供了顺序性。 当需要一个按照排序顺序来存储键值对的映射时使用,例如范围查找或排序输出。注意 TreeMap 的键需要具有可比性,要么实现 Comparable 接口,要么在构造 TreeMap 时提供 Comparator。

如何创建Map的一个实例?如何向由键和值组成的映射表中添加一个条目?如何从映射表中删除一个条目?如何获取映射表的大小?如何遍历映射表中的条目?

// 创建Map实例
Map<KeyType, ValueType> map = new HashMap<>(); // 创建 HashMap 实例
Map<KeyType, ValueType> linkedHashMap = new LinkedHashMap<>(); // 创建 LinkedHashMap 实例
Map<KeyType, ValueType> treeMap = new TreeMap<>(); // 创建 TreeMap 实例
// KeyType 和 ValueType 分别代表映射表中键和值的类型。

// 向映射表中添加一个条目
map.put(key, value); // 添加或更新键值对
// 如果映射表中已存在相同的键,这个方法会更新其对应的值。

// 从映射表中删除一个条目
map.remove(key); // 删除指定键的条目
这个方法会删除映射表中与指定键关联的条目。

// 获取映射表的大小
int size = map.size(); // 获取映射表中的条目数

// 遍历映射表中的条目
// for-each 循环遍历
for (Map.Entry<KeyType, ValueType> entry : map.entrySet()) {
    KeyType key = entry.getKey();
    ValueType value = entry.getValue();
    // 处理键和值
}
// 使用 Java8 的 forEach 方法遍历
map.forEach((key, value) -> {
    // 处理键和值
});

什么是可运行对象?线程是什么?

  • 可运行对象
    在 Java 中,可运行对象(Runnable object)通常是指实现了 Runnable 接口的类的实例。Runnable 接口是一个函数式接口,它只有一个没有参数的方法 run()。当一个类实现了这个接口,它就需要提供 run() 方法的实现。这个 run() 方法定义了当线程启动时要执行的操作。
    可运行对象通常用于多线程编程。通过将任务的代码放在 run() 方法中,可以将任务的执行和线程的控制分离开来,这是一种符合单一职责原则的设计。

    public class MyRunnable implements Runnable {
        public void run() {
            // 定义任务
        }
    }
  • 线程
    线程是程序执行流的最小单元。它是进程中的一个实体,是被系统独立调度和分派的基本单位。线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器、一组寄存器和栈),但它可以与同属一个进程的其他线程共享进程所拥有的全部资源。
    线程在运行时具有自己的执行路径,而且可以与其他线程并发执行。使用线程可以实现异步处理和并行处理,提高程序的执行效率和响应速度。但是,线程的使用也带来了线程安全、死锁等多线程编程中的复杂问题,需要仔细处理。

    可以通过两种方式实现:

    1. 继承 Thread 类:通过继承 Thread 类并重写其 run() 方法来定义线程的行为。

      public class MyThread extends Thread {
          public void run() {
      // 定义线程执行的任务
          }
      }
    2. 实现 Runnable 接口:更通用的方式是实现 Runnable 接口并将其实例传递给 Thread 类的实例。

      MyRunnable myRunnable = new MyRunnable();
      Thread thread = new Thread(myRunnable);
      thread.start(); // 启动线程

如何定义一个任务类?如何为任务创建一个线程?

见上

run()换成start()

  • 使用 start() 方法:
    当你调用 start() 方法时,它会启动一个新的线程,并在新线程中执行 run() 方法。
    多个线程同时执行,实现并发执行,可以提高程序的效率。
    start() 方法不会阻塞当前线程,而是立即返回。
  • 使用 run() 方法:
    直接调用 run() 方法不会启动新线程,而是在当前线程中执行 run() 方法的内容。
    不会实现多线程的并发执行,而是按照顺序依次执行。

Start:乱序执行
Run:顺序执行

如下代码有什么错误?

// 原
// 在构造器 Test() 中,有一个无限递归的调用。new Test(); 这个调用会不断地创建 Test 类的新实例,并且每个新实例又会创建更多的实例,最终导致 StackOverflowError
public class Test implements Runnable {
    public static void main(String[] args) {
        new Test();
    }

    @Override
    public Test(){
        Test task = new test();
        new thread(task).start();
    }

    public void run() {
        System.out.println("test");
    }
}

// 改
// 为了避免无限递归,应该在 main 方法中创建一个 Test 对象,并从那里开始执行线程。
public class Test implements Runnable {
    public static void main(String[] args) {
        Test task = new Test();
        new Thread(task).start();
    }

    @Override
    public void run() {
        System.out.println("test");
    }
}
// 原
// 在构造器 Test() 中,尝试启动同一个线程两次。在 Java 中,一个线程只能被启动一次,第二次调用 start() 会抛出 IllegalThreadStateException。
public class Test implements Runnable {
    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        Thread t = new Thread(this);
        t.start();
        t.start();
    }

    @Override
    public void run() {
        System.out.println("test");
    }
}

// 改
// 删除第二次调用 t.start();,以确保线程只被启动一次。
public class Test implements Runnable {
    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        Thread t = new Thread(this);
        t.start();
    }

    @Override
    public void run() {
        System.out.println("test");
    }
}

使用Platform.runLater的目的是什么?

用于确保一个 Runnable 对象的 run 方法将在 JavaFX 的主线程(也称为 GUI 线程或事件派发线程)上运行。
在 JavaFX 中,与 UI 相关的操作必须在主线程上执行以防止线程冲突和不一致的 UI 更新。
使用情形:

  1. 从非GUI线程更新UI
    如果在后台线程中执行长时间运行的任务,并且需要更新 UI(如进度条、文本标签等),应该使用 Platform.runLater 来确保这些更新在正确的线程上执行。
  2. 延迟UI操作
    有时可能希望在应用程序的其他部分已经完全启动和渲染后才执行某些 UI 操作。Platform.runLater 可以用来排队这些操作,直到主线程准备好执行它们。
  3. 避免线程问题:
    由于JavaFX的UI组件不是线程安全的,尝试从另一个线程修改它们会导致运行时错误。Platform.runLater 可以保证在主线程上执行修改,从而避免这些问题。
// 假设我们在一个后台线程中完成了一些工作,现在需要更新UI
Thread backgroundThread = new Thread(() -> {
    // 执行一些耗时任务
    // ...
    // 现在需要更新UI,使用Platform.runLater
    Platform.runLater(() -> {
        // 这段代码现在在JavaFX主线程上执行
        label.setText("任务完成!");
    });
});
backgroundThread.start();

// 在这个例子中,后台线程完成一些工作后,使用 Platform.runLater 来更新一个标签(label)的文本。这个 Runnable 会被安排在 JavaFX 主线程上执行,从而安全地更新 UI 组件。

可以将代码替换为 Platform.runLater(e-> lblText.setText(text))

可以

什么是线程状态?描述一个线程的状态

线程状态 描述
新建 (New) 线程已经被创建,但是还没有开始执行(还没有调用 start() 方法)。
可运行 (Runnable) 线程可能正在运行,也可能正在等待CPU分配时间片,这取决于线程调度器
阻塞 (Blocked) 线程被阻止,等待监视器锁(进入同步块或方法)。
等待 (Waiting) 线程在等待另一个线程执行一个(无时间限制的)操作。例如,调用了 Object.wait()、Thread.join() 或 LockSupport.park()。
定时等待 (Timed Waiting) 线程在等待另一个线程执行一个(有时间限制的)操作。例如,调用了 Thread.sleep()、Object.wait() 带有时间的版本、Thread.join() 带有时间的版本或 LockSupport.parkNanos()/parkUntil()。
终止 (Terminated) 线程的 run() 方法执行完成,线程正常结束或者出现异常导致结束。
  1. 新建状态 (New)

    • Thread thread = new Thread(runnableTask); 创建线程对象 thread
  2. 可运行状态 (Runnable)

    • thread.start(); 线程可以被运行。
    • 线程可能正在运行,也可能正在等待 CPU 分配时间。
  3. 阻塞状态 (Blocked)

    • 线程试图进入同步块,但该块被其他线程锁定。
  4. 等待状态 (Waiting)

    • 线程在对象上调用 wait(),等待其他线程的通知。
  5. 定时等待状态 (Timed Waiting)

    • 线程调用 sleep()wait(long timeout) 等,有一定的时限。
  6. 终止状态 (Terminated)

    • 线程的 run() 方法执行完毕,或者线程被中断。

(统计输入数字的个数)编写一个程序,读取个数不定的整数,然后查找其中出现频率最高的数字。当输人为0时,表示结束输人。如果出现频率最高的数字不是一个而是多个,则应该将它们全部报告

import java.util.*;

public class FrequencyCounter {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        Map<Integer, Integer> numberFrequency = new HashMap<>();
        int maxFrequency = 0;

        System.out.println("输入数字 (到 0 终止):");

        while (true) {
            int num = scanner.nextInt();
            if (num == 0) {
                break;
            }

            int currentFrequency = numberFrequency.getOrDefault(num, 0) + 1;
            numberFrequency.put(num, currentFrequency);
            maxFrequency = Math.max(maxFrequency, currentFrequency);
        }

        // 找到所有出现频率最高的数字
        List<Integer> maxNumbers = new ArrayList<>();
        for (Map.Entry<Integer, Integer> entry : numberFrequency.entrySet()) {
            if (entry.getValue() == maxFrequency) {
                maxNumbers.add(entry.getKey());
            }
        }

        // 报告结果
        System.out.println("频次最高为" + maxFrequency +", 对应的数字为"+ ":");
        for (int num : maxNumbers) {
            System.out.println(num);
        }

        scanner.close();
    }
}
输出:
输入数字 (到 0 终止):
1 2 3 2 2 3 3 0
频次最高为3, 对应的数字为:
2
3

(改写程序清单21-9)改写程序清单21-9,将单词按出现频率的升序显示

提示:创建一个名为 WordOccurence 的类实现Comparable接口。这个类包含两个域:word和count。使用compareTo方法比较单词的出现次数。对程序清单21-9 散列集中的每个对创建WordOccurence的一个实例,并把它储存到一个数组线性表中。使用Collections.sort 方法对该数组线性表进行排序。如果将WordOccurence的实例存入树形集,会发生什么错误?

import java.util.*;
import java.util.stream.Collectors;

public class WordCounter {
 public static void main(String[] args) {
  String text = "The using directive in the example " +
    "enables you to use the library identifiers " +
    "without having to write out the namespace prefix " +
    "oneapi::tbb before each identifier. " +
    "The rest of the examples assume that such a using directive is present.";

  Map<String, WordOccurrence> map = new TreeMap<>();

  // 正则分词
  String[] words = text.split("\\W+");
  for (String word : words) {
   String key = word.toLowerCase();

   map.computeIfAbsent(key, k -> new WordOccurrence(k, 0))
     .incrementCount();
  }

  map.values().forEach(v -> System.out.println(v));

  // Use Stream API to sort and collect the words by frequency
  List<WordOccurrence> sortedByFrequency = map.values().stream()
    .sorted(Comparator.comparingInt(WordOccurrence::getCount).reversed())
    .collect(Collectors.toList());

  System.out.println(sortedByFrequency);
 }
}

class WordOccurrence implements Comparable<WordOccurrence> {
 private final String word;
 private int count;

 public WordOccurrence(String word, int count) {
  this.word = word;
  this.count = count;
 }

 public void incrementCount() {
  this.count++;
 }

 public int getCount() {
  return count;
 }

 @Override
 public int compareTo(WordOccurrence other) {
  return Integer.compare(other.count, this.count); 
 }

 @Override
 public String toString() {
  return word + " " + count;
 }
}
a 1
assume 1
before 1
directive 2
each 1
enables 1
example 1
examples 1
having 1
identifier 1
identifiers 1
in 1
is 1
library 1
namespace 1
of 1
oneapi 1
out 1
prefix 1
present 1
rest 1
such 1
tbb 1
that 1
the 6
to 2
use 1
using 2
without 1
write 1
you 1
[the 6, directive 2, to 2, using 2, a 1, assume 1, before 1, each 1, enables 1, example 1, examples 1, having 1, identifier 1, identifiers 1, in 1, is 1, library 1, namespace 1, of 1, oneapi 1, out 1, prefix 1, present 1, rest 1, such 1, tbb 1, that 1, use 1, without 1, write 1, you 1]

(升旗)使用线程模拟升旗动画来改写程序清单 15-13。通过将两个序中的延迟时间都设置为10,比较此程序和程序清单15-13中的程序,哪个运行动画快一些?

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;

public class RisingFlag extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        Pane pane = new Pane();
        ImageView imageView = new ImageView("E:\\Repos\\Course\\Java\\my\\code\\W3\\demo\\src\\images\\china.gif");
        pane.getChildren().add(imageView);
        imageView.setLayoutX(20);
        imageView.setLayoutY(200);
        new Thread(() -> {
            try {
                while (true) {

                    Platform.runLater(() -> {
                        if (imageView.getLayoutY() >= 5) {
 imageView.setLayoutY(imageView.getLayoutY() - 5);
                        } else {
 imageView.setLayoutY(200);
                        }
                    });
                    Thread.sleep(10);
                }
            } catch (InterruptedException e) {

                e.printStackTrace();
            }
        }).start();

        Scene scene = new Scene(pane, 250, 200);
        primaryStage.setTitle("RisingFlag");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

应该是这个版本快一点,线程独立不会被阻塞。