一、回顾单词统计源码

 package counter;

 import java.net.URI;

 import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Counter;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.mapreduce.lib.partition.HashPartitioner; public class WordCountApp {
static final String INPUT_PATH = "hdfs://hadoop:9000/hello";
static final String OUT_PATH = "hdfs://hadoop:9000/out"; public static void main(String[] args) throws Exception { Configuration conf = new Configuration(); final FileSystem fileSystem = FileSystem.get(new URI(INPUT_PATH), conf);
final Path outPath = new Path(OUT_PATH); if(fileSystem.exists(outPath)){
fileSystem.delete(outPath, true);
} final Job job = new Job(conf , WordCountApp.class.getSimpleName()); FileInputFormat.setInputPaths(job, INPUT_PATH);//1.1指定读取的文件位于哪里 job.setInputFormatClass(TextInputFormat.class);//指定如何对输入文件进行格式化,把输入文件每一行解析成键值对 job.setMapperClass(MyMapper.class);//1.2 指定自定义的map类
job.setMapOutputKeyClass(Text.class);//map输出的<k,v>类型。如果<k3,v3>的类型与<k2,v2>类型一致,则可以省略
job.setMapOutputValueClass(LongWritable.class); job.setPartitionerClass(HashPartitioner.class);//1.3 分区
job.setNumReduceTasks(1);//有一个reduce任务运行 job.setReducerClass(MyReducer.class);//2.2 指定自定义reduce类
job.setOutputKeyClass(Text.class);//指定reduce的输出类型
job.setOutputValueClass(LongWritable.class); FileOutputFormat.setOutputPath(job, outPath);//2.3 指定写出到哪里 job.setOutputFormatClass(TextOutputFormat.class);//指定输出文件的格式化类 job.waitForCompletion(true);//把job提交给JobTracker运行
} /**
* KEYIN 即k1 表示行的偏移量
* VALUEIN 即v1 表示行文本内容
* KEYOUT 即k2 表示行中出现的单词
* VALUEOUT 即v2 表示行中出现的单词的次数,固定值1
*/
static class MyMapper extends Mapper<LongWritable, Text, Text, LongWritable>{
protected void map(LongWritable k1, Text v1, Context context) throws java.io.IOException ,InterruptedException {
// final Counter helloCounter = context.getCounter("Sensitive Words", "hello"); final String line = v1.toString();
/* if(line.contains("hello")){
//记录敏感词出现在一行中
helloCounter.increment(1L);
}*/
final String[] splited = line.split(" ");
for (String word : splited) {
context.write(new Text(word), new LongWritable(1));
}
};
} /**
* KEYIN 即k2 表示行中出现的单词
* VALUEIN 即v2 表示行中出现的单词的次数
* KEYOUT 即k3 表示文本中出现的不同单词
* VALUEOUT 即v3 表示文本中出现的不同单词的总次数
*
*/
static class MyReducer extends Reducer<Text, LongWritable, Text, LongWritable>{
protected void reduce(Text k2, java.lang.Iterable<LongWritable> v2s, Context ctx) throws java.io.IOException ,InterruptedException {
long times = 0L;
for (LongWritable count : v2s) {
times += count.get();
}
ctx.write(k2, new LongWritable(times));
};
} }

代码1.1

二、原理与代码解析

2.1 MapReduce的任务与原理

2.1.1 MapReduce的工作原理

  MapReduce的工作原理如下图2.1所示。

图 2.1

  在图中我们已看出,关于File有两种划分,一个是split分片一个是block,注意分片只是逻辑划分,并不是像划分block那样,将文件真是的划分为多个部分,他只是逻辑上的的划分,可以说是只是读取时候按分片来读取。关于分片的大小默认为块大小,为什么要这样呢?那因为MapReduce作业 处理的文件是存放在DataNode上的,而且文件在DataNode上是按block存放的,而不同的block可是存放在不同的DataNode上的,如果分片大小大于block块大小,那么说明一个块满足不 了该分片,那么就需要再读取一个block块,这样当这两个block块位于不同的DataNode上 时,就要通过网络访问另一个节点,这样就可能造成网络延迟影响Mapreduce的执行效率,所以一般分片大小会默认为block块大小。

  在分析一下该图,不难看出,每一个split都分配了一个MappperTask,每个MapperTask又有三个箭头,有三个不同的走向表示分了三个区,那就有三个ReducerTask,而最终的结果会分不同的痛的部分存放在DataNode目录中。我们也可以对比下面这张图来对比理解MapReduce的工作原理,如图2.2所示。

