码迷,mamicode.com
首页 > 其他好文 > 详细

Hive的基本操作

时间:2016-05-27 12:35:37      阅读:228      评论:0      收藏:0      [点我收藏+]

标签:

Hive数据类型

基础数据类型:

TINYINT,SMALLINT,INT,BIGINT,BOOLEAN,FLOAT,DOUBLE,STRING,BINARY,TIMESTAMP,DECIMAL,CHAR,VARCHAR,DATE。

复杂数据类型:

包括ARRAY(数组),MAP(字典),STRUCT(结构体),UNION(联合体),这些复杂类型是由基础类型组成的。

 

ARRAY:ARRAY类型是由一系列相同数据类型元素组成的,这些元素可以通过下标来访问。比如有一个ARRAY类型的变量fruits,它是由[‘apple’,’orange’,’mango’]组成,那么可以通过fruits[1]来访问orange;

 

MAP:MAP包含key->value键值对,可以通过key来访问元素。比如”userlist”是一个map类型(其中username是key,password是value),那么我们可以通过userlist[‘username’]来得到这个用户对应的password; 

 

STRUCT:STRUCT可以包含不同数据类型的元素。这些元素可以通过点的方式来得到,比如user是一个STRUCT类型,那么可以通过user.address得到这个用户的地址。 

 

UNION: UNIONTYPE

 

示例:

Array演示:

create table test

(

    name string,

    value array<string>

) row format delimited

fields terminated by ‘|‘

collection items terminated by ‘,‘;

 

 

文件:array.txt

bigdata|spark,hadoop

warehouse|hive,impala,hbase

 

load data local inpath ‘/hadoop/ylxsource/myspark/testdata/array.txt‘ into table test;

select name, value[0] from test;

 

Map演示:

create table test_map(name string, value map<string,int>)

row format delimited

fields terminated by ‘|‘

collection items terminated by ‘,‘

map keys terminated by‘:‘;

 

文件:map.txt

bigdata|Spark:95,Hadoop:85

warehouse|Hive:80,Impala:70,HBase:90

 

load data local inpath ‘/hadoop/ylxsource/myspark/testdata/map.txt‘ into table test_map;

 

select name,value["Spark"] from test_map;

 

 

Struct演示:

create table test_struct(id int,name struct<bigdata:string,value:int>)

row format delimited

fields terminated by ‘|‘

collection items terminated by ‘,‘;

 

文件:struct.txt

1|Spark,100

2|Hive,90

 

load data local inpath ‘/hadoop/ylxsource/myspark/testdata/struct.txt‘ into table test_struct;

 

select id, name.bigdata from test_struct;

 

 

Union演示:

create table test_union(id int,name map<string,array<string>>)

row format delimited fields terminated by ‘|‘

collection items terminatedby ‘,‘

map keys terminated by‘:‘;

 

1|Spark:95,10,85

2|Hadoop:85,20,65

 

 

 

 

Hive文件格式

1.      Textfile

Hive默认格式,数据不做压缩,磁盘开销大,数据解析开销大。

可结合Gzip、Bzip2、Snappy等使用(系统自动检查,执行查询时自动解压),但使用这种方式,hive不会对数据进行切分,从而无法对数据进行并行操作。

 

2.      Sequencefile

Hadoop API提供的一种二进制文件支持,它将数据(key,value)的形式序列化到文件中,其具有使用方便、可分割、可压缩的特点。SequenceFile支持三种压缩选择:NONE,RECORD,BLOCK。RECORD压缩率低,一般建议使用BLOCK压缩。

 

3.      Rcfile

RCFile是Hive推出的一种专门面向列的数据格式。 它遵循“先按列划分,再垂直划分”的设计理念。 

当查询过程中,针对它并不关心的列时,它会在IO上跳过这些列。需要说明的是,RCFile在map阶段从远端拷贝仍然是拷贝整个数据块,并且拷贝到本地目录后,RCFile并不是真正直接跳过不需要的列,并跳到需要读取的列,而是通过扫描每一个row group的头部定义来实现的。 

但是在整个HDFS Block 级别的头部并没有定义每个列从哪个row group起始到哪个row group结束。所以在读取所有列的情况下,RCFile的性能反而没有SequenceFile高。

 

4. ORC

Hive命令行常见操作

