Java Process类的浅学习

 

      今天用了下java.lang.Process类,只是初步的学习,并没有深入实践,因为感觉它的用途并不是很大,偶尔才可能用上,如果要经常使用它的人可以自行参考JDk文档。

      对Process类的简要说明:

     Process类是一个抽象类,方法都是抽象的,它封装了一个进程,也就是一个可执行的程序

     该类提供进程的输入、执行输出到进程、等待进程的完成和检查进程的退出状态及销毁进程的方法

     ProcessBuilder.start()和Runtime.exec方法创建一个本机进程并返回Process子类的一个实例,该实例可以控制进程并获取相关的信息

     其它的概要请参考JDK文档

     下面就开始举几个简单的示例:

     (1)执行简单的DOS命令,如打开一个记事本

    

Java代码 复制代码
  1. package com.iwtxokhtd.other;   
  2.   
  3. import java.io.IOException;   
  4.   
  5. public class ProcessTest {   
  6.   
  7.     public static void main(String[] args) {   
  8.         try {   
  9.                         Process proc=Runtime.getRuntime().exec("notepad");   
  10.         } catch (IOException e) {   
  11.             // TODO Auto-generated catch block   
  12.             e.printStackTrace();   
  13.         }   
  14.   
  15.     }   
  16.   
  17. }  
  1. package com.iwtxokhtd.other;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. public class ProcessTest {  
  6.   
  7.     public static void main(String[] args) {  
  8.         try {  
  9.                         Process proc=Runtime.getRuntime().exec("notepad");  
  10.         } catch (IOException e) {  
  11.             // TODO Auto-generated catch block   
  12.             e.printStackTrace();  
  13.         }  
  14.   
  15.     }  
  16.   
  17. }  
package com.iwtxokhtd.other;

import java.io.IOException;

public class ProcessTest {