图 2.2

2.1.2 map()和reduce的任务

<1>map任务处理
  1) 读取输入文件内容,解析成key、value对。对输入文件的每一行,解析成key、value对。每一个键值对调用一次map函数。
  2) 写自己的逻辑,对输入的key、value处理,转换成新的key、value输出。
  3) 对输出的key、value进行分区。
  4) 对不同分区的数据,按照key进行排序、分组。相同key的value放到一个集合中。
<2>reduce任务处理
  1) 对多个map任务的输出,按照不同的分区,通过网络copy到不同的reduce节点。
  2) 对多个map任务的输出进行合并、排序。写reduce函数自己的逻辑,对输入的key、value处理,转换成新的key、value输出。
  3) 把reduce的输出保存到文件中。

2.2源码任务的对比分析

  关于任务和源码的对应分析主要是针对map的第一项和第二项任务。第一项任务是:读取输入文件内容,解析成key、value对。对输入文件的每一行,解析成key、value对。每一个键值对调用一次map函数。第二项任务是:写自己的逻辑,对输入的key、value处理,转换成新的key、value输出。

2.2.1 第一项任务

  从上面代码1.1中,可以看出这项任务是由下面这段代码来完成,如代码2.1所示。

 FileInputFormat.setInputPaths(job, INPUT_PATH);//1.1指定读取的文件位于哪里
job.setInputFormatClass(TextInputFormat.class);//指定如何对输入文件进行格式化,把输入文件每一行解析成键值对

代码 2.1

  分析这段代码,可以知道,由代码中的TextInputFromat这个类主要是来完成分割的任务的,下面先来看一下该类的树结构,如下图2.1所示。

图 2.2

  从图中可知,TextInputFormat的继承的关系为,TextInputFormat--->FileInputformat--->InputFormat,那么看进入TextInputFormat类,看一下该类的注释,和其中的方法,如下代码2.2,2.3,注释中的@link表示后面跟的是一个连接,可以点击查看。

 * <code>InputFormat</code> describes the input-specification for a          InputFormat用来描述Map-Reduce的输入规格
* Map-Reduce job.
*
* <p>The Map-Reduce framework relies on the <code>InputFormat</code> of the Map-reduce框架依赖于一个job的InputFormat
* job to:<p>
* <ol>
* <li>
* Validate the input-specification of the job.                验证job的输入规格
* <li>
* Split-up the input file(s) into logical {@link InputSplit}s, each of    把输入文件拆分成逻辑Inputsplit,每一个
* which is then assigned to an individual {@link Mapper}.           InputSplit都会被分配到一个独立的Mapper
* </li>
* <li>
* Provide the {@link RecordReader} implementation to be used to glean   提供实现类RcordReader,用于为Mapper任务,从逻辑InputSplit
* input records from the logical <code>InputSplit</code> for processing by 收集输入记录。
* the {@link Mapper}.
* </li>
* </ol>

代码 2.2

 /**
* Logically split the set of input files for the job. 为job逻辑切分输入文件
* @param context job configuration.
* @return an array of {@link InputSplit}s for the job.
*/
public abstract
List<InputSplit> getSplits(JobContext context
) throws IOException, InterruptedException; /**
* Create a record reader for a given split. The framework will call    为分片创建一个记录读取器
* {@link RecordReader#initialize(InputSplit, TaskAttemptContext)} before
* the split is used.
* @param split the split to be read
* @param context the information about the task
* @return a new record reader
* @throws IOException
* @throws InterruptedException
*/
public abstract
RecordReader<K,V> createRecordReader(InputSplit split,
TaskAttemptContext context
) throws IOException,
InterruptedException;