l  查看所有数据库

show databases;

 

l  使用数据库

use hive;

 

l  查看数据库信息

desc database hive;

 

l  查看当前使用的数据库

set hive.cli.print.current.db=true;

 

l  显示列头

set hive.cli.print.header=true;

 

l  创建数据库

create database hive;

 

l  删除数据库

drop database hello;

如果数据库不为空,则使用cascade关键字:

drop database IF EXISTS hello cascade;

 

l  查看数据库的表

show tables;

show tables like ‘*test*‘;

show tables in myspark;

 

l  获取表信息

show create table test;

desc formatted test;

desc extended test;

 

 

l  创建表

 

CREATE [TEMPORARY] [EXTERNAL] TABLE [IF NOT EXISTS][db_name.]table_name    -- (Note: TEMPORARY available inHive 0.14.0 and later)

  [(col_namedata_type [COMMENT col_comment], ...)]

  [COMMENTtable_comment]

  [PARTITIONED BY (col_name data_type [COMMENT col_comment], ...)]

  [CLUSTEREDBY (col_name, col_name, ...) [SORTED BY (col_name [ASC|DESC], ...)] INTOnum_buckets BUCKETS]

  [SKEWEDBY (col_name, col_name,...)                 -- (Note: Available in Hive 0.10.0 and later)]

     ON((col_value, col_value, ...), (col_value, col_value, ...), ...)

     [STOREDAS DIRECTORIES]

  [

   [ROWFORMAT row_format] 

   [STOREDAS file_format]

     |STORED BY ‘storage.handler.class.name‘ [WITH SERDEPROPERTIES (...)]  -- (Note: Available inHive 0.6.0 and later)

  ]

  [LOCATIONhdfs_path]

  [TBLPROPERTIES(property_name=property_value, ...)]   -- (Note: Available inHive 0.6.0 and later)

  [ASselect_statement];   -- (Note: Available in Hive 0.5.0 and later; not supported for external tables)

 

CREATE[TEMPORARY] [EXTERNAL] TABLE [IF NOT EXISTS] [db_name.]table_name

  LIKEexisting_table_or_view_name

  [LOCATION hdfs_path];

 

 

data_type

  :primitive_type

  |array_type

  |map_type

  |struct_type

  |union_type  -- (Note: Available in Hive 0.7.0 and later)

 

primitive_type

  :TINYINT

  |SMALLINT

  |INT

  |BIGINT

  |BOOLEAN

  |FLOAT

  |DOUBLE

  |STRING

  |BINARY      -- (Note: Available inHive 0.8.0 and later)

  |TIMESTAMP   -- (Note: Available in Hive 0.8.0 and later)

  |DECIMAL     -- (Note: Available inHive 0.11.0 and later)

  |DECIMAL(precision, scale)  -- (Note: Available inHive 0.13.0 and later)

  |DATE       -- (Note: Available in Hive 0.12.0 and later)

  |VARCHAR     -- (Note: Available inHive 0.12.0 and later)

  |CHAR        -- (Note:Available in Hive 0.13.0 and later)

 

array_type

  :ARRAY < data_type >

 

map_type

  :MAP < primitive_type, data_type >

 

struct_type

  :STRUCT < col_name : data_type [COMMENT col_comment], ...>

 

union_type

   :UNIONTYPE < data_type, data_type, ... >  -- (Note: Available inHive 0.7.0 and later)

 

row_format

  :DELIMITED [FIELDS TERMINATED BY char [ESCAPED BY char]] [COLLECTION ITEMS TERMINATED BY char]

        [MAPKEYS TERMINATED BY char] [LINES TERMINATED BY char]

        [NULLDEFINED AS char]   -- (Note: Available in Hive 0.13 and later)

  |SERDE serde_name [WITH SERDEPROPERTIES (property_name=property_value,property_name=property_value, ...)]

 

file_format:

  :SEQUENCEFILE

  |TEXTFILE   -- (Default, depending onhive.default.fileformat configuration)

  |RCFILE      -- (Note: Available inHive 0.6.0 and later)

  |ORC         -- (Note: Available inHive 0.11.0 and later)

  |PARQUET     -- (Note: Available inHive 0.13.0 and later)

  |AVRO        -- (Note:Available in Hive 0.14.0 and later)

  |INPUTFORMAT input_format_classname OUTPUTFORMAToutput_format_classname

 

