Azureus中解析Torrent种子文件的源代码进行了适度裁剪,得到下面这样一个解析torrent文件的示例代码,如下所示:

  1. /*  
  2.  * BeDecoder.java  
  3.  *  
  4.  */ 
  5.  
  6. package com.vista.test;  
  7.  
  8. import java.io.*;  
  9. import java.nio.ByteBuffer;  
  10. import java.nio.CharBuffer;  
  11. import java.nio.charset.Charset;  
  12. import java.util.ArrayList;  
  13. import java.util.HashMap;  
  14. import java.util.Iterator;  
  15. import java.util.List;  
  16. import java.util.Map;  
  17.  
  18. /**  
  19.  * A set of utility methods to decode a bencoded array of byte into a Map.  
  20.  * integer are represented as Long, String as byte[], dictionnaries as Map, and list as List.  
  21.  *  
  22.  */ 
  23. public class BDecoder   
  24. {  
  25.     //字符集  
  26.     public static final String BYTE_ENCODING = "UTF8";  
  27.     public static Charset    BYTE_CHARSET;  
  28.     static 
  29.     {  
  30.         try 
  31.         {  
  32.             BYTE_CHARSET     = Charset.forName(BYTE_ENCODING);  
  33.         }  
  34.         catch( Throwable e )  
  35.         {  
  36.             e.printStackTrace();  
  37.         }  
  38.     }  
  39.     private static final boolean TRACE    = true;  
  40.       
  41.     private boolean recovery_mode;  
  42.  
  43.     public static Map decode(BufferedInputStream is) throws Exception  
  44.     {//解码  
  45.         returnnew BDecoder().decodeStream( is ));  
  46.     }  
  47.       
  48.     public BDecoder()   
  49.     {      
  50.     }  
  51.       
  52.     public Map decodeStream(BufferedInputStream data ) throws Exception  
  53.     {  
  54.         Object    res = decodeInputStream(new BDecoderInputStreamStream(data), 0);//0指定递归层次从第一层开始  
  55.         if ( res == null )  
  56.         {  
  57.             thrownew Exception( "BDecoder: zero length file" ));  
  58.  
  59.         }  
  60.         else if ( !(res instanceof Map ))  
  61.         {  
  62.             thrownew Exception( "BDecoder: top level isn't a Map" ));  
  63.         }  
  64.         return((Map)res );  
  65.     }  
  66.  
  67.     /**  
  68.      *   
  69.      * @param dbis  
  70.      * @param nesting 递归层次  
  71.      * @throws Exception  
  72.      */ 
  73.     private Object decodeInputStream(BDecoderInputStream dbis,int nesting ) throws Exception   
  74.     {  
  75.         if (nesting == 0 && !dbis.markSupported())  
  76.         {  
  77.             throw new IOException("InputStream must support the mark() method");  
  78.         }  
  79.         //set a mark  
  80.         dbis.mark(Integer.MAX_VALUE);  
  81.  
  82.         //read a byte  
  83.         int tempByte = dbis.read();//读一个字节  
  84.  
  85.         //decide what to do          
  86.         switch (tempByte)  
  87.         {  
  88.             case 'd' :  
  89.             {//是字典  
  90.                 //create a new dictionary object  
  91.                 Map tempMap = new HashMap();  
  92.                 try 
  93.                 {  
  94.                     //get the key     
  95.                     byte[] tempByteArray = null;  
  96.                     while ((tempByteArray = (byte[]) decodeInputStream(dbis, nesting+1)) != null)  
  97.                     {  
  98.                         //decode some more  
  99.                         Object value = decodeInputStream(dbis,nesting+1);//读值                  
  100.                         // value interning is too CPU-intensive, let's skip that for now  
  101.                         //if(value instanceof byte[] && ((byte[])value).length < 17)  
  102.                         //value = StringInterner.internBytes((byte[])value);  
  103.                         // keys often repeat a lot - intern to save space                  
  104.                         String    key = null;  
  105.       
  106.                         if ( key == null )  
  107.                         {  
  108.                             CharBuffer    cb = BYTE_CHARSET.decode(ByteBuffer.wrap(tempByteArray));  
  109.                             key = new String(cb.array(),0,cb.limit());//键  
  110.                         }  
  111.                         if ( TRACE )  
  112.                         {  
  113.                             System.out.println( key + "->" + value + ";" );  
  114.                         }  
  115.                           
  116.                             // recover from some borked encodings that I have seen whereby the value has  
  117.                             // not been encoded. This results in, for example,   
  118.                             // 18:azureus_propertiesd0:e  
  119.                             // we only get null back here if decoding has hit an 'e' or end-of-file  
  120.                             // that is, there is no valid way for us to get a null 'value' here  
  121.                           
  122.                         if ( value == null )  
  123.                         {  
  124.                             //Debug.out( "Invalid encoding - value not serialsied for '" + key + "' - ignoring" );  
  125.                             break;  
  126.                         }  
  127.                         tempMap.put( key, value);//放入结果集中  
  128.                     }  
  129.       
  130.                     dbis.mark(Integer.MAX_VALUE);  
  131.                     tempByte = dbis.read();  
  132.                     dbis.reset();  
  133.                     if ( nesting > 0 && tempByte == -1 )  
  134.                     {  
  135.                         thrownew Exception( "BDecoder: invalid input data, 'e' missing from end of dictionary"));  
  136.                     }  
  137.                 }catch( Throwable e )  
  138.                 {  
  139.                     if ( !recovery_mode )  
  140.                     {  
  141.                         if ( e instanceof IOException )  
  142.                         {  
  143.                             throw((IOException)e);  
  144.                         }  
  145.       
  146.                         thrownew IOException(e.getMessage()));  
  147.                     }  
  148.                 }  
  149.                 return tempMap;  
  150.             }  
  151.         case 'l' :  
  152.         {  
  153.             //create the list  
  154.             ArrayList tempList = new ArrayList();  
  155.             try 
  156.             {  
  157.                 //create the key  
  158.                 Object tempElement = null;  
  159.                 while ((tempElement = decodeInputStream(dbis, nesting+1)) != null)  
  160.                 {  
  161.                     //add the element  
  162.                     tempList.add(tempElement);//读取列表元素并加入列表中  
  163.                 }  
  164.  
  165.                 tempList.trimToSize();  
  166.                 dbis.mark(Integer.MAX_VALUE);  
  167.                 tempByte = dbis.read();  
  168.                 dbis.reset();  
  169.                 if ( nesting > 0 && tempByte == -1 )  
  170.                 {  
  171.                     thrownew Exception( "BDecoder: invalid input data, 'e' missing from end of list"));  
  172.                 }  
  173.             }  
  174.             catch( Throwable e )  
  175.             {  
  176.                 if ( !recovery_mode )  
  177.                 {  
  178.                     if ( e instanceof IOException )  
  179.                     {  
  180.                         throw((IOException)e);  
  181.                     }  
  182.                     thrownew IOException(e.getMessage()));  
  183.                 }  
  184.             }  
  185.                 //return the list  
  186.             return tempList;  
  187.         }  
  188.         case 'e' :  
  189.         case -1 :  
  190.             return null;//当前结束  
  191.  
  192.         case 'i' :  
  193.             return new Long(getNumberFromStream(dbis, 'e'));//整数  
  194.  
  195.         case '0' :  
  196.         case '1' :  
  197.         case '2' :  
  198.         case '3' :  
  199.         case '4' :  
  200.         case '5' :  
  201.         case '6' :  
  202.         case '7' :  
  203.         case '8' :  
  204.         case '9' :  
  205.                 //move back one  
  206.             dbis.reset();  
  207.                 //get the string  
  208.             return getByteArrayFromStream(dbis);//读取指定长度字符串  
  209.  
  210.         default :  
  211.         {  
  212.             int    rem_len = dbis.available();  
  213.             if ( rem_len > 256 )  
  214.             {  
  215.                 rem_len    = 256;  
  216.             }  
  217.  
  218.             byte[] rem_data = new byte[rem_len];  
  219.             dbis.read( rem_data );  
  220.             thrownew Exception("BDecoder: unknown command '" + tempByte + ", remainder = " + new String( rem_data )));  
  221.         }  
  222.         }  
  223.     }  
  224.  
  225.     /** only create the array once per decoder instance (no issues with recursion as it's only used in a leaf method)  
  226.      */ 
  227.     private final char[] numberChars = new char[32];  
  228.       
  229.     private long getNumberFromStream(BDecoderInputStream dbis, char parseChar) throws IOException   
  230.     {  
  231.         int tempByte = dbis.read();  
  232.         int pos = 0;  
  233.         while ((tempByte != parseChar) && (tempByte >= 0))   
  234.         {//读取整数字节,直到终结字符'e'  
  235.             numberChars[pos++] = (char)tempByte;  
  236.             if ( pos == numberChars.length )  
  237.             {  
  238.                 thrownew NumberFormatException( "Number too large: " + new String(numberChars,0,pos) + "" ));  
  239.             }  
  240.             tempByte = dbis.read();  
  241.         }  
  242.  
  243.         //are we at the end of the stream?  
  244.         if (tempByte < 0)   
  245.         {  
  246.             return -1;  
  247.         }  
  248.         else if ( pos == 0 )  
  249.         {  
  250.             // support some borked impls that sometimes don't bother encoding anything  
  251.             return(0);  
  252.         }  
  253.         return( parseLong( numberChars, 0, pos ));//转换为Long型整数  
  254.     }  
  255.  
  256.     public static long parseLong(char[]    chars,int start,int    length )  
  257.     {//转换为Long型整数  
  258.         long result = 0;  
  259.         boolean negative = false;  
  260.         int     i     = start;  
  261.         int    max = start + length;  
  262.         long limit;  
  263.         if ( length > 0 )  
  264.         {  
  265.             if ( chars[i] == '-' )  
  266.             {  
  267.                 negative = true;  
  268.                 limit = Long.MIN_VALUE;  
  269.                 i++;  
  270.             }  
  271.             else 
  272.             {  
  273.                 limit = -Long.MAX_VALUE;  
  274.             }  
  275.  
  276.             if ( i < max )  
  277.             {  
  278.                 int digit = chars[i++] - '0';  
  279.                 if ( digit < 0 || digit > 9 )  
  280.                 {  
  281.                     throw new NumberFormatException(new String(chars,start,length));  
  282.                 }  
  283.                 else 
  284.                 {  
  285.                     result = -digit;  
  286.                 }  
  287.             }  
  288.  
  289.             long multmin = limit / 10;  
  290.  
  291.             while ( i < max )  
  292.             {  
  293.                 // Accumulating negatively avoids surprises near MAX_VALUE  
  294.                 int digit = chars[i++] - '0';  
  295.                 if ( digit < 0 || digit > 9 )  
  296.                 {  
  297.                     throw new NumberFormatException(new String(chars,start,length));  
  298.                 }  
  299.                 if ( result < multmin )  
  300.                 {  
  301.                     throw new NumberFormatException(new String(chars,start,length));  
  302.                 }  
  303.                 result *= 10;  
  304.                 if ( result < limit + digit )  
  305.                 {  
  306.                     throw new NumberFormatException(new String(chars,start,length));  
  307.                 }  
  308.  
  309.                 result -= digit;  
  310.             }  
  311.         }  
  312.         else 
  313.         {  
  314.             throw new NumberFormatException(new String(chars,start,length));  
  315.         }  
  316.  
  317.         if ( negative )  
  318.         {  
  319.             if ( i > start+1 )  
  320.             {  
  321.                 return result;  
  322.  
  323.             }  
  324.             else 
  325.             {    /* Only got "-" */ 
  326.                 throw new NumberFormatException(new String(chars,start,length));  
  327.             }  
  328.         }  
  329.         else 
  330.         {  
  331.             return -result;  
  332.         }    
  333.     }  
  334.  
  335.     private byte[] getByteArrayFromStream(BDecoderInputStream dbis ) throws IOException   
  336.     {  
  337.         int length = (int) getNumberFromStream(dbis, ':');  
  338.         if (length < 0)  
  339.         {  
  340.             return null;  
  341.         }  
  342.         // note that torrent hashes can be big (consider a 55GB file with 2MB pieces  
  343.         // this generates a pieces hash of 1/2 meg  
  344.         if ( length > 8*1024*1024 )  
  345.         {  
  346.             thrownew IOException( "Byte array length too large (" + length + ")"));  
  347.         }  
  348.  
  349.         byte[] tempArray = new byte[length];  
  350.         int count = 0;  
  351.         int len = 0;  
  352.         //get the string  
  353.         while (count != length && (len = dbis.read(tempArray, count, length - count)) > 0)   
  354.         {  
  355.             count += len;  
  356.         }  
  357.         if ( count != tempArray.length )  
  358.         {  
  359.             thrownew IOException( "BDecoder::getByteArrayFromStream: truncated"));  
  360.         }  
  361.         return tempArray;  
  362.     }  
  363.  
  364.     public void setRecoveryMode(boolean    r )  
  365.     {  
  366.         recovery_mode    = r;  
  367.     }  
  368.       
  369.     public static void print(PrintWriter writer,Object obj )  
  370.     {  
  371.         print( writer, obj, ""false );  
  372.     }  
  373.  
  374.     private static void print(PrintWriter writer,Object obj,String indent,boolean    skip_indent )  
  375.     {  
  376.         String    use_indent = skip_indent?"":indent;  
  377.         if ( obj instanceof Long )  
  378.         {  
  379.             writer.println( use_indent + obj );  
  380.  
  381.         }  
  382.         else if ( obj instanceof byte[])  
  383.         {  
  384.             byte[]    b = (byte[])obj;  
  385.             if ( b.length==20 )  
  386.             {  
  387.                 writer.println( use_indent + " { "+ ByteFormatter.nicePrint( b )+ " }" );  
  388.             }  
  389.             else if ( b.length < 64 )  
  390.             {  
  391.                 writer.println( new String(b) + " [" + ByteFormatter.encodeString( b ) + "]" );  
  392.             }else{  
  393.                 writer.println( "[byte array length " + b.length );  
  394.             }  
  395.         }else if ( obj instanceof String )  
  396.         {  
  397.             writer.println( use_indent + obj );  
  398.  
  399.         }  
  400.         else if ( obj instanceof List )  
  401.         {  
  402.             List    l = (List)obj;  
  403.             writer.println( use_indent + "[" );  
  404.             for (int i=0;i<l.size();i++)  
  405.             {  
  406.                 writer.print( indent + "  (" + i + ") " );  
  407.                 print( writer, l.get(i), indent + "    "true );  
  408.             }  
  409.             writer.println( indent + "]" );  
  410.  
  411.         }  
  412.         else 
  413.         {  
  414.             Map    m = (Map)obj;  
  415.             Iterator    it = m.keySet().iterator();  
  416.             while( it.hasNext())  
  417.             {  
  418.                 String    key = (String)it.next();  
  419.                 if ( key.length() > 256 )  
  420.                 {  
  421.                     writer.print( indent + key.substring(0,256) + " = " );  
  422.                 }  
  423.                 else 
  424.                 {  
  425.                     writer.print( indent + key + " = " );  
  426.                 }  
  427.                 print( writer, m.get(key), indent + "  "true );  
  428.             }  
  429.         }  
  430.     }  
  431.  
  432.     private static void print(File f,File output)  
  433.     {  
  434.         try 
  435.         {  
  436.             BDecoder    decoder = new BDecoder();//×××  
  437.             PrintWriter    pw = new PrintWriter( new FileWriter( output ));//输出结果  
  438.             print( pw, decoder.decodeStream( new BufferedInputStream( new FileInputStream( f ))));  
  439.             pw.flush();  
  440.         }  
  441.         catch( Throwable e )  
  442.         {  
  443.             e.printStackTrace();  
  444.         }  
  445.     }  
  446.  
  447.     private interface BDecoderInputStream  
  448.     {  
  449.         public int read() throws IOException;  
  450.         public int read(byte[] buffer)throws IOException;  
  451.         public int read(byte[]     buffer,int    offset,int    length )throws IOException;  
  452.         public int available()throws IOException;  
  453.         public boolean markSupported();  
  454.         public void mark(int limit );  
  455.         public void reset() throws IOException;  
  456.     }  
  457.  
  458.     private class BDecoderInputStreamStream implements BDecoderInputStream  
  459.     {  
  460.         final private BufferedInputStream is;  
  461.         private BDecoderInputStreamStream(BufferedInputStream _is )  
  462.         {  
  463.             is    = _is;  
  464.         }  
  465.         /**  
  466.          * 从此输入流中读取下一个数据字节。返回一个 0 到 255 范围内的 int 字节值。  
  467.          * 如果因为已经到达流末尾而没有字节可用,则返回 -1。  
  468.          * 在输入数据可用、检测到流末尾或抛出异常之前,此方法将一直阻塞。   
  469.          */ 
  470.         public int read()throws IOException  
  471.         {  
  472.             return( is.read());  
  473.         }  
  474.         /**  
  475.          * 从此输入流中将 byte.length 个字节的数据读入一个 byte 数组中。在某些输入可用之前,此方法将阻塞。   
  476.          */ 
  477.         public int read(byte[] buffer )throws IOException  
  478.         {  
  479.             return( is.read( buffer ));  
  480.         }  
  481.         /**  
  482.          * 从此字节输入流中给定偏移量处开始将各字节读取到指定的 byte 数组中。  
  483.          */ 
  484.         public int read(byte[]     buffer,int    offset,int    length)throws IOException  
  485.         {  
  486.             return( is.read( buffer, offset, length ));    
  487.         }  
  488.         /**  
  489.          * 返回可以从此输入流读取(或跳过)、且不受此输入流接下来的方法调用阻塞的估计字节数。  
  490.          */ 
  491.         public int available() throws IOException  
  492.         {  
  493.             return( is.available());  
  494.         }  
  495.  
  496.         /**  
  497.          * 测试此输入流是否支持 mark 和 reset 方法。  
  498.          */ 
  499.         public boolean markSupported()  
  500.         {  
  501.             return( is.markSupported());  
  502.         }  
  503.  
  504.         /**  
  505.          * 在输入流中的当前位置上作标记。reset 方法的后续调用将此流重新定位在最后标记的位置上,以便后续读取操作重新读取相同的字节。  
  506.          * @param limit 在标记位置变为无效之前可以读取字节的最大限制。  
  507.          */ 
  508.         public void mark(int limit )  
  509.         {  
  510.             is.mark( limit );  
  511.         }  
  512.         /**  
  513.          * 将此流重新定位到对此输入流最后调用 mark 方法时的位置。  
  514.          */ 
  515.         public void reset()throws IOException  
  516.         {  
  517.             is.reset();  
  518.         }  
  519.     }  
  520.  
  521.     public static void main(String[]    args )  
  522.     {        
  523.         print(new File( "C:\\1001.torrent" ),new File( "C:\\tables.txt" ));  
  524.     }  
  525. }