当前位置:首页 > Java >
《Java2实用教程(第三版)实验指导与习题解答》电子书封面

Java2实用教程(第三版)实验指导与习题解答

  • 发布时间:2021年03月02日 09:10:20
  • 作者:张跃平,耿祥义
  • 大小:18.75MB
  • 类别:Java电子书
  • 格式:PDF
  • 版本:清晰版
  • 评分:9.5

    Java2实用教程(第三版)实验指导与习题解答 PDF 清晰版

      给大家带来的一篇关于Java相关的电子书资源,介绍了关于Java2、Java教程、Java方面的内容,本书是由清华大学出版社出版,格式为PDF,资源大小18.75MB,张跃平,耿祥义编写,目前豆瓣、亚马逊、当当、京东等电子书综合评分为:9.3分

      Tags:java Java2 Java教程 

      内容介绍

      JAVA2实用教程实验指导与习题解答(第3版)

      JAVA2实用教程实验指导与习题解答

      作者:张跃平、耿祥义

      清华大学

      出版时间:2006-10-1

      丛编项:普通高等院校计算机专业本科实用教程系列

      本书是《Java 2实用教程》(第三版)(清华大学出版社)的配套实验指导和习题解答。本书的第一部分为12次上机实践的内容,每次上机实践由3个实验组成。每个实验由相关知识点、实验目的、实验要求、程序效果示例、实验模板、实验指导、实验后的练习和实验报告组成。在进行实验之前,首先通过实验目的了解实验要完成的关键主题,通过实验要求知道本实验应达到怎样的标准,然后,完成实验模板,填写实验报告。本书的第二部分为主教材的习题参考解答。

      目录

      • 上机实践1 初识Java
      • 实验1 一个简单的应用程序
      • 实验2 一个简单的Java Applet程序
      • 实验3 联合编译
      • 自测题
      • 上机实践2 基本数据类型与控制语句
      • 实验1 输出希腊字母表
      • 实验2 回文数
      • 实验3 猜数字游戏
      • 自测题
      • 上机实践3 类与对象
      • 实验1 三角形、梯形和圆形的类封装
      • 实验2 实例成员与类成员
      • 实验3 使用package语句与import语句
      • 自测题
      • 上机实践4 继承与接口
      • 实验1 继承
      • 实验2 上转型对象
      • 实验3 接口回调
      • 自测题
      • 上机实践5 字符串、时间与数字
      • 实验1 String类的常用方法
      • 实验2 比较日期的大小
      • 实验3 处理大整数
      • 自测题
      • 上机实践6 组件及事件处理(1)
      • 实验1 算术测试
      • 实验2 信号灯
      • 实验3 布局与日历
      • 自测题
      • 上机实践7 组件及事件处理(2)
      • 实验1 方程求根
      • 实验2 字体对话框
      • 实验3 英语单词拼写训练
      • 自测题
      • 上机实践8 多线程
      • 实验1 汉字打字练习
      • 实验2 旋转的行星
      • 实验3 双线程接力
      • 自测题 98
      • 上机实践9 输入输出流
      • 实验1 学读汉字
      • 实验2 统计英文单词
      • 实验3 读取Zip文件
      • 自测题
      • 上机实践10 Java 中的网络编程
      • 实验1 读取服务器端文件
      • 实验2 使用套接字读取服务器端对象
      • 实验3 基于UDP的图像传输
      • 自测题
      • 上机实践11 数据结构
      • 实验1 扫雷小游戏
      • 实验2 排序与查找
      • 实验3 使用TreeSet排序
      • 自测题
      • 上机实践12 java Swing
      • 实验1 JLayeredPane分层窗格
      • 实验2 使用表格显示日历
      • 实验3 多文档界面(MDI)
      • 自测题
      • 习题解答
      • 第1章习题
      • 第2章习题
      • 第3章习题
      • 第4章习题
      • 第5章习题
      • 第6章习题
      • 第7章习题
      • 第8章习题
      • 第9章习题
      • 第10章习题
      • 第11章习题
      • 第12章习题
      • 第13章习题
      • 第14章习题
      • 第15章习题
      • 第16章习题
      • 第17章习题

      读书笔记

      Java使用FFmpeg处理视频文件的方法教程

      前言

      本文主要讲述如何使用Java + FFmpeg实现对视频文件的信息提取、码率压缩、分辨率转换等功能;

      之前在网上浏览了一大圈Java使用FFmpeg处理音视频的文章,大多都讲的比较简单,楼主在实操过程中踩了很多坑也填了很多坑,希望这份详细的踩坑&填坑指南能帮助到大家;

      1. 什么是FFmpeg

      点我了解

      2. 开发前准备

      在使用Java调用FFmpeg处理音视频之前,需要先安装FFmpeg,安装方法分为两种:

      • 引入封装了FFmpeg的开源框架
      • 在系统中手动安装FFmpeg

      2.1 引入封装了FFmpeg的开源框架

      JAVE.jar(官网点我) 是一个封装了FFmpeg的Java框架,在项目中能直接调用它的API来处理音视频文件;

      优点:使用方便,直接在项目中引入JAVE.jar即可处理媒体文件,且开发完成后可以随工程一起打包发布,不需要在目标运行环境内手动安装FFmpeg相关的类库

      缺点:JAVE.jar最后一次更新是2009年,其封装的FFmpeg版本是09年或更早前的版本,比较老旧,无法使用一些新特性
      (当然也可以看看有没有其他比较新的封装了FFmpeg的框架)

      Maven坐标如下:

      <dependency>
       <groupId>org.ffmpeg</groupId>
       <artifactId>sdk</artifactId>
       <version>1.0.2</version>
      </dependency>

      2.2 在系统中手动安装FFmpeg

      在运行环境中手动安装FFmpeg稍微有一些麻烦,可以百度 windows/mac安装FFmpeg 这样的关键字,根据网上的安装教程将FFmpeg安装到系统中;

      懒人链接:Windows安装教程 Mac安装教程

      优点:可以直接调用FFmpeg的相关API处理音视频,FFmpeg版本可控

      缺点:手动安装较为麻烦,开发环境与目标运行环境都需要先安装好FFmpeg

      3. 使用FFmpeg处理音视频

      使用JAVE.jar进行开发与直接使用FFmpeg开发的代码有一些不同,这里以直接使用FFmpeg进行开发的代码进行讲解(开发环境MacOS);(使用JAVE的代码、直接使用FFmpeg的代码都会附在文末供大家下载参考)

      通过MediaUtil.java类及其依赖的类,你将可以实现:

      • 解析源视频的基本信息,包括视频格式、时长、码率等;
      • 解析音频、图片的基本信息;
      • 将源视频转换成不同分辨率、不同码率、带或不带音频的新视频;
      • 抽取源视频中指定时间点的帧画面,来生成一张静态图;
      • 抽取源视频中指定时间段的帧画面,来生成一个GIF动态图;
      • 截取源视频中的一段来形成一个新视频;
      • 抽取源视频中的音频信息,生成单独的MP3文件;
      • 对音视频等媒体文件执行自定义的FFmpeg命令;

      3.1 代码结构梳理

      MediaUtil.java是整个解析程序中的核心类,封装了各种常用的解析方法供外部调用;

      MetaInfo.java定义了多媒体数据共有的一些属性,VideoMetaInfo.java MusicMetaInfo.java ImageMetaInfo.java都继承自MetaInfo.java,分别定义了视频、音频、图片数据相关的一些属性;

      AnimatedGifEncoder.java LZWEncoder.java NeuQuant.java在抽取视频帧数、制作GIF动态图的时候会使用到;

      CrfValueEnum.java 定义了三种常用的FFmpeg压缩视频时使用到的crf值,PresetVauleEnum.java定义了FFmpeg压缩视频时常用的几种压缩速率值;

      有关crf、preset的延伸阅读点我

      3.2 MediaUtil.java主程序类解析

      3.2.1 使用前需要注意的几点

      1、指定正确的FFmpeg程序执行路径

      MacOS安装好FFmpeg后,可以在控制台中通过which ffmpeg命令获取FFmpeg程序的执行路径,在调用MediaUtil.java前先通过其 setFFmpegPath() 方法设置好FFmpeg程序在系统中的执行路径,然后才能顺利调用到FFmpeg去解析音视频;

      Windows系统下该路径理论上应设置为:FFmpeg可执行程序在系统中的绝对路径(实际情况有待大家补充)

      2、指定解析音视频信息时需要的正则表达式

      因项目需要解析后缀格式为 .MP4 .WMV .AAC 的视频和音频文件,所以我研究了JAVE.jar底层调用FFmpeg时的解析逻辑后,在MediaUtil.java中设置好了匹配这三种格式的正则表达式供解析时使用(参考程序中的 durationRegex videoStreamRegex musicStreamRegex 这三个表达式值);

      注意:如果你需要解析其他后缀格式如 .MKV .MP3 这样的媒体文件时,你很可能需要根据实际情况修改durationRegex videoStreamRegex musicStreamRegex 这三个正则表达式的值,否则可能无法解析出正确的信息;

      3、程序中的很多默认值你可以根据实际需要修改,比如视频帧抽取的默认宽度或高度值、时长等等;

      3.2.2 MediaUtil.java代码

      package media;
      
      import lombok.extern.slf4j.Slf4j;
      import media.domain.ImageMetaInfo;
      import media.domain.MusicMetaInfo;
      import media.domain.VideoMetaInfo;
      import media.domain.gif.AnimatedGifEncoder;
      import org.apache.commons.collections4.CollectionUtils;
      import org.apache.commons.io.FileUtils;
      import org.apache.commons.lang3.StringUtils;
      
      import javax.imageio.ImageIO;
      import java.awt.image.BufferedImage;
      import java.io.*;
      import java.sql.Time;
      import java.util.ArrayList;
      import java.util.Arrays;
      import java.util.LinkedList;
      import java.util.List;
      import java.util.regex.Matcher;
      import java.util.regex.Pattern;
      
      /**
       * 基于FFmpeg内核来编解码音视频信息;
       * 使用前需手动在运行环境中安装FFmpeg运行程序,然后正确设置FFmpeg运行路径后MediaUtil.java才能正常调用到FFmpeg程序去处理音视频;
       *
       * Author: dreamer-1
       * 
       * version: 1.0
       *
       */
      @Slf4j
      public class MediaUtil {
      
       /**
       * 可以处理的视频格式
       */
       public final static String[] VIDEO_TYPE = { "MP4", "WMV" };
       /**
       * 可以处理的图片格式
       */
       public final static String[] IMAGE_TYPE = { "JPG", "JPEG", "PNG", "GIF" };
       /**
       * 可以处理的音频格式
       */
       public final static String[] AUDIO_TYPE = { "AAC" };
      
       /**
       * 视频帧抽取时的默认时间点,第10s(秒)
       * (Time类构造参数的单位:ms)
       */
       private static final Time DEFAULT_TIME = new Time(0, 0, 10);
       /**
       * 视频帧抽取的默认宽度值,单位:px
       */
       private static int DEFAULT_WIDTH = 320;
       /**
       * 视频帧抽取的默认时长,单位:s(秒)
       */
       private static int DEFAULT_TIME_LENGTH = 10;
       /**
       * 抽取多张视频帧以合成gif动图时,gif的播放速度
       */
       private static int DEFAULT_GIF_PLAYTIME = 110;
       /**
       * FFmpeg程序执行路径
       * 当前系统安装好ffmpeg程序并配置好相应的环境变量后,值为ffmpeg可执行程序文件在实际系统中的绝对路径
       */
       private static String FFMPEG_PATH = "/usr/bin/ffmpeg"; // /usr/bin/ffmpeg
      
      
       /**
       * 视频时长正则匹配式
       * 用于解析视频及音频的时长等信息时使用;
       *
       * (.*?)表示:匹配任何除\r\n之外的任何0或多个字符,非贪婪模式
       *
       */
       private static String durationRegex = "Duration: (\\d*?):(\\d*?):(\\d*?)\\.(\\d*?), start: (.*?), bitrate: (\\d*) kb\\/s.*";
       private static Pattern durationPattern;
       /**
       * 视频流信息正则匹配式
       * 用于解析视频详细信息时使用;
       */
       private static String videoStreamRegex = "Stream #\\d:\\d[\\(]??\\S*[\\)]??: Video: (\\S*\\S$?)[^\\,]*, (.*?), (\\d*)x(\\d*)[^\\,]*, (\\d*) kb\\/s, (\\d*[\\.]??\\d*) fps";
       private static Pattern videoStreamPattern;
       /**
       * 音频流信息正则匹配式
       * 用于解析音频详细信息时使用;
       */
       private static String musicStreamRegex = "Stream #\\d:\\d[\\(]??\\S*[\\)]??: Audio: (\\S*\\S$?)(.*), (.*?) Hz, (.*?), (.*?), (\\d*) kb\\/s";;
       private static Pattern musicStreamPattern;
      
       /**
       * 静态初始化时先加载好用于音视频解析的正则匹配式
       */
       static {
       durationPattern = Pattern.compile(durationRegex);
       videoStreamPattern = Pattern.compile(videoStreamRegex);
       musicStreamPattern = Pattern.compile(musicStreamRegex);
       }
      
       /**
       * 获取当前多媒体处理工具内的ffmpeg的执行路径
       * @return
       */
       public static String getFFmpegPath() {
       return FFMPEG_PATH;
       }
      
       /**
       * 设置当前多媒体工具内的ffmpeg的执行路径
       * @param ffmpeg_path ffmpeg可执行程序在实际系统中的绝对路径
       * @return
       */
       public static boolean setFFmpegPath(String ffmpeg_path) {
       if (StringUtils.isBlank(ffmpeg_path)) {
       log.error("--- 设置ffmpeg执行路径失败,因为传入的ffmpeg可执行程序路径为空! ---");
       return false;
       }
       File ffmpegFile = new File(ffmpeg_path);
       if (!ffmpegFile.exists()) {
       log.error("--- 设置ffmpeg执行路径失败,因为传入的ffmpeg可执行程序路径下的ffmpeg文件不存在! ---");
       return false;
       }
       FFMPEG_PATH = ffmpeg_path;
       log.info("--- 设置ffmpeg执行路径成功 --- 当前ffmpeg可执行程序路径为: " + ffmpeg_path);
       return true;
       }
      
       /**
       * 测试当前多媒体工具是否可以正常工作
       * @return
       */
       public static boolean isExecutable() {
       File ffmpegFile = new File(FFMPEG_PATH);
       if (!ffmpegFile.exists()) {
       log.error("--- 工作状态异常,因为传入的ffmpeg可执行程序路径下的ffmpeg文件不存在! ---");
       return false;
       }
       List<String> cmds = new ArrayList<>(1);
       cmds.add("-version");
       String ffmpegVersionStr = executeCommand(cmds);
       if (StringUtils.isBlank(ffmpegVersionStr)) {
       log.error("--- 工作状态异常,因为ffmpeg命令执行失败! ---");
       return false;
       }
       log.info("--- 工作状态正常 ---");
       return true;
       }
      
      
       /**
       * 执行FFmpeg命令
       * @param commonds 要执行的FFmpeg命令
       * @return FFmpeg程序在执行命令过程中产生的各信息,执行出错时返回null
       */
       public static String executeCommand(List<String> commonds) {
       if (CollectionUtils.isEmpty(commonds)) {
       log.error("--- 指令执行失败,因为要执行的FFmpeg指令为空! ---");
       return null;
       }
       LinkedList<String> ffmpegCmds = new LinkedList<>(commonds);
       ffmpegCmds.addFirst(FFMPEG_PATH); // 设置ffmpeg程序所在路径
       log.info("--- 待执行的FFmpeg指令为:---" + ffmpegCmds);
      
       Runtime runtime = Runtime.getRuntime();
       Process ffmpeg = null;
       try {
       // 执行ffmpeg指令
       ProcessBuilder builder = new ProcessBuilder();
       builder.command(ffmpegCmds);
       ffmpeg = builder.start();
       log.info("--- 开始执行FFmpeg指令:--- 执行线程名:" + builder.toString());
      
       // 取出输出流和错误流的信息
       // 注意:必须要取出ffmpeg在执行命令过程中产生的输出信息,如果不取的话当输出流信息填满jvm存储输出留信息的缓冲区时,线程就回阻塞住
       PrintStream errorStream = new PrintStream(ffmpeg.getErrorStream());
       PrintStream inputStream = new PrintStream(ffmpeg.getInputStream());
       errorStream.start();
       inputStream.start();
       // 等待ffmpeg命令执行完
       ffmpeg.waitFor();
      
       // 获取执行结果字符串
       String result = errorStream.stringBuffer.append(inputStream.stringBuffer).toString();
      
       // 输出执行的命令信息
       String cmdStr = Arrays.toString(ffmpegCmds.toArray()).replace(",", "");
       String resultStr = StringUtils.isBlank(result) ? "【异常】" : "正常";
       log.info("--- 已执行的FFmepg命令: ---" + cmdStr + " 已执行完毕,执行结果: " + resultStr);
       return result;
      
       } catch (Exception e) {
       log.error("--- FFmpeg命令执行出错! --- 出错信息: " + e.getMessage());
       return null;
      
       } finally {
       if (null != ffmpeg) {
       ProcessKiller ffmpegKiller = new ProcessKiller(ffmpeg);
       // JVM退出时,先通过钩子关闭FFmepg进程
       runtime.addShutdownHook(ffmpegKiller);
       }
       }
       }
      
      
       /**
       * 视频转换
       *
       * 注意指定视频分辨率时,宽度和高度必须同时有值;
       *
       * @param fileInput 源视频路径
       * @param fileOutPut 转换后的视频输出路径
       * @param withAudio 是否保留音频;true-保留,false-不保留
       * @param crf 指定视频的质量系数(值越小,视频质量越高,体积越大;该系数取值为0-51,直接影响视频码率大小),取值参考:CrfValueEnum.code
       * @param preset 指定视频的编码速率(速率越快压缩率越低),取值参考:PresetVauleEnum.presetValue
       * @param width 视频宽度;为空则保持源视频宽度
       * @param height 视频高度;为空则保持源视频高度
       */
       public static void convertVideo(File fileInput, File fileOutPut, boolean withAudio, Integer crf, String preset, Integer width, Integer height) {
       if (null == fileInput || !fileInput.exists()) {
       throw new RuntimeException("源视频文件不存在,请检查源视频路径");
       }
       if (null == fileOutPut) {
       throw new RuntimeException("转换后的视频路径为空,请检查转换后的视频存放路径是否正确");
       }
      
       if (!fileOutPut.exists()) {
       try {
       fileOutPut.createNewFile();
       } catch (IOException e) {
       log.error("视频转换时新建输出文件失败");
       }
       }
      
       String format = getFormat(fileInput);
       if (!isLegalFormat(format, VIDEO_TYPE)) {
       throw new RuntimeException("无法解析的视频格式:" + format);
       }
      
       List<String> commond = new ArrayList<String>();
       commond.add("-i");
       commond.add(fileInput.getAbsolutePath());
       if (!withAudio) { // 设置是否保留音频
       commond.add("-an"); // 去掉音频
       }
       if (null != width && width > 0 && null != height && height > 0) { // 设置分辨率
       commond.add("-s");
       String resolution = width.toString() + "x" + height.toString();
       commond.add(resolution);
       }
      
       commond.add("-vcodec"); // 指定输出视频文件时使用的编码器
       commond.add("libx264"); // 指定使用x264编码器
       commond.add("-preset"); // 当使用x264时需要带上该参数
       commond.add(preset); // 指定preset参数
       commond.add("-crf"); // 指定输出视频质量
       commond.add(crf.toString()); // 视频质量参数,值越小视频质量越高
       commond.add("-y"); // 当已存在输出文件时,不提示是否覆盖
       commond.add(fileOutPut.getAbsolutePath());
      
       executeCommand(commond);
       }
      
      
       /**
       * 视频帧抽取
       * 默认抽取第10秒的帧画面
       * 抽取的帧图片默认宽度为300px
       *
       * 转换后的文件路径以.gif结尾时,默认截取从第10s开始,后10s以内的帧画面来生成gif
       * 
       * @param videoFile 源视频路径
       * @param fileOutPut 转换后的文件路径
       */
       public static void cutVideoFrame(File videoFile, File fileOutPut) {
       cutVideoFrame(videoFile, fileOutPut, DEFAULT_TIME);
       }
      
       /**
       * 视频帧抽取(抽取指定时间点的帧画面)
       * 抽取的视频帧图片宽度默认为320px
       *
       * 转换后的文件路径以.gif结尾时,默认截取从指定时间点开始,后10s以内的帧画面来生成gif
       * 
       * @param videoFile 源视频路径
       * @param fileOutPut 转换后的文件路径
       * @param time 指定抽取视频帧的时间点(单位:s)
       */
       public static void cutVideoFrame(File videoFile, File fileOutPut, Time time) {
       cutVideoFrame(videoFile, fileOutPut, time, DEFAULT_WIDTH);
       }
      
       /**
       * 视频帧抽取(抽取指定时间点、指定宽度值的帧画面)
       * 只需指定视频帧的宽度,高度随宽度自动计算
       *
       * 转换后的文件路径以.gif结尾时,默认截取从指定时间点开始,后10s以内的帧画面来生成gif
       * 
       * @param videoFile 源视频路径
       * @param fileOutPut 转换后的文件路径
       * @param time 指定要抽取第几秒的视频帧(单位:s)
       * @param width 抽取的视频帧图片的宽度(单位:px)
       */
       public static void cutVideoFrame(File videoFile, File fileOutPut, Time time, int width) {
       if (null == videoFile || !videoFile.exists()) {
       throw new RuntimeException("源视频文件不存在,请检查源视频路径");
       }
       if (null == fileOutPut) {
       throw new RuntimeException("转换后的视频路径为空,请检查转换后的视频存放路径是否正确");
       }
       VideoMetaInfo info = getVideoMetaInfo(videoFile);
       if (null == info) {
       log.error("--- 未能解析源视频信息,视频帧抽取操作失败 --- 源视频: " + videoFile);
       return;
       }
       int height = width * info.getHeight() / info.getWidth(); // 根据宽度计算适合的高度,防止画面变形
       cutVideoFrame(videoFile, fileOutPut, time, width, height);
       }
      
       /**
       * 视频帧抽取(抽取指定时间点、指定宽度值、指定高度值的帧画面)
       *
       * 转换后的文件路径以.gif结尾时,默认截取从指定时间点开始,后10s以内的帧画面来生成gif
       * 
       * @param videoFile 源视频路径
       * @param fileOutPut 转换后的文件路径
       * @param time 指定要抽取第几秒的视频帧(单位:s)
       * @param width 抽取的视频帧图片的宽度(单位:px)
       * @param height 抽取的视频帧图片的高度(单位:px)
       */
       public static void cutVideoFrame(File videoFile, File fileOutPut, Time time, int width, int height) {
       if (null == videoFile || !videoFile.exists()) {
       throw new RuntimeException("源视频文件不存在,请检查源视频路径");
       }
       if (null == fileOutPut) {
       throw new RuntimeException("转换后的视频路径为空,请检查转换后的视频存放路径是否正确");
       }
       String format = getFormat(fileOutPut);
       if (!isLegalFormat(format, IMAGE_TYPE)) {
       throw new RuntimeException("无法生成指定格式的帧图片:" + format);
       }
       String fileOutPutPath = fileOutPut.getAbsolutePath();
       if (!"GIF".equals(StringUtils.upperCase(format))) {
       // 输出路径不是以.gif结尾,抽取并生成一张静态图
       cutVideoFrame(videoFile, fileOutPutPath, time, width, height, 1, false);
       } else {
       // 抽取并生成一个gif(gif由10张静态图构成)
       String path = fileOutPut.getParent();
       String name = fileOutPut.getName();
       // 创建临时文件存储多张静态图用于生成gif
       String tempPath = path + File.separator + System.currentTimeMillis() + "_" + name.substring(0, name.indexOf("."));
       File file = new File(tempPath);
       if (!file.exists()) {
       file.mkdir();
       }
       try {
       cutVideoFrame(videoFile, tempPath, time, width, height, DEFAULT_TIME_LENGTH, true);
       // 生成gif
       String images[] = file.list();
       for (int i = 0; i < images.length; i++) {
       images[i] = tempPath + File.separator + images[i];
       }
       createGifImage(images, fileOutPut.getAbsolutePath(), DEFAULT_GIF_PLAYTIME);
       } catch (Exception e) {
       log.error("--- 截取视频帧操作出错 --- 错误信息:" + e.getMessage());
       } finally {
       // 删除用于生成gif的临时文件
       String images[] = file.list();
       for (int i = 0; i < images.length; i++) {
       File fileDelete = new File(tempPath + File.separator + images[i]);
       fileDelete.delete();
       }
       file.delete();
       }
       }
       }
      
       /**
       * 视频帧抽取(抽取指定时间点、指定宽度值、指定高度值、指定时长、指定单张/多张的帧画面)
       *
       * @param videoFile 源视频
       * @param path 转换后的文件输出路径
       * @param time 开始截取视频帧的时间点(单位:s)
       * @param width 截取的视频帧图片的宽度(单位:px)
       * @param height 截取的视频帧图片的高度(单位:px,需要大于20)
       * @param timeLength 截取的视频帧的时长(从time开始算,单位:s,需小于源视频的最大时长)
       * @param isContinuty false - 静态图(只截取time时间点的那一帧图片),true - 动态图(截取从time时间点开始,timelength这段时间内的多张帧图)
       */
       private static void cutVideoFrame(File videoFile, String path, Time time, int width, int height, int timeLength, boolean isContinuty) {
       if (videoFile == null || !videoFile.exists()) {
       throw new RuntimeException("源视频文件不存在,源视频路径: ");
       }
       if (null == path) {
       throw new RuntimeException("转换后的文件路径为空,请检查转换后的文件存放路径是否正确");
       }
       VideoMetaInfo info = getVideoMetaInfo(videoFile);
       if (null == info) {
       throw new RuntimeException("未解析到视频信息");
       }
       if (time.getTime() + timeLength > info.getDuration()) {
       throw new RuntimeException("开始截取视频帧的时间点不合法:" + time.toString() + ",因为截取时间点晚于视频的最后时间点");
       }
       if (width <= 20 || height <= 20) {
       throw new RuntimeException("截取的视频帧图片的宽度或高度不合法,宽高值必须大于20");
       }
       try {
       List<String> commond = new ArrayList<String>();
       commond.add("-ss");
       commond.add(time.toString());
       if (isContinuty) {
       commond.add("-t");
       commond.add(timeLength + "");
       } else {
       commond.add("-vframes");
       commond.add("1");
       }
       commond.add("-i");
       commond.add(videoFile.getAbsolutePath());
       commond.add("-an");
       commond.add("-f");
       commond.add("image2");
       if (isContinuty) {
       commond.add("-r");
       commond.add("3");
       }
       commond.add("-s");
       commond.add(width + "*" + height);
       if (isContinuty) {
       commond.add(path + File.separator + "foo-%03d.jpeg");
       } else {
       commond.add(path);
       }
      
       executeCommand(commond);
       } catch (Exception e) {
       log.error("--- 视频帧抽取过程出错 --- 错误信息: " + e.getMessage());
       }
       }
      
       /**
       * 截取视频中的某一段,生成新视频
       *
       * @param videoFile 源视频路径
       * @param outputFile 转换后的视频路径
       * @param startTime 开始抽取的时间点(单位:s)
       * @param timeLength 需要抽取的时间段(单位:s,需小于源视频最大时长);例如:该参数值为10时即抽取从startTime开始之后10秒内的视频作为新视频
       */
       public static void cutVideo(File videoFile, File outputFile, Time startTime, int timeLength) {
       if (videoFile == null || !videoFile.exists()) {
       throw new RuntimeException("视频文件不存在:");
       }
       if (null == outputFile) {
       throw new RuntimeException("转换后的视频路径为空,请检查转换后的视频存放路径是否正确");
       }
       VideoMetaInfo info = getVideoMetaInfo(videoFile);
       if (null == info) {
       throw new RuntimeException("未解析到视频信息");
       }
       if (startTime.getTime() + timeLength > info.getDuration()) {
       throw new RuntimeException("截取时间不合法:" + startTime.toString() + ",因为截取时间大于视频的时长");
       }
       try {
       if (!outputFile.exists()) {
       outputFile.createNewFile();
       }
       List<String> commond = new ArrayList<String>();
       commond.add("-ss");
       commond.add(startTime.toString());
       commond.add("-t");
       commond.add("" + timeLength);
       commond.add("-i");
       commond.add(videoFile.getAbsolutePath());
       commond.add("-vcodec");
       commond.add("copy");
       commond.add("-acodec");
       commond.add("copy");
       commond.add(outputFile.getAbsolutePath());
       executeCommand(commond);
       } catch (IOException e) {
       log.error("--- 视频截取过程出错 ---");
       }
       }
      
       /**
       * 抽取视频里的音频信息
       * 只能抽取成MP3文件
       * @param videoFile 源视频文件
       * @param audioFile 从源视频提取的音频文件
       */
       public static void getAudioFromVideo(File videoFile, File audioFile) {
       if (null == videoFile || !videoFile.exists()) {
       throw new RuntimeException("源视频文件不存在: ");
       }
       if (null == audioFile) {
       throw new RuntimeException("要提取的音频路径为空:");
       }
       String format = getFormat(audioFile);
       if (!isLegalFormat(format, AUDIO_TYPE)) {
       throw new RuntimeException("无法生成指定格式的音频:" + format + " 请检查要输出的音频文件是否是AAC类型");
       }
       try {
       if (!audioFile.exists()) {
       audioFile.createNewFile();
       }
      
       List<String> commond = new ArrayList<String>();
       commond.add("-i");
       commond.add(videoFile.getAbsolutePath());
       commond.add("-vn"); // no video,去除视频信息
       commond.add("-y");
       commond.add("-acodec");
       commond.add("copy");
       commond.add(audioFile.getAbsolutePath());
       executeCommand(commond);
       } catch (Exception e) {
       log.error("--- 抽取视频中的音频信息的过程出错 --- 错误信息: " + e.getMessage());
       }
       }
      
       /**
       * 解析视频的基本信息(从文件中)
       *
       * 解析出的视频信息一般为以下格式:
       * Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '6.mp4':
       * Duration: 00:00:30.04, start: 0.000000, bitrate: 19031 kb/s
       * Stream #0:0(eng): Video: h264 (Main) (avc1 / 0x31637661), yuv420p(tv, bt709), 1920x1080, 18684 kb/s, 25 fps, 25 tbr, 25k tbn, 50 tbc (default)
       * Stream #0:1(eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 317 kb/s (default)
       *
       * 注解:
       * Duration: 00:00:30.04【视频时长】, start: 0.000000【视频开始时间】, bitrate: 19031 kb/s【视频比特率/码率】
       * Stream #0:0(eng): Video: h264【视频编码格式】 (Main) (avc1 / 0x31637661), yuv420p(tv, bt709), 1920x1080【视频分辨率,宽x高】, 18684【视频比特率】 kb/s, 25【视频帧率】 fps, 25 tbr, 25k tbn, 50 tbc (default)
       * Stream #0:1(eng): Audio: aac【音频格式】 (LC) (mp4a / 0x6134706D), 48000【音频采样率】 Hz, stereo, fltp, 317【音频码率】 kb/s (default)
       *
       * @param videoFile 源视频路径
       * @return 视频的基本信息,解码失败时返回null
       */
       public static VideoMetaInfo getVideoMetaInfo(File videoFile) {
       if (null == videoFile || !videoFile.exists()) {
       log.error("--- 解析视频信息失败,因为要解析的源视频文件不存在 ---");
       return null;
       }
      
       VideoMetaInfo videoInfo = new VideoMetaInfo();
      
       String parseResult = getMetaInfoFromFFmpeg(videoFile);
      
       Matcher durationMacher = durationPattern.matcher(parseResult);
       Matcher videoStreamMacher = videoStreamPattern.matcher(parseResult);
       Matcher videoMusicStreamMacher = musicStreamPattern.matcher(parseResult);
      
       Long duration = 0L; // 视频时长
       Integer videoBitrate = 0; // 视频码率
       String videoFormat = getFormat(videoFile); // 视频格式
       Long videoSize = videoFile.length(); // 视频大小
      
       String videoEncoder = ""; // 视频编码器
       Integer videoHeight = 0; // 视频高度
       Integer videoWidth = 0; // 视频宽度
       Float videoFramerate = 0F; // 视频帧率
      
       String musicFormat = ""; // 音频格式
       Long samplerate = 0L; // 音频采样率
       Integer musicBitrate = 0; // 音频码率
      
       try {
       // 匹配视频播放时长等信息
       if (durationMacher.find()) {
       long hours = (long)Integer.parseInt(durationMacher.group(1));
       long minutes = (long)Integer.parseInt(durationMacher.group(2));
       long seconds = (long)Integer.parseInt(durationMacher.group(3));
       long dec = (long)Integer.parseInt(durationMacher.group(4));
       duration = dec * 100L + seconds * 1000L + minutes * 60L * 1000L + hours * 60L * 60L * 1000L;
       //String startTime = durationMacher.group(5) + "ms";
       videoBitrate = Integer.parseInt(durationMacher.group(6));
       }
       // 匹配视频分辨率等信息
       if (videoStreamMacher.find()) {
       videoEncoder = videoStreamMacher.group(1);
       String s2 = videoStreamMacher.group(2);
       videoWidth = Integer.parseInt(videoStreamMacher.group(3));
       videoHeight = Integer.parseInt(videoStreamMacher.group(4));
       String s5 = videoStreamMacher.group(5);
       videoFramerate = Float.parseFloat(videoStreamMacher.group(6));
       }
       // 匹配视频中的音频信息
       if (videoMusicStreamMacher.find()) {
       musicFormat = videoMusicStreamMacher.group(1); // 提取音频格式
       //String s2 = videoMusicStreamMacher.group(2);
       samplerate = Long.parseLong(videoMusicStreamMacher.group(3)); // 提取采样率
       //String s4 = videoMusicStreamMacher.group(4);
       //String s5 = videoMusicStreamMacher.group(5);
       musicBitrate = Integer.parseInt(videoMusicStreamMacher.group(6)); // 提取比特率
       }
       } catch (Exception e) {
       log.error("--- 解析视频参数信息出错! --- 错误信息: " + e.getMessage());
       return null;
       }
      
       // 封装视频中的音频信息
       MusicMetaInfo musicMetaInfo = new MusicMetaInfo();
       musicMetaInfo.setFormat(musicFormat);
       musicMetaInfo.setDuration(duration);
       musicMetaInfo.setBitRate(musicBitrate);
       musicMetaInfo.setSampleRate(samplerate);
       // 封装视频信息
       VideoMetaInfo videoMetaInfo = new VideoMetaInfo();
       videoMetaInfo.setFormat(videoFormat);
       videoMetaInfo.setSize(videoSize);
       videoMetaInfo.setBitRate(videoBitrate);
       videoMetaInfo.setDuration(duration);
       videoMetaInfo.setEncoder(videoEncoder);
       videoMetaInfo.setFrameRate(videoFramerate);
       videoMetaInfo.setHeight(videoHeight);
       videoMetaInfo.setWidth(videoWidth);
       videoMetaInfo.setMusicMetaInfo(musicMetaInfo);
      
       return videoMetaInfo;
       }
      
       /**
       * 获取视频的基本信息(从流中)
       *
       * @param inputStream 源视频流路径
       * @return 视频的基本信息,解码失败时返回null
       */
       public static VideoMetaInfo getVideoMetaInfo(InputStream inputStream) {
       VideoMetaInfo videoInfo = new VideoMetaInfo();
       try {
       File file = File.createTempFile("tmp", null);
       if (!file.exists()) {
       return null;
       }
       FileUtils.copyInputStreamToFile(inputStream, file);
       videoInfo = getVideoMetaInfo(file);
       file.deleteOnExit();
       return videoInfo;
       } catch (Exception e) {
       log.error("--- 从流中获取视频基本信息出错 --- 错误信息: " + e.getMessage());
       return null;
       }
       }
      
       /**
       * 获取音频的基本信息(从文件中)
       * @param musicFile 音频文件路径
       * @return 音频的基本信息,解码失败时返回null
       */
       public static MusicMetaInfo getMusicMetaInfo(File musicFile) {
       if (null == musicFile || !musicFile.exists()) {
       log.error("--- 无法获取音频信息,因为要解析的音频文件为空 ---");
       return null;
       }
       // 获取音频信息字符串,方便后续解析
       String parseResult = getMetaInfoFromFFmpeg(musicFile);
      
       Long duration = 0L; // 音频时长
       Integer musicBitrate = 0; // 音频码率
       Long samplerate = 0L; // 音频采样率
       String musicFormat = ""; // 音频格式
       Long musicSize = musicFile.length(); // 音频大小
      
       Matcher durationMacher = durationPattern.matcher(parseResult);
       Matcher musicStreamMacher = musicStreamPattern.matcher(parseResult);
      
       try {
       // 匹配音频播放时长等信息
       if (durationMacher.find()) {
       long hours = (long)Integer.parseInt(durationMacher.group(1));
       long minutes = (long)Integer.parseInt(durationMacher.group(2));
       long seconds = (long)Integer.parseInt(durationMacher.group(3));
       long dec = (long)Integer.parseInt(durationMacher.group(4));
       duration = dec * 100L + seconds * 1000L + minutes * 60L * 1000L + hours * 60L * 60L * 1000L;
       //String startTime = durationMacher.group(5) + "ms";
       musicBitrate = Integer.parseInt(durationMacher.group(6));
       }
       // 匹配音频采样率等信息
       if (musicStreamMacher.find()) {
       musicFormat = musicStreamMacher.group(1); // 提取音频格式
       //String s2 = videoMusicStreamMacher.group(2);
       samplerate = Long.parseLong(musicStreamMacher.group(3)); // 提取采样率
       //String s4 = videoMusicStreamMacher.group(4);
       //String s5 = videoMusicStreamMacher.group(5);
       musicBitrate = Integer.parseInt(musicStreamMacher.group(6)); // 提取比特率
       }
       } catch (Exception e) {
       log.error("--- 解析音频参数信息出错! --- 错误信息: " + e.getMessage());
       return null;
       }
      
       // 封装视频中的音频信息
       MusicMetaInfo musicMetaInfo = new MusicMetaInfo();
       musicMetaInfo.setFormat(musicFormat);
       musicMetaInfo.setDuration(duration);
       musicMetaInfo.setBitRate(musicBitrate);
       musicMetaInfo.setSampleRate(samplerate);
       musicMetaInfo.setSize(musicSize);
       return musicMetaInfo;
       }
      
       /**
       * 获取音频的基本信息(从流中)
       * @param inputStream 源音乐流路径
       * @return 音频基本信息,解码出错时返回null
       */
       public static MusicMetaInfo getMusicMetaInfo(InputStream inputStream) {
       MusicMetaInfo musicMetaInfo = new MusicMetaInfo();
       try {
       File file = File.createTempFile("tmp", null);
       if (!file.exists()) {
       return null;
       }
       FileUtils.copyInputStreamToFile(inputStream, file);
       musicMetaInfo = getMusicMetaInfo(file);
       file.deleteOnExit();
       return musicMetaInfo;
       } catch (Exception e) {
       log.error("--- 从流中获取音频基本信息出错 --- 错误信息: " + e.getMessage());
       return null;
       }
       }
      
      
       /**
       * 获取图片的基本信息(从流中)
       *
       * @param inputStream 源图片路径
       * @return 图片的基本信息,获取信息失败时返回null
       */
       public static ImageMetaInfo getImageInfo(InputStream inputStream) {
       BufferedImage image = null;
       ImageMetaInfo imageInfo = new ImageMetaInfo();
       try {
       image = ImageIO.read(inputStream);
       imageInfo.setWidth(image.getWidth());
       imageInfo.setHeight(image.getHeight());
       imageInfo.setSize(Long.valueOf(String.valueOf(inputStream.available())));
       return imageInfo;
       } catch (Exception e) {
       log.error("--- 获取图片的基本信息失败 --- 错误信息: " + e.getMessage());
       return null;
       }
       }
      
       /**
       * 获取图片的基本信息 (从文件中)
       *
       * @param imageFile 源图片路径
       * @return 图片的基本信息,获取信息失败时返回null
       */
       public static ImageMetaInfo getImageInfo(File imageFile) {
       BufferedImage image = null;
       ImageMetaInfo imageInfo = new ImageMetaInfo();
       try {
       if (null == imageFile || !imageFile.exists()) {
       return null;
       }
       image = ImageIO.read(imageFile);
       imageInfo.setWidth(image.getWidth());
       imageInfo.setHeight(image.getHeight());
       imageInfo.setSize(imageFile.length());
       imageInfo.setFormat(getFormat(imageFile));
       return imageInfo;
       } catch (Exception e) {
       log.error("--- 获取图片的基本信息失败 --- 错误信息: " + e.getMessage());
       return null;
       }
       }
      
       /**
       * 检查文件类型是否是给定的类型
       * @param inputFile 源文件
       * @param givenFormat 指定的文件类型;例如:{"MP4", "AVI"}
       * @return
       */
       public static boolean isGivenFormat(File inputFile, String[] givenFormat) {
       if (null == inputFile || !inputFile.exists()) {
       log.error("--- 无法检查文件类型是否满足要求,因为要检查的文件不存在 --- 源文件: " + inputFile);
       return false;
       }
       if (null == givenFormat || givenFormat.length <= 0) {
       log.error("--- 无法检查文件类型是否满足要求,因为没有指定的文件类型 ---");
       return false;
       }
       String fomat = getFormat(inputFile);
       return isLegalFormat(fomat, givenFormat);
       }
      
       /**
       * 使用FFmpeg的"-i"命令来解析视频信息
       * @param inputFile 源媒体文件
       * @return 解析后的结果字符串,解析失败时为空
       */
       public static String getMetaInfoFromFFmpeg(File inputFile) {
       if (inputFile == null || !inputFile.exists()) {
       throw new RuntimeException("源媒体文件不存在,源媒体文件路径: ");
       }
       List<String> commond = new ArrayList<String>();
       commond.add("-i");
       commond.add(inputFile.getAbsolutePath());
       String executeResult = MediaUtil.executeCommand(commond);
       return executeResult;
       }
      
       /**
       * 检测视频格式是否合法
       * @param format
       * @param formats
       * @return
       */
       private static boolean isLegalFormat(String format, String formats[]) {
       for (String item : formats) {
       if (item.equals(StringUtils.upperCase(format))) {
       return true;
       }
       }
       return false;
       }
      
       /**
       * 创建gif
       *
       * @param image 多个jpg文件名(包含路径)
       * @param outputPath 生成的gif文件名(包含路径)
       * @param playTime 播放的延迟时间,可调整gif的播放速度
       */
       private static void createGifImage(String image[], String outputPath, int playTime) {
       if (null == outputPath) {
       throw new RuntimeException("转换后的GIF路径为空,请检查转换后的GIF存放路径是否正确");
       }
       try {
       AnimatedGifEncoder encoder = new AnimatedGifEncoder();
       encoder.setRepeat(0);
       encoder.start(outputPath);
       BufferedImage src[] = new BufferedImage[image.length];
       for (int i = 0; i < src.length; i++) {
       encoder.setDelay(playTime); // 设置播放的延迟时间
       src[i] = ImageIO.read(new File(image[i])); // 读入需要播放的jpg文件
       encoder.addFrame(src[i]); // 添加到帧中
       }
       encoder.finish();
       } catch (Exception e) {
       log.error("--- 多张静态图转换成动态GIF图的过程出错 --- 错误信息: " + e.getMessage());
       }
       }
      
      
       /**
       * 获取指定文件的后缀名
       * @param file
       * @return
       */
       private static String getFormat(File file) {
       String fileName = file.getName();
       String format = fileName.substring(fileName.indexOf(".") + 1);
       return format;
       }
      
      
       /**
       * 在程序退出前结束已有的FFmpeg进程
       */
       private static class ProcessKiller extends Thread {
       private Process process;
      
       public ProcessKiller(Process process) {
       this.process = process;
       }
      
       @Override
       public void run() {
       this.process.destroy();
       log.info("--- 已销毁FFmpeg进程 --- 进程名: " + process.toString());
       }
       }
      
      
       /**
       * 用于取出ffmpeg线程执行过程中产生的各种输出和错误流的信息
       */
       static class PrintStream extends Thread {
       InputStream inputStream = null;
       BufferedReader bufferedReader = null;
       StringBuffer stringBuffer = new StringBuffer();
      
       public PrintStream(InputStream inputStream) {
       this.inputStream = inputStream;
       }
      
       @Override
       public void run() {
       try {
       if (null == inputStream) {
       log.error("--- 读取输出流出错!因为当前输出流为空!---");
       }
       bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
       String line = null;
       while ((line = bufferedReader.readLine()) != null) {
       log.info(line);
       stringBuffer.append(line);
       }
       } catch (Exception e) {
       log.error("--- 读取输入流出错了!--- 错误信息:" + e.getMessage());
       } finally {
       try {
       if (null != bufferedReader) {
       bufferedReader.close();
       }
       if (null != inputStream) {
       inputStream.close();
       }
       } catch (IOException e) {
       log.error("--- 调用PrintStream读取输出流后,关闭流时出错!---");
       }
       }
       }
       }
      
      }

      3.2.3 踩坑&填坑

      1、在Linux等服务器上部署Java程序进行视频压缩时,多注意一下运行账号的权限问题,有时候可能是由于运行程序没有足够的文件操作权限,导致压缩过程失败;

      2、第一版程序上线后,偶尔会出现这样的问题:

      调用MediaUtil.java进行视频压缩过程中,整个程序突然“卡住”,后台也没有日志再打印出来,此时整个压缩过程还没有完成,像是线程突然阻塞住了;

      经过多番查找,发现Java调用FFmpeg时,实际是在JVM里产生一个子进程来执行压缩过程,这个子进程与JVM建立三个通道链接(包括标准输入、标准输出、标准错误流),在压缩过程中,实际会不停地向标准输出和错误流中写入信息;

      因为本地系统对标准输出及错误流提供的缓冲区大小有限,当写入标准输出和错误流的信息填满缓冲区时,执行压缩的进程就会阻塞住;

      所以在压缩过程中,需要单独创建两个线程不停读取标准输出及错误流中的信息,防止整个压缩进程阻塞;(参考MediaUtil.java中的 executeCommand() 方法中的 errorStream 和 inputStream 这两个内部类实例的操作)

      3.3 在CentOS服务器安装FFmpeg指南

      因项目最后部署在CentOS服务器上,需提前在服务器上安装好FFmpeg程序,这过程中也踩了不少坑,针对此写了另一篇总结文章,参考这里 点我哦

      4. 源码下载

      这里提供两种版本的源码供大家下载参考:

      • 引入封装了FFmpeg的开源框架Jave.jar的版本 点我下载
      • 在系统中手动安装FFmpeg的版本 点我下载

      总结

      以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对码农之家的支持。

      以上就是本次介绍的Java电子书的全部相关内容,希望我们整理的资源能够帮助到大家,感谢大家对码农之家的支持。

      上一篇:Java2实用教程(第三版)

      下一篇:Java程序员职场全攻略:从小工到专家

      展开 +

      收起 -

      下载地址:百度网盘下载
      Java 相关电子书
      廖雪峰Java快速入门教程
      廖雪峰Java快速入门教程 PDF 超清版

      Java介于编译型语言和解释型语言之间。编译型语言如C、C++,代码是直接编译成机器码执行,但是不同的平台(x86、ARM等)CPU的指令集不同,因此,需要编译出每一种平台的对应机器码。解释型语言如Python、Ruby没有这个问题,可以由解释器直接加载源码然后运行,代价是运行效率太低。而Java是将代码编译成一种ldquo;字节码rdquo;,它类似于抽象的CPU指令,然后,针对不同平台编写虚拟机,不同平台的虚拟机负责加载字节码并执行,这样就实现了ldquo;一次编

      立即下载
      JavaScript前端开发案例教程
      JavaScript前端开发案例教程 PDF 超清扫描版

      JavaScript是这种运用于Web前端开发的开发语言,具备简易、易懂、实用的特性,用JavaScript开发设计网页页面能够提高网页页面的参与性,为客户出示即时的、动态性的互动感受。 《 JavaScript前端

      立即下载
      Java执行JavaScript代码教程 PDF 完整版

      主要为大家详细介绍了Java执行JavaScript代码的具体操作方法,感兴趣的小伙伴们可以参考一下

      立即下载
      Java虚拟机基础教程
      Java虚拟机基础教程 PDF 中文扫描版

      概述JVM及其特性,介绍了Java、Scala、Clojure、Kotlin和Groovy这5种基于JVM的语言,分别探讨它们的特性和用例,适合所有Java开发人员以及对JVM感兴趣的读者

      立即下载
      读者心得
      74小时57分钟前回答

      JavaScript指定断点操作实例教程

      前言 虽然网上已经有多的数不清的调试教程了,但仍然没有发现哪篇文章写的通俗易懂,索性自己尝试写写自己的一些使用习惯或者说是心得,希望对那些还不是很懂得使用断点调试的孩子有一些帮助 本文将给大家详细介绍关于JavaScript指定断点操作的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧 什么是断点操作(Breakpoint action) 做前端开发的小伙伴,或许对这个断点操作不是很熟悉。不过你要是问其他语言(比如C,C++ ,C #等)的开发者,他们应该都挺熟悉断点操作,这种断点操作在诸如XCode或者Visual Studio的IDE中都会有提供。 以下一段话来自知乎 断点操作 (Ac……

      25小时4分钟前回答

      Java关于远程调试程序教程(以Eclipse为例)

      本节尝试一下Java远程调试的东西,记录一遍简单入门的东西。也就算是使用记录吧! 写一个简单程序打成jar丢到远程服务器运行,模拟远程Server在运行。就拿Java调用shell脚本提交作业程序为例分析。源码如下(如下程序就是一个简单示例代码,不要在乎代码规范): import java.io.InputStream;public class JavaShell { public static void main(String[] args) throws Exception { try { String grant = "chmod u+x submit-job.sh"; Runtime runtime = Runtime.getRuntime(); Process grantProc = runtime.exec(grant); int resultCode = grantProc.waitFor(); System.out.println(resultCode); grantProc = runtime.exec("./submit-job.sh"); resultCode = grantProc.waitFor(); System.out.println(resultCode); InputStream in = grantProc……

      码农之家

      能华美 提供上传

      资源
      13
      粉丝
      11
      喜欢
      172
      评论
      14

      Copyright 2018-2021 www.xz577.com 码农之家

      版权投诉 / 书籍推广:520161757@qq.com