·        用户可以用 IF NOT EXIST 选项来忽略这个异常。

·        EXTERNAL关键字可以让用户创建一个外部表,在建表的同时指定一个指向实际数据的路径(LOCATION),Hive 创建内部表时,会将数据移动到数据仓库指向的路径;若创建外部表,仅记录数据所在的路径,不对数据的位置做任何改变。在删除表的时候,内部表的元数据和数据会被一起删除,而外部表只删除元数据,不删除数据。

·        LIKE允许用户复制现有的表结构,但是不复制数据。

·        用户在建表的时候可以自定义 SerDe 或者使用自带的 SerDe。如果没有指定 ROW FORMAT 或者 ROW FORMAT DELIMITED,将会使用自带的 SerDe。在建表的时候,用户还需要为表指定列,用户在指定表的列的同时也会指定自定义的 SerDe,Hive 通过 SerDe 确定表的具体的列的数据。

·        如果文件数据是纯文本,可以使用 STORED AS TEXTFILE。如果数据需要压缩,使用 STORED AS SEQUENCE 。

·        有分区的表可以在创建的时候使用 PARTITIONED BY 语句。一个表可以拥有一个或者多个分区,每一个分区单独存在一个目录下。而且,表和分区都可以对某个列进行 CLUSTERED BY 操作,将若干个列放入一个桶(bucket)中。也可以利用SORT BY 对数据进行排序。这样可以为特定应用提高性能。

·        表名和列名不区分大小写,SerDe 和属性名区分大小写。表和列的注释是字符串

SerDe是Serialize/Deserilize的简称,目的是用于序列化和反序列化。

 

 

 

 

 

 

l  加载数据

LOAD DATA [LOCAL] INPATH ‘filepath‘ [OVERWRITE]INTO TABLE tablename [PARTITION (partcol1=val1, partcol2=val2 ...)]

 

l  创建桶

 

 

create table par_table

(

   id int,

   type string,

   value string

)

partitioned by(data_date string, pos string)

clustered by(id) sorted by(value) into 32 buckets

row format delimited

fields terminated by ‘|‘

lines terminated by ‘\n‘

stored as sequencefile;

 

#强制分桶

set hive.enforce.bucketing = true;

 

对于每一个表(table)或者分区, Hive可以进一步组织成桶,也就是说桶是更为细粒度的数据范围划分。Hive也是针对某一列进行桶的组织。Hive采用对列值哈希,然后除以桶的个数求余的方式决定该条记录存放在哪个桶当中。

 

把表(或者分区)组织成桶(Bucket)有两个理由:

1.      获得更高的查询处理效率。桶为表加上了额外的结构,Hive 在处理有些查询时能利用这个结构。具体而言,连接两个在(包含连接列的)相同列上划分了桶的表,可以使用 Map 端连接(Map-side join)高效的实现。比如JOIN操作。对于JOIN操作两个表有一个相同的列,如果对这两个表都进行了桶操作。那么将保存相同列值的桶进行JOIN操作就可以,可以大大较少JOIN的数据量。

 

2.      使取样(sampling)更高效。在处理大规模数据集时,在开发和修改查询的阶段,如果能在数据集的一小部分数据上试运行查询,会带来很多方便。

 

对于Map端连接的情况,两个表以相同方式划分桶。处理左边表内某个桶的 mapper知道右边表内相匹配的行在对应的桶内。因此,mapper只需要获取那个桶 (这只是右边表内存储数据的一小部分)即可进行连接。这一优化方法并不一定要求两个表必须桶的个数相同,两个表的桶个数是倍数关系也可以。

 

 

l  Hive Client数据操作

hive -help

hive --help

  

hive -e "use hive;select * from words"

hive -S -e "use hive;select * from words"

 

hive -f hivecli.sql

hive -S -f hivecli.sql

hive (hive)> source /hadoop/ylxsource/myspark/testdata/hivecli.sql;

 

hive -S -f hivecli.sql > result.txt

 

 

hive (hive)> set val=bigdata;

select * from test where name = ‘${hiveconf:val}‘;

 

 

通过sh脚本执行:

test.sh

#!/bin/bash

hive-S -e "

use hive;

select * from test;"

&n

