当前位置: 首页 > article >正文

每日 Java 面试题分享【第 13 天】

欢迎来到每日 Java 面试题分享栏目!
订阅专栏,不错过每一天的练习

今日分享 3 道面试题目!

评论区复述一遍印象更深刻噢~

目录

  • 问题一:如何在 Java 中调用外部可执行程序或系统命令?
  • 问题二:如果一个线程在 Java 中被两次调用 start() 方法,会发生什么?
  • 问题三:栈和队列在 Java 中的区别是什么?

问题一:如何在 Java 中调用外部可执行程序或系统命令?

在 Java 中,可以通过 RuntimeProcessBuilder 调用外部可执行程序或系统命令。以下是两种方法的详细说明和使用示例。


方法 1:使用 Runtime

代码示例
public class RuntimeExample {
    public static void main(String[] args) {
        try {
            // 调用系统命令(如 Windows 的 dir 或 Linux 的 ls)
            Process process = Runtime.getRuntime().exec("ls"); // 替换为需要的命令
            // 获取命令执行的输出
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    System.out.println(line);
                }
            }
            // 等待命令执行完成
            int exitCode = process.waitFor();
            System.out.println("退出码:" + exitCode);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
注意
  1. 返回的 Process 对象
    • Process 表示外部进程,可以用来获取输出流、错误流,并控制进程的生命周期。
  2. 输出流的读取
    • 如果不读取或关闭进程的输出流,可能会导致进程阻塞。
优点
  • 简单直接,代码量少。
缺点
  • 不够灵活,难以传递复杂参数或处理多个 I/O。

方法 2:使用 ProcessBuilder

代码示例
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class ProcessBuilderExample {
    public static void main(String[] args) {
        try {
            // 创建 ProcessBuilder
            ProcessBuilder processBuilder = new ProcessBuilder();
            // 设置要执行的命令(可带参数)
            processBuilder.command("ping", "www.google.com");
            // 合并错误流和标准输出流(可选)
            processBuilder.redirectErrorStream(true);

            // 启动进程
            Process process = processBuilder.start();

            // 读取进程输出
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    System.out.println(line);
                }
            }

            // 等待进程完成并获取退出码
            int exitCode = process.waitFor();
            System.out.println("退出码:" + exitCode);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
优点
  • 更灵活
    • 支持设置环境变量:processBuilder.environment().put("ENV_VAR", "value");
    • 支持设置工作目录:processBuilder.directory(new File("/path/to/dir"));
  • 可读性好:链式调用清晰明了。
缺点
  • Runtime 稍微复杂一点。

两种方法的对比

特性RuntimeProcessBuilder
使用简单性更简单略复杂
灵活性较低较高
支持环境变量设置不支持支持
合并输出和错误流需要手动实现直接支持(redirectErrorStream
推荐程度适合简单命令调用更推荐,适合复杂调用场景

常见使用场景

  1. 运行系统命令

    • 例如在 Linux 上执行 ls 或在 Windows 上执行 dir
    • 使用 ProcessBuildercommand 方法可以方便地传递参数。
  2. 调用外部可执行程序

    • 例如运行 .exe 文件、Python 脚本、Shell 脚本等。
    • 确保路径正确,并且有足够权限执行外部程序。
  3. 环境变量控制

    • 使用 ProcessBuilder.environment() 可以轻松传递自定义的环境变量。
  4. 读取命令输出

    • 无论是标准输出还是错误输出,Java 都可以捕获并处理。

注意事项

  1. 路径问题

    • 确保外部命令或可执行程序的路径正确,建议使用绝对路径。
    • 如果使用相对路径,请确保工作目录正确设置(ProcessBuilder.directory())。
  2. 阻塞问题

    • 如果外部进程产生大量输出,但未被读取,会导致阻塞。
    • 建议及时读取或关闭进程的输出和错误流。
  3. 跨平台性

    • 不同操作系统的命令语法可能不同,编写代码时需注意适配性。
  4. 权限问题

    • 运行外部程序可能需要特定的权限,特别是在受限的环境(如服务器)中。

扩展

如何执行带空格的命令或参数?
  • 使用 ProcessBuilder.command() 方法,将每个参数单独传递为列表元素。

    ProcessBuilder processBuilder = new ProcessBuilder();
    processBuilder.command("cmd.exe", "/c", "echo", "Hello World!");
    
如何处理输入流(标准输入)?
  • 使用 Process 对象的 getOutputStream() 方法,向外部进程写入数据。

    Process process = new ProcessBuilder("cat").start();
    try (BufferedWriter writer = new BufferedWriter(
            new OutputStreamWriter(process.getOutputStream()))) {
        writer.write("Hello from Java!");
        writer.flush();
    }
    

总结

在 Java 中调用外部程序时:

  • 简单任务使用 Runtime.getRuntime().exec()
  • 复杂任务优先使用 ProcessBuilder,以获得更好的灵活性和控制力。

问题二:如果一个线程在 Java 中被两次调用 start() 方法,会发生什么?

问题分析

在 Java 中,Thread 类的 start() 方法被用来启动一个新线程。如果尝试对同一个线程对象调用两次 start() 方法,会发生异常。


答案

如果对同一个线程对象调用两次 start() 方法,第二次调用会抛出 IllegalThreadStateException 异常。这是因为线程一旦启动后,其状态会从 NEW(新建) 转变为其他状态(如 RUNNABLETERMINATED 等)。根据 Java 线程模型,已经启动过的线程对象不能被重新启动。


代码示例

public class ThreadStartExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("线程正在运行…");
        });

