概要模式

数值概要

数值概要模式是计算数值聚合统计值的一般性模式。这种模式一般是按照键来对进行分组,并对每个分组计算一些统计值。

常见应用:

  • 单词计数
  • 记录计数
    例如,对特定时间间隔(每周或者每天或者每小时等)计算数据每个时间周期内的数据数量。
  • 最大值或最小值计数
  • 平均值、中位数、标准差

倒排索引概要

倒排索引模式一般是建立一个项到标识符列表的映射。顾名思义就是建立一个索引列表。例如,搜索引擎一般需要建立的关键字到网址的列表,从而用户可以通过关键字找到与关键字相关的网页。

计数器计数

这个模式使用 MapReduce框架自身的计数器在不产生任何输出的情况下,在map阶段计算出一个全局计数。因此,我们可以用计数器来重新实现一下“单词计数”的算法,而且用计数器来实现计数效率要高很多。需要注意的是,由于计数器是存在全局内存中的,map工作完成后所有的TaskTracker将统计结果汇总给JobTracker(一般就是namenode),所以计数器的数量过多会造成namenode内存瓶颈或者溢出,因而,计数器的数量最多不能超过100个,一般应该控制在50个以下为好。

过滤模式

过滤

顾名思义就是对输入的所有行进行过滤,输出留下满足条件的行,忽略不满足条件的行。这种模式只有map没有reduce,在map中对每一行进行是否满足条件的判断,根据判断结果决定是输出(留下)该行数据还是忽略(扔掉)该行数据。

布隆过滤

布隆过滤与上面的过滤很相似,不同点在于布隆过滤中预先设定一个列表,列表中有一些值,只要输入的当前行包含其中的任意一个值就认为该行满足条件可以输出。布隆过滤仍然只有map没有reduce。

Top 10

Top10顾名思义就是从所有的输入行中过滤出某字段的前10名输入行。这种模式map阶段维护一个长度为10的列表,列表的长度小于10的时候向列表中直接添加,直到列表的长度等于10时,则删除列表中最小的元素并添加新元素。需要注意的是这种模式可以有多个map但只能有一个reduce,reduce从N个map输出的10N个元素中找出前10作为最终的输出。可以看出,由于只能有一个reduce所以如果取Top100甚至更多时,效率是很低的。因此这种模式只适合较小的取值。

去重

去重既将所有输入中某个字段重复的输入过滤掉,例如,在所有用户评论中找出所有不同的用户名。这种模式中map的输出键是map的输入值,map的输出值是null。reduece只需输出reduece的输入key即可。

数据组织模式

分层结构

该模式可将基于行的数据转换为分层的格式。
例如,给定一个帖子列表文件和评论列表文件,创建一个结构化的XML层次结构,嵌套的表示帖子及其相关评论。输出的形式如下:

Posts
 Post
  第一个帖子
  Comment
   第一个帖子的第一条评论
  /Comment
  Comment
   第一个帖子的第二条评论
  /Comment
 /Post
 Post
  第二个帖子
  Comment
   第二个帖子的第一条评论
  /Comment
  Comment
   第二个帖子的第二条评论
  Comment
 /Post
Posts

输入为两个文件,一个文件中每一行是一个帖子内容,其中有一个字段保存帖子id;另一个文件中每一行是一个评论,其中有一个字段保存与该评论对应的帖子id。

具体实现思路为,map以两个文件为输入,实现两个mapper类分别用于处理帖子文件和评论文件。PostMapper用于处理帖子文件,输出的key为帖子id,输出value为字母“P”+输入的value(每一行的内容)。CommentMapper用于处理评论文件,输出key为该评论对应的帖子id,输出value为字母C+输入的value。其中,字母P和C是为了在reduce中key值相同的一组值中哪些是帖子哪些是评论。由于用到两个mapper,所以main函数中需要用MultipleInput.addInputPath()为每一个mapper指定相应的文件。reduce可以很容易的区分出帖子和与该帖子对应的评论,将他们利用函数构建成xml格式再输出即可。

分区

分区模式是将记录进行分类,但并不关心记录的顺序。需要注意的是必须要预先知道要分多少个分区。用一个实例来描述一下分区的用法。

  • 问题一:给定一组用户信息,按照最近访问日期中的年份信息对记录进行分区,一年对应一个分区。

main函数需要配置成使用自定义分区器,分区器也需要配置,另外需要配置reduce的数目,每一个分区对应一个reduce。

1
2
3
4
5
6
7
//配置自定义分区器
job.setPartitionerClass(LastAccessDatePartitioner.class);
//为分区设置最小的年份,例如文件中的用户是从2008到2011,那么就是分成4个分区,每个分区的标号就是当前年份减去最小年份
//2008对应的是标号为0的分区,2009年对应的是标号为1的分区
LastAccessDatePartitioner.setMinLastAccessDate(job,2008);
//4个分区应该设置4个reduce
job.setNumReduceTasks(4);

map函数的输出key是年份,输出value就是输入value。然后需要实现LastAccessDatePartitioner类,完成分区器的定义,reduce直接输出即可。

由于第一个例子不是特别清楚,我又在网上找个一个例子,现在对分区的理解应该是正确的了。直接上例子。

  • 问题二:按年龄段找出不同年龄段的男性和女性的最高收入。

问题的输入如下

  • Id Name Age Gender Salary
    1201 gopal 45 Male 50,000
    1202 manisha 40 Female 50,000
    1203 khalil 34 Male 30,000
    1204 prasanth 30 Male 30,000
    1205 kiran 20 Male 40,000
    1206 laxmi 25 Female 35,000
    1207 bhavya 20 Female 15,000
    1208 reshma 19 Female 15,000
    1209 kranthi 22 Male 22,000
    1210 Satish 24 Male 25,000
    1211 Krishna 25 Male 25,000
    1212 Arshad 28 Male 20,000
    1213 lavanya 18 Female 8,000