bash test.sh

 

Hive数据加载(内部表,外部表,分区表)

内部表数据加载

1.      创建表时加载数据

create table test_bak as select * from test;

 

2.      创建表时指定数据路径

create table if not exists test2(

   id int,

   name string,

   score int

)

row format delimited fields terminated by‘|‘

lines terminated by ‘\n‘

stored as textfile

location ‘file:/hadoop/ylxsource/myspark/testdata/location‘;

表删除时,数据目录也会删除

 

 

3.      本地数据加载

create table if not exists test2(

   id int,

   name string,

   score int

)

row format delimited fields terminated by ‘|‘

lines terminated by ‘\n‘

stored as textfile;

 

load data local inpath ‘/hadoop/ylxsource/myspark/testdata/external.txt‘ into table test2;

 

4.      HDFS数据加载

create table if not exists test2(

   id int,

   name string,

   score int

)

row format delimited fields terminated by ‘|‘

lines terminated by ‘\n‘

stored as textfile;

 

load data inpath ‘hdfs://SZB-L0007971:28888/testdata/external.txt‘ into table test2;

load data inpath ‘hdfs://SZB-L0007971:28888/testdata/external.txt‘ overwrite into table test2;

 

5.      通过hdfs命令将数据拷贝到指定的表的目录下面

 

hdfs dfs -put external.txt hdfs://SZB-L0007971:28888/user/hive/myspark/hive.db/test2/external2.txt

此种方式和内部表相同。

 

6.      由查询语句加载数据

insert overwrite table test2 select * from test3;

insert into table test2 select * from test3;

 

外部数据加载

1.      创建外部表时指定分区

create external table if not exists test2(

    id int,

    name string,

    score int

)

row format delimited fields terminated by ‘|‘

lines terminated by ‘\n‘

stored as textfile

location ‘hdfs://SZB-L0007971:28888/testdata/external/‘;

 

load data inpath ‘hdfs://SZB-L0007971:28888/testdata/external‘ into table test2 partition(dt=20160115);

 

show partitions  test2;

 

2.      查询时插入数据

drop table IF EXISTS xxx ;

create table IF NOT EXISTS xxx 

(

   polno                   stringcomment ‘保单号‘,

   plan_code               stringcomment ‘险种代码‘,

   deptno                  stringcomment ‘部门‘,

   p_number               decimal(18) comment ‘人数‘

)

partitioned by (flag string, data_date string)

row format delimited fields terminated by ‘\001‘ ;

 

insert overwrite table xxx partition(flag, data_date)

select polno,

      plan_code,

      deptno,

      count(distinct insno) as p_number,

      ‘A1‘ as flag,

      ‘20160101‘ as data_date  

  from xxx_tmp

 where to_date(undwrt_date) >= ‘2015-12-01‘ and to_date(undwrt_date)< ‘2016-01-01‘

  group by polno,

        plan_code,

        deptno

 

3.      加载数据到分区表

 

load data local inpath ‘/home/hadoop/data.txt‘ overwrite into table test partition (ds=‘2013-08-16‘);

 

load data inpath ‘hdfs://SZB-L0007971:28888/hadoop‘ overwrite into table test partition (ds=‘2013-08-16‘);

 

Hive数据加载注意问题

分隔符问题:

分隔符默认只有单个字符。如果有多个字符,默认取第一个字符作为分隔符。 

数据类型对应问题: 
load数据数据,字段类型不能互相转化时,查询结果返回NULL。而实际的数据仍然存在。 

select查询插入,字段类型不能互相转化时,插入数据为NULL。而实际的数据也为NULL。 

其他: 

select查询插入数据,字段值顺序要与表中字段顺序一致,名称可不一致。 
Hive在数据加载时不做检查,查询时检查。 

外部分区表需要添加分区才能看到数据。

 

Hive数据导出

使用HDFS命令导出

desc formatted test;获取location路径

使用get方式获取:

hdfs dfs -get hdfs://SZB-L0007971:28888/user/hive/myspark/hive.db/test/array.txtresult.txt

使用text方式获取:

hdfs dfs -text hdfs://SZB-L0007971:28888/user/hive/myspark/hive.db/test/array.txt > ok

通过INSERT … DIRECTORY方式

insert overwrite local directory‘/hadoop/ylxsource/myspark/testdata/test2‘