代码 2.3


  从上面的代码中可以知道InputFormat是一个抽象类,两面有两个抽象方法getSplit和createRecordReader,由于抽象类中只有方法的声明,并没有方法的实现,所以要分析该类的实现类FileInputFormat,在该实现类中,实现了他的父类InputFormat的getSplits()方法,查看该类的源码及注释如下代码2.4所示。

  /**
* Generate the list of files and make them into FileSplits. 生成一个文件列表并创建FileSplits
*/
public List<InputSplit> getSplits(JobContext job
) throws IOException {
long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job)); //该值等于1
long maxSize = getMaxSplitSize(job); //该值等于263-1
// generate splits
List<InputSplit> splits = new ArrayList<InputSplit>();
List<FileStatus> files = listStatus(job);  //读取文件夹下的所有文件
for (FileStatus file: files) {      //遍历文件夹下的所有文件
Path path = file.getPath();    //获取文件路径
FileSystem fs = path.getFileSystem(job.getConfiguration());     //根据该路径获取文件系统
long length = file.getLen();    //文件长度
BlockLocation[] blkLocations = fs.getFileBlockLocations(file, 0, length);    //块位置
if ((length != 0) && isSplitable(job, path)) {     //判断文件数量是否不为空且文件允许被拆分
long blockSize = file.getBlockSize();
long splitSize = computeSplitSize(blockSize, minSize, maxSize);  //计算分片大小,该分片大小和blockSize, minSize, maxSize有关系,默认为block块大小
long bytesRemaining = length;  //文件初始长度
while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {    //分片
int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
splits.add(new FileSplit(path, length-bytesRemaining, splitSize,
blkLocations[blkIndex].getHosts()));
bytesRemaining -= splitSize;
} if (bytesRemaining != 0) {
splits.add(new FileSplit(path, length-bytesRemaining, bytesRemaining,
blkLocations[blkLocations.length-1].getHosts()));
}
} else if (length != 0) {    //如果该文件不能够被切分就,就直接生成分片
splits.add(new FileSplit(path, 0, length, blkLocations[0].getHosts()));
} else {
//Create empty hosts array for zero length files
splits.add(new FileSplit(path, 0, length, new String[0]));
}
}
// Save the number of input files in the job-conf
job.getConfiguration().setLong(NUM_INPUT_FILES, files.size());
LOG.debug("Total # of splits: " + splits.size());
return splits;
}

代码 2.4

  注意,分片FileinputSplit只是逻辑划分,并不是像划分block那样,将文件真是的划分为多个部分,他只是逻辑上的的划分,可以说是只是读取时候按分片来读取,分片InputSplit大小默认为块大小,为什么要这样呢?那因为MapReduce作业 处理的文件是存放在datanode上的,而且文件在DataNode上是按block存放的,如果分片大小大于block块大小,那么说明一个块满足不了该分片需要再读取一个block块,而不同的block可是存放在不同的DataNode上的,这样当这两个block块位于不同的DataNode上时,就要通过网络访问另一个节点,这样就可能造成网络延迟影响Mapre-duce的执行效率,所以一般分片大小会默认为block块大小。


  我们知道FileInputFormat实现了,inputFormat的 getSplits()的抽象方法,那么另一个抽象方法createRecordReader由谁来实现呢,我们看一下该类的两个实现类FileIn putFormat和TextInputFormat这两个实现类的源码,看一发现createRecordReader是在TextInputFormat这个实现类中实现的,我们看一下该类的源码如下代码2.5所示。

 /** An {@link InputFormat} for plain text files.  Files are broken into lines.      文件被解析成行
* Either linefeed or carriage-return are used to signal end of line. Keys are   无论是换行符还是回车符都表示一行结束
* the position in the file, and values are the line of text.. */           键是该行在文件中的位置,值为该行的内容
public class TextInputFormat extends FileInputFormat<LongWritable, Text> { @Override
public RecordReader<LongWritable, Text>
createRecordReader(InputSplit split,
TaskAttemptContext context) {
return new LineRecordReader();
} @Override
protected boolean isSplitable(JobContext context, Path file) {
CompressionCodec codec =
new CompressionCodecFactory(context.getConfiguration()).getCodec(file);
if (null == codec) {
return true;
}
return codec instanceof SplittableCompressionCodec;
} }