	public static void main(String[] args) {
		try {
						Process proc=Runtime.getRuntime().exec("notepad");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

}

 

(2)使用它的其它构造方法执行相关的命令,如下例:

 

Java代码 复制代码
  1. package com.iwtxokhtd.other;   
  2.   
  3. import java.io.IOException;   
  4.   
  5. public class ProcessTest {   
  6.   
  7.     public static void main(String[] args) {   
  8.         try {   
  9.                
  10.             String exeFullPathName="C:/Program Files/Internet Explorer/IEXPLORE.EXE";   
  11.             String message="www.google.com";   
  12.             String []cmd={exeFullPathName,message};   
  13.             Process proc=Runtime.getRuntime().exec(cmd);   
  14.         } catch (IOException e) {   
  15.             // TODO Auto-generated catch block   
  16.             e.printStackTrace();   
  17.         }   
  18.   
  19.     }   
  20.   
  21. }  
  1. package com.iwtxokhtd.other;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. public class ProcessTest {  
  6.   
  7.     public static void main(String[] args) {  
  8.         try {  
  9.               
  10.             String exeFullPathName="C:/Program Files/Internet Explorer/IEXPLORE.EXE";  
  11.             String message="www.google.com";  
  12.             String []cmd={exeFullPathName,message};  
  13.             Process proc=Runtime.getRuntime().exec(cmd);  
  14.         } catch (IOException e) {  
  15.             // TODO Auto-generated catch block   
  16.             e.printStackTrace();  
  17.         }  
  18.   
  19.     }  
  20.   
  21. }  
package com.iwtxokhtd.other;

import java.io.IOException;

public class ProcessTest {

	public static void main(String[] args) {
		try {
			
			String exeFullPathName="C:/Program Files/Internet Explorer/IEXPLORE.EXE";
			String message="www.google.com";
			String []cmd={exeFullPathName,message};
			Process proc=Runtime.getRuntime().exec(cmd);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

}

 

执行上述命令可以打开Google网站

(3)列出系统正在运行的所有进程信息

  

Java代码 复制代码
  1. package com.iwtxokhtd.other;   
  2.   
  3. import java.io.BufferedReader;   
  4. import java.io.IOException;   
  5. import java.io.InputStreamReader;   
  6.   
  7. public class ListAllProcessTest {   
  8.   
  9.     //列出所有的进程信息   
  10.     public static void main(String[] args) {   
  11.         BufferedReader br=null;   
  12.         try {   
  13.             Process proc=Runtime.getRuntime().exec("tasklist");   
  14.             br=new BufferedReader(new InputStreamReader(proc.getInputStream()));   
  15.             @SuppressWarnings("unused")   
  16.             String line=null;   
  17.             System.out.println("打印所有正在运行的进程信息");   
  18.             while((line=br.readLine())!=null){   
  19.                 System.out.println(br.readLine());   
  20.             }   
  21.         } catch (IOException e) {   
  22.             e.printStackTrace();   
  23.         }finally{   
  24.             if(br!=null){   
  25.                 try {   
  26.                     br.close();   
  27.                 } catch (Exception e) {   
  28.                     e.printStackTrace();   
  29.                 }   
  30.             }   
  31.         }   
  32.            
  33.   
  34.     }   
  35.   
  36. }  
  1. package com.iwtxokhtd.other;  
  2.   
  3. import java.io.BufferedReader;  
  4. import java.io.IOException;  
  5. import java.io.InputStreamReader;  
  6.   
  7. public class ListAllProcessTest {  
  8.   
  9.     //列出所有的进程信息   
  10.     public static void main(String[] args) {  
  11.         BufferedReader br=null;  
  12.         try {  
  13.             Process proc=Runtime.getRuntime().exec("tasklist");  
  14.             br=new BufferedReader(new InputStreamReader(proc.getInputStream()));  
  15.             @SuppressWarnings("unused")  
  16.             String line=null;  
  17.             System.out.println("打印所有正在运行的进程信息");  
  18.             while((line=br.readLine())!=null){  
  19.                 System.out.println(br.readLine());  
  20.             }  
  21.         } catch (IOException e) {  
  22.             e.printStackTrace();  
  23.         }finally{  
  24.             if(br!=null){  
  25.                 try {  
  26.                     br.close();  
  27.                 } catch (Exception e) {  
  28.                     e.printStackTrace();  
  29.                 }  
  30.             }  
  31.         }  
  32.           
  33.   
  34.     }  
  35.   
  36. }  
package com.iwtxokhtd.other;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class ListAllProcessTest {

	//列出所有的进程信息
	public static void main(String[] args) {
		BufferedReader br=null;
		try {
			Process proc=Runtime.getRuntime().exec("tasklist");
			br=new BufferedReader(new InputStreamReader(proc.getInputStream()));
			@SuppressWarnings("unused")
			String line=null;
			System.out.println("打印所有正在运行的进程信息");
			while((line=br.readLine())!=null){
				System.out.println(br.readLine());
			}
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			if(br!=null){
				try {
					br.close();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
		

	}

}

 

(4)判断一个具体的进程是否正在运行,如下例:

Java代码 复制代码
  1. package com.iwtxokhtd.other;   
  2. import java.io.BufferedReader;   
  3. import java.io.InputStreamReader;   
  4. public class FindProcessExeTest   
  5. {   
  6.     public static void main(String []args){   
  7.            
  8.         if(findProcess("QQ.exe")){   
  9.             System.out.println("------判断指定的进程是否在运行------");   
  10.             System.out.println("QQ.exe该进程正在运行!");   
  11.         }else{   
  12.             System.out.println("------判断指定的进程是否在运行------");   
  13.             System.out.println("QQ.exe该进程没有在运行!");   
  14.         }   
  15.   
  16.     }   
  17.     public static boolean findProcess(String processName){   
  18.         BufferedReader br=null;   
  19.         try{   
  20.               
  21.             //下面这句是列出含有processName的进程图像名   
  22.             Process proc=Runtime.getRuntime().exec("tasklist /FI /"IMAGENAME eq "+processName+"/"");   
  23.             br=new BufferedReader(new InputStreamReader(proc.getInputStream()));   
  24.             String line=null;   
  25.             while((line=br.readLine())!=null){   
  26.                 //判断指定的进程是否在运行   
  27.                 if(line.contains(processName)){   
  28.                     return true;   
  29.                 }   
  30.             }   
  31.                
  32.             return false;   
  33.         }catch(Exception e){   
  34.             e.printStackTrace();   
  35.             return false;   
  36.         }finally{   
  37.             if(br!=null){   
  38.                 try{   
  39.                     br.close();   
  40.                 }catch(Exception ex){   
  41.                 }   
  42.             }   
  43.                
  44.         }   
  45.     }   
  46. }  
  1. package com.iwtxokhtd.other;  
  2. import java.io.BufferedReader;  
  3. import java.io.InputStreamReader;  
  4. public class FindProcessExeTest  
  5. {  
  6.     public static void main(String []args){  
  7.           
  8.         if(findProcess("QQ.exe")){  
  9.             System.out.println("------判断指定的进程是否在运行------");  
  10.             System.out.println("QQ.exe该进程正在运行!");  
  11.         }else{  
  12.             System.out.println("------判断指定的进程是否在运行------");  
  13.             System.out.println("QQ.exe该进程没有在运行!");  
  14.         }  
  15.   
  16.     }  
  17.     public static boolean findProcess(String processName){  
  18.         BufferedReader br=null;  
  19.         try{  
  20.              
  21.             //下面这句是列出含有processName的进程图像名   
  22.             Process proc=Runtime.getRuntime().exec("tasklist /FI /"IMAGENAME eq "+processName+"/"");  
  23.             br=new BufferedReader(new InputStreamReader(proc.getInputStream()));  
  24.             String line=null;  
  25.             while((line=br.readLine())!=null){  
  26.                 //判断指定的进程是否在运行   
  27.                 if(line.contains(processName)){  
  28.                     return true;  
  29.                 }  
  30.             }  
  31.               
  32.             return false;  
  33.         }catch(Exception e){  
  34.             e.printStackTrace();  
  35.             return false;  
  36.         }finally{  
  37.             if(br!=null){  
  38.                 try{  
  39.                     br.close();  
  40.                 }catch(Exception ex){  
  41.                 }  
  42.             }  
  43.               
  44.         }  
  45.     }  
  46. }  
package com.iwtxokhtd.other;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class FindProcessExeTest
{
	public static void main(String []args){
		
		if(findProcess("QQ.exe")){
			System.out.println("------判断指定的进程是否在运行------");
			System.out.println("QQ.exe该进程正在运行!");
		}else{
			System.out.println("------判断指定的进程是否在运行------");
			System.out.println("QQ.exe该进程没有在运行!");
		}

	}
	public static boolean findProcess(String processName){
		BufferedReader br=null;
		try{
           
			//下面这句是列出含有processName的进程图像名
			Process proc=Runtime.getRuntime().exec("tasklist /FI /"IMAGENAME eq "+processName+"/"");
			br=new BufferedReader(new InputStreamReader(proc.getInputStream()));
			String line=null;
			while((line=br.readLine())!=null){
				//判断指定的进程是否在运行
				if(line.contains(processName)){
					return true;
				}
			}
			
			return false;
		}catch(Exception e){
			e.printStackTrace();
			return false;
		}finally{
			if(br!=null){
				try{
					br.close();
			    }catch(Exception ex){
				}
			}
			
		}
	}
}

 

 其它的用法可以参考JDK文档,这里就不一一举例,毕竟它用得不多。

 
 
 
    在编写Java程序时,有时候我们需要调用其他的诸如exe,shell这样的程序或脚本。在Java中提供了两种方法来启动其他程序:

     (1) 使用Runtime的exec()方法

     (2) 使用ProcessBuilder的start()方法

       Runtime和ProcessBulider提供了不同的方式来启动程序,设置启动参数、环境变量和工作目录。但是这两种方法都会返回一个用于管理操作系统进程的Process对象。这个对象中的waitFor()是我们今天要讨论的重点。

      来说说我遇到的实际情况:我想调用ffmpeg程序来对一首歌曲进行转码,把高音质版本的歌曲转为多种低码率的文件。但是在转码完成之后需要做以下操作:读取文件大小,写入ID3信息等。这时我们就想等转码操作完成之后我们可以知道。

如下这样代码

 

Java代码    收藏代码
  1. Process p = null;  
  2. try {  
  3.     p = Runtime.getRuntime().exec("notepad.exe");  
  4. catch (Exception e) {  
  5.     e.printStackTrace();  
  6. }  
  7. System.out.println("我想被打印...");  

 在notepad.exe被执行的同时,打印也发生了,但是我们想要的是任务完成之后它才被打印。


之后发现在Process类中有一个waitFor()方法可以实现。如下:

 

Java代码    收藏代码
  1. Process p = null;  
  2. try {  
  3.     p = Runtime.getRuntime().exec("notepad.exe");  
  4.     p.waitFor();  
  5. catch (Exception e) {  
  6.     e.printStackTrace();  
  7. }  
  8. System.out.println("我想被打印...");  

 这下又出现了这样的现象,必须要等我们把记事本关闭, 打印语句才会被执行。并且你不能手动关闭它那程序就一直不动,程序貌似挂了.....这是什么情况,想调用个别的程序有这么难吗?让我们来看看waitFor()的说明:


JDK帮助文档上这么说:如有必要,一直要等到由该 Process 对象表示的进程已经终止。如果已终止该子进程,此方法立即返回。但是直接调用这个方法会导致当前线程阻塞,直到退出子进程。对此JDK文档上还有如此解释:因为本地的系统对标准输入和输出所提供的缓冲池有效,所以错误的对标准输出快速的写入何从标准输入快速的读入都有可能造成子进程的所,甚至死锁。好了,

        问题的关键在缓冲区这个地方:可执行程序的标准输出比较多,而运行窗口的标准缓冲区不够大,所以发生阻塞。

        接着来分析缓冲区,哪来的这个东西,当Runtime对象调用exec(cmd)后,JVM会启动一个子进程,该进程会与JVM进程建立三个管道连接标准输入标准输出标准错误流

 

假设该程序不断在向标准输出流和标准错误流写数据,而JVM不读取的话,当缓冲区满之后将无法继续写入数据,最终造成阻塞在waitfor()这里。 知道问题所在,我们解决问题就好办了。查看网上说的方法多数是开两个线程在waitfor()命令之前读出窗口的标准输出缓冲区和标准错误流的内容。代码如下:

 

Java代码    收藏代码
  1. Runtime rt = Runtime.getRuntime();  
  2. String command = "cmd /c ffmpeg -loglevel quiet -i "+srcpath+" -ab "+bitrate+"k -acodec libmp3lame "+desfile;  
  3. try {  
  4.  p = rt.exec(command ,null,new File("C:\\ffmpeg-git-670229e-win32-static\\bin"));  
  5.  //获取进程的标准输入流  
  6.  final InputStream is1 = p.getInputStream();   
  7.  //获取进城的错误流  
  8.  final InputStream is2 = p.getErrorStream();  
  9.  //启动两个线程,一个线程负责读标准输出流,另一个负责读标准错误流  
  10.  new Thread() {  
  11.     public void run() {  
  12.        BufferedReader br1 = new BufferedReader(new InputStreamReader(is1));  
  13.         try {  
  14.             String line1 = null;  
  15.             while ((line1 = br1.readLine()) != null) {  
  16.                   if (line1 != null){}  
  17.               }  
  18.         } catch (IOException e) {  
  19.              e.printStackTrace();  
  20.         }  
  21.         finally{  
  22.              try {  
  23.                is1.close();  
  24.              } catch (IOException e) {  
  25.                 e.printStackTrace();  
  26.             }  
  27.           }  
  28.         }  
  29.      }.start();  
  30.                                 
  31.    new Thread() {   
  32.       public void  run() {   
  33.        BufferedReader br2 = new  BufferedReader(new  InputStreamReader(is2));   
  34.           try {   
  35.              String line2 = null ;   
  36.              while ((line2 = br2.readLine()) !=  null ) {   
  37.                   if (line2 != null){}  
  38.              }   
  39.            } catch (IOException e) {   
  40.                  e.printStackTrace();  
  41.            }   
  42.           finally{  
  43.              try {  
  44.                  is2.close();  
  45.              } catch (IOException e) {  
  46.                  e.printStackTrace();  
  47.              }  
  48.            }  
  49.         }   
  50.       }.start();    
  51.                                 
  52.       p.waitFor();  
  53.       p.destroy();   
  54.      System.out.println("我想被打印...");  
  55.     } catch (Exception e) {  
  56.             try{  
  57.                 p.getErrorStream().close();  
  58.                 p.getInputStream().close();  
  59.                 p.getOutputStream().close();  
  60.                 }  
  61.              catch(Exception ee){}  
  62.           }  
  63.    }  

 这个方法确实可以解决调用waitFor()方法阻塞无法返回的问题。但是在其中过程中我却发现真正起关键作用的缓冲区是getErrorStream()所对应的那个缓冲区没有被清空,意思就是说其实只要及时读取标准错误流缓冲区的数据程序就不会被block

 

Java代码    收藏代码
  1. StringBuffer sb = new StringBuffer();  
  2. try {  
  3. Process pro = Runtime.getRuntime().exec(cmdString);  
  4. BufferedReader br = new BufferedReader(new InputStreamReader(pro.getInputStream()), 4096);  
  5. String line = null;  
  6. int i = 0;  
  7. while ((line = br.readLine()) != null) {  
  8. if (0 != i)  
  9. sb.append("\r\n");  
  10. i++;  
  11. sb.append(line);  
  12. }  
  13. catch (Exception e) {  
  14. sb.append(e.getMessage());  
  15. }  
  16. return sb.toString();  

 

 不过这种写法不知道是不是适合所有的情况,网上其他人说的需要开两个线程可能不是没有道理。这个还是具体问题具体对待吧。

 

到这里问题的原因也清楚了,问题也被解决了,是不是就结束了。让我们回过头来再分析一下,问题的关键是处在输入流缓冲区那个地方,子进程的产生的输出流没有被JVM及时的读取最后缓冲区满了就卡住了。如果我们能够不让子进程向输入流写入数据,是不是可以解决这个问题。对于这个想法直接去ffmpeg官网查找,最终发现真的可以关闭子进程向窗口写入数据。命令如下:
ffmpeg.exe -loglevel quiet -i 1.mp3 -ab 16k -ar 22050 -acodec libmp3lame r.mp3
稍微分析一下:-acodec 音频流编码方式 -ab 音频流码率(默认是同源文件码率,也需要视codec而定) -ar 音频流采样率(大多数情况下使用44100和48000,分别对应PAL制式和NTSC制式,根据需要选择),重点就是-loglevel quiet这句 

 

这才是我们想要的结果:

 

Java代码    收藏代码
  1. try {  
  2.   p = Runtime.getRuntime().exec("cmd /c ffmpeg -loglevel quiet -i     D:\\a.mp3 -ab 168k -ar 22050 -acodec libmp3lame D:\\b.mp3",null,  
  3.                     new File( "C:\\ffmpeg-git-670229e-win32-static\\bin"));  
  4.   p.waitFor();  
  5. catch (Exception e) {  
  6.     e.printStackTrace();  
  7. }  
  8. System.out.println("我想被打印...");  

 

最后是自己写的一个简单的操作MP3文件的类

 

Java代码    收藏代码
  1. package com.yearsaaaa.util;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileInputStream;  
  5. import java.math.BigDecimal;  
  6.   
  7. import javazoom.jl.decoder.Bitstream;  
  8. import javazoom.jl.decoder.Header;  
  9.   
  10. /** 
  11.  * @className:MP3Util.java 
  12.  * @classDescription: 
  13.  * @author:MChen 
  14.  * @createTime:2012-2-9 
  15.  */  
  16. public class MP3Util {  
  17.       
  18.     /** 
  19.      * 获取文件大小,以M为单位,保留小数点两位 
  20.      */  
  21.     public static double getMP3Size(String path)  
  22.     {  
  23.         File file = new File(path);  
  24.         double size = (double)file.length()/(1024*1024);  
  25.         size = new BigDecimal(size).setScale(2,BigDecimal.ROUND_UP).doubleValue();  
  26.         System.out.println("MP3文件的大小为:"+size);  
  27.         return size;  
  28.     }  
  29.       
  30.     /** 
  31.      * 该方法只能获取mp3格式的歌曲长度 
  32.      * 库地址:http://www.javazoom.net/javalayer/javalayer.html 
  33.      */  
  34.     public static String getMP3Time(String path)  
  35.     {  
  36.         String songTime = null;  
  37.         FileInputStream fis = null;  
  38.         Bitstream bt = null;  
  39.         File file = new File(path);  
  40.         try {  
  41.             fis = new FileInputStream(file);  
  42.             int b=fis.available();  
  43.             bt=new Bitstream(fis);  
  44.             Header h=bt.readFrame();  
  45.             int time=(int) h.total_ms(b);  
  46.             int i=time/1000;  
  47.             bt.close();  
  48.             fis.close();  
  49.             if(i%60 == 0)  
  50.                 songTime = (i/60+":"+i%60+"0");  
  51.             if(i%60 <10)  
  52.                 songTime = (i/60+":"+"0"+i%60);  
  53.             else  
  54.                 songTime = (i/60+":"+i%60);  
  55.             System.out.println("该歌曲的长度为:"+songTime);  
  56.         }  
  57.         catch (Exception e) {  
  58.             try {  
  59.                 bt.close();  
  60.                 fis.close();  
  61.             } catch (Exception ee) {  
  62.                 ee.printStackTrace();  
  63.             }  
  64.         }  
  65.         return songTime;  
  66.     }  
  67.       
  68.     /** 
  69.      * 将源MP3向下转码成低品质的文件 
  70.      * @参数: @param srcPath 源地址 
  71.      * @参数: @param bitrate 比特率 
  72.      * @参数: @param desfile 目标文件 
  73.      * @return void    
  74.      * @throws 
  75.      */  
  76.     public static void mp3Transcoding(String srcPath,String bitrate,String desFile)  
  77.     {     
  78.         //Java调用CMD命令时,不能有空格  
  79.         String srcpath = srcPath.replace(" ""\" \"");  
  80.         String desfile = desFile.replace(" ""\" \"");  
  81.         Runtime rt = Runtime.getRuntime();  
  82.         String command = "cmd /c ffmpeg -loglevel quiet -i "+srcpath+" -ab "+bitrate+"k -acodec libmp3lame "+desfile;  
  83.         System.out.println(command);  
  84.         Process p = null;  
  85.         try{  
  86.             //在Linux下调用是其他写法  
  87.             p = rt.exec(command ,null,new File("C:\\ffmpeg-git-670229e-win32-static\\bin"));  
  88.             p.waitFor();  
  89.             System.out.println("线程返回,转码后的文件大小为:"+desFile.length()+",现在可以做其他操作了,比如重新写入ID3信息。");  
  90.         }  
  91.         catch(Exception e){  
  92.             e.printStackTrace();  
  93.             try{  
  94.                 p.getErrorStream().close();  
  95.                 p.getInputStream().close();  
  96.                 p.getOutputStream().close();  
  97.                 }  
  98.             catch(Exception ee){}  
  99.         }  
  100.     }  
  101.       
  102.     public static void main(String[] args) {  
  103.         //String[] str = {"E:\\Kugou\\陈慧娴 - 不羁恋人.mp3","E:\\Kugou\\三寸天堂.mp3","E:\\Tmp\\陈淑桦 - 梦醒时分.mp3","E:\\Tmp\\1.mp3","E:\\Test1\\走天涯、老猫 - 杨望.acc","E:\\Test1\\因为爱情 铃.mp3"};  
  104.         String[] str = {"E:\\Kugou\\三寸天堂.mp3"};  
  105.         for(String s : str)  
  106.         {  
  107.             //getMP3Size(s);  
  108.             //getMP3Time(s);  
  109.             File f = new File(s);  
  110.             mp3Transcoding(f.getAbsolutePath(),"64","d:\\chenmiao.mp3");  
  111.         }  
  112.     }  
  113. }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值