row format delimited fields terminated by‘\t‘

select * from test2;

 

insert overwrite directory‘hdfs://SZB-L0007971:28888/testdata/test2‘

row format delimited fields terminated by ‘\t‘

select * from test2;

 

Shell命令方式

hive -S -e "use hive; select * fromtest2;" > result.txt

 

第三方工具Sqoop

sqoop import/export

 

Hive动态分区

动态分区指不需要为不同的分区添加不同的插入语句,分区不确定,需要从数据中自动获取。

 

相关参数配置

#开启动态分区

set hive.exec.dynamic.partition=true;

 

#如果模式是strict,则必须有一个静态分区,且放在最前面

set hive.exec.dynamic.partition.mode=nonstrict;

 

#每个节点生成动态分区最大个数

set hive.exec.max.dynamic.partitions.pernode=10000;

 

#生成动态分区最大个数,如果自动分区数大于这个参数,将会报错

set hive.exec.max.dynamic.partitions=100000;

 

#一个任务最多可以创建的文件数目

set hive.exec.max.created.files=150000;

 

#限定一次最多打开的文件数

set dfs.datanode.max.xcievers=8192;

 

create table dynamic_table(

   id int

)

partitioned by (name string,score string)

row format delimited

fields terminated by ‘\t‘

lines terminated by ‘\n‘

stored as textfile;

 

insert overwrite table dynamic_table partition(name,score)

select id,name,score from test2;

 

show partitions dynamic_table;

 

如果参数set hive.exec.dynamic.partition.mode=strict,则必须指定一个静态分区字段:

set hive.exec.dynamic.partition.mode=strict;

再执行上面的语句:

insert overwrite table dynamic_tablepartition(name,score)

select id,name,score from test2;

会报如下错误:

FAILED: SemanticException [Error 10096]: Dynamic partition strict mode requires at least one staticpartition column. To turn this off sethive.exec.dynamic.partition.mode=nonstrict

 

需要如下方式指定一个静态分区字段:

insert overwrite table dynamic_table partition(name=‘Bigdata‘,score)

select id,score from test2;

 

Hive表字段属性修改

修改表名

alter table test2 rename to new_test2;

 

添加字段

alter table new_test2 add columns(count1 intcomment ‘count1‘, count2 int comment ‘count2‘);

 

修改字段

create table test_change (a int, b int, c int);

 

alter table test_change change a a1 int;

 

alter table test_change change a1 a2 string after b;

 

alter table test_change change c c1 int first;

   

修改表属性

alter table new_test2 set tblproperties(‘comment‘= ‘this is the table new_test2‘);

 

外部表和内部表互转

内部表转外部表:

alter table new_test2 set TBLPROPERTIES (‘EXTERNAL‘ = ‘TRUE‘);

 

外部表转内部表:

alter table new_test2 set TBLPROPERTIES(‘EXTERNAL‘=‘FALSE‘);

  

 

Hive常见内置函数及其使用

查看当前可用函数等信息

show functions;

 

显示函数的描述信息

DESC FUNCTION concat;

 

显示函数的扩展描述信息 

DESC FUNCTION EXTENDED concat;

 

简单函数

函数的计算粒度为单条记录。

关系运算

数学运算

逻辑运算

数值计算

类型转换

日期函数

条件函数

字符串函数

统计函数

 

聚合函数

函数处理的数据粒度为多条记录

sum()—求和

count()—求数据量

avg()—求平均直

distinct—求不同值数

min—求最小值

max—求最人值

 

集合函数

复合类型构建

复杂类型访问

复杂类型长度

 

 

 

 

特殊函数

1) 窗口函数

应用场景:

用于分区排序

动态Group By

Top N

累计计算

层次查询

 

函数:

lead

lag

last_day

last_value

 

2) 分析函数

RANK

ROW_NUMBER

DENSE_RANK

CUME_DIST

PERCENT_RANK

NTILE

 

 

3) 混合函数

java_method(class,method [,arg1 [,arg2])

reflect(class,method [,arg1 [,arg2..]])

hash(a1 [,a2...])

 

4) UDTF

lateralView: LATERAL VIEW udtf(expression)tableAlias AS columnAlias (‘,‘ columnAlias)* 

fromClause: FROM baseTable (lateralView)* 

 