代码2.5

  我们再分析一下createRecordReader()方法的返回值,他的返回值类型为RecordReader,返回值是new LineRecordReader (),而他继承了RecordReader,我们先看一下RecordReader源码如代码2.6所示。

 package org.apache.hadoop.mapreduce;

 import java.io.Closeable;
import java.io.IOException; /**
* The record reader breaks the data into key/value pairs for input to the 将数据解析成Mapper能够处理的键值对
* {@link Mapper}.
* @param <KEYIN>
* @param <VALUEIN>
*/
public abstract class RecordReader<KEYIN, VALUEIN> implements Closeable { /**
* Called once at initialization.
* @param split the split that defines the range of records to read
* @param context the information about the task
* @throws IOException
* @throws InterruptedException
*/
public abstract void initialize(InputSplit split,
TaskAttemptContext context
) throws IOException, InterruptedException; /**
* Read the next key, value pair.
* @return true if a key/value pair was read
* @throws IOException
* @throws InterruptedException
*/
public abstract boolean nextKeyValue() throws IOException, InterruptedException; public abstract KEYIN getCurrentKey() throws IOException, InterruptedException;
public abstract VALUEIN getCurrentValue() throws IOException, InterruptedException;
public abstract float getProgress() throws IOException, InterruptedException;
public abstract void close() throws IOException;
}

代码 2.6

  从上面的代码中我们可以发现,RecordReader类是一个抽象类,其中的抽象方法initialize(),主要是用来将内容解析成键值对的,nextKeyValue(), getCurrentKey() ,getCurrentValue() 主要是用来获取键值对的内容的,他们的使用方法如下面代码2.7所示。

 while(xxx.nextKeyValue()){
key=xxx.getCurrenKey();
value=xxx.getCurrentValue();
}

代码 2.7

从RecordReader的类中回到 LineRecordReader类我们可以看到,该类对RecordReader类的三个抽象方法nextKeyValue(), getCurrentKey(),getCurrentValue()进行了实现,LineRecordReader类源码如代码2.8所示。

 public boolean nextKeyValue() throws IOException {
if (key == null) {
key = new LongWritable();
}
key.set(pos);  //第一次调用时pos为零,key也就为零,key表示该行的偏移量
if (value == null) {
value = new Text();
}
int newSize = 0;  //表示当前读取的字节数
// We always read one extra line, which lies outside the upper
// split limit i.e. (end - 1)
while (getFilePosition() <= end) {  //读取一行内容给value
newSize = in.readLine(value, maxLineLength,
Math.max(maxBytesToConsume(pos), maxLineLength));
if (newSize == 0) {
break;
}
pos += newSize;  //读取一行重置pos
if (newSize < maxLineLength) {
break;
} // line too long. try again
LOG.info("Skipped line of size " + newSize + " at pos " +
(pos - newSize));
}
if (newSize == 0) {
key = null;
value = null;
return false;
} else {
return true;
}
}

代码 2.8

  通过以上对TextInputFormat的一系列分析,我们可以知道文件是如何分片的,分片是如何被解析成键值对的。那么这些键值对是如何被提交到Mapper上的呢?我们一步步分析,首先我们知道,分片是被createRecordReader()解析成键值对的,他的返回值是new LineRecordReader (),代表被解析成的键值对,那么我们就分析一下 LineRecordRe ader和Mapper的关系。好那么我们就看一下,Mapper的源码,如代码2.9所示。

 public class Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {

   public class Context
extends MapContext<KEYIN,VALUEIN,KEYOUT,VALUEOUT> {
public Context(Configuration conf, TaskAttemptID taskid,
RecordReader<KEYIN,VALUEIN> reader,
RecordWriter<KEYOUT,VALUEOUT> writer,
OutputCommitter committer,
StatusReporter reporter,
InputSplit split) throws IOException, InterruptedException {
super(conf, taskid, reader, writer, committer, reporter, split);
}
} /**
* Called once at the beginning of the task.
*/
protected void setup(Context context
) throws IOException, InterruptedException {
// NOTHING
} /**
* Called once for each key/value pair in the input split. Most applications
* should override this, but the default is the identity function.
*/
@SuppressWarnings("unchecked")
protected void map(KEYIN key, VALUEIN value,
Context context) throws IOException, InterruptedException {
context.write((KEYOUT) key, (VALUEOUT) value);
} /**
* Called once at the end of the task.
*/
protected void cleanup(Context context
) throws IOException, InterruptedException {
// NOTHING
} /**
* Expert users can override this method for more complete control over the
* execution of the Mapper.
* @param context
* @throws IOException
*/
public void run(Context context) throws IOException, InterruptedException {
setup(context);
while (context.nextKeyValue()) {
map(context.getCurrentKey(), context.getCurrentValue(), context);
}
cleanup(context);
}
}

