zoukankan      html  css  js  c++  java
  • job源码分析

    1. /** 
    2.  * Licensed to the Apache Software Foundation (ASF) under one 
    3.  * or more contributor license agreements.  See the NOTICE file 
    4.  * distributed with this work for additional information 
    5.  * regarding copyright ownership.  The ASF licenses this file 
    6.  * to you under the Apache License, Version 2.0 (the 
    7.  * "License"); you may not use this file except in compliance 
    8.  * with the License.  You may obtain a copy of the License at 
    9.  * 
    10.  *     http://www.apache.org/licenses/LICENSE-2.0 
    11.  * 
    12.  * Unless required by applicable law or agreed to in writing, software 
    13.  * distributed under the License is distributed on an "AS IS" BASIS, 
    14.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
    15.  * See the License for the specific language governing permissions and 
    16.  * limitations under the License. 
    17.  */  
    18.   
    19. package org.apache.hadoop.mapreduce;  
    20.   
    21. import java.io.IOException;  
    22. import java.security.PrivilegedExceptionAction;  
    23.   
    24. import org.apache.hadoop.conf.Configuration;  
    25. import org.apache.hadoop.fs.Path;  
    26. import org.apache.hadoop.io.RawComparator;  
    27. import org.apache.hadoop.mapreduce.TaskAttemptID;  
    28. import org.apache.hadoop.mapred.JobClient;  
    29. import org.apache.hadoop.mapred.JobConf;  
    30. import org.apache.hadoop.mapred.RunningJob;  
    31. import org.apache.hadoop.mapred.TaskCompletionEvent;  
    32.   
    33. /** 
    34.  * The job submitter's view of the Job. It allows the user to configure the 
    35.  * job, submit it, control its execution, and query the state. The set methods 
    36.  * only work until the job is submitted, afterwards they will throw an  
    37.  * IllegalStateException. 
    38.  * job 提交者看到的job的视图。它允许用户配置job,提交job,控制job的执行,并且查询他的状态 
    39.  * set方法只有在job提交的时候才会工作 
    40.  */  
    41. public class Job extends JobContext {    
    42.   public static enum JobState {DEFINE, RUNNING};//job的状态,有定义好的和正在运行  
    43.   private JobState state = JobState.DEFINE;  
    44.   private JobClient jobClient;  
    45.   private RunningJob info;  
    46.   
    47.   /** 
    48.    * Creates a new {@link Job} 
    49.    * A Job will be created with a generic {@link Configuration}. 
    50.    *创建一个新的job,用通用的configuration 
    51.    * @return the {@link Job} 
    52.    * @throws IOException 
    53.    */  
    54.   public static Job getInstance() throws IOException {  
    55.     // create with a null Cluster  
    56.     return getInstance(new Configuration());  
    57.   }  
    58.   
    59.   /** 
    60.    * Creates a new {@link Job} with a given {@link Configuration}. 
    61.    * The <code>Job</code> makes a copy of the <code>Configuration</code> so 
    62.    * that any necessary internal modifications do not reflect on the incoming 
    63.    * parameter. 
    64.    *使用给定的configuration创建job 
    65.    *这里对configuration进行了备份,如此,任何必要的对configuration内部修改,都不会影响传进来的conf参数 
    66.    * @param conf the {@link Configuration} 
    67.    * @return the {@link Job} 
    68.    * @throws IOException 
    69.    */  
    70.   public static Job getInstance(Configuration conf) throws IOException {  
    71.     // create with a null Cluster 没有任何集群的创建  
    72.     JobConf jobConf = new JobConf(conf);  
    73.     return new Job(jobConf);  
    74.   }  
    75.   
    76.   /** 
    77.    * Creates a new {@link Job} with a given {@link Configuration} 
    78.    * and a given jobName. 
    79.    *用给定的conf和jobname 
    80.    * The <code>Job</code> makes a copy of the <code>Configuration</code> so 
    81.    * that any necessary internal modifications do not reflect on the incoming 
    82.    * parameter. 
    83.    * 
    84.    * @param conf the {@link Configuration} 
    85.    * @param jobName the job instance's name 
    86.    * @return the {@link Job} 
    87.    * @throws IOException 
    88.    */  
    89.   public static Job getInstance(Configuration conf, String jobName)  
    90.            throws IOException {  
    91.     // create with a null Cluster  
    92.     Job result = getInstance(conf);  
    93.     result.setJobName(jobName);  
    94.     return result;  
    95.   }  
    96.   
    97.   public Job() throws IOException {  
    98.     this(new Configuration());  
    99.   }  
    100.   
    101.   public Job(Configuration conf) throws IOException {  
    102.     super(conf, null);  
    103.   }  
    104.   
    105.   public Job(Configuration conf, String jobName) throws IOException {  
    106.     this(conf);  
    107.     setJobName(jobName);  
    108.   }  
    109.   
    110.   JobClient getJobClient() {  
    111.     return jobClient;  
    112.   }  
    113.     
    114.   //确保job的状态  
    115.   private void ensureState(JobState state) throws IllegalStateException {  
    116.     if (state != this.state) {  
    117.       throw new IllegalStateException("Job in state "+ this.state +   
    118.                                       " instead of " + state);  
    119.     }  
    120.   
    121.     if (state == JobState.RUNNING && jobClient == null) {  
    122.       throw new IllegalStateException("Job in state " + JobState.RUNNING +   
    123.                                       " however jobClient is not initialized!");  
    124.     }  
    125.   }  
    126.   
    127.   /** 
    128.    * Set the number of reduce tasks for the job. 
    129.    * 设置reducer的个数 常用 
    130.    * @param tasks the number of reduce tasks 
    131.    * @throws IllegalStateException if the job is submitted 
    132.    */  
    133.   public void setNumReduceTasks(int tasks) throws IllegalStateException {  
    134.     ensureState(JobState.DEFINE);  
    135.     conf.setNumReduceTasks(tasks);  
    136.   }  
    137.   
    138.   /** 
    139.    * Set the current working directory for the default file system. 
    140.    * 为默认文件系统 设置当前工作目录 
    141.    * @param dir the new current working directory. 
    142.    * @throws IllegalStateException if the job is submitted 
    143.    */  
    144.   public void setWorkingDirectory(Path dir) throws IOException {  
    145.     ensureState(JobState.DEFINE);  
    146.     conf.setWorkingDirectory(dir);  
    147.   }  
    148.   
    149.   /** 
    150.    * Set the {@link InputFormat} for the job. 
    151.    * @param cls the <code>InputFormat</code> to use 
    152.    * @throws IllegalStateException if the job is submitted 
    153.    */  
    154.   public void setInputFormatClass(Class<? extends InputFormat> cls  
    155.                                   ) throws IllegalStateException {  
    156.     ensureState(JobState.DEFINE);  
    157.     conf.setClass(INPUT_FORMAT_CLASS_ATTR, cls, InputFormat.class);  
    158.   }  
    159.   
    160.   /** 
    161.    * Set the {@link OutputFormat} for the job. 
    162.    * @param cls the <code>OutputFormat</code> to use 
    163.    * @throws IllegalStateException if the job is submitted 
    164.    */  
    165.   public void setOutputFormatClass(Class<? extends OutputFormat> cls  
    166.                                    ) throws IllegalStateException {  
    167.     ensureState(JobState.DEFINE);  
    168.     conf.setClass(OUTPUT_FORMAT_CLASS_ATTR, cls, OutputFormat.class);  
    169.   }  
    170.   
    171.   /** 
    172.    * Set the {@link Mapper} for the job. 
    173.    * @param cls the <code>Mapper</code> to use 
    174.    * @throws IllegalStateException if the job is submitted 
    175.    */  
    176.   public void setMapperClass(Class<? extends Mapper> cls  
    177.                              ) throws IllegalStateException {  
    178.     ensureState(JobState.DEFINE);  
    179.     conf.setClass(MAP_CLASS_ATTR, cls, Mapper.class);  
    180.   }  
    181.   
    182.   /** 
    183.    * Set the Jar by finding where a given class came from. 
    184.    * 设置jar包,hadoop根据给定的class来寻找他的jar包 
    185.    * @param cls the example class 
    186.    */  
    187.   public void setJarByClass(Class<?> cls) {  
    188.     conf.setJarByClass(cls);  
    189.   }  
    190.     
    191.   /** 
    192.    * Get the pathname of the job's jar. 
    193.    * @return the pathname 
    194.    */  
    195.   public String getJar() {  
    196.     return conf.getJar();  
    197.   }  
    198.   
    199.   /** 
    200.    * Set the combiner class for the job. 
    201.    * @param cls the combiner to use 
    202.    * @throws IllegalStateException if the job is submitted 
    203.    */  
    204.   public void setCombinerClass(Class<? extends Reducer> cls  
    205.                                ) throws IllegalStateException {  
    206.     ensureState(JobState.DEFINE);  
    207.     conf.setClass(COMBINE_CLASS_ATTR, cls, Reducer.class);  
    208.   }  
    209.   
    210.   /** 
    211.    * Set the {@link Reducer} for the job. 
    212.    * @param cls the <code>Reducer</code> to use 
    213.    * @throws IllegalStateException if the job is submitted 
    214.    */  
    215.   public void setReducerClass(Class<? extends Reducer> cls  
    216.                               ) throws IllegalStateException {  
    217.     ensureState(JobState.DEFINE);  
    218.     conf.setClass(REDUCE_CLASS_ATTR, cls, Reducer.class);  
    219.   }  
    220.   
    221.   /** 
    222.    * Set the {@link Partitioner} for the job. 
    223.    * @param cls the <code>Partitioner</code> to use 
    224.    * @throws IllegalStateException if the job is submitted 
    225.    */  
    226.   public void setPartitionerClass(Class<? extends Partitioner> cls  
    227.                                   ) throws IllegalStateException {  
    228.     ensureState(JobState.DEFINE);  
    229.     conf.setClass(PARTITIONER_CLASS_ATTR, cls, Partitioner.class);  
    230.   }  
    231.   
    232.   /** 
    233.    * Set the key class for the map output data. This allows the user to 
    234.    * specify the map output key class to be different than the final output 
    235.    * value class. 
    236.    *  
    237.    * @param theClass the map output key class. 
    238.    * @throws IllegalStateException if the job is submitted 
    239.    */  
    240.   public void setMapOutputKeyClass(Class<?> theClass  
    241.                                    ) throws IllegalStateException {  
    242.     ensureState(JobState.DEFINE);  
    243.     conf.setMapOutputKeyClass(theClass);  
    244.   }  
    245.   
    246.   /** 
    247.    * Set the value class for the map output data. This allows the user to 
    248.    * specify the map output value class to be different than the final output 
    249.    * value class. 
    250.    *  
    251.    * @param theClass the map output value class. 
    252.    * @throws IllegalStateException if the job is submitted 
    253.    */  
    254.   public void setMapOutputValueClass(Class<?> theClass  
    255.                                      ) throws IllegalStateException {  
    256.     ensureState(JobState.DEFINE);  
    257.     conf.setMapOutputValueClass(theClass);  
    258.   }  
    259.   
    260.   /** 
    261.    * Set the key class for the job output data. 
    262.    *  
    263.    * @param theClass the key class for the job output data. 
    264.    * @throws IllegalStateException if the job is submitted 
    265.    */  
    266.   public void setOutputKeyClass(Class<?> theClass  
    267.                                 ) throws IllegalStateException {  
    268.     ensureState(JobState.DEFINE);  
    269.     conf.setOutputKeyClass(theClass);  
    270.   }  
    271.   
    272.   /** 
    273.    * Set the value class for job outputs. 
    274.    *  
    275.    * @param theClass the value class for job outputs. 
    276.    * @throws IllegalStateException if the job is submitted 
    277.    */  
    278.   public void setOutputValueClass(Class<?> theClass  
    279.                                   ) throws IllegalStateException {  
    280.     ensureState(JobState.DEFINE);  
    281.     conf.setOutputValueClass(theClass);  
    282.   }  
    283.   
    284.   /** 
    285.    * Define the comparator that controls how the keys are sorted before they 
    286.    * are passed to the {@link Reducer}. 
    287.    * @param cls the raw comparator 
    288.    * @throws IllegalStateException if the job is submitted 
    289.    */  
    290.   public void setSortComparatorClass(Class<? extends RawComparator> cls  
    291.                                      ) throws IllegalStateException {  
    292.     ensureState(JobState.DEFINE);  
    293.     conf.setOutputKeyComparatorClass(cls);  
    294.   }  
    295.   
    296.   /** 
    297.    * Define the comparator that controls which keys are grouped together 
    298.    * for a single call to  
    299.    * {@link Reducer#reduce(Object, Iterable,  
    300.    *                       org.apache.hadoop.mapreduce.Reducer.Context)} 
    301.    * @param cls the raw comparator to use 
    302.    * @throws IllegalStateException if the job is submitted 
    303.    */  
    304.   public void setGroupingComparatorClass(Class<? extends RawComparator> cls  
    305.                                          ) throws IllegalStateException {  
    306.     ensureState(JobState.DEFINE);  
    307.     conf.setOutputValueGroupingComparator(cls);  
    308.   }  
    309.   
    310.   /** 
    311.    * Set the user-specified job name. 
    312.    *  
    313.    * @param name the job's new name. 
    314.    * @throws IllegalStateException if the job is submitted 
    315.    */  
    316.   public void setJobName(String name) throws IllegalStateException {  
    317.     ensureState(JobState.DEFINE);  
    318.     conf.setJobName(name);  
    319.   }  
    320.     
    321.   /** 
    322.    * Turn speculative execution on or off for this job.  
    323.    * 设置推测执行的开关 
    324.    * @param speculativeExecution <code>true</code> if speculative execution  
    325.    *                             should be turned on, else <code>false</code>. 
    326.    */  
    327.   public void setSpeculativeExecution(boolean speculativeExecution) {  
    328.     ensureState(JobState.DEFINE);  
    329.     conf.setSpeculativeExecution(speculativeExecution);  
    330.   }  
    331.   
    332.   /** 
    333.    * Turn speculative execution on or off for this job for map tasks.  
    334.    *  
    335.    * @param speculativeExecution <code>true</code> if speculative execution  
    336.    *                             should be turned on for map tasks, 
    337.    *                             else <code>false</code>. 
    338.    */  
    339.   public void setMapSpeculativeExecution(boolean speculativeExecution) {  
    340.     ensureState(JobState.DEFINE);  
    341.     conf.setMapSpeculativeExecution(speculativeExecution);  
    342.   }  
    343.   
    344.   /** 
    345.    * Turn speculative execution on or off for this job for reduce tasks.  
    346.    *  
    347.    * @param speculativeExecution <code>true</code> if speculative execution  
    348.    *                             should be turned on for reduce tasks, 
    349.    *                             else <code>false</code>. 
    350.    */  
    351.   public void setReduceSpeculativeExecution(boolean speculativeExecution) {  
    352.     ensureState(JobState.DEFINE);  
    353.     conf.setReduceSpeculativeExecution(speculativeExecution);  
    354.   }  
    355.   
    356.   /** 
    357.    * Get the URL where some job progress information will be displayed. 
    358.    * 得到 一些job 进度信息会展示的url地址 
    359.    * @return the URL where some job progress information will be displayed. 
    360.    */  
    361.   public String getTrackingURL() {  
    362.     ensureState(JobState.RUNNING);  
    363.     return info.getTrackingURL();  
    364.   }  
    365.   
    366.   /** 
    367.    * Get the <i>progress</i> of the job's setup, as a float between 0.0  
    368.    * and 1.0.  When the job setup is completed, the function returns 1.0. 
    369.    *  
    370.    * @return the progress of the job's setup. 
    371.    * @throws IOException 
    372.    */  
    373.   public float setupProgress() throws IOException {  
    374.     ensureState(JobState.RUNNING);  
    375.     return info.setupProgress();  
    376.   }  
    377.   
    378.   /** 
    379.    * Get the <i>progress</i> of the job's map-tasks, as a float between 0.0  
    380.    * and 1.0.  When all map tasks have completed, the function returns 1.0. 
    381.    *  
    382.    * @return the progress of the job's map-tasks. 
    383.    * @throws IOException 
    384.    */  
    385.   public float mapProgress() throws IOException {  
    386.     ensureState(JobState.RUNNING);  
    387.     return info.mapProgress();  
    388.   }  
    389.   
    390.   /** 
    391.    * Get the <i>progress</i> of the job's reduce-tasks, as a float between 0.0  
    392.    * and 1.0.  When all reduce tasks have completed, the function returns 1.0. 
    393.    *  
    394.    * @return the progress of the job's reduce-tasks. 
    395.    * @throws IOException 
    396.    */  
    397.   public float reduceProgress() throws IOException {  
    398.     ensureState(JobState.RUNNING);  
    399.     return info.reduceProgress();  
    400.   }  
    401.   
    402.   /** 
    403.    * Check if the job is finished or not.  
    404.    * This is a non-blocking call. 
    405.    *  
    406.    * @return <code>true</code> if the job is complete, else <code>false</code>. 
    407.    * @throws IOException 
    408.    */  
    409.   public boolean isComplete() throws IOException {  
    410.     ensureState(JobState.RUNNING);  
    411.     return info.isComplete();  
    412.   }  
    413.   
    414.   /** 
    415.    * Check if the job completed successfully.  
    416.    *  
    417.    * @return <code>true</code> if the job succeeded, else <code>false</code>. 
    418.    * @throws IOException 
    419.    */  
    420.   public boolean isSuccessful() throws IOException {  
    421.     ensureState(JobState.RUNNING);  
    422.     return info.isSuccessful();  
    423.   }  
    424.   
    425.   /** 
    426.    * Kill the running job.  Blocks until all job tasks have been 
    427.    * killed as well.  If the job is no longer running, it simply returns. 
    428.    * 杀掉正在运行的job 直到所有的job tasks都被杀掉之后 才会停止。 
    429.    * 如果job不再运行来 他就会返回 
    430.    * @throws IOException 
    431.    */  
    432.   public void killJob() throws IOException {  
    433.     ensureState(JobState.RUNNING);  
    434.     info.killJob();  
    435.   }  
    436.       
    437.   /** 
    438.    * Get events indicating completion (success/failure) of component tasks. 
    439.    *   
    440.    * @param startFrom index to start fetching events from 
    441.    * @return an array of {@link TaskCompletionEvent}s 
    442.    * @throws IOException 
    443.    */  
    444.   public TaskCompletionEvent[] getTaskCompletionEvents(int startFrom  
    445.                                                        ) throws IOException {  
    446.     ensureState(JobState.RUNNING);  
    447.     return info.getTaskCompletionEvents(startFrom);  
    448.   }  
    449.     
    450.   /** 
    451.    * Kill indicated task attempt. 
    452.    *  
    453.    * @param taskId the id of the task to be terminated. 
    454.    * @throws IOException 
    455.    */  
    456.   public void killTask(TaskAttemptID taskId) throws IOException {  
    457.     ensureState(JobState.RUNNING);  
    458.     info.killTask(org.apache.hadoop.mapred.TaskAttemptID.downgrade(taskId),   
    459.                   false);  
    460.   }  
    461.   
    462.   /** 
    463.    * Fail indicated task attempt. 
    464.    *  
    465.    * @param taskId the id of the task to be terminated. 
    466.    * @throws IOException 
    467.    */  
    468.   public void failTask(TaskAttemptID taskId) throws IOException {  
    469.     ensureState(JobState.RUNNING);  
    470.     info.killTask(org.apache.hadoop.mapred.TaskAttemptID.downgrade(taskId),   
    471.                   true);  
    472.   }  
    473.   
    474.   /** 
    475.    * Gets the counters for this job. 
    476.    *  
    477.    * @return the counters for this job. 
    478.    * @throws IOException 
    479.    */  
    480.   public Counters getCounters() throws IOException {  
    481.     ensureState(JobState.RUNNING);  
    482.     return new Counters(info.getCounters());  
    483.   }  
    484.   
    485.   private void ensureNotSet(String attr, String msg) throws IOException {  
    486.     if (conf.get(attr) != null) {  
    487.       throw new IOException(attr + " is incompatible with " + msg + " mode.");  
    488.     }      
    489.   }  
    490.     
    491.   /** 
    492.    * Sets the flag that will allow the JobTracker to cancel the HDFS delegation 
    493.    * tokens upon job completion. Defaults to true. 
    494.    */  
    495.   public void setCancelDelegationTokenUponJobCompletion(boolean value) {  
    496.     ensureState(JobState.DEFINE);  
    497.     conf.setBoolean(JOB_CANCEL_DELEGATION_TOKEN, value);  
    498.   }  
    499.   
    500.   /** 
    501.    * Default to the new APIs unless they are explicitly set or the old mapper or 
    502.    * reduce attributes are used. 
    503.    * @throws IOException if the configuration is inconsistant 
    504.    */  
    505.   private void setUseNewAPI() throws IOException {  
    506.     int numReduces = conf.getNumReduceTasks();  
    507.     String oldMapperClass = "mapred.mapper.class";  
    508.     String oldReduceClass = "mapred.reducer.class";  
    509.     conf.setBooleanIfUnset("mapred.mapper.new-api",  
    510.                            conf.get(oldMapperClass) == null);  
    511.     if (conf.getUseNewMapper()) {  
    512.       String mode = "new map API";  
    513.       ensureNotSet("mapred.input.format.class", mode);  
    514.       ensureNotSet(oldMapperClass, mode);  
    515.       if (numReduces != 0) {  
    516.         ensureNotSet("mapred.partitioner.class", mode);  
    517.        } else {  
    518.         ensureNotSet("mapred.output.format.class", mode);  
    519.       }        
    520.     } else {  
    521.       String mode = "map compatability";  
    522.       ensureNotSet(JobContext.INPUT_FORMAT_CLASS_ATTR, mode);  
    523.       ensureNotSet(JobContext.MAP_CLASS_ATTR, mode);  
    524.       if (numReduces != 0) {  
    525.         ensureNotSet(JobContext.PARTITIONER_CLASS_ATTR, mode);  
    526.        } else {  
    527.         ensureNotSet(JobContext.OUTPUT_FORMAT_CLASS_ATTR, mode);  
    528.       }  
    529.     }  
    530.     if (numReduces != 0) {  
    531.       conf.setBooleanIfUnset("mapred.reducer.new-api",  
    532.                              conf.get(oldReduceClass) == null);  
    533.       if (conf.getUseNewReducer()) {  
    534.         String mode = "new reduce API";  
    535.         ensureNotSet("mapred.output.format.class", mode);  
    536.         ensureNotSet(oldReduceClass, mode);     
    537.       } else {  
    538.         String mode = "reduce compatability";  
    539.         ensureNotSet(JobContext.OUTPUT_FORMAT_CLASS_ATTR, mode);  
    540.         ensureNotSet(JobContext.REDUCE_CLASS_ATTR, mode);     
    541.       }  
    542.     }     
    543.   }  
    544.   
    545.   /** 
    546.    * Submit the job to the cluster and return immediately. 
    547.    * 提交job到集群上面 并且立刻返回 
    548.    * @throws IOException 
    549.    */  
    550.   public void submit() throws IOException, InterruptedException,   
    551.                               ClassNotFoundException {  
    552.     ensureState(JobState.DEFINE);  
    553.     setUseNewAPI();  
    554.       
    555.     // Connect to the JobTracker and submit the job  
    556.     //连接到jobtracker 并且提交作业  
    557.     connect();  
    558.     info = jobClient.submitJobInternal(conf);//这里才真正的提交作业  
    559.     super.setJobID(info.getID());  
    560.     state = JobState.RUNNING;  
    561.    }  
    562.     
    563.   /** 
    564.    * Open a connection to the JobTracker 
    565.    * 打开到jobtracker的连接 
    566.    * @throws IOException 
    567.    * @throws InterruptedException  
    568.    */  
    569.   private void connect() throws IOException, InterruptedException {  
    570.     ugi.doAs(new PrivilegedExceptionAction<Object>() {  
    571.       public Object run() throws IOException {  
    572.         jobClient = new JobClient((JobConf) getConfiguration());      
    573.         return null;  
    574.       }  
    575.     });  
    576.   }  
    577.     
    578.   /** 
    579.    * Submit the job to the cluster and wait for it to finish. 
    580.    * @param verbose print the progress to the user 
    581.    * @return true if the job succeeded 
    582.    * @throws IOException thrown if the communication with the  
    583.    *         <code>JobTracker</code> is lost 
    584.    */  
    585.   public boolean waitForCompletion(boolean verbose  
    586.                                    ) throws IOException, InterruptedException,  
    587.                                             ClassNotFoundException {  
    588.     if (state == JobState.DEFINE) {  
    589.       submit();  
    590.     }  
    591.     if (verbose) {  
    592.       jobClient.monitorAndPrintJob(conf, info);  
    593.     } else {  
    594.       info.waitForCompletion();  
    595.     }  
    596.     return isSuccessful();  
    597.   }  
    598.     
    599. }  
  • 相关阅读:
    ASP.NET使用SWFUpload上传大文件教学
    Gridview自定义分页(转载)
    扩展GridView之个性分页(转载)
    畅通工程
    一个人的旅行
    Stockbroker Grapevine
    Arbitrage
    More is better
    HDU Today
    Minimum Transport Cost
  • 原文地址:https://www.cnblogs.com/jxhd1/p/6528605.html
Copyright © 2011-2022 走看看