小菜毛毛技術(shù)分享

          與大家共同成長(zhǎng)

            BlogJava :: 首頁(yè) :: 聯(lián)系 :: 聚合  :: 管理
            164 Posts :: 141 Stories :: 94 Comments :: 0 Trackbacks

          那就首先說(shuō)點(diǎn)Runtime類吧,他是一個(gè)與JVM運(yùn)行時(shí)環(huán)境有關(guān)的類,這個(gè)類是Singleton的。我說(shuō)幾個(gè)自己覺(jué)得重要的地方。

          1、Runtime.getRuntime()可以取得當(dāng)前JVM的運(yùn)行時(shí)環(huán)境,這也是在Java中唯一一個(gè)得到運(yùn)行時(shí)環(huán)境的方法。

          2、Runtime上其他大部分的方法都是實(shí)例方法,也就是說(shuō)每次進(jìn)行運(yùn)行時(shí)調(diào)用時(shí)都要用到getRuntime方法。

          3、Runtime中的exit方法是退出當(dāng)前JVM的方法,估計(jì)也是唯一的一個(gè)吧,因?yàn)槲铱吹絊ystem類中的exit實(shí)際上也是通過(guò)調(diào)用Runtime.exit()來(lái)退出JVM的,這里說(shuō)明一下Java對(duì)Runtime返回值的一般規(guī)則(后邊也提到了),0代表正常退出,非0代表異常中止,這只是Java的規(guī)則,在各個(gè)操作系統(tǒng)中總會(huì)發(fā)生一些小的混淆。


          4、Runtime.addShutdownHook()方法可以注冊(cè)一個(gè)hook在JVM執(zhí)行shutdown的過(guò)程中,方法的參數(shù)只要是一個(gè)初始化過(guò)但是沒(méi)有執(zhí)行的Thread實(shí)例就可以。(注意,Java中的Thread都是執(zhí)行過(guò)了就不值錢的哦)

          5、說(shuō)到addShutdownHook這個(gè)方法就要說(shuō)一下JVM運(yùn)行環(huán)境是在什么情況下shutdown或者abort的。文檔上是這樣寫的,當(dāng)最后一個(gè)非精靈進(jìn)程退出或者收到了一個(gè)用戶中斷信號(hào)、用戶登出、系統(tǒng)shutdown、Runtime的exit方法被調(diào)用時(shí)JVM會(huì)啟動(dòng)shutdown的過(guò)程,在這個(gè)過(guò)程開始后,他會(huì)并行啟動(dòng)所有登記的shutdown hook(注意是并行啟動(dòng),這就需要線程安全和防止死鎖)。當(dāng)shutdown過(guò)程啟動(dòng)后,只有通過(guò)調(diào)用halt方法才能中止shutdown的過(guò)程并退出JVM。

          那什么時(shí)候JVM會(huì)abort退出那?首先說(shuō)明一下,abort退出時(shí)JVM就是停止運(yùn)行但并不一定進(jìn)行shutdown。這只有JVM在遇到SIGKILL信號(hào)或者windows中止進(jìn)程的信號(hào)、本地方法發(fā)生類似于訪問(wèn)非法地址一類的內(nèi)部錯(cuò)誤時(shí)會(huì)出現(xiàn)。這種情況下并不能保證shutdown hook是否被執(zhí)行。


          現(xiàn)在開始看這篇文章,呵呵。


          首先講的是Runtime.exec()方法的所有重載。這里要注意的有一點(diǎn),就是public Process exec(String [] cmdArray, String [] envp);這個(gè)方法中cmdArray是一個(gè)執(zhí)行的命令和參數(shù)的字符串?dāng)?shù)組,數(shù)組的第一個(gè)元素是要執(zhí)行的命令往后依次都是命令的參數(shù),envp我個(gè)人感覺(jué)應(yīng)該和C中的execve中的環(huán)境變量是一樣的,envp中使用的是name=value的方式。


          <!--[if !supportLists]-->1、 <!--[endif]-->一個(gè)很糟糕的調(diào)用程序,代碼如下,這個(gè)程序用exec調(diào)用了一個(gè)外部命令之后馬上使用exitValue就對(duì)其返回值進(jìn)行檢查,讓我們看看會(huì)出現(xiàn)什么問(wèn)題。


          import java.util.*;
          import java.io.*;

          public class BadExecJavac
          {
          public static void main(String args[])
          {
          try
          {
          Runtime rt = Runtime.getRuntime();
          Process proc = rt.exec("javac");
          int exitVal = proc.exitValue();
          System.out.println("Process exitValue: " + exitVal);
          } catch (Throwable t)
          {
          t.printStackTrace();
          }
          }
          }

          A run of BadExecJavac produces:


          E:classescomjavaworldjpitfallsarticle2>java BadExecJavac
          java.lang.IllegalThreadStateException: process has not exited
          at java.lang.Win32Process.exitValue(Native Method)
          at BadExecJavac.main(BadExecJavac.java:13)


          這里看原文就可以了解,這里主要的問(wèn)題就是錯(cuò)誤的調(diào)用了exitValue來(lái)取得外部命令的返回值(呵呵,這個(gè)錯(cuò)誤我也曾經(jīng)犯過(guò)),因?yàn)閑xitValue這個(gè)方法是不阻塞的,程序在調(diào)用這個(gè)方法時(shí)外部命令并沒(méi)有返回所以造成了異常的出現(xiàn),這里是由另外的方法來(lái)等待外部命令執(zhí)行完畢的,就是waitFor方法,這個(gè)方法會(huì)一直阻塞直到外部命令執(zhí)行結(jié)束,然后返回外部命令執(zhí)行的結(jié)果,作者在這里一頓批評(píng)設(shè)計(jì)者的思路有問(wèn)題,呵呵,反正我是無(wú)所謂阿,能用就可以拉。但是作者在這里有一個(gè)說(shuō)明,就是exitValue也是有好多用途的。因?yàn)楫?dāng)你在一個(gè)Process上調(diào)用waitFor方法時(shí),當(dāng)前線程是阻塞的,如果外部命令無(wú)法執(zhí)行結(jié)束,那么你的線程就會(huì)一直阻塞下去,這種意外會(huì)影響我們程序的執(zhí)行。所以在我們不能判斷外部命令什么時(shí)候執(zhí)行完畢而我們的程序還需要繼續(xù)執(zhí)行的情況下,我們就應(yīng)該循環(huán)的使用exitValue來(lái)取得外部命令的返回狀態(tài),并在外部命令返回時(shí)作出相應(yīng)的處理。


          2、對(duì)exitValue處改進(jìn)了的程序

          import java.util.*;
          import java.io.*;

          public class BadExecJavac2
          {
          public static void main(String args[])
          {
          try
          {
          Runtime rt = Runtime.getRuntime();
          Process proc = rt.exec("javac");
          int exitVal = proc.waitFor();
          System.out.println("Process exitValue: " + exitVal);
          } catch (Throwable t)
          {
          t.printStackTrace();
          }
          }
          }

          不幸的是,這個(gè)程序也無(wú)法執(zhí)行完成,它沒(méi)有輸出但卻一直懸在那里,這是為什么那?


          JDK文檔中對(duì)此有如此的解釋:因?yàn)楸镜氐南到y(tǒng)對(duì)標(biāo)準(zhǔn)輸入和輸出所提供的緩沖池有效,所以錯(cuò)誤的對(duì)標(biāo)準(zhǔn)輸出快速的寫入和從標(biāo)準(zhǔn)輸入快速的讀入都有可能造成子進(jìn)程的鎖,甚至死鎖。


          文檔引述完了,作者又開始批評(píng)了,他說(shuō)JDK僅僅說(shuō)明為什么問(wèn)題會(huì)發(fā)生,卻并沒(méi)有說(shuō)明這個(gè)問(wèn)題怎么解決,這的確是個(gè)問(wèn)題哈。緊接著作者說(shuō)出自己的做法,就是在執(zhí)行完外部命令后我們要控制好Process的所有輸入和輸出(視情況而定),在這個(gè)例子里邊因?yàn)檎{(diào)用的是Javac,而他在沒(méi)有參數(shù)的情況下會(huì)將提示信息輸出到標(biāo)準(zhǔn)出錯(cuò),所以在下面的程序中我們要對(duì)此進(jìn)行處理。


          import java.util.*;
          import java.io.*;

          public class MediocreExecJavac
          {
          public static void main(String args[])
          {
          try
          {
          Runtime rt = Runtime.getRuntime();
          Process proc = rt.exec("javac");
          InputStream stderr = proc.getErrorStream();
          InputStreamReader isr = new InputStreamReader(stderr);
          BufferedReader br = new BufferedReader(isr);
          String line = null;
          System.out.println("<ERROR>");
          while ( (line = br.readLine()) != null)
          System.out.println(line);
          System.out.println("</ERROR>");
          int exitVal = proc.waitFor();
          System.out.println("Process exitValue: " + exitVal);
          } catch (Throwable t)
          {
          t.printStackTrace();
          }
          }
          }


          程序的運(yùn)行結(jié)果為

          E:classescomjavaworldjpitfallsarticle2>java MediocreExecJavac
          <ERROR>
          Usage: javac <options> <source files>

          where <options> includes:
          -g Generate all debugging info
          -g:none Generate no debugging info
          -g:{lines,vars,source} Generate only some debugging info
          -O Optimize; may hinder debugging or enlarge class files
          -nowarn Generate no warnings
          -verbose Output messages about what the compiler is doing
          -deprecation Output source locations where deprecated APIs are used
          -classpath <path> Specify where to find user class files
          -sourcepath <path> Specify where to find input source files
          -bootclasspath <path> Override location of bootstrap class files
          -extdirs <dirs> Override location of installed extensions
          -d <directory> Specify where to place generated class files
          -encoding <encoding> Specify character encoding used by source files
          -target <release> Generate class files for specific VM version
          </ERROR>
          Process exitValue: 2


          哎,不管怎么說(shuō)還是出來(lái)了結(jié)果,作者作了一下總結(jié),就是說(shuō),為了處理好外部命令大量輸出的情況,你要確保你的程序處理好外部命令所需要的輸入或者輸出。


          下一個(gè)題目,當(dāng)我們調(diào)用一個(gè)我們認(rèn)為是可執(zhí)行程序的時(shí)候容易發(fā)生的錯(cuò)誤(今天晚上我剛剛犯這個(gè)錯(cuò)誤,沒(méi)事做這個(gè)練習(xí)時(shí)候發(fā)生的)

          import java.util.*;
          import java.io.*;

          public class BadExecWinDir
          {
          public static void main(String args[])
          {
          try
          {
          Runtime rt = Runtime.getRuntime();
          Process proc = rt.exec("dir");
          InputStream stdin = proc.getInputStream();
          InputStreamReader isr = new InputStreamReader(stdin);
          BufferedReader br = new BufferedReader(isr);
          String line = null;
          System.out.println("<OUTPUT>");
          while ( (line = br.readLine()) != null)
          System.out.println(line);
          System.out.println("</OUTPUT>");
          int exitVal = proc.waitFor();
          System.out.println("Process exitValue: " + exitVal);
          } catch (Throwable t)
          {
          t.printStackTrace();
          }
          }
          }

          A run of BadExecWinDir produces:


          E:classescomjavaworldjpitfallsarticle2>java BadExecWinDir
          java.io.IOException: CreateProcess: dir error=2
          at java.lang.Win32Process.create(Native Method)
          at java.lang.Win32Process.<init>(Unknown Source)
          at java.lang.Runtime.execInternal(Native Method)
          at java.lang.Runtime.exec(Unknown Source)
          at java.lang.Runtime.exec(Unknown Source)
          at java.lang.Runtime.exec(Unknown Source)
          at java.lang.Runtime.exec(Unknown Source)
          at BadExecWinDir.main(BadExecWinDir.java:12)


          說(shuō)實(shí)在的,這個(gè)錯(cuò)誤還真是讓我摸不著頭腦,我覺(jué)得在windows中返回2應(yīng)該是沒(méi)有找到這個(gè)文件的緣故,可能windows 2000中只有cmd命令,dir命令不是當(dāng)前環(huán)境變量能夠解釋的吧。我也不知道了,慢慢往下看吧。

          嘿,果然和作者想的一樣,就是因?yàn)閐ir命令是由windows中的解釋器解釋的,直接執(zhí)行dir時(shí)無(wú)法找到dir.exe這個(gè)命令,所以會(huì)出現(xiàn)文件未找到這個(gè)2的錯(cuò)誤。如果我們要執(zhí)行這樣的命令,就要先根據(jù)操作系統(tǒng)的不同執(zhí)行不同的解釋程序command.com 或者cmd.exe。

          作者對(duì)上邊的程序進(jìn)行了修改

          import java.util.*;
          import java.io.*;

          class StreamGobbler extends Thread
          {
          InputStream is;
          String type;

          StreamGobbler(InputStream is, String type)
          {
          this.is = is;
          this.type = type;
          }

          public void run()
          {
          try
          {
          InputStreamReader isr = new InputStreamReader(is);
          BufferedReader br = new BufferedReader(isr);
          String line=null;
          while ( (line = br.readLine()) != null)
          System.out.println(type + ">" + line);
          } catch (IOException ioe)
          {
          ioe.printStackTrace();
          }
          }
          }

          public class GoodWindowsExec
          {
          public static void main(String args[])
          {
          if (args.length < 1)
          {
          System.out.println("USAGE: java GoodWindowsExec <cmd>");
          System.exit(1);
          }

          try
          {
          String osName = System.getProperty("os.name" );
          String[] cmd = new String[3];

          if( osName.equals( "Windows NT" ) )
          {
          cmd[0] = "cmd.exe" ;
          cmd[1] = "/C" ;
          cmd[2] = args[0];
          }
          else if( osName.equals( "Windows 95" ) )
          {
          cmd[0] = "command.com" ;
          cmd[1] = "/C" ;
          cmd[2] = args[0];
          }

          Runtime rt = Runtime.getRuntime();
          System.out.println("Execing " + cmd[0] + " " + cmd[1]
          + " " + cmd[2]);
          Process proc = rt.exec(cmd);
          // any error message?
          StreamGobbler errorGobbler = new
          StreamGobbler(proc.getErrorStream(), "ERROR");

          // any output?
          StreamGobbler outputGobbler = new
          StreamGobbler(proc.getInputStream(), "OUTPUT");

          // kick them off
          errorGobbler.start();
          outputGobbler.start();

          // any error???
          int exitVal = proc.waitFor();
          System.out.println("ExitValue: " + exitVal);
          } catch (Throwable t)
          {
          t.printStackTrace();
          }
          }
          }

          Running GoodWindowsExec with the dir command generates:


          E:classescomjavaworldjpitfallsarticle2>java GoodWindowsExec "dir *.java"
          Execing cmd.exe /C dir *.java
          OUTPUT> Volume in drive E has no label.
          OUTPUT> Volume Serial Number is 5C5F-0CC9
          OUTPUT>
          OUTPUT> Directory of E:classescomjavaworldjpitfallsarticle2
          OUTPUT>
          OUTPUT>10/23/00 09:01p 805 BadExecBrowser.java
          OUTPUT>10/22/00 09:35a 770 BadExecBrowser1.java
          OUTPUT>10/24/00 08:45p 488 BadExecJavac.java
          OUTPUT>10/24/00 08:46p 519 BadExecJavac2.java
          OUTPUT>10/24/00 09:13p 930 BadExecWinDir.java
          OUTPUT>10/22/00 09:21a 2,282 BadURLPost.java
          OUTPUT>10/22/00 09:20a 2,273 BadURLPost1.java
          ... (some output omitted for brevity)
          OUTPUT>10/12/00 09:29p 151 SuperFrame.java
          OUTPUT>10/24/00 09:23p 1,814 TestExec.java
          OUTPUT>10/09/00 05:47p 23,543 TestStringReplace.java
          OUTPUT>10/12/00 08:55p 228 TopLevel.java
          OUTPUT> 22 File(s) 46,661 bytes
          OUTPUT> 19,678,420,992 bytes free
          ExitValue: 0

          這里作者教了一個(gè)windows中很有用的方法,呵呵,至少我是不知道的,就是cmd.exe /C +一個(gè)windows中注冊(cè)了后綴的文檔名,windows會(huì)自動(dòng)地調(diào)用相關(guān)的程序來(lái)打開這個(gè)文檔,我試了一下,的確很好用,但是好像文件路徑中有空格的話就有點(diǎn)問(wèn)題,我加上引號(hào)也無(wú)法解決。

          這里作者強(qiáng)調(diào)了一下,不要假設(shè)你執(zhí)行的程序是可執(zhí)行的程序,要清楚自己的程序是單獨(dú)可執(zhí)行的還是被解釋的,本章的結(jié)束作者會(huì)介紹一個(gè)命令行工具來(lái)幫助我們分析。

          這里還有一點(diǎn),就是得到process的輸出的方式是getInputStream,這是因?yàn)槲覀円獜腏ava 程序的角度來(lái)看,外部程序的輸出對(duì)于Java來(lái)說(shuō)就是輸入,反之亦然。


          最后的一個(gè)漏洞的地方就是錯(cuò)誤的認(rèn)為exec方法會(huì)接受所有你在命令行或者Shell中輸入并接受的字符串。這些錯(cuò)誤主要出現(xiàn)在命令作為參數(shù)的情況下,程序員錯(cuò)誤的將所有命令行中可以輸入的參數(shù)命令加入到exec中(這段翻譯的不好,湊合看吧)。下面的例子中就是一個(gè)程序員想重定向一個(gè)命令的輸出。


          import java.util.*;
          import java.io.*;

          // StreamGobbler omitted for brevity

          public class BadWinRedirect
          {
          public static void main(String args[])
          {
          try
          {
          Runtime rt = Runtime.getRuntime();
          Process proc = rt.exec("java jecho 'Hello World' > test.txt");
          // any error message?
          StreamGobbler errorGobbler = new
          StreamGobbler(proc.getErrorStream(), "ERROR");

          // any output?
          StreamGobbler outputGobbler = new
          StreamGobbler(proc.getInputStream(), "OUTPUT");

          // kick them off
          errorGobbler.start();
          outputGobbler.start();

          // any error???
          int exitVal = proc.waitFor();
          System.out.println("ExitValue: " + exitVal);
          } catch (Throwable t)
          {
          t.printStackTrace();
          }
          }
          }

          Running BadWinRedirect produces:


          E:classescomjavaworldjpitfallsarticle2>java BadWinRedirect
          OUTPUT>'Hello World' > test.txt
          ExitValue: 0

          程序員的本意是將Hello World這個(gè)輸入重訂向到一個(gè)文本文件中,但是這個(gè)文件并沒(méi)有生成,jecho僅僅是將命令行中的參數(shù)輸出到標(biāo)準(zhǔn)輸出中,用戶覺(jué)得可以像dos中重定向一樣將輸出重定向到一個(gè)文件中,但這并不能實(shí)現(xiàn),用戶錯(cuò)誤的將exec認(rèn)為是一個(gè)shell解釋器,但它并不是,如果你想將一個(gè)程序的輸出重定向到其他的程序中,你必須用程序來(lái)實(shí)現(xiàn)他。可用java.io中的包。


          import java.util.*;
          import java.io.*;

          class StreamGobbler extends Thread
          {
          InputStream is;
          String type;
          OutputStream os;

          StreamGobbler(InputStream is, String type)
          {
          this(is, type, null);
          }

          StreamGobbler(InputStream is, String type, OutputStream redirect)
          {
          this.is = is;
          this.type = type;
          this.os = redirect;
          }

          public void run()
          {
          try
          {
          PrintWriter pw = null;
          if (os != null)
          pw = new PrintWriter(os);

          InputStreamReader isr = new InputStreamReader(is);
          BufferedReader br = new BufferedReader(isr);
          String line=null;
          while ( (line = br.readLine()) != null)
          {
          if (pw != null)
          pw.println(line);
          System.out.println(type + ">" + line);
          }
          if (pw != null)
          pw.flush();
          } catch (IOException ioe)
          {
          ioe.printStackTrace();
          }
          }
          }

          public class GoodWinRedirect
          {
          public static void main(String args[])
          {
          if (args.length < 1)
          {
          System.out.println("USAGE java GoodWinRedirect <outputfile>");
          System.exit(1);
          }

          try
          {
          FileOutputStream fos = new FileOutputStream(args[0]);
          Runtime rt = Runtime.getRuntime();
          Process proc = rt.exec("java jecho 'Hello World'");
          // any error message?
          StreamGobbler errorGobbler = new
          StreamGobbler(proc.getErrorStream(), "ERROR");

          // any output?
          StreamGobbler outputGobbler = new
          StreamGobbler(proc.getInputStream(), "OUTPUT", fos);

          // kick them off
          errorGobbler.start();
          outputGobbler.start();

          // any error???
          int exitVal = proc.waitFor();
          System.out.println("ExitValue: " + exitVal);
          fos.flush();
          fos.close();
          } catch (Throwable t)
          {
          t.printStackTrace();
          }
          }
          }

          Running GoodWinRedirect produces:


          E:classescomjavaworldjpitfallsarticle2>java GoodWinRedirect test.txt
          OUTPUT>'Hello World'
          ExitValue: 0

          這里就不多說(shuō)了,看看就明白,緊接著作者給出了一個(gè)監(jiān)測(cè)命令的小程序

          import java.util.*;
          import java.io.*;

          // class StreamGobbler omitted for brevity

          public class TestExec
          {
          public static void main(String args[])
          {
          if (args.length < 1)
          {
          System.out.println("USAGE: java TestExec "cmd"");
          System.exit(1);
          }

          try
          {
          String cmd = args[0];
          Runtime rt = Runtime.getRuntime();
          Process proc = rt.exec(cmd);

          // any error message?
          StreamGobbler errorGobbler = new
          StreamGobbler(proc.getErrorStream(), "ERR");

          // any output?
          StreamGobbler outputGobbler = new
          StreamGobbler(proc.getInputStream(), "OUT");

          // kick them off
          errorGobbler.start();
          outputGobbler.start();

          // any error???
          int exitVal = proc.waitFor();
          System.out.println("ExitValue: " + exitVal);
          } catch (Throwable t)
          {
          t.printStackTrace();
          }
          }
          }

          對(duì)這個(gè)程序進(jìn)行運(yùn)行:
          E:classescomjavaworldjpitfallsarticle2>java TestExec "e:javadocsindex.html"
          java.io.IOException: CreateProcess: e:javadocsindex.html error=193
          at java.lang.Win32Process.create(Native Method)
          at java.lang.Win32Process.<init>(Unknown Source)
          at java.lang.Runtime.execInternal(Native Method)
          at java.lang.Runtime.exec(Unknown Source)
          at java.lang.Runtime.exec(Unknown Source)
          at java.lang.Runtime.exec(Unknown Source)
          at java.lang.Runtime.exec(Unknown Source)
          at TestExec.main(TestExec.java:45)

          193在windows中是說(shuō)這不是一個(gè)win32程序,這說(shuō)明路徑中找不到這個(gè)網(wǎng)頁(yè)的關(guān)聯(lián)程序,下面作者決定用一個(gè)絕對(duì)路徑來(lái)試一下。

          E:classescomjavaworldjpitfallsarticle2>java TestExec
          "e:program filesnetscapeprogramnetscape.exe e:javadocsindex.html"
          ExitValue: 0


          好用了,這個(gè)我也試了一下,用的是IE。


          最后,作者總結(jié)了幾條規(guī)則,防止我們?cè)谶M(jìn)行Runtime.exec()調(diào)用時(shí)出現(xiàn)錯(cuò)誤。


          <!--[if !supportLists]-->1、 <!--[endif]-->在一個(gè)外部進(jìn)程執(zhí)行完之前你不能得到他的退出狀態(tài)

          <!--[if !supportLists]-->2、 <!--[endif]-->在你的外部程序開始執(zhí)行的時(shí)候你必須馬上控制輸入、輸出、出錯(cuò)這些流。

          <!--[if !supportLists]-->3、 <!--[endif]-->你必須用Runtime.exec()去執(zhí)行程序

          <!--[if !supportLists]-->4、 <!--[endif]-->你不能象命令行一樣使用Runtime.exec()。

          posted on 2010-01-19 13:50 小菜毛毛 閱讀(559) 評(píng)論(0)  編輯  收藏 所屬分類: J2EE相關(guān)技術(shù)與框架
          主站蜘蛛池模板: 云林县| 黄大仙区| 石家庄市| 雷波县| 雅安市| 贵德县| 枣阳市| 永吉县| 东阿县| 芦山县| 宕昌县| 泰州市| 诸暨市| 崇仁县| 沂南县| 石渠县| 仁化县| 和平县| 建瓯市| 安福县| 磐石市| 永清县| 白水县| 樟树市| 赣榆县| 丰镇市| 米泉市| 遂宁市| 三明市| 龙胜| 上栗县| 连南| 德清县| 瑞丽市| 关岭| 松原市| 临邑县| 台东县| 肇州县| 宝应县| 西峡县|