代码 2.9

  我们分析一下这段代码,其中的getCurrentKey(),getCurrentValue(),nextKeyValue(),在RecordReader也见过,那么是不是他的呢?我们点击getCurrentKey(),然后进入到,MapContext类,看一下他的一段代码如代码2.10所示。

 public class MapContext<KEYIN,VALUEIN,KEYOUT,VALUEOUT>
extends TaskInputOutputContext<KEYIN,VALUEIN,KEYOUT,VALUEOUT> {
private RecordReader<KEYIN,VALUEIN> reader;
private InputSplit split; public MapContext(Configuration conf, TaskAttemptID taskid,
RecordReader<KEYIN,VALUEIN> reader,
RecordWriter<KEYOUT,VALUEOUT> writer,
OutputCommitter committer,
StatusReporter reporter,
InputSplit split) {
super(conf, taskid, writer, committer, reporter);
this.reader = reader;
this.split = split;
} /**
* Get the input split for this map.
*/
public InputSplit getInputSplit() {
return split;
} @Override
public KEYIN getCurrentKey() throws IOException, InterruptedException {
return reader.getCurrentKey();
} @Override
public VALUEIN getCurrentValue() throws IOException, InterruptedException {
return reader.getCurrentValue();
} @Override
public boolean nextKeyValue() throws IOException, InterruptedException {
return reader.nextKeyValue();
} }

代码 2.10

  我们从上面的代码,发现Reader的类型就是RecordReader类型,我们又知道他的子类就是,LineRecordReader我们这样就知道了他与Mapper之间的关系了。那么我们也就清楚了计算机在Mapper第一阶段所做的事如图2.4所示。

图 2.4


  与TextInputFormat相对应的是OutputFormat,他的继承关系结构如图2.3所示,关于对他们的分析,可依据前面对InputFormat的分析方法进行分析在这里不再分析。

图 2.3

2.2.2 第二项任务

  这项任务主要是由我们自己来做,通过对map()函数进行覆盖来实现我们的业务逻辑,这也是我们在MapReduce编程过程中的主要工作量。在单词统计的项目中,在未经map()函数处理时,初始键值对<K1,V1>中,键K1表示存储位置,V2表示某一行的内容。由于我们要统计单词的个数,为了便于实现我们的目的,所以我们的中间结果<K2,V2>,K2表示单词,V2用特定的值1来表示。然后在经过reduce函数处理,得到我们的最终结果。