例如:

select t.word_list,count(1) from

(

select word_list from words w lateral viewexplode(split(w.word,‘ ‘)) ww as word_list

) t group by t.word_list;

 

lateral view用于和split,explodeUDTF一起使用,它能够将一行数据拆成多行数据,在此基础上可以对拆分后的数据进行聚合。lateral view首先为原始表的每行调用UDTFUTDF会把一行拆分成一或者多行,lateral view再把结果组合,产生一个支持别名表的虚拟表。

 

常用函数

1. 获取当前时间

select from_unixtime(unix_timestamp(),‘yyyy-MM-dd HH:mm:ss‘);

 

2. 截取时间

select regexp_replace(substr(‘2016-01-18‘,0,7),‘-‘,‘‘);

select to_date(‘2016-01-18 12:24:12‘);

 

3. 修改日期格式

select from_unixtime(to_unix_timestamp(‘2014/6/18‘,‘yyyy/MM/dd‘),‘yyyy-MM-ddHH:mm:ss‘);

 

4. 数据类型强制转换

select cast(‘1314‘ as int);

 

5. 判断语句

select ossdkversion,

           osversion,

           mobilemodel,

           carrier,

           networkconntype,

           org_name,

           e_version,

           channel,

           data_date,

           ‘登录‘ as module,                  

           case tmp.lable

           when ‘用户登陆‘ then ‘用户名+密码登录次数

           when ‘手势登录成功‘ then ‘手势密码登录成功次数

            when ‘手势设置失败‘then ‘手势密码设置失败次数

           when ‘手势登录失败‘ then ‘手势密码登录失败次数

           when ‘忘记密码‘ then ‘忘记密码

           end flag

    from e_temp tmp

where tmp.lable in (‘用户登陆‘,‘手势登录成功‘,‘手势设置失败‘,‘手势登录失败‘,‘忘记密码‘)

      

select if(‘o‘=‘o‘,‘o_o‘,‘^o^‘);

      

       6. 解析Json格式内容

       selectget_json_object(‘{"name":"spark","age":"10"}‘,‘$.name‘);

   

       7. 解析URL地址

       SELECT parse_url(‘http://facebook.com/path/p1.php?query=1‘, ‘HOST‘);

       SELECT parse_url(‘http://facebook.com/path/p1.php?query=1‘, ‘QUERY‘)

       SELECT parse_url(‘http://facebook.com/path/p1.php?query=1‘,‘QUERY‘, ‘query‘);

      

       8. 字符串连接函数

       select concat(‘S‘,‘p‘,‘a‘,‘r‘,‘k‘);

select concat_ws(‘-‘,‘S‘,‘p‘,‘a‘,‘r‘,‘k‘);

 

9. 输出数组集合

列出该字段所有不重复的值,相当于去重

collect_set(polno)  //返回的是数组

 

列出该字段所有的值,列出来不去重

collect_list(polno)   //返回的是数组

 

select collect_set(polno) from myspark.pol_ben_s;

select collect_list(polno) from myspark.pol_ben_s;

 

10. 窗口函数:

first_value

select polno,certno,first_value(certno) over (partition bypolno order by certno desc ) from myspark.pol_ben_s;

rank

select polno,certno, rank() over (partition by polno order bycertno) as rank from myspark.pol_ben_s;

row_number

select polno,certno, row_number() over (partition by polnoorder by certno) as rank from myspark.pol_ben_s;

 

row_number()是没有重复值的排序(即使两天记录相等也是不重复的),可以利用它来实现分页

dense_rank()是连续排序,两个第二名仍然跟着第三名

rank()是跳跃拍学,两个第二名下来就是第四名

 

11. 使用正则表达式

select regexp_extract(‘x=a3&x=18abc&x=2&y=3&x=4‘,‘x=([0-9]+)([a-z]+)‘,0);

select regexp_extract(‘x=a3&x=18abc&x=2&y=3&x=4‘,‘x=([0-9]+)([a-z]+)‘,1);

select regexp_extract(‘x=a3&x=18abc&x=2&y=3&x=4‘,‘x=([0-9]+)([a-z]+)‘,2);

 

 

Hive的基本操作

标签:

原文地址:http://blog.csdn.net/jiangshouzhuang/article/details/51488231

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!