        // 第一次启动线程
        thread.start();

        // 再次调用 start() 方法
        try {
            thread.start(); // 这里会抛出 IllegalThreadStateException
        } catch (IllegalThreadStateException e) {
            System.out.println("异常信息:线程已经启动过,不能再次调用 start()");
        }
    }
}
运行结果
线程正在运行…
异常信息:线程已经启动过,不能再次调用 start()

原因分析

1. 线程生命周期

线程的生命周期如下:

  • NEW:线程对象被创建,但未调用 start()
  • RUNNABLE:调用 start() 后,线程处于可运行状态。
  • TERMINATED:线程运行完毕,进入终止状态。

当线程离开 NEW 状态后,不能回到 NEW,因此无法再次启动。

2. start() 方法的作用

start() 方法的核心功能是:

  • 通知 JVM 创建一个新的线程(底层通过本地方法调用操作系统线程)。
  • 将线程状态从 NEW 改为 RUNNABLE,并让线程进入可调度队列。

第二次调用 start() 时,由于线程不再是 NEW 状态,JVM 会拒绝这个操作,抛出异常。

3. 设计初衷

Java 线程模型的设计目的是让每个 Thread 对象只启动一次,避免复杂的状态管理(如重新初始化线程)。如果需要再次启动线程,应该创建一个新的 Thread 实例。


扩展讲解

如何避免这种问题?
  • 检查线程状态:如果需要对线程进行管理,可以通过 Thread.getState() 方法检查其状态。

    示例代码:

    public class ThreadStateCheck {
        public static void main(String[] args) {
            Thread thread = new Thread(() -> {
                System.out.println("线程运行中…");
            });
    
            System.out.println("线程状态:" + thread.getState()); // NEW
            thread.start();
            System.out.println("线程状态:" + thread.getState()); // RUNNABLE 或 TERMINATED
    
            try {
                thread.start(); // 再次调用会抛异常
            } catch (IllegalThreadStateException e) {
                System.out.println("异常:线程已经启动过");
            }
        }
    }
    
  • 重新创建线程对象:如果需要重复执行任务,可以通过新建线程实现:

    Thread thread1 = new Thread(() -> System.out.println("任务 1"));
    thread1.start();
    
    Thread thread2 = new Thread(() -> System.out.println("任务 2"));
    thread2.start();
    
线程池的使用

如果需要多次执行相同任务,推荐使用线程池(ExecutorService),而非手动管理 Thread 对象。例如:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2);

        Runnable task = () -> System.out.println("任务正在运行…");

        executor.execute(task); // 启动任务
        executor.execute(task); // 再次启动任务

        executor.shutdown();
    }
}

线程池可以高效管理线程复用,避免直接操作线程带来的复杂性。


总结

  1. 对一个线程对象调用两次 start() 方法会抛出 IllegalThreadStateException
  2. 每个线程对象只能启动一次。如果需要重新运行任务,需要新建线程实例或使用线程池。
  3. 推荐使用线程池(如 ExecutorService)来管理多次任务执行,避免手动控制线程的复杂性。

问题三:栈和队列在 Java 中的区别是什么?

栈和队列的区别

栈(Stack)和队列(Queue)是两种常用的线性数据结构,它们在数据存取方式和应用场景上有显著的区别。以下从定义、操作规则、实现和应用等方面进行分析:


1. 栈 (Stack)

定义

栈是一种**后进先出(LIFO, Last In First Out)**的数据结构,即最后插入的数据最先被取出。

核心操作
  • push(E item):将元素压入栈顶。
  • pop():移除并返回栈顶元素。
  • peek():仅返回栈顶元素,但不移除。
Java 实现
  • 使用 java.util.Stack 类。

  • 示例代码:

    import java.util.Stack;
    
    public class StackExample {
        public static void main(String[] args) {
            Stack<Integer> stack = new Stack<>();
            stack.push(10);
            stack.push(20);
            stack.push(30);
    
            System.out.println("栈顶元素:" + stack.peek()); // 输出 30
            System.out.println("弹出元素:" + stack.pop()); // 输出 30
            System.out.println("弹出后栈顶:" + stack.peek()); // 输出 20
        }
    }
    