其中第一行的内容是对数据每一列含义的解释,在真实的输入文件中是没有这一行的。

问题期望的输出如下:

  • 输出文件一 Part-00000
    Female 15000
    Male 40000

  • 输出文件二 Part-00001
    Female 35000
    Male 31000

  • 输出文件三 Part-00002
    Female 51000
    Male 50000

输出的每一个文件对应一个年龄段。

下面我来阐述实现的思路。首先需要明确几点:

  • 分区器是在map结束以后,reduce开始之前工作的。
  • 分区器分几个分区,就应该有几个reduce,所以设置reduce数量的时候要注意。
  • 每个reduce产生一个输出文件,也就是分在一个区里的数据对应的输出在同一个输出文件中。

map阶段输出key为性别字段,输出value为输入value;接着在partition阶段将年龄小于20、年龄大于20小于30以及年龄大于30的人分别分给三个reduce;因此,第一个reduce下面的应该是年龄小于20的两组数据,一组的key是Female,一组的key是Male,以此类推其他的两个reduce下面的数据就清楚了。reduce的工作很简单就是找出每组的最大值并输出即可。附上源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import java.io.*;
import org.apache.hadoop.io.*;
import org.apache.hadoop.mapreduce.*;
import org.apache.hadoop.conf.*;
import org.apache.hadoop.conf.*;
import org.apache.hadoop.fs.*;
import org.apache.hadoop.mapreduce.lib.input.*;
import org.apache.hadoop.mapreduce.lib.output.*;
import org.apache.hadoop.util.*;
public class PartitionerExample extends Configured implements Tool
{
//Map class
public static class MapClass extends Mapper<LongWritable,Text,Text,Text>
{
public void map(LongWritable key, Text value, Context context)
{
try{
String[] str = value.toString().split("\t", -3);
String gender=str[3];
context.write(new Text(gender), new Text(value));
}
catch(Exception e)
{
System.out.println(e.getMessage());
}
}
}
//Reducer class
public static class ReduceClass extends Reducer<Text,Text,Text,IntWritable>
{
public int max = -1;
public void reduce(Text key, Iterable <Text> values, Context context) throws IOException, InterruptedException
{
max = -1;
for (Text val : values)
{
String [] str = val.toString().split("\t", -3);
if(Integer.parseInt(str[4])>max)
max=Integer.parseInt(str[4]);
}
context.write(new Text(key), new IntWritable(max));
}
}
//Partitioner class
public static class CaderPartitioner extends
Partitioner < Text, Text >
{
@Override
public int getPartition(Text key, Text value, int numReduceTasks)
{
String[] str = value.toString().split("\t");
int age = Integer.parseInt(str[2]);
if(numReduceTasks == 0)
{
return 0;
}
if(age<=20)
{
return 0;
}
else if(age>20 && age<=30)
{
return 1 % numReduceTasks;
}
else
{
return 2 % numReduceTasks;
}
}
}
@Override
public int run(String[] arg) throws Exception
{
Configuration conf = getConf();
Job job = new Job(conf, "topsal");
job.setJarByClass(PartitionerExample.class);
FileInputFormat.setInputPaths(job, new Path(arg[0]));
FileOutputFormat.setOutputPath(job,new Path(arg[1]));
job.setMapperClass(MapClass.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
//set partitioner statement
job.setPartitionerClass(CaderPartitioner.class);
job.setReducerClass(ReduceClass.class);
job.setNumReduceTasks(3);
job.setInputFormatClass(TextInputFormat.class);
job.setOutputFormatClass(TextOutputFormat.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
System.exit(job.waitForCompletion(true)? 0 : 1);
return 0;
}
public static void main(String ar[]) throws Exception
{
int res = ToolRunner.run(new Configuration(), new PartitionerExample(),ar);
System.exit(0);
}

分箱

分箱模式是在map阶段对数据进行拆分,即将数据分为几类。这种模式只有map阶段。假设将数据分为n类,即有n个箱子,然后有m个mapper,由于每个mapper都有可能能会处理到这n类数据,所以每个mapper都会输出n个文件,那么一共就会输出m×n个文件。这也是这种模式的缺点所在。下面举一个实际例子说明一下这种模式的应用场景。

  • 问题:给定一组论坛的帖子,根据帖子的标签,将带有hadoop、pig、hive和hbase标签的帖子分别放到4个箱子中,另外,将帖子内容中提及hadoop的帖子放到一个额外的箱子中。

由于不同的箱子要输出到不同的文件中,所以需要用到MultipleOutputs类。main函数中需要的操作如下:

1
2
3
4
//配置输出文件放在名为“bin”的目录下
MultipleOutputs.addNameOutput(job,"bin",TextOutputFormat.class,Text.class,NullWritable.class);
//不需要reduce,所以将reduce数量设为0
job.setNumReduceTasks(0);

具体的实现思路其实很简单,map主要做的就是提取出每个输入帖子的标签,然后用多个if else语句来判断是否含有我们的目标标签(上面的4个标签),若含有则将这个帖子写到相应的箱子中。

全排序

(这个模式没有理解好)

混排

这个模式简单来说就是将有序的数据进行打乱。打乱的方式很简单map的输出键为一个随机的数,输出值为输入值。reduce仅输出value即可。

未完待续

算法概述

算法的目标是,根据用户已经看过的电影来为用户合理推荐没有看过的电影。详细问题描述在这里 用hadoop构建电影推荐系统

算法输入格式

1
2
3
4
5
6
7
1,101,5.0
1,102,3.0
1,103,2.5
2,101,2.0
2,102,2.5
2,103,5.0
2,104,2.0

第一行中,“1”代表用户id;“101”代表电影id;“5.0”表示该用户看过这部电影,并评分为5.0。

算法输出格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1 101,44.0
1 102,31.5
1 103,39.0
1 104,33.5
1 105,15.5
1 106,18.0
1 107,5.0
2 101,45.5
2 102,32.5
2 103,41.5
2 104,36.0
2 105,15.5
2 106,20.5
2 107,4.0

其中,以第一行为例,“1”代表用户id;“101”代表电影id;“44.0”表示该电影的综合推荐值,这个值越大表示这个电影越值得推荐。这里我们对数据集中的所有电影都进行综合评估,没有排除掉那些用户已经看过的电影,后续可能会继续完善一下。

算法流程

我们分五个步骤来完成这件事,每一个步骤都是一个mapreduce任务,在主函数中依次调用这五个任务。首先,给出主函数代码。

1
2
3
4
5
6
7
8
9
10
11
12
public class Main {
public static void main(String[] args) throws Exception
{
Step1.run();
Step2.run();
Step3.run();
Step4.run();
Step5.run();
System.exit(0);
}
}

Step1

步骤一将处理成如下结构

1
2
3
4
5
1 101:5.0,102:3.0,103:2.5
2 101:2.0,102:2.5,103:5.0,104:2.0
3 107:5.0,105:4.5,104:4.0,101:2.0
4 106:4.0,103:3.0,101:5.0,104:4.5
5 104:4.0,105:3.5,106:4.0,101:4.0,102:3.0,103:2.0

以第一行为例,意义为:用户1看了101,102,103这三部电影,分别评分为5.0,3.0,2.5。至于如何得到这个结果,我先贴出map和reduce两个函数的源码。

1
2
3
4
5
6
7
public void map(LongWritable key,Text value,Context context)
throws IOException,InterruptedException
{
String line=value.toString();
String[] tmpArr=line.split(",");
context.write(new IntWritable(Integer.parseInt(tmpArr[0])), new Text(tmpArr[1]+":"+tmpArr[2]));
}

map函数非常简单,将每一行用“,”切分成字符串数组后,以用户id为key,电影id和评分连接起来的新字符串作为值。

1
2
3
4
5
6
7
8
9
10
11
public void reduce(IntWritable key,Iterable<Text> values,Context context)
throws IOException,InterruptedException
{
String result="";
for(Text val:values)
{
result+=val+",";
}
result=result.substring(0, result.length()-1);
context.write(key, new Text(result));
}

reduce函数以map的输出为输入,同样key值的map输出为同一个reduce group,所以reduce就是将key值相同的所有value拼接起来,也就有了上面的结果。

Step2

步骤二以Step1的输出为输入,得到如下结果:

1
2
3
4
5
6
7
8
9
10
11
101:101 5
101:102 3
101:103 4
101:104 4
101:105 2
101:106 2
101:107 1
102:101 3
102:102 3
102:103 3
102:104 2

以第2行为例,意义为:有三个用户既看了101电影又看了102电影。具体实现如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void map(LongWritable key,Text value,Context context)
throws IOException,InterruptedException
{
String line=value.toString();
String[] tmpArr1=line.split(" ");
String[] tmpArr=tmpArr1[1].split(",");
for(int i=0;i<tmpArr.length;i++)
{
String Item1ID=tmpArr[i].split(":")[0];
for(int j=0;j<tmpArr.length;j++)
{
String Item2ID=tmpArr[j].split(":")[0];
context.write(new Text(Item1ID+":"+Item2ID), new IntWritable(1));
}
}
}

map函数针对每一行输入,进行切分,切分后用双重for循环对电影ID进行两两组合,并将组合结果作为key输出,value为1。

1
2
3
4
5
6
7
8
9
10
public void reduce(Text key,Iterable<IntWritable> values,Context context)
throws IOException,InterruptedException
{
int result=0;
for(IntWritable val:values)
{
result+=val.get();
}
context.write(key, new IntWritable(result));
}

reduce函数很简单,其实就是一个按组计数的过程。

Step3

步骤三以Step1的输出为输入,生成如下文件

1
2
3
4
5
6
7
101 1:5.0,5:4.0,4:5.0,3:2.0,2:2.0
102 2:2.5,5:3.0,1:3.0
103 1:2.5,4:3.0,5:2.0,2:5.0
104 3:4.0,4:4.5,5:4.0,2:2.0
105 5:3.5,3:4.5
106 4:4.0,5:4.0
107 3:5.0

应该。。。不用。。。解释。。。能看出。。。是什么意思的吧。恩,就默认大家都能看懂了吧。直接上代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
public void map(LongWritable key,Text value,Context context)
throws IOException,InterruptedException
{
String line=value.toString();
String[] tmpArr=line.split(" ");
String UserID=tmpArr[0];
String[] Item2pres=tmpArr[1].split(",");
for(int i=0;i<Item2pres.length;i++)
{
String[] tmp=Item2pres[i].split(":");
context.write(new Text(tmp[0]), new Text(UserID+":"+tmp[1]));
}
}

map以电影id为key,用户id:评分为值。

1
2
3
4
5
6
7
8
9
10
11
public void reduce(Text key,Iterable<Text> values,Context context)
throws IOException,InterruptedException
{
String result="";
for(Text val:values)
{
result+=val+",";
}
result=result.substring(0, result.length()-1);
context.write(key, new Text(result));
}

reduce就是将同一个key的value连接为一个字符串。

Step4

这个步骤的任务是把Step3和Step2的输出合并为一个文件,但是重点在于是将两个文件的相应行进行合并。合并结果应该是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
101:103 4 101 1:5.0,5:4.0,4:5.0,3:2.0,2:2.0
101:105 2 101 1:5.0,5:4.0,4:5.0,3:2.0,2:2.0
101:101 5 101 1:5.0,5:4.0,4:5.0,3:2.0,2:2.0
101:106 2 101 1:5.0,5:4.0,4:5.0,3:2.0,2:2.0
101:107 1 101 1:5.0,5:4.0,4:5.0,3:2.0,2:2.0
101:104 4 101 1:5.0,5:4.0,4:5.0,3:2.0,2:2.0
101:102 3 101 1:5.0,5:4.0,4:5.0,3:2.0,2:2.0
102:101 3 102 2:2.5,5:3.0,1:3.0
102:102 3 102 2:2.5,5:3.0,1:3.0
102:103 3 102 2:2.5,5:3.0,1:3.0
102:104 2 102 2:2.5,5:3.0,1:3.0
102:105 1 102 2:2.5,5:3.0,1:3.0
102:106 1 102 2:2.5,5:3.0,1:3.0
103:101 4 103 1:2.5,4:3.0,5:2.0,2:5.0
103:102 3 103 1:2.5,4:3.0,5:2.0,2:5.0
103:103 4 103 1:2.5,4:3.0,5:2.0,2:5.0
103:104 3 103 1:2.5,4:3.0,5:2.0,2:5.0
103:106 2 103 1:2.5,4:3.0,5:2.0,2:5.0
103:105 1 103 1:2.5,4:3.0,5:2.0,2:5.0

以第一行为例,意义就是:所有用户中有4个用户的既看了101电影又看了103电影,所有看了101电影的用户对它的评分如下。之所以处理成这样,是为了最后一步的矩阵计算做准备。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void map(LongWritable key,Text value,Context context)
throws IOException,InterruptedException
{
String line=value.toString();
String[] tmpArr=line.split(" ");
String[] tmpArr2=tmpArr[0].split(":");
if(tmpArr2.length==2)
{
context.write(new Text(tmpArr2[0]), value);
}
else
{
dict.put(tmpArr[0], line);
}
}

这个步骤的特殊之处在于它的输入是两种格式不同的文件,对它们要进行不同的处理,并且要将结果统一成key和value的形式再传给reduce。map函数中用if和else来分别处理两个文件,if处理的是Step2的输出,输出的key是文件的第一个字段,value是输入的这一行文件。else处理的是Step3的输出,将这个文件每一行按键值对的方式存放再全局字典中(Java的HashMap)。

1
2
3
4
5
6
7
8
9
public void reduce(Text key,Iterable<Text> values,Context context)
throws IOException,InterruptedException
{
for(Text val:values)
{
String ItemID=new String(key.getBytes());
context.write(val, new Text(dict.get(ItemID)));
}
}

reduce将key值相同的map输出和dit中对应的值连接起来。

Step5

这个步骤应该是最复杂的了。先上个图
矩阵计算推荐结果

上面的图中,左边的矩阵中的每个数值表示所有用户中既看了该行对应的电影又看了该列对应的电影的用户数量,也就是Step2中我们的计算结果,右边的列向量表示用户3对每个电影的评分,等号右边就是对每个电影的推荐值。具体实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void map(LongWritable key,Text value,Context context)
throws IOException,InterruptedException
{
String line=value.toString();
String[] tmpArr1=line.split(" ");
String Item=tmpArr1[0].split(":")[1];
String[] userPre2CurItems=tmpArr1[3].split(",");
for(int i=0;i<userPre2CurItems.length;i++)
{
String[] tmp=userPre2CurItems[i].split(":");
double curValue=Integer.parseInt(tmpArr1[1])*Double.parseDouble(tmp[1]);
context.write(new Text(tmp[0]+" "+Item), new DoubleWritable(curValue));
}
}

map输出的key是用户id和当前评价的电影id,输出的值其实是当前评价的电影的推荐值的一部分。for循环是针对每一个用户计算,并不是针对每一个电影计算。

1
2
3
4
5
6
7
8
9
10
11
12
public void reduce(Text key,Iterable<DoubleWritable> values,Context context)
throws IOException,InterruptedException
{
String tmpStr=new String(key.getBytes());
String[] tmp=tmpStr.split(" ");
double result=0;
for(DoubleWritable val:values)
{
result+=val.get();
}
context.write(new Text(tmp[0]), new Text(tmp[1]+","+result));
}

reduce将用户id和电影id都相同的值累加,最终输出最终结果。

源码下载

MySQL的安装与启动

ubuntu下命令行安装

1
$ sudo apt-get install mysql-server mysql-client

启动MySQL服务器端和客户端

1
2
$ /etc/init.d/mysql start
$ mysql -u root -p

常用数据库命令

创建数据库

1
mysql> create database test;

查看数据库编码方式

1
mysql> show create database test;

修改数据库编码

1
2
3
4
5
6
7
mysql> alter database test character set = utf8;
``` bash
### 查看存在的数据库
``` bash
mysql> show databases;

删除数据库

1
mysql> drop database teat;

一、用户管理

who

功能:查询当前主机上的用户登录情况

参数 说明
-a 打印能打印的全部
-d 打印死掉的进程
-m 同am i,mom likes
-q 打印当前登录用户数及用户名
-u 打印当前登录用户登录信息
-r 打印运行等级

adduser

功能: 添加用户

用法: (sudo) adduser 新用户名

默认会在/home下创建一个以“新用户名”为名的文件夹

默认情况下新创建的用户是不具有root权限的,也不在sudo用户组,也就无法通过sudo命令临时获取root权限

groups

功能:查看用户的所属组

用法:groups 用户名 (输出的内容冒号之前表示用户,之后表示该用户的所属组)

新建用户时,如果没有指定所属组,系统会默认创建一个与新建用户名相同的组
用户组信息存储在/etc/group文件中,文件中一行对应一个用户组的信息,内容格式如下:

1
用户组:用户组口令:GID:该用户组所包含的用户

其中“用户组口令”字段为“X”表示密码不可见

将其他用户加入sudo用户组

当使用sudo命令系统提示“用户不在sudoers文件中”时,说明该用户没有在sudo用户组里,利用usermod命令为用户添加用户组,不要修改sudoers文件!

usermod命令

使用usermod命令可以为用户添加用户组,同样使用该命令你必需有root权限,你可以直接使用root用户为其它用户添加用户组,或者用其它已经在sudo用户组的用户使用sudo命令获取权限来执行该命令

1
2
3
4
$ su root //或者某个已经在sudo组里的用户
$ groups 要被加入到sudo组里的用户名 //查看该用户的所属组
$ sudo usermod -G sudo 要被加入到sudo组里的用户名 //为该用户添加用户组sudo
$ groups 要被加入到sudo组里的用户名 //再次查看该用户的所属组

deluser

功能:删除用户

用法: sudo deluser 用户名 –remove-home

二、文件权限

ls

功能:查看文件权限

用法:ls -l (使用长格式列出文件)

显示的内容格式如下:

1
文件类型权限 连接数 所有者 所属用户组 文件大小 最后修改时间 文件名

第一个字段其实是分为两个内容,文件类型和权限。第一个字符代表文件类型,接下来的九个字符分为三组,分别表示:拥有着权限,所属组权限,其他用户权限。

文件类型

  • ‘d’,目录
  • ‘l’,软连接
  • ‘b’,块设备
  • ‘c’,字符设备
  • ‘s’,socket
  • ‘p’,管道
  • ‘-‘,普通文件

关于文件类型,这里有一点你必需时刻牢记linux里面一切皆文件,正因为这一点才有了设备文件(/dev目录下有各种设备文件,大都跟具体的硬件设备相关)这一说,还有socket(网络套接字,具体是什么,感兴趣的用户可以自己去了解或期待实验楼的后续相关课程),和pipe(管道,这个东西很重要,我们以后将会讨论到,这里你先知道有它的存在即可)。软链接文件,链接文件是分为两种的,另一种当然是“硬链接”(硬链接不常用,具体内容不作为本课程讨论重点,而软链接等同于windows上的快捷方式,你记住这一点就够了)

权限

  • ‘r’,允许读,值为4
  • ‘w’,允许写,值为2
  • ‘x’,允许执行,值为1

ls的补充用法

ls -A //显示隐藏文件(隐藏文件以’.’开头)

ls -AsSh //显示所有文件大小,并以普通人类能看懂的方式呈现(其中小s为显示文件大小,大S为按文件大小排序)

chown

功能:变更文件所有者

用法:chown 所有者 文件名

chmod

功能:变更文件权限

用法:chmod 700 文件名 //700可以换成其他数值

一、变量

变量的声明、赋值与读取

使用declare命令创建一个变量,例如创建一个变量名为tmp的变量

1
$ declare tmp

变量的赋值

1
$ tmp=shiyanlou

读取变量的值,使用echo命令和$符号($符号用于表示引用一个变量的值

1
$ echo $tmp

注意!变量名只能是英文字母,数字或者下划线,且不能以数字作为开头!

二、环境变量

shell的环境变量作用于自身和它的子进程。在所有的Unix和类Unix系统中,每个进程都有其各自的环境变量设置,且默认情况下,当一个进程被创建时,处理创建过程中明确指定的话,它将继承其父进程的绝大部分环境设置。shell程序也作为一个进程运行在操作系统之上,而我们在shell中运行的大部分命令都将以shell的子进程的方式运行。

进程演示图

相关命令

三种环境变量相关的命令,set,env,export

命令 说明
set 显示当前shell所有环境变量,包括其内建环境变量(与shell外观等相关),用户自定义变量及导出的环境变量
env 显示与当前用户相关的环境变量,还可以让命令在指定环境中运行
export 显示从shell中导出成环境变量的变量,也能通过它将自定义变量导出为环境变量

可以通过以下测试体会环境变量和普通变量的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ temp=shiyanlou
$ echo $temp
shiyanlou
$ zsh //创建一个子shell,在ubuntu中默认shell为bash
$ echo $temp
//显示空
$ exit //推出子shell
$ export temp //把temp变量导出为环境变量
$ zsh //重新创建子shell
$ echo $temp
shiyanlou

!!!注意!!! 为了与普通变量区分,通常我们习惯将环境变量名设为大写

##命令的查找路径与顺序

我们在hell中输入一个命令,shell是怎么知道在哪去找到这个命令然后执行的呢。这是通过环境变量PATH来进行搜索的。这个PATH里面就保存了shell中执行的命令的搜索路径。

查看PATH环境变量的内容

1
$ echo $PATH

默认情况下你会看到如下输出

1
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games

通常这一类目录下放的都是可执行文件,当我们在shell中执行一个命令时,系统就会按照PATH中设定的路径按照顺序依次到目录中去查找,如果存在同名的命令,则执行先找到的那个。下面我们将练习创建一个最简单的可执行shell脚本和一个使用C语言创建的”hello world”程序。

创建一个shell脚本文件

1
$ vim hello_shell.sh

在脚本中添加如下内容,保存并退出

1
2
3
4
5
6
7
#!/bin/zsh
for ((i=0; i<10; i++));do
echo "hello shell"
done
exit 0

为文件添加可执行权限

1
$ chmod 755 hello_shell.sh

创建一个c语言”hello world”程序:

1
$ vim hello_world.c
1
2
3
4
5
6
7
#include <stdio.h>
int main(void)
{
printf("hello world!\n");
return 0;
}

使用gcc生成可执行文件

1
$ gcc -o hello_world hello_world.c

在shiyanlou家目录创建一个mybin目录,并将上述hello_shell.sh和hello_world文件移动到其中

1
2
$ mkdir mybin
$ mv hello_shell.sh hello_world mybin/

现在你可以在mybin目录中分别运行你刚刚创建的两个程序

1
2
3
$ cd mybin
$ ./hello_shell.sh
$ ./hello_world

然后你回到上一级目录,也就是shiyanlou家目录,你再想运行那两个程序,会发现提示命令找不到,除非你加上命令的完整路径,但是这样不是很麻烦嘛,如何做到想使用系统命令一样执行自己创建的脚本文件或者程序呢。那就要将命令所在路径添加到PATH环境变量了。

添加自定义路径到”PATH”环境变量

在前面我们应该注意到PATH里面的路径是以:作为分割符,所以我们可以这样添加自定义路径

1
$ PATH=$PATH:/home/shiyanlou/mybin

!!!注意这里一定要使用绝对路径

现在你就可以在其他任意目录执行那两个命令了。你可能会意识到这样还并没有很好的解决问题,因为我给PATH环境变量追加了一个路径,它也只是在当前shell有效,我一旦退出终端,再打开就会发现又失效了。有没有方法让添加的环境变量全局有效又或者每次启动shell时自动执行上面添加自定义路径到PATH的命令了。下面我们就来说说后一种方式——让它自动执行。

在每个用户的家目录中有一个shell每次启动时会默认执行一个配置脚本,以初始化环境,包括添加一些用户自定义环境变量等等。zsh的配置文件是.zshrc,相应bash的配置文件为.bashrc。它们在etc下还都有一个或多个全局的配置文件,不过我们一般只修改用户目录下的配置文件。

我们可以简单的使用下面命令直接添加内容到.zshrc中

1
$ echo "PATH=$PATH:/home/shiyanlou/mybin" >> .zshrc

上述命令中 >>表示将标准输出以追加的方式重定向到一个文件中,注意前面用到的 >是以覆盖的方式重定向到一个文件中,使用的时候一定要注意分辨。在指定文件不存在的情况下都会创建新的文件

##修改和删除已有的变量

###变量的修改

变量的修改有以下几种方式:

变量设置方式 说明
${变量名#匹配字串} 从头向后开始匹配,删除符合匹配字串的最短数据
${变量名##匹配字串} 从头向后开始匹配,删除符合匹配字串的最长数据
${变量名%匹配字串} 从尾向前开始匹配,删除符合匹配字串的最短数据
${变量名%%匹配字串} 从尾向前开始匹配,删除符合匹配字串的最长数据
${变量名/旧的字串/新的字串} 将符合旧字串的第一个字串替换为新的字串
${变量名//旧的字串/新的字串} 比如要修改我们前面添加到PATH的环境变量:

比如修改我们前面添加到PATH的环境变量

为了避免操作失误导致命令找不到,我们先将PATH赋值给一个新的自定义变量path将符合旧字串的全部字串替换为新的字串

1
2
3
4
5
$ path=$PATH
$ echo $path
$ path=${path%/home/shiyanlou/mybin}
# 或使用通配符,*表示任意多个任意字符
$ path=${path%*/mybin}

变量删除

可以使用unset命令删除一个环境变量

1
$ unset temp

如何让环境变量立即生效

在上面我们在shell中修改了一个配置脚本文件之后(比如zsh的配置文件home目录下的.zshrc),每次都要退出终端重新打开甚至重启主机之后其才能生效,很是麻烦,我们可以使用source命令来让其立即生效。

例如

1
$ source .zshrc

source命令还有一个别名就是.,注意与表示当前路径的那个点区分开,虽然形式一样,但作用和使用方式一样,上面的命令如果替换成.的方式就该是

1
$ . ./.zshrc

注意第一个点后面有一个空格,而且后面的文件必须指定完整的绝对或相对路径名,source则不需要

三、搜索文件

与搜索相关的命令常用的有如下几个whereis,which,find,locate

whereis简单快速

很快,它快是因为它并没有从硬盘老老实实挨个去找,而是直接从数据库中查询。whereis只能搜索二进制文件(-b),man帮助文件(-m)和源代码文件(-s)。

locate快而全

查找/etc下所有以sh开头的文件

1
$ locate /etc/sh

注意,它不只是在etc目录下查找并会自动递归子目录进行查找

查找/usr/share/下所有jpg文件

1
$ locate /usr/share/\*.jpg

注意要添加*号前面的反斜杠转义,否则会无法找到

如果想只统计数目可以加上-c参数,-i参数可以忽略大小写进行查找,whereis的-b,-m,-s同样可以是使用

which小而精

which本身是shell内建的一个命令,我们通常使用which来确定是否安装了某个指定的软件,因为它只从PATH环境变量指定的路径中去搜索命令

find精而细

find应该是这几个命令中最强大的了,它不但可以通过文件类型、文件名进行查找而且可以根据文件的属性(如文件的时间戳,文件的权限等)进行搜索。find命令强大到,要把它将明白至少需要单独好几节课程才行,我们这里就只介绍一些常用的内容

在指定目录下搜索指定文件名的文件

1
$ find /etc/ -name interfaces

注意find命令的路径是作为第一个参数的, 基本命令格式为 find [path] [option] [action]

与时间相关的命令参数

参数 说明
-atime 最后访问时间
-ctime 创建时间
-mtime 最后修改时间

下面以-mtime参数举例

  • -mtime n: n 为数字,表示为在n天之前的”一天之内“修改过的文件
  • -mtime +n: 列出在n天之前(不包含n天本身)被修改过的文件
  • -mtime -n: 列出在n天之前(包含n天本身)被修改过的文件
  • newer file: file为一个已存在的文件,列出比file还要新的文件名

时间轴

列出home目录中,当天(24小时之内)有改动的文件

1
$ find ~ -mtime 0

列出比某个文件新的所有文件

1
2
# 姑且利用一下工程师配置环境时遗留的test.c~文件吧-_-||
$ find ~ newer Documents/test.c\~

一、Quick-Find

##算法思想

将相互连通的节点归类于集合里,每一个集合有自己的集合编号。用连续的数组来储存所有节点,数组的每个元素的内容为所属集合的集合编号(初始值为自己的index)。
每次连接(union)两个节点时,将其中一个节点所属的集合中的所有节点的集合号改为另一个节点的集合编号。
每次查询(find)两个节点是否已连接时,只需查看两个节点的集合编号是否一致。

##算法效率
find快,union慢。union访问数组次数为N,find访问数组次数为1

二、Quick-Union

算法思想

利用树结构,用连续数组储存所有的节点,每个数组元素的内容为该节点的父节点(初始值为自己的index)。所以树的root节点的父节点为自己。
Union:找到两个节点所在的树的root,并将其中一个节点的root改为另一个节点的root
Find:找到两个节点所在的树的root,并对比是否相同。(遍历本节点的父节点的,知道父节点的root与index相同)

算法效率

Union快,Find慢。union访问数组次数为N,find访问数组次数为N

三、Weighted quick-union

算法思想

在quick-union的基础上,改良union的操作。增加一个数组来记录所有节点所在树的深度。union两个节点的时候将树深度浅的节点树深度深的节点的root上,因此可以使形成的树深度较浅,从而减少find和union时遍历树的时间

算法效率

find和union都为lgN

四、Quick-Union+Path-Compression

算法思想

在quick-union的基础上,进行路径压缩,即在返回当前节点root函数中,添加id[i]=id[id[i]],将本节点的父节点改为本节点父节点的父节点,从而达到减小树深的作用。

五、Weighted quick-union+Path-Compression

算法思想

在Weighted quick-union的基础上,进行路径压缩。效率最高。

程序员 And 黑客

如果想做一个程序员,我可以现在开始,啃几本编程宝典,从早到晚的码程序。也许一年后,我可以找到一份还过的去的程序员工作,然后继续在实践中练习coding,几年后我会成为一个称职的程序员。

But 我并不想只做一个程序猿。

I want to be a hacker.

在我眼中,黑客是一种比程序员更酷的生物。成为一名黑客要比成为一名程序员难得多,黑客需要懂得不仅仅只是coding,还需要了解整个系统的层次细节。就Web来说,黑客要懂得是整个的Web九层结构,任何一层都有可能是系统漏洞的根源。然而一名黑客,应需要了解每一层,以及每一层之间的神秘联系,方能在任何一个Web系统里面游刃有余的找出问题,想出对策。当然,我指的是白帽子。

我知道很多黑客已经抵挡不住现实中的生活压力,转而做开发。这并不意味着堕落,开发是创造,是从无到有。重要的是,它也其乐无穷。再现实一点说,开发是当前的潮流,是能满足IT人高薪需求的职业。

但,我相信,开发市场终究会有饱和的时候,就像在有限的土地上盖房子一样。

而安全不一样,安全的问题会一直存在,因为永远没有绝对的安全。如果有一个系统还没有被黑掉,只能说明暂时还没有人发现他的漏洞。

coding 是hacking的好伙伴,它是能让hacker随心所欲的好帮手。

技术 Or 学历

当你不是一个天才的时候,你就需要在这两者之间做抉择了。幸运的是,IT是一个相对公平的行业,只要你有能力,肯努力,一定会有所成。

随着时间积累,慢慢沉淀下来的技术是一个技术人一生最大的财富。

说到这里,不难看出,我已经有了坚定的答案。

所以,我还需要时间,还需要沉淀。

所以,我不得不加入考研大潮。

但我无心追求名校,原因有三:

  • 一、只要坚持,无论在哪里,一样能做成想做的事。(主要归功于网络的发达,信息的丰富)

  • 二、我所在的地方并不糟糕,然而我可以努力的争取一个不错的导师。

  • 三、我可以把省下的时间和精力用在做更多我认为有意义的多的东西上。比如,作为一个菜鸟,应该踏踏实实的打好实践的技术基础,而不是仅仅能考得一手好试

所以我这样选择。

如果时间继续像现在这样,一点一点的证明我选择的正确性,我会就这样坚持下去。

Question

今天给Ubuntu server版安装一个软件,但是获取源的时候却提示我404错误,ping了这个源的IP是通的~这个时候我知道问题又要来了> _ <

问题大概是这个格式的:

问题描述

获取源的时候忘了截图。。。盗用一下人家的图哈~

于是,我求助度娘~在看了几个类似的情况之后,我很幸运的找到了一个小白非常容易看懂的解决办法!

Solution

直接暴力地附上传送门!

Ubuntu 14.04 命令行下更换更新源的方法

这位兄台是把更新源全部换成了163的,于是我也默默的都换了163,其他的源估计也是可以的。

OK! 问题解决!

安装软件

注意安装顺序!

1.安装Node.js Node.js下载传送门

2.安装Git Git下载传送门

用Hexo克隆主题

安装Hexo

打开Git

1
$ npm install -g hexo

部署Hexo

在我的电脑中建立一个名字叫[Hexo]的文件夹,然后再此文件夹中右键打开Git Bash。

1
2
$ hexo init
$ npm install

Hexo随后会自动在目标文件夹中建立网站所需的所有文件。
现在我们已经搭建起本地的hexo博客了,执行以下命令(在Hexo文件夹下),然后到浏览器输入localhost:4000看看。

1
2
$ hexo g
$ hexo s

复制主题

1
2
3
$ hexo clean
$ hexo g
$ hexo s

建立了Hexo文件之后复制wuchong的修改的主题

1
$ git clone https://github.com/wuchong/jacman.git themes/jacman

启用主题

修改Hexo目录下的config.yml配置文件中的theme属性,将其设置为jacman。(是Hexo根目录下的config.yml)

theme: jacman

更新主题

1
2
$ cd themes/jacman
$ git pull origin master

主题的配置

接下来通过来修改/themes/jacman/config.yml 来配置主题。

主题的作者已经提供了详细的配置说明,配置文件传送门

每次改完配置文件后,输入下面的命令重构本地网站

1
2
3
$ git pull origin master
$ hexo g #生成
$ hexo s #启动本地服务,进行文章预览

后两条命令可以用下面的组合命令代替

1
$ hexo d -g

将网站部署到Github上

配置根目录下的_config.yml文件

deploy:
type: git

repo: repository url.git

(例如,我的repo:https://github.com/Lazy-Pig/lazy-pig.github.io.git)

branch: master

执行deploy将网站push到Github上

之后执行下列指令即可完成部署,注意部署会覆盖掉你之前在版本库中存放的文件。

1
2
3
$ hexo clean
$ hexo generate
$ hexo deploy

现在访问 username.github.io 就可以看到你的博客啦!

参考博客及相关链接

1.如何搭建一个独立博客——简明Github Pages与Hexo教程

2.Hexo官网

3.Markdown的在线编辑网站StackEdit

搭建环境

  • Ubuntu Desktop 14.04 LTS

  • Apache2

  • MySQL

  • PHP

搭建步骤

ubuntu下Apache2的安装与启动

安装Apache2

1
$ sudo apt-get install apache2

重启Apache2

1
$ sudo /etc/init.d/apache2 restart

重启时系统提示错误信息:

1
2
apache2: Could not reliably determine the server's fully
qualified domain name,using 127.0.1.1 for ServerName

看来是Apache的配置有问题,需要做一点小修改。
Ubuntu下的Apache配置文件在: /etc/apache2/apache2.conf

解决方法:

1
$ sudo gedit /etc/apache2/apache2.conf

在文件后面加上:

1
2
#Server Name
ServerName 127.0.0.1

再执行重启Apache命令:

1
$ sudo /etc/init.d/apache2 restart

查看port80是否已经监听:

1
$ netstat -tulnp | grep '80'

显示:

1
tcp6 0 0 :::80 :::* LISTEN 4916/apache2

证明apache2已经正常启动,port 80已经处于监听状态

###打开浏览器进行测试

此时,在Web服务器本机浏览器输入127.0.0.1已经可以看到apache的默认网页。
另外,如果你的Web服务器架设在内网,链接上内网的所有设备(智能机、电脑、pad等),它们的浏览器输入你的服务器IP也可以正常访问你的网站。
我的网站就架设在家里的局域网内部,IP:192.168.1.X

网站的对外开放

想要这个局域网内的网站外部也能访问很简单,只要进入家里路由器的设置界面,做一个“端口映射”设置就好。

  • 如果你想问“端口映射”是什么鬼的话,我附上度娘的解释:什么是端口映射? 还是很通俗易懂的吧!

  • 我的路由器是FAST路由器,设置端口映射的方法在这里:FAST路由器设置端口映射

  • 这时,外部网络浏览器输入宽带拨号获得的IP(在路由器设置界面的首页,运行状态中,WAN口状态里可以看到PPPoE拨号得到的IP)就可以访问你的网站啦!

  • 另外如果你想要用一个域名而不是IP地址访问你的网站的话,需要注册域名,no-ip网站也可以申请免费的动态域名,安装动态域名解析软件(貌似很多人用的花生壳)。(我没有搞域名,对我来说无所谓,所以这里没有详细说明啦!)

###给网站做实体内容

喜欢前端设计的小伙伴可以 自行设计喜欢的页面。鉴于笔者对Web前端开发不感兴趣,当然,也没有这个天赋,于是用了人家做的模板,黑色泡沫背景,很漂亮。

源代码下载地址:一个nice的纯黑泡沫背景首页

这里只是一个版面,有兴趣的话可以自己添加网站具体内容(利用前端设计技术,Javascripts,HTML,CSS,DOM等),随便你做个bi格很高的个人blog,或是搞一个经济适用的电商网站,很酷炫哈!!

遗留问题

如果不把网站架设在局域网内部,也就是Ubuntu直接ADSL拨号上网,此时外部用我拨号获得的IP居然无法访问我的网站?!有知道原因的大神麻烦给我留言!