Hadoop日记Day14---MapReduce源代码回顾总结的更多相关文章

  1. Hadoop日记系列目录

    下面是Hadoop日记系列的目录,由于目前时间不是很充裕,以后的更新的速度会变慢,会按照一星期发布一期的原则进行,希望能和大家相互学习.交流. 目录安排 1>  Hadoop日记Day1---H ...

  2. 每天收获一点点------Hadoop之初始MapReduce

    一.神马是高大上的MapReduce MapReduce是Google的一项重要技术,它首先是一个编程模型,用以进行大数据量的计算.对于大数据量的计算,通常采用的处理手法就是并行计算.但对许多开发者来 ...

  3. hadoop系列四:mapreduce的使用(二)

    转载请在页首明显处注明作者与出处 一:说明 此为大数据系列的一些博文,有空的话会陆续更新,包含大数据的一些内容,如hadoop,spark,storm,机器学习等. 当前使用的hadoop版本为2.6 ...

  4. MapReduce源代码分析之JobSubmitter(一)

    JobSubmitter.顾名思义,它是MapReduce中作业提交者,而实际上JobSubmitter除了构造方法外.对外提供的唯一一个非private成员变量或方法就是submitJobInter ...

  5. Hadoop 中利用 mapreduce 读写 mysql 数据

    Hadoop 中利用 mapreduce 读写 mysql 数据   有时候我们在项目中会遇到输入结果集很大,但是输出结果很小,比如一些 pv.uv 数据,然后为了实时查询的需求,或者一些 OLAP ...

  6. 从Hadoop框架与MapReduce模式中谈海量数据处理(含淘宝技术架构) (转)

    转自:http://blog.csdn.net/v_july_v/article/details/6704077 从hadoop框架与MapReduce模式中谈海量数据处理 前言 几周前,当我最初听到 ...

  7. Hadoop权威指南:MapReduce应用开发

    Hadoop权威指南:MapReduce应用开发 [TOC] 一般流程 编写map函数和reduce函数 编写驱动程序运行作业 用于配置的API Hadoop中的组件是通过Hadoop自己的配置API ...

  8. hadoop系列三:mapreduce的使用(一)

    转载请在页首明显处注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/7224772.html 一:说明 此为大数据系列的一些博文,有空的话会陆续更新,包含大数据的 ...

  9. 【Big Data - Hadoop - MapReduce】初学Hadoop之图解MapReduce与WordCount示例分析

    Hadoop的框架最核心的设计就是:HDFS和MapReduce.HDFS为海量的数据提供了存储,MapReduce则为海量的数据提供了计算. HDFS是Google File System(GFS) ...

  10. 初学Hadoop之图解MapReduce与WordCount示例分析

    Hadoop的框架最核心的设计就是:HDFS和MapReduce.HDFS为海量的数据提供了存储,MapReduce则为海量的数据提供了计算. HDFS是Google File System(GFS) ...

随机推荐

  1. 用例设计之APP用例覆盖准则

    基本原则 本文主要讨论APP功能用例的覆盖,基本原则: 用户场景闭环(从哪来到哪去) 遍历所有的实现逻辑路径 需求点覆盖 覆盖维度 APP功能用例设计主要使用传统的黑盒用例设计方法.同时,作为移动AP ...

  2. 简单了解Tomcat与OSGi的类加载器架构

    前言: 本次博客主要是对Tomcat与OSGi的类加载器架构,所以就需要对tomcat.OSGi以及类加载机制有所了解 类加载可以在http://www.cnblogs.com/ghoster/p/7 ...

  3. Log4Net记录到文件

    将这篇文章的配置文件中的log4net节点下的内容替换成下面的 https://www.cnblogs.com/RambleLife/p/9165248.html <log4net debug= ...

  4. javascript模块导入导出

    第一次知道javascript有模块的概念通常都是使用<script>标签进行引入,不过只能在html文件上使用 增加的模块就如同php里的include.require可以使用引入的内容 ...

  5. redis之禁用保护模式以及修改监听IP

    今天在安装filebeat的时候,出现了关于redis报错的问题,所以来总结一下: 报错信息是: (error) DENIED Redis is running in protected mode b ...

  6. MySQL运维之---mysqldump备份、select...into outfile、mysql -e 等工具的使用

    1.mysqldump备份一个数据库 mysqldump命令备份一个数据库的基本语法: mysqldump -u user -p pwd dbname > Backup.sql 我们来讲解一下备 ...

  7. mysql用户管理与权限

    1.设置密码 set password for 用户名@localhost = password('密码'); 2.取消密码 set password for 用户名@localhost = pass ...

  8. Eclipse 中怎样自动格式化代码?

    首先 有一个 检查代码风格的工具叫checkstyle,具体怎么下载,请自行百度.. 当你在eclipse安装好 checkstyle后,对于使用google标准的人来说,选择一个项目,右键,点击ch ...

  9. [python] 私有变量和私有方法

    1.在Python中要想定义的方法或者变量只在类内部使用不被外部调用,可以在方法和变量前面加 两个 下划线 #-*- coding:utf-8 -*- class A(object): name = ...

  10. 关于Javascript的des加密

    参考文章:https://www.cnblogs.com/MSMXQ/p/4484348.html 需要先下载CryptoJS文件,然后引入其中的两个文件,可以在github中找到. 直接上代码 &l ...