2. 队列 (Queue)

定义

队列是一种**先进先出(FIFO, First In First Out)**的数据结构,即最先插入的数据最先被取出。

核心操作
  • add(E item)offer(E item):将元素添加到队列尾部。
  • remove()poll():移除并返回队列头部元素。
  • element()peek():仅返回队列头部元素,但不移除。
Java 实现
  • 使用 java.util.Queue 接口的实现类,例如 LinkedListArrayDeque

  • 示例代码:

    import java.util.LinkedList;
    import java.util.Queue;
    
    public class QueueExample {
        public static void main(String[] args) {
            Queue<Integer> queue = new LinkedList<>();
            queue.offer(10);
            queue.offer(20);
            queue.offer(30);
    
            System.out.println("队列头元素:" + queue.peek()); // 输出 10
            System.out.println("移除元素:" + queue.poll()); // 输出 10
            System.out.println("移除后队列头:" + queue.peek()); // 输出 20
        }
    }
    

3. 栈与队列的主要区别

特性栈 (Stack)队列 (Queue)
访问规则后进先出(LIFO)先进先出(FIFO)
常用方法push()pop()peek()offer()poll()peek()
插入位置栈顶队尾
移除位置栈顶队头
实现方式使用 java.util.Stack使用 java.util.Queue 接口及实现类
常见应用场景递归、括号匹配、函数调用栈、回溯算法消息队列、任务调度、广度优先搜索

4. 特殊队列:双端队列 (Deque)

定义

双端队列(Deque, Double-Ended Queue)允许在队首和队尾同时插入和移除元素。

实现
  • 使用 java.util.ArrayDequejava.util.LinkedList

  • 示例代码:

    import java.util.Deque;
    import java.util.ArrayDeque;
    
    public class DequeExample {
        public static void main(String[] args) {
            Deque<Integer> deque = new ArrayDeque<>();
            deque.addFirst(10); // 插入到队首
            deque.addLast(20);  // 插入到队尾
    
            System.out.println("队首元素:" + deque.peekFirst()); // 输出 10
            System.out.println("队尾元素:" + deque.peekLast());  // 输出 20
    
            deque.removeFirst(); // 移除队首
            deque.removeLast();  // 移除队尾
        }
    }
    
应用
  • 双端队列可用于实现栈或队列的功能,也可以用作滑动窗口算法等高级场景。

5. 实际应用场景

  • 栈:
    • 函数调用栈
    • 括号匹配
    • 表达式求值
    • 深度优先搜索(DFS)
  • 队列:
    • 任务调度
    • 广度优先搜索(BFS)
    • 消息队列
    • 缓冲区管理

总结

  • 栈是后进先出的数据结构,常用于递归、回溯等场景。
  • 队列是先进先出的数据结构,适合任务调度和广度优先搜索等场景。
  • 双端队列是栈和队列的通用化版本,既可以实现栈的功能,也可以实现队列的功能。

总结

今天的 3 道 Java 面试题,您是否掌握了呢?持续关注我们的每日分享,深入学习 Java 面试的各个细节,快速提升技术能力!如果有任何疑问,欢迎在评论区留言,我们会第一时间解答!

明天见!🎉


http://www.kler.cn/a/521180.html

相关文章:

  • 第1章 量子暗网中的血色黎明
  • 2025多目标优化创新路径汇总
  • 重回C语言之老兵重装上阵(十六)C语言可变参数
  • C# 添加、替换、提取、或删除Excel中的图片
  • 缓存穿透和缓存雪崩
  • CAN总线
  • 2025最新 Docker 国内可用镜像源仓库地址(01月02日更新)
  • 【UE】Level、World
  • Android AutoMotive--CarPropertyService
  • AIGC专栏18——EasyAnimateV5.1版本详解 应用Qwen2 VL作为文本编码器,支持轨迹控制与相机镜头控制
  • 【WebRTC - STUN/TURN服务 - COTURN配置】
  • Linux二进制部署K8s集群的平滑升级教程
  • uniapp版本升级
  • 菜鸟开发之多表联合增删改
  • Crewai框架添加日志功能
  • GD32F470 USB虚拟串口
  • Day40:列表的排序
  • python 变量范围的定义与用法
  • 汽车网络信息安全-ISO/SAE 21434解析(中)
  • 拖拽移动(Semi Design)
  • 《一起做很甜的梦!》
  • sqlite3 学习笔记
  • 数据分箱 baggingboosting onehot独热编码 woe编码 sklearn的ensemble(集成学习)
  • python:taichi 高性能可视化 Demo 展览
  • 基于SpringBoot的母婴护理知识共享管理系统
  • 代码随想录算法【Day32】