Java.lang.String 中的trim()方法详解(转)

本文详细探讨了Java中String类的trim()方法,揭示了它不仅仅去除两端的空格,而是能移除25种特定空白字符。通过分析源码,解释了TrimHelper方法的工作原理,包括不同trimType参数的影响,并提到了TrimStart和TrimEnd方法作为补充。建议在需要保留某些空白字符时谨慎使用trim()。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

String.Trim()方法到底为我们做了什么,仅仅是去除字符串两端的空格吗?

一直以为Trim()方法就是把字符串两端的空格字符给删去,其实我错了,而且错的比较离谱。

首先我直接反编译String类,找到Trim()方法:

public string Trim(){     
    return this.TrimHelper(WhitespaceChars, 2);
}

TrimHelper方法有两个参数,第一个参数名WhitespaceChars,首字母尽然是大写的,肯定有文章,真不出我所料:

1 internal static readonly char[] WhitespaceChars;

 

 这里只是定义它,没有赋值,而且是静态的,我们看看构造函数去,果然找到:

static String(){ 
 Empty = " "; WhitespaceChars = new char[] { '/t', '/n', '/v', '/f', '/r', ' ', '/x0085',
 '/x00a0', '?', ' ', ' ', ' ', ' ', '?', '?', '?', '?', '?', ' ', '?', '?', '/u2028', '/u2029', ' ', '?' };
}  

Trim方法就是把字符串两端的这些字符给删去?我很坚定的猜想到。

继续我们的探索,直接反编译TrimHelper,哇,也许这个才是我想要的,私有的TrimHelper方法:

private string TrimHelper(char[] trimChars, int trimType)
{
    int num = this.Length - 1;
    int startIndex = 0;
    if (trimType != 1)
    {
        startIndex = 0;
        while (startIndex < this.Length)
        {
            int index = 0;
            char ch = this[startIndex];
            index = 0;
            while (index < trimChars.Length)
            {
                if (trimChars[index] == ch)
                {
                    break;
                }
                index++;
            }
            if (index == trimChars.Length)
            {
                break;
            }
            startIndex++;
        }
    }
    if (trimType != 0)
    {
        num = this.Length - 1;
        while (num >= startIndex)
        {
            int num4 = 0;
            char ch2 = this[num];
            num4 = 0;
            while (num4 < trimChars.Length)
            {
                if (trimChars[num4] == ch2)
                {
                    break;
                }
                num4++;
            }
            if (num4 == trimChars.Length)
            {
                break;
            }
            num--;
        }
    }
    int length = (num - startIndex) + 1;
    if (length == this.Length)
    {
        return this;
    }
    if (length == 0)
    {
        return Empty;
    }
    return this.InternalSubString(startIndex, length, false);
}

 

 经过分析和运行,基本上知道了这个方法是干什么的了。

TrimHelper方法有两个参数:

第一个参数trimChars,是要从字符串两端删除掉的字符的数组;

第二个参数trimType,是标识Trim的类型。就目前发现,trimType的取值有3个。当传入0时,去除字符串头部的空白字符,传入1时去除字符串尾部的空白字符,传入其他数值(比如2) 去除字符串两端的空白字符。

最后再看看真正执行字符串截取的方法:

private unsafe string InternalSubString(int startIndex, int length, bool fAlwaysCopy)
{
    if (((startIndex == 0) && (length == this.Length)) && !fAlwaysCopy)
    {
        return this;
    }
    string str = FastAllocateString(length);
    fixed (char* chRef = &str.m_firstChar)
    {
        fixed (char* chRef2 = &this.m_firstChar)
        {
            wstrcpy(chRef, chRef2 + startIndex, length);
        }
    }
    return str;
}

 

 原来也用指针的?第一次看到,效率应该比较高吧。 
最后总结一下: 
String.Trim()方法会去除字符串两端,不仅仅是空格字符,它总共能去除25种字符: 
('/t', '/n', '/v', '/f', '/r', ' ', '/x0085', '/x00a0', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '?', '/u2028', '/u2029', ' ', '?')


如果你想保留其中的一个或多个(例如/t制表符,/n换行符,/r回车符等),请慎用Trim方法。

请注意,Trim删除的过程为从外到内,直到碰到一个非空白的字符为止,所以不管前后有多少个连续的空白字符都会被删除掉。

 最后附上两个相关的方法(也是String类直接提供的),分别去除字符串头部空白字符的TrimStart方法和去除字符串尾部空白字符的 TrimEnd方法:

TrimStart和TrimEnd方法

如果想去除字符串两端其他任意字符,可以考虑Trim他的重载兄弟:String.Trim(Char[]),传入你想要去除的哪些字符的数组。

源码奉上:

public string Trim(params char[] trimChars)
{
    if ((trimChars == null) || (trimChars.Length == 0))
    {
        trimChars = WhitespaceChars;
    }
    return this.TrimHelper(trimChars, 2);
}

 

 

空格 != 空白字符,删除空格请使用: Trim(‘ ‘);

<think>我们遇到`java.lang.IllegalThreadStateException:processhasnotexited`异常时,通常是因为在尝试获取进程的退出状态(通过`exitValue()`方法)时,该进程尚未终止。在Java中,`Process`对象代表一个正在执行的进程。`Process.exitValue()`方法会立即返回进程的退出值,但如果进程尚未终止,则会抛出`IllegalThreadStateException`。为了避免这个异常,我们应该使用`Process.waitFor()`方法,该方法会阻塞当前线程直到进程执行完成,然后返回退出值。以下我们将提供两种解决方案:1.使用`waitFor()`方法等待进程结束。2.使用带超时的`waitFor(longtimeout,TimeUnitunit)`方法Java8+)。同时,我们也会处理进程的输出流和错误流,以避免进程阻塞(因为输出缓冲区可能被填满,导致进程挂起)。下面是一个改进的工具类方法,用于执行命令并获取结果,同时避免上述异常。###解决方案:安全执行外部进程```javaimportjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStream;importjava.io.InputStreamReader;importjava.util.concurrent.TimeUnit;publicclassProcessExecutor{/***执行命令并等待完成(无限等待)*@paramcommand命令数组*@return进程退出码*@throwsIOException如果发生I/O错误*@throwsInterruptedException如果进程被中断*/publicstaticintexecuteCommand(String[]command)throwsIOException,InterruptedException{Processprocess=Runtime.getRuntime().exec(command);//处理输出流和错误流,防止阻塞StreamGobbleroutputGobbler=newStreamGobbler(process.getInputStream());StreamGobblererrorGobbler=newStreamGobbler(process.getErrorStream());outputGobbler.start();errorGobbler.start();//等待进程结束returnprocess.waitFor();}/***执行命令并设置超时*@paramcommand命令数组*@paramtimeout超时时间*@paramunit时间单位*@return进程退出码(如果超时则返回-1)*@throwsIOException如果发生I/O错误*@throwsInterruptedException如果进程被中断*/publicstaticintexecuteCommandWithTimeout(String[]command,longtimeout,TimeUnitunit)throwsIOException,InterruptedException{Processprocess=Runtime.getRuntime().exec(command);StreamGobbleroutputGobbler=newStreamGobbler(process.getInputStream());StreamGobblererrorGobbler=newStreamGobbler(process.getErrorStream());outputGobbler.start();errorGobbler.start();//带超时的等待if(process.waitFor(timeout,unit)){returnprocess.exitValue();}else{//超时,销毁进程process.destroyForcibly();return-1;//或者抛出自定义异常}}//用于消费流的线程类privatestaticclassStreamGobblerextendsThread{privateInputStreaminputStream;publicStreamGobbler(InputStreaminputStream){this.inputStream=inputStream;}@Overridepublicvoidrun(){try(BufferedReaderreader=newBufferedReader(newInputStreamReader(inputStream))){Stringline;while((line=reader.readLine())!=null){//这里可以记录日志或者丢弃输出,根据需求调整//例如:System.out.println(line);}}catch(IOExceptione){e.printStackTrace();}}}}```###解释代码1.**`executeCommand`方法**:-使用`Runtime.getRuntime().exec()`启动进程。-创建两个`StreamGobbler`线程分别消费进程的标准输出流和错误输出流。这是因为如果不消费这些流,当缓冲区满时,进程可能会被阻塞。-调用`process.waitFor()`等待进程结束,并返回退出码。2.**`executeCommandWithTimeout`方法**:-与上面类似,但使用`process.waitFor(longtimeout,TimeUnitunit)`方法,该方法在指定的时间内等待进程结束。-如果超时,则强制销毁进程并返回-1(也可以选择抛出异常)。3.**`StreamGobbler`类**:-一个简单的线程类,用于读取并丢弃(或处理)进程的输出。如果不处理这些输出,当输出缓冲区满时,进程可能会挂起。###使用示例```javapublicstaticvoidmain(String[]args){String[]command={"ping","www.google.com"};try{intexitCode=ProcessExecutor.executeCommandWithTimeout(command,5,TimeUnit.SECONDS);System.out.println("Exitcode:"+exitCode);}catch(IOException|InterruptedExceptione){e.printStackTrace();}}```###注意事项-在调用`waitFor()`之前处理输出流,否则可能会导致进程阻塞。-对于长时间运行的进程,建议使用带超时的版本,避免永久等待。-强制销毁进程可能会导致资源未正确释放,应谨慎使用。###在之前的FFmpeg截帧工具中的改进我们可以修改之前的`VideoFrameExtractor`类,使用上述安全执行进程的方法。例如,在`getVideoDuration`方法中,可以替换为:```javaprivatestaticdoublegetVideoDuration(StringvideoPath)throwsIOException,InterruptedException{String[]command={FFPROBE_PATH,"-v","error","-show_entries","format=duration","-of","default=noprint_wrappers=1:nokey=1",videoPath};//使用带超时的执行intexitCode=ProcessExecutor.executeCommandWithTimeout(command,5,TimeUnit.SECONDS);if(exitCode!=0){thrownewIOException("FFprobe执行失败,退出码:"+exitCode);}//这里需要重新设计:如何获取输出?我们可以修改ProcessExecutor来捕获输出//由于需要输出,我们需要改进上面的ProcessExecutor,使其能够返回输出内容}```因此,我们需要增强`ProcessExecutor`来捕获输出。下面是一个改进版本:###增强版:捕获输出并支持超时```javaimportjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStream;importjava.io.InputStreamReader;importjava.util.concurrent.TimeUnit;publicclassProcessExecutorWithOutput{publicstaticclassProcessResult{privatefinalintexitCode;privatefinalStringoutput;privatefinalStringerror;publicProcessResult(intexitCode,Stringoutput,Stringerror){this.exitCode=exitCode;this.output=output;this.error=error;}publicintgetExitCode(){returnexitCode;}publicStringgetOutput(){returnoutput;}publicStringgetError(){returnerror;}}publicstaticProcessResultexecuteCommand(String[]command)throwsIOException,InterruptedException{Processprocess=Runtime.getRuntime().exec(command);//处理输出流和错误流Stringoutput=readStream(process.getInputStream());Stringerror=readStream(process.getErrorStream());intexitCode=process.waitFor();returnnewProcessResult(exitCode,output,error);}publicstaticProcessResultexecuteCommandWithTimeout(String[]command,longtimeout,TimeUnitunit)throwsIOException,InterruptedException{Processprocess=Runtime.getRuntime().exec(command);//启动读取流的线程StreamReaderoutputReader=newStreamReader(process.getInputStream());StreamReadererrorReader=newStreamReader(process.getErrorStream());outputReader.start();errorReader.start();if(process.waitFor(timeout,unit)){outputReader.join();//确保读取完成errorReader.join();returnnewProcessResult(process.exitValue(),outputReader.getResult(),errorReader.getResult());}else{process.destroyForcibly();outputReader.join();//仍然等待读取线程结束errorReader.join();returnnewProcessResult(-1,outputReader.getResult(),errorReader.getResult());}}privatestaticStringreadStream(InputStreaminputStream)throwsIOException{StringBuilderresult=newStringBuilder();try(BufferedReaderreader=newBufferedReader(newInputStreamReader(inputStream))){Stringline;while((line=reader.readLine())!=null){result.append(line).append("\n");}}returnresult.toString();}privatestaticclassStreamReaderextendsThread{privatefinalInputStreaminputStream;privateStringresult;publicStreamReader(InputStreaminputStream){this.inputStream=inputStream;this.result="";}publicStringgetResult(){returnresult;}@Overridepublicvoidrun(){try{result=readStream(inputStream);}catch(IOExceptione){result=e.getMessage();}}}}```###在`VideoFrameExtractor`中使用增强版```javaprivatestaticdoublegetVideoDuration(StringvideoPath)throwsIOException,InterruptedException{String[]command={FFPROBE_PATH,"-v","error","-show_entries","format=duration","-of","default=noprint_wrappers=1:nokey=1",videoPath};ProcessExecutorWithOutput.ProcessResultresult=ProcessExecutorWithOutput.executeCommandWithTimeout(command,5,TimeUnit.SECONDS);if(result.getExitCode()!=0){thrownewIOException("FFprobe执行失败:"+result.getError());}returnDouble.parseDouble(result.getOutput().trim());}```这样,我们就能够安全地执行外部进程,避免`IllegalThreadStateException`异常,同时还能处理超时和流数据。§§相关问题§§:1.Java中,除了使用`Runtime.exec()`,还有哪些方式可以执行外部进程?2.如何处理外部进程的输入流(即向进程输入数据)?3.Java中,如何异步执行外部进程并实时获取输出?4.在多线程环境下执行外部进程需要注意哪些问题?</think># 解决 `java.lang.IllegalThreadStateException: process has not exited` 异常 这个异常发生在尝试获取进程退出状态(`exitValue()`)时,但进程尚未完成执行。让我们深入分析原因并提供完整的解决方案。 ## 问题原因分析 `IllegalThreadStateException` 通常发生在以下情况: 1. 调用 `Process.exitValue()` 时进程仍在运行 2. 未正确处理进程的输出流,导致缓冲区填满后进程阻塞 3. 未使用 `waitFor()` 方法等待进程完成 4. 进程执行时间过长,但未设置超时处理 ## 完整解决方案 ### 1. 安全执行外部进程的通用工具类 ```java import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.concurrent.TimeUnit; public class ProcessExecutor { /** * 安全执行外部命令 * @param command 命令数组 * @param timeout 超时时间(秒) * @return 包含退出码和输出的结果对象 * @throws IOException 如果发生I/O错误 * @throws InterruptedException 如果进程被中断 */ public static ProcessResult executeCommand(String[] command, long timeout) throws IOException, InterruptedException { Process process = new ProcessBuilder(command).start(); // 启动线程读取输出流和错误流 StreamConsumer outputConsumer = new StreamConsumer(process.getInputStream()); StreamConsumer errorConsumer = new StreamConsumer(process.getErrorStream()); outputConsumer.start(); errorConsumer.start(); // 等待进程完成(带超时) boolean completed = process.waitFor(timeout, TimeUnit.SECONDS); if (!completed) { // 超时处理 process.destroyForcibly(); throw new RuntimeException("命令执行超时: " + String.join(" ", command)); } // 确保消费线程完成 outputConsumer.join(1000); errorConsumer.join(1000); int exitCode = process.exitValue(); return new ProcessResult(exitCode, outputConsumer.getOutput(), errorConsumer.getOutput()); } /** * 流消费者线程 - 防止缓冲区填满导致进程阻塞 */ private static class StreamConsumer extends Thread { private final InputStream inputStream; private final StringBuilder output = new StringBuilder(); public StreamConsumer(InputStream inputStream) { this.inputStream = inputStream; } @Override public void run() { try (BufferedReader reader = new BufferedReader( new InputStreamReader(inputStream))) { String line; while ((line = reader.readLine()) != null) { output.append(line).append(System.lineSeparator()); } } catch (IOException e) { // 忽略读取中的异常 } } public String getOutput() { return output.toString(); } } /** * 进程执行结果封装类 */ public static class ProcessResult { private final int exitCode; private final String output; private final String error; public ProcessResult(int exitCode, String output, String error) { this.exitCode = exitCode; this.output = output; this.error = error; } public int getExitCode() { return exitCode; } public String getOutput() { return output; } public String getError() { return error; } public boolean isSuccess() { return exitCode == 0; } } } ``` ### 2. 在 FFmpeg 截帧工具中使用安全执行器 ```java public class VideoFrameExtractor { // ... [其他代码不变] ... private static double getVideoDuration(String videoPath) throws IOException, InterruptedException { String[] command = { FFPROBE_PATH, "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", videoPath }; ProcessResult result = ProcessExecutor.executeCommand(command, 10); if (!result.isSuccess()) { throw new IOException("FFprobe执行失败: " + result.getError()); } return Double.parseDouble(result.getOutput().trim()); } private static void captureFrameAtTime(String videoPath, String outputPath, double time) throws IOException, InterruptedException { // ... [创建输出目录代码] ... String[] command = { FFMPEG_PATH, "-ss", String.valueOf(time), "-i", videoPath, "-vframes", "1", "-q:v", "2", outputPath }; ProcessResult result = ProcessExecutor.executeCommand(command, 30); if (!result.isSuccess() || !new File(outputPath).exists()) { throw new IOException("截帧失败: " + result.getError()); } } } ``` ## 关键解决方案详解 ### 1. 正确使用 `waitFor()` 方法 ```java boolean completed = process.waitFor(timeout, TimeUnit.SECONDS); ``` - 使用带超时的 `waitFor()` 方法替代直接调用 `exitValue()` - 设置合理的超时时间(根据命令执行时间预估) ### 2. 流处理机制 ```java // 启动线程读取输出流 StreamConsumer outputConsumer = new StreamConsumer(process.getInputStream()); outputConsumer.start(); ``` - 创建独立线程消费进程的输出流和错误流 - 防止输出缓冲区填满导致进程阻塞 - 保存输出内容用于错误诊断 ### 3. 超时处理 ```java if (!completed) { process.destroyForcibly(); throw new RuntimeException("命令执行超时: " + String.join(" ", command)); } ``` - 超时后强制终止进程 - 抛出有意义的异常信息 ### 4. 结果封装 ```java public static class ProcessResult { private final int exitCode; private final String output; private final String error; } ``` - 封装退出码、标准输出和错误输出 - 便于调用者统一处理结果 ## 在 Spring Boot 控制器中的使用示例 ```java @RestController public class VideoController { @PostMapping("/capture-frame") public ResponseEntity<?> captureFrame( @RequestParam("video") MultipartFile videoFile, @RequestParam(value = "outputDir", defaultValue = "./captures") String outputDir) { try { Path videoPath = Paths.get(outputDir, videoFile.getOriginalFilename()); videoFile.transferTo(videoPath); String outputImage = Paths.get(outputDir, "capture_" + System.currentTimeMillis() + ".jpg").toString(); VideoFrameExtractor.captureRandomFrame( videoPath.toString(), outputImage); // 返回图片文件 File file = new File(outputImage); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.IMAGE_JPEG); headers.setContentLength(file.length()); return new ResponseEntity<>(Files.readAllBytes(file.toPath()), headers, HttpStatus.OK); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("截帧失败: " + e.getMessage()); } } } ``` ## 最佳实践建议 1. **始终使用 `waitFor()` 替代 `exitValue()`** - 前者会阻塞直到进程完成,后者只是检查当前状态 2. **处理所有进程输出流** - 不处理输出流可能导致进程阻塞 - 特别是当输出量较大时 3. **设置合理的超时时间** - 根据命令类型设置不同的超时值 - 视频处理通常比简单命令需要更长时间 4. **使用 ProcessBuilder 替代 Runtime.exec()** - ProcessBuilder 提供更精细的控制 - 更好的参数处理(避免空格问题) 5. **记录详细的错误信息** - 保存进程的退出码、标准输出和错误输出 - 便于诊断问题 6. **资源清理** - 确保关闭所有流 - 超时后强制终止进程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值