02 – Hadoop3.1.4的单机安装

1、准备工作

(1)主机名映射S

# vi /etc/hosts vi /etc/hosts

#127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
#::1         localhost localhost.localdomain localhost6 localhost6.localdomain6

192.168.186.100 hadoop100

注意:宿主的WIndows系统也要一样配置hadoop100并且映射到虚拟机的ip上

(2)SSH免密登录

Centos7默认安装了sshd服务,可使用ssh协议远程开启对其他主机shell的服务。

如果没有可以使用yum安装

sudo yum -y install openssl-devel

由于Hadoop集群的机器之间ssh通信默认需要输入密码,在集群运行时我们不可能为每一次通信都手动输入密码,因此需要配置机器之间的ssh的免密登录。

即使是单机伪分布式的Hadoop环境也不例外,一样需要配置本地对本地ssh连接的免密。

hadoop@hadoop100 ~]$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/hadoop/.ssh/id_rsa): 
Created directory '/home/hadoop/.ssh'.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/hadoop/.ssh/id_rsa.
Your public key has been saved in /home/hadoop/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:yQYChs4eniVLeICaI2bCB9HopbXUBE9v0lpLjBACUnM hadoop@hadoop100
The key's randomart image is:
+---[RSA 2048]----+
|==O+Eo           |
|=+.O+.=          |
|Bo* o+.B         |
|B%.+ .*o..       |
|OoB  . .S        |
| =     .         |
|                 |
|                 |
|                 |
+----[SHA256]-----+
[hadoop@hadoop100 ~]$ cd ~/.ssh/
[hadoop@hadoop100 .ssh]$ cat id_rsa.pub >> authorized_keys
[hadoop@hadoop100 .ssh]$ chmod 600 authorized_keys 
[hadoop@hadoop100 .ssh]$ ssh hadoop@hadoop100
The authenticity of host 'hadoop100 (192.168.186.100)' can't be established.
ECDSA key fingerprint is SHA256:aGLhdt3bIuqtPgrFWnhgrfTKUbDh4CWVTfIgr5E5oV0.
ECDSA key fingerprint is MD5:b8:bd:b3:65:fe:77:2c:06:2d:ec:58:3a:97:51:dd:ca.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'hadoop100,192.168.186.100' (ECDSA) to the list of known hosts.
Last login: Sat Jan  9 10:16:53 2021 from 192.168.186.1
[hadoop@hadoop100 ~]$ exit
登出
Connection to hadoop100 closed.
[hadoop@hadoop100 .ssh]$

大致流程:

  1. ssh-keygen命令生成RSA加密的密钥对(公钥和私钥)
  2. 将公钥添加到~/.ssh目录下的authorized_keys文件中
    1. 如机器A需要SSH连接到机器B,就需要将机器A生成的公钥发送到机器B的authorized_keys文件中进行认证
    2. 如果需要和自己通信,就把自己生成的公钥放在自己的authorized_keys文件中即可
  3. 使用ssh命令连接本地终端,如果不需要输入密码则本地的免密配置成功

(3)配置时间同步

集群中的通信和文件传输一般是以系统时间作为约定条件的。所以当集群中机器之间系统如果不一致可能导致各种问题发生,比如访问时间过长,甚至失败。所以配置机器之间的时间同步非常重要。

另外,单机伪分布式可以无需配置时间同步服务器,只需要定时使用nptdate命令校准即可。

以下操作必须切换成root用户

安装ntp

[hadoop@hadoop100 .ssh]$ su root
密码:
[root@hadoop100 .ssh]# rpm -qa | grep ntp
[root@hadoop100 .ssh]# yum install -y ntp
  1. rpm -qa查询是否已经安装了ntp
  2. 如果没有则安装ntp

修改/etc/sysconfig/ntpd,增加如下内容(让硬件时间和系统时间一起同步)

SYNC_HWCLOCK=yes

(4)定时校正时间

使用ntpdate命令手动的校正时间。

[root@hadoop100 .ssh]#  ntpdate cn.pool.ntp.org
 9 Jan 11:49:13 ntpdate[11274]: adjust time server 185.209.85.222 offset -0.016417 sec
[root@hadoop100 .ssh]# date
2021年 01月 09日 星期六 11:49:32 CST

观察时间是否与你所在的时区一致,如果不一致请查看【常见问题】寻找解决办法。

接下来使用crontab定时任务来每10分钟校准一次

# 编辑新的定时任务
[root@hadoop100 .ssh]# crontab -e
*/10 * * * * /usr/sbin/ntpdate cn.pool.ntp.org

# 查看root账户下所有定时任务
[root@hadoop100 .ssh]# crontab -l
*/10 * * * * /usr/sbin/ntpdate cn.pool.ntp.org

为了测试,先修改时间为错误时间

[root@hadoop100 .ssh]# date -s '2020-1-1 01:01'
2020年 01月 01日 星期三 01:01:00 CST 

10分钟后再次查看

[root@hadoop100 .ssh]# date
2021年 01月 09日 星期六 14:12:12 CST

说明自动校准已经设置

默认crontab定时任务就是开机自动启动的

[root@hadoop100 .ssh]# systemctl list-unit-files | grep crond
crond.service                                 enabled 

(5)统一目录结构

接下来切换用户为hadoop账户

[hadoop@hadoop100 opt]$ sudo mkdir /opt/download
[hadoop@hadoop100 opt]$ sudo mkdir /opt/data
[hadoop@hadoop100 opt]$ sudo mkdir /opt/bin
[hadoop@hadoop100 opt]$ sudo mkdir /opt/tmp
[hadoop@hadoop100 opt]$ sudo mkdir /opt/pkg

# 更改opt下目录的用户及其所在用户组为hadoop
[hadoop@hadoop100 opt]$ sudo chown hadoop:hadoop /opt/*
[hadoop@hadoop100 opt]$ ls
bin  data  download  pkg  tmp
[hadoop@hadoop100 opt]$ ll
总用量 0
drwxr-xr-x. 2 hadoop hadoop 6 1月   9 14:29 bin
drwxr-xr-x. 2 hadoop hadoop 6 1月   9 14:29 data
drwxr-xr-x. 2 hadoop hadoop 6 1月   9 14:29 download
drwxr-xr-x. 2 hadoop hadoop 6 1月   9 14:37 pkg
drwxr-xr-x. 2 hadoop hadoop 6 1月   9 14:36 tmp

目录规划如下:

/opt/ 
    ├── bin         # shell脚本
    ├── data        # 程序需要使用的数据
    ├── download    # 下载的软件安装包
    ├── pkg         # 解压方式安装的软件
    └── tmp         # 存放程序生成的临时文件

2、安装jdk

检查是否已经安装过JDK

[hadoop@hadoop100 download]$ rpm -qa | grep java
# 或者
[hadoop@hadoop100 download]$ yum list installed | grep java

如果没有jdk或者jdk版本低于1.8,则重新安装jdk1.8

下载安装包到/opt/download/然后解压到/opt/pkg

[hadoop@hadoop100 download]$ tar -zxvf jdk-8u261-linux-x64.tar.gz 
[hadoop@hadoop100 download]$ mv jdk1.8.0_261 /opt/pkg/java

配置java环境变量

确认jdk的解压路径

[hadoop@hadoop100 java]$ pwd
/opt/pkg/java

编辑/etc/profile.d/env.sh配置文件(没有则创建)

[hadoop@hadoop100 java]$ sudo vim /etc/profile.d/env.sh

在后面添加新的环境变量配置

# JAVA_HOME
export JAVA_HOME=/opt/pkg/java
export PATH=$JAVA_HOME/bin:$PATH

使新的环境变量立刻生效

[hadoop@hadoop100 java]$ source /etc/profile.d/env.sh

验证环境变量

[hadoop@hadoop100 java]$ java -version
[hadoop@hadoop100 java]$ java
[hadoop@hadoop100 java]$ javac

3、安装Hadoop

  1. 解压

    [hadoop@hadoop100 hadoop]$ tar -zxvf hadoop.tar.gz -C /opt/pkg/
  2. 编辑/etc/profile.d/env.sh配置文件,添加环境变量

    # JAVA_HOME
    export JAVA_HOME=/opt/pkg/java
    export PATH=$JAVA_HOME/bin:$PATH
    # HADOOP_HOME
    export HADOOP_HOME=/opt/pkg/hadoop
    export PATH=$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$PATH
  3. 使新的环境变量立刻生效

    [hadoop@hadoop100 opt]$ source /etc/profile.d/env.sh
  4. 验证

    [hadoop@hadoop100 opt]$ hadoop version
  5. 修改相关命令执行环境

    hadoop/etc/hadoop/hadoop-env.sh - hadoop命令执行环境

      # The java implementation to use.
      export JAVA_HOME=/opt/pkg/java
    • 修改JAVA_HOME为真实JDK路径即可
  6. hadoop/etc/hadoop/yarn.env.sh - yarn命令执行环境

      # export JAVA_HOME=/home/y/libexec/jdk1.6.0/
      export JAVA_HOME=/opt/pkg/java
    • 添加JAVA_HOME为真实JDK路径即可
  7. hadoop/etc/hadoop/mapred.env.sh - map reducer命令执行环境

      # export JAVA_HOME=/home/y/libexec/jdk1.6.0/
      export JAVA_HOME=/opt/pkg/java
    • 添加JAVA_HOME为真实JDK路径即可
  8. 修改配置-实现伪分布式环境,来到 hadoop/etc/hadoop/,修改以下配置文件

    hadoop/etc/hadoop/core-site.xml - 核心配置文件

      <configuration>
          <!-- 指定NameNode的地址和端口. -->
          <property>
              <name>fs.defaultFS</name>
              <value>hdfs://hadoop100:8020</value>
          </property>
          <!-- 指定HDFS系统运行时产生的文件的存储目录. -->
          <property>
              <name>hadoop.tmp.dir</name>
              <value>/opt/pkg/hadoop/data/tmp</value>
          </property>
          <!--  缓冲区大小,实际工作中根据服务器性能动态调整;默认值4096 -->
       <property>
           <name>io.file.buffer.size</name>
           <value>4096</value>
       </property>
       <!--  开启hdfs的垃圾桶机制,删除掉的数据可以从垃圾桶中回收,单位分钟;默认值0 -->
       <property>
           <name>fs.trash.interval</name>
           <value>10080</value>
       </property>
    </configuration>
    1. 主机名修改成本机的主机名

    2. hadoop.tmp.dir十分重要,保存这个hadoop集群中namenode和datanode的所有数据

  9. hadoop/etc/hadoop/hdfs-site.xml - HDFS相关配置

      <configuration>
          <!-- 设置HDFS中的数据副本数. -->
          <property>
             <name>dfs.replication</name>
             <value>1</value>
          </property>
           <!-- 设置Hadoop的Secondary NameNode的主机配置 -->
          <property>
             <name>dfs.namenode.secondary.http-address</name>
             <value>hadoop100:9868</value>
          </property>
          <property>
              <name>dfs.namenode.http-address</name>
              <value>hadoop100:9870</value>
          </property>
          <!-- 是否检查操作HDFS文件系统的用户权限. -->
          <property>
          <name>dfs.permissions</name>
          <value>false</value>
       </property>
    </configuration>
    • dfs.replication默认是3,为了节省虚拟机资源,这里设置为1

    • 全分布式情况下,SecondaryNameNode和NameNode 应分开部署

    • dfs.namenode.secondary.http-address默认就是本地,如果是伪分布式可以不用配置

  10. hadoop/etc/hadoop/mapred-site.xml - mapreduce 相关配置

    <configuration>
        <!-- 指定MapReduce程序由Yarn进行调度. -->
        <property>
            <name>mapreduce.framework.name</name>
            <value>yarn</value>
        </property>
            <!-- Mapreduce的Job历史记录服务器主机端口设置. -->
        <property>
            <name>mapreduce.jobhistory.address</name>
            <value>hadop100:10020</value>
        </property>
            <!-- Mapreduce的Job历史记录的Webapp端地址. -->
        <property>
            <name>mapreduce.jobhistory.webapp.address</name>
            <value>hadoop100:19888</value>
        </property>
    
        <property>
            <name>yarn.app.mapreduce.am.env</name>
            <value>HADOOP_MAPRED_HOME=/opt/pkg/hadoop</value>
        </property>
        <property>
            <name>mapreduce.map.env</name>
            <value>HADOOP_MAPRED_HOME=/opt/pkg/hadoop</value>
        </property>
        <property>
            <name>mapreduce.reduce.env</name>
            <value>HADOOP_MAPRED_HOME=/opt/pkg/hadoop</value>
        </property>
    
    </configuration>

    • mapreduce.jobhistory相关配置是可选配置,用于查看MR任务的历史日志

      • 这里主机名千万不要弄错,不然任务执行会失败,且不容易找原因
        • 需要手动启动MapReduceJobHistory后台服务才能在Yarn的页面打开历史日志
  11. 配置 yarn-site.xml

      <configuration>
          <!-- 设置Yarn的ResourceManager节点主机名. -->
        <property>
           <name>yarn.resourcemanager.hostname</name>
           <value>hadoop100</value>
        </property>
           <!-- 设置Mapper端将数据发送到Reducer端的方式. -->
        <property>
           <name>yarn.nodemanager.aux-services</name>
           <value>mapreduce_shuffle</value>
        </property>
          <!-- 是否开启日志手机功能. -->
        <property>
           <name>yarn.log-aggregation-enable</name>
           <value>true</value>
        </property>
          <!-- 日志保留时间(7天). -->
        <property>
           <name>yarn.log-aggregation.retain-seconds</name>
           <value>604800</value>
        </property>
          <!-- 如果vmem、pmem资源不够,会报错,此处将资源监察置为false -->
       <property>
           <name>yarn.nodemanager.vmem-check-enabled</name>
           <value>false</value>
       </property>
       <property>
           <name>yarn.nodemanager.pmem-check-enabled</name>
           <value>false</value>
       </property>
    </configuration>
  12. workers DataNode 节点配置

      vi workers
      [hadoop@hadoop100 hadoop]$ vi workers
      hadoop100
    • 如果是伪分布式可以不进行修改,默认是localhost, 也可以改成本机的主机名
  • 全分布式配置则需要每行输入一个DataNode主机名

    • 注意不要有空格和空行,因为其他脚本会获取相关主机名信息

4、格式化名称节点

格式化:HDFS(NameNode)

hadoop@hadoop100 hadoop]$ hdfs namenode -format

21/01/09 19:27:21 INFO namenode.NameNode: STARTUP_MSG: 
/************************************************************
STARTUP_MSG: Starting NameNode
STARTUP_MSG:   host = hadoop100/192.168.186.100
STARTUP_MSG:   args = [-format]
STARTUP_MSG:   version = 2.7.3
************************************************************/
21/01/09 19:27:21 INFO namenode.NameNode: registered UNIX signal handlers for [TERM, HUP, INT]
21/01/09 19:27:21 INFO namenode.NameNode: createNameNode [-format]
Formatting using clusterid: CID-08318e9e-e202-48f3-bcb1-548ca50310c9
21/01/09 19:27:22 INFO util.GSet: Computing capacity for map BlocksMap
21/01/09 19:27:22 INFO util.GSet: VM type       = 64-bit
21/01/09 19:27:22 INFO util.GSet: 2.0% max memory 966.7 MB = 19.3 MB
21/01/09 19:27:22 INFO util.GSet: capacity      = 2^21 = 2097152 entries
21/01/09 19:27:22 INFO blockmanagement.BlockManager: dfs.block.access.token.enable=false
21/01/09 19:27:22 INFO blockmanagement.BlockManager: defaultReplication         = 1
21/01/09 19:27:22 INFO blockmanagement.BlockManager: maxReplication             = 512
21/01/09 19:27:22 INFO blockmanagement.BlockManager: minReplication             = 1
21/01/09 19:27:22 INFO blockmanagement.BlockManager: maxReplicationStreams      = 2
21/01/09 19:27:22 INFO blockmanagement.BlockManager: replicationRecheckInterval = 3000
21/01/09 19:27:22 INFO blockmanagement.BlockManager: encryptDataTransfer        = false
21/01/09 19:27:22 INFO blockmanagement.BlockManager: maxNumBlocksToLog          = 1000
21/01/09 19:27:22 INFO namenode.FSNamesystem: fsOwner             = hadoop (auth:SIMPLE)
21/01/09 19:27:22 INFO namenode.FSNamesystem: supergroup          = supergroup
21/01/09 19:27:22 INFO namenode.FSNamesystem: isPermissionEnabled = false
21/01/09 19:27:22 INFO namenode.FSNamesystem: HA Enabled: false
21/01/09 19:27:22 INFO namenode.FSNamesystem: Append Enabled: true
21/01/09 19:27:23 INFO common.Storage: Storage directory /opt/pkg/hadoop/data/tmp/dfs/name has been successfully formatted.
/************************************************************
SHUTDOWN_MSG: Shutting down NameNode at hadoop100/192.168.186.100
************************************************************/
  • 注意格式化后会在hdfs-site.xml中指定的hadoop.tmp.dir目录中生成相关数据

  • 其中NameNode和DataNode的数据文件夹中应保存着一致的ClusterID(CID)

    [hadoop@hadoop100 hadoop]$ cat /opt/pkg/hadoop/data/tmp/dfs/name/current/VERSION
    #Sat Jan 09 19:27:23 CST 2021
    namespaceID=637773384
    clusterID=CID-08318e9e-e202-48f3-bcb1-548ca50310c9
    cTime=0
    storageType=NAME_NODE
    blockpoolID=BP-1926974917-192.168.186.100-1610191643341
    layoutVersion=-63
    
    [hadoop@hadoop100 hadoop]$ cat /opt/pkg/hadoop/data/tmp/dfs/data/current/VERSION 
    #Sat Jan 09 19:33:49 CST 2021
    storageID=DS-6abf02d0-274c-4b7a-9d1d-05ed7d73636a
    clusterID=CID-08318e9e-e202-48f3-bcb1-548ca50310c9
    cTime=0
    datanodeUuid=44ff2304-01b1-4d8a-8a42-a2ad6e62ebba
    storageType=DATA_NODE
    layoutVersion=-56
    
    • 如果多次格式化就会导致NamdeNode新生成的CID和DataNode不一致
    • 解决办法,停止集群,将DN的CID修改成和NN的CID一致,再启动集群

5、运行和测试

启动Hadoop环境,刚启动Hadoop的HDFS系统后会有几秒的安全模式,安全模式期间无法进行任何数据处理,这也是为什么不建议使用start-all.sh脚本一次性启动DFS进程和Yarn进程,而是先启动dfs后过30秒左右再启动Yarn相关进程。

启动DFS进程:

[hadoop@hadoop100 hadoop]$ start-dfs.sh
Starting namenodes on [hadoop100]
hadoop100: starting namenode, logging to /opt/pkg/hadoop/logs/hadoop-hadoop-namenode-hadoop100.out
hadoop100: starting datanode, logging to /opt/pkg/hadoop/logs/hadoop-hadoop-datanode-hadoop100.out
Starting secondary namenodes [hadoop100]
hadoop100: starting secondarynamenode, logging to /opt/pkg/hadoop/logs/hadoop-hadoop-secondarynamenode-hadoop100.out

启动YARN进程:

[hadoop@hadoop100 hadoop]$ start-yarn.sh
starting yarn daemons
starting resourcemanager, logging to /opt/pkg/hadoop/logs/yarn-hadoop-resourcemanager-hadoop100.out
hadoop100: starting nodemanager, logging to /opt/pkg/hadoop/logs/yarn-hadoop-nodemanager-hadoop100.out

启动MapReduceJobHistory后台服务 - 用于查看MR执行的历史日志

[hadoop@hadoop100 mapreduce]$ mr-jobhistory-daemon.sh start historyserver

停止集群(可以做成脚本)

stop-dfs.sh
stop-yarn.sh 
# 已过时 mr-jobhistory-daemon.sh stop historyserver
mapred --daemon stop historyserver

单个进程逐个启动

# 在主节点上使用以下命令启动 HDFS NameNode: 
# 已过时 hadoop-daemon.sh start namenode 
hdfs --daemon start namenode

# 在主节点上使用以下命令启动 HDFS SecondaryNamenode: 
# 已过时 hadoop-daemon.sh start secondarynamenode 
hdfs --daemon start secondarynamenode

# 在每个从节点上使用以下命令启动 HDFS DataNode: 
# 已过时 hadoop-daemon.sh start datanode
hdfs --daemon start datanode

# 在主节点上使用以下命令启动 YARN ResourceManager: 
# 已过时 yarn-daemon.sh start resourcemanager 
yarn --daemon start resourcemanager

# 在每个从节点上使用以下命令启动 YARN nodemanager: 
# 已过时 yarn-daemon.sh start nodemanager 
yarn --daemon start nodemanager

以上脚本位于$HADOOP_HOME/sbin/目录下。如果想要停止某个节点上某个角色,只需要把命令中的start 改为stop 即可。

Web界面进行验证

HDFS:http://hadoop100:9870

image-20210109193629973

Yarn:http://hadoop100:8088

image-20210109193653338

执行jps命令,看看是否会有如下进程:

[hadoop@hadoop100 hadoop]$ jps
14608 NodeManager
14361 SecondaryNameNode
14203 DataNode
14510 ResourceManager
14079 NameNode

14910 Jps

使用官方自带的示例程序测试

[hadoop@hadoop100 mapreduce]$ cd /opt/pkg/hadoop/share/hadoop/mapreduce
[hadoop@hadoop100 mapreduce]$ hadoop jar hadoop-mapreduce-examples-2.7.3.jar wordcount
Usage: wordcount <in> [<in>...] <out>

准备输入文件,并上传到HDFS系统

[hadoop@hadoop100 input]$ cat /opt/data/mapred/input/wc.txt
hadoop hadoop hadoop
hi hi hi hello hadoop
hello world hadoop

[hadoop@hadoop100 input]$ hadoop fs -mkdir -p /input/wc
[hadoop@hadoop100 input]$ hadoop fs -put wc.txt /input/wc/
Found 1 items
-rw-r--r--   1 hadoop supergroup         62 2021-01-09 20:15 /input/wc/wc.txt

[hadoop@hadoop100 input]$ hadoop fs -cat /input/wc/wc.txt
hadoop hadoop hadoop
hi hi hi hello hadoop
hello world hadoop

运行示例wordcount程序,并将结果输出到/output/wc之中

[hadoop@hadoop100 mapreduce]$ cd /opt/pkg/hadoop/share/hadoop/mapreduce
[hadoop@hadoop100 mapreduce]$ hadoop jar hadoop-mapreduce-examples-2.7.3.jar wordcount /input/wc/ /output/wc/
21/01/09 20:23:27 INFO client.RMProxy: Connecting to ResourceManager at hadoop100/192.168.186.100:8032
21/01/09 20:23:28 INFO input.FileInputFormat: Total input paths to process : 1
21/01/09 20:23:28 INFO mapreduce.JobSubmitter: number of splits:1
21/01/09 20:23:29 INFO mapreduce.JobSubmitter: Submitting tokens for job: job_1610194940581_0001
21/01/09 20:23:29 INFO impl.YarnClientImpl: Submitted application application_1610194940581_0001
21/01/09 20:23:29 INFO mapreduce.Job: The url to track the job: http://hadoop100:8088/proxy/application_1610194940581_0001/
21/01/09 20:23:29 INFO mapreduce.Job: Running job: job_1610194940581_0001
21/01/09 20:23:43 INFO mapreduce.Job: Job job_1610194940581_0001 running in uber mode : false
21/01/09 20:23:43 INFO mapreduce.Job:  map 0% reduce 0%
21/01/09 20:23:52 INFO mapreduce.Job:  map 100% reduce 0%
21/01/09 20:24:00 INFO mapreduce.Job:  map 100% reduce 100%
21/01/09 20:24:01 INFO mapreduce.Job: Job job_1610194940581_0001 completed successfully
21/01/09 20:24:02 INFO mapreduce.Job: Counters: 49
    File System Counters
        FILE: Number of bytes read=52
        FILE: Number of bytes written=237407
        FILE: Number of read operations=0
        FILE: Number of large read operations=0
        FILE: Number of write operations=0
        HDFS: Number of bytes read=164
        HDFS: Number of bytes written=30
        HDFS: Number of read operations=6
        HDFS: Number of large read operations=0
        HDFS: Number of write operations=2
    Job Counters 
        Launched map tasks=1
        Launched reduce tasks=1
        Data-local map tasks=1
        Total time spent by all maps in occupied slots (ms)=7041
        Total time spent by all reduces in occupied slots (ms)=5566
        Total time spent by all map tasks (ms)=7041
        Total time spent by all reduce tasks (ms)=5566
        Total vcore-milliseconds taken by all map tasks=7041
        Total vcore-milliseconds taken by all reduce tasks=5566
        Total megabyte-milliseconds taken by all map tasks=7209984
        Total megabyte-milliseconds taken by all reduce tasks=5699584
    Map-Reduce Framework
        Map input records=3
        Map output records=11
        Map output bytes=106
        Map output materialized bytes=52
        Input split bytes=102
        Combine input records=11
        Combine output records=4
        Reduce input groups=4
        Reduce shuffle bytes=52
        Reduce input records=4
        Reduce output records=4
        Spilled Records=8
        Shuffled Maps =1
        Failed Shuffles=0
        Merged Map outputs=1
        GC time elapsed (ms)=146
        CPU time spent (ms)=2570
        Physical memory (bytes) snapshot=339308544
        Virtual memory (bytes) snapshot=4163043328
        Total committed heap usage (bytes)=219676672
    Shuffle Errors
        BAD_ID=0
        CONNECTION=0
        IO_ERROR=0
        WRONG_LENGTH=0
        WRONG_MAP=0
        WRONG_REDUCE=0
    File Input Format Counters 
        Bytes Read=62
    File Output Format Counters 
        Bytes Written=30
  • 注意,输入是文件夹,可以指定多个
  • 输出是一个必须不能存在的文件夹路径
  • 计算结果会写入到output指定的文件夹中

查看保存在HDFS上的结果

[hadoop@hadoop100 mapreduce]$ hadoop fs -ls /output/wc/
Found 2 items
-rw-r--r--   1 hadoop supergroup          0 2021-01-09 20:23 /output/wc/_SUCCESS
-rw-r--r--   1 hadoop supergroup         30 2021-01-09 20:23 /output/wc/part-r-00000
[hadoop@hadoop100 mapreduce]$ hadoop fs -cat /output/wc/part-r-00000
hadoop  5
hello   2
hi  3
world   1

在MR任务执行时,可以通过Yarn的界面查看进度

image-20210109203547507

执行完毕以后点击TrackingUI下的History可以查看历史日志记录

如果跳转页面报404说明没有启动JobHistoryServer服务

[hadoop@hadoop100 mapreduce]$ mr-jobhistory-daemon.sh start historyserver

image-20210109224932545

也可以在HDFS的Web界面上查看结果

image-20210109213522691

Utilities -> HDFS browser -> /output/wc/ -> click part-r-00000 -> download part-r-00000

[hadoop@hadoop100 hadoop]$ stop-all.sh

This script is Deprecated. Instead use stop-dfs.sh and stop-yarn.sh
Stopping namenodes on [hadoop100]
hadoop100: stopping namenode
hadoop100: stopping datanode
Stopping secondary namenodes [hadoop100]
hadoop100: stopping secondarynamenode
stopping yarn daemons
stopping resourcemanager
hadoop100: stopping nodemanager
no proxyserver to stop

6、常见问题:

(1)时间校准后仍然偏差几个小时

使用nptdate 进行时间校准后发现校准后的时间仍然偏移很多,这是因为时区的设置问题(如果是纽约时间, 则相差13个小时),修改时区的方法就是删除原来的软链。软链的位置:

ll /etc/localtime
/etc/localtime -> ../usr/share/zoneinfo/America/New_York

rm /etc/localtime

删除后时间就变成了UTC时间 - 格林威治时间,与中国时间相差8个小时

接下来只需要重新生成一个指向中国时区的软链,就可以正常显示中国时区的时间了

$ sudo ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

这样时区就没问题了

(2)没有DataNode

如果多次格式化会导致NamdeNode新生成的CID和DataNode不一致

  • 解决办法,

    • 停止集群,将DN的CID修改成和NN的CID一致,格式化NN,再启动集群
    • 建议
    • 停止集群,删除hadoop.tmp.dir下的所有内容,格式化NN,再启动集群
    • 不建议,会丢失数据,如果不怕丢失数据可以这样做

(3)MapReducer任务失败

有很多原因,主要是分析日志的信息

一般的原因是:

  • 缺少服务进程

    • 查找对应服务的日志
  • 输出目录已经存在

    • 删除已经存在的目录或者改为不存在的目录
  • Host无法解析

    • 检查/etc/hosts的映射的主机名和ip是否准确
    • 检查所有配置文件中的主机名和端口号是否正确
  • 其他的错误

    • 可以通过MR的历史日志进行定位

    • 或者通过MR任务的Yarn日志查看任务执行细节

    yarn logs -applicationId <applicationId>

Views: 114

01- 安装Centos7虚拟机

安装Centos7

1、下载

网易、阿里、搜狐等公司都有开源镜像站提供CentOS7的镜像文件下载,以阿里云镜像站为例:

Index of /centos/7/isos/x86_64/


../
0_README.txt                                       06-Nov-2020 14:32                2495
CentOS-7-x86_64-DVD-2009.iso                       04-Nov-2020 11:37          4712300544
CentOS-7-x86_64-DVD-2009.torrent                   06-Nov-2020 14:44              180308
CentOS-7-x86_64-Everything-2009.iso                02-Nov-2020 15:18         10200547328
CentOS-7-x86_64-Everything-2009.torrent            06-Nov-2020 14:44              389690
CentOS-7-x86_64-Minimal-2009.iso                   03-Nov-2020 14:55          1020264448
CentOS-7-x86_64-Minimal-2009.torrent               06-Nov-2020 14:44               39479
CentOS-7-x86_64-NetInstall-2009.iso                26-Oct-2020 16:26           602931200
CentOS-7-x86_64-NetInstall-2009.torrent            06-Nov-2020 14:44               23567
sha256sum.txt                                      04-Nov-2020 11:38                 398
sha256sum.txt.asc    

下载最小化安装版本 CentOS-7-x86_64-Minimal-2009.iso

2、配置虚拟机软件

这里使用VmwareStation15.5

为了方便,采用NAT连接方式,NAT模式的虚拟网卡名称默认为VMnet8

为了统一网段,修改虚拟机网络编辑器配置如下,将第三段修改为186

image-20210109071819453

网关也要对应修改

image-20210109071924873

3、创建虚拟机

  1. 创建虚拟机,选择典型,安装程序光盘映像文件选择前面下载的iso文件。
  2. 安装位置不要采用默认设置,最好自己指定
  3. 磁盘大小,为了便于扩展,最大容量选择50G
  4. 为了拷贝方便,选择拆分多个文件
  5. 如果宿主内存足够,虚拟机内存可设置为4096MB,但最好不要超过宿主内存的一半以上
  6. 网络适配器默认采用NAT连接

最终结果

image-20210109072813301

然后选择完成进行系统安装,在安装界面设置

  1. 本地化

    1. 安装界面语言-简体中文
    2. 日期默认亚洲上海时区
    3. 键盘采用默认配置即可
    4. 语言支持除了中文外添加English(United States)
  2. 软件

    1. 安装源 - 无需设置
    2. 软件选择 - 由于已经是最小安装,因此无需设置
  3. 系统

    1. 安装位置

      1. 选择本地标准磁盘(40G)
      2. 选择我要配置分区
        1. 创建挂载点
        2. /boot 200 MiB
        3. /swap 2048 MiB
        4. / 剩余的37.8 GiB
      3. 完成 - 接受更改
    2. KDUMP - 忽略

    3. 网络和主机名(也可以忽略,以后自行修改)

      1. 开启以太网ens33
      2. 编辑ens33网络
        1. 方法手动
        2. 添加地址
        3. IP = 192.168.186.100
        4. 子网掩码 = 255.255.255.0
        5. 网关 192.168.186.2 - 和虚拟机的NAT连接网络设置保持一致
        6. DNS = 223.5.5.5,8.8.8.8 - 前置是国内通用DNS地址,后者是谷歌的DNS地址(可能被墙)
    4. 开始安装

      1. 设置ROOT密码 - niit1234, 两次确认完成简单密码(由于是学习用的虚拟机)的设置
      2. 创建用户 - 忽略,以后可以自行创建
    5. 重启

4、网络配置

测试能否ping通百度

[root@localhost ~]# ping baidu.com
PING baidu.com (220.181.38.148) 56(84) bytes of data.
64 bytes from 220.181.38.148 (220.181.38.148): icmp_seq=1 ttl=128 time=53.5 ms
64 bytes from 220.181.38.148 (220.181.38.148): icmp_seq=2 ttl=128 time=57.5 ms
64 bytes from 220.181.38.148 (220.181.38.148): icmp_seq=3 ttl=128 time=52.5 ms
(Ctrl-C退出)

如果不能,则检查配置,其中注释处重点检查

[root@localhost ~]# vi /etc/sysconfig/network-scripts/ifcfg-ens33 

TYPE="Ethernet"
PROXY_METHOD="none"
BROWSER_ONLY="no"
BOOTPROTO="static"                            # static - 静态分配IP地址
DEFROUTE="yes"
IPV4_FAILURE_FATAL="no"
IPV6INIT="yes"
IPV6_AUTOCONF="yes"
IPV6_DEFROUTE="yes"
IPV6_FAILURE_FATAL="no"
IPV6_ADDR_GEN_MODE="stable-privacy"
NAME="ens33"
UUID="b00b9ac0-60c2-4d34-ab88-2413055463cf"
DEVICE="ens33"
ONBOOT="yes"                              # 系统启动时网络接口是否有效
IPADDR="192.168.186.100"                   # 静态IP
PREFIX="24"
GATEWAY="192.168.186.2"                        # 网关
DNS1="223.5.5.5"                          
DNS2="8.8.8.8"
IPV6_PRIVACY="no"
  1. 为了避免动态分配ip造成ip变动导致集群不稳定,一定要修改成为静态ip
  2. ONBOOT="yes" 可以开机时开启网络接口,便于联网
  3. 网关要和虚拟机的VMnet8的配置相同

修改后重启网络服务

[root@localhost ~]# service network restart
Restarting network (via systemctl):                        [  确定  ]

如果报错,则使用reboot命令重启系统

5、主机名配置

查看

[root@localhost ~]# hostname
localhost.localdomain

修改(主机名全小写,且不要有下划线)

[root@localhost ~]# hostnamectl --static set-hostname hadoop100

[root@localhost ~]# hostname
hadoop100

查看虚拟机IP

[hadoop@hadoop100 ~]$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:05:2a:0e brd ff:ff:ff:ff:ff:ff
    inet 192.168.186.100/24 brd 192.168.186.255 scope global noprefixroute ens33
       valid_lft forever preferred_lft forever
    inet6 fe80::ac3e:51e8:9275:6fcd/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever

其中ens33的 inet 192.168.186.100 即为当前虚拟机的ipv4的地址

修改虚拟机的IP和主机名映射文件

# vi /etc/hosts vi /etc/hosts

#127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
#::1         localhost localhost.localdomain localhost6 localhost6.localdomain6

192.168.186.100 hadoop100

同时也需要修改Windows的主机映射文件

C:\Windows\System32\drivers\etc\HOSTS

# hadoop
192.168.186.100 hadoop100
192.168.186.101 hadoop101
192.168.186.102 hadoop102

其中

  1. hadoop100-hadoop102为大数据全分布式环境的主机名设置

另,新修改的主机名要重新登录终端或者重启系统后才能显示正常,如 [root@hadoop100 ~]#

6、防火墙设置

为了方便Windows或其他系统可以访问Linux虚拟机内的服务,为了方便学习的目的可以关闭虚拟机的防火墙服务(真实服务器上不要这样做)

关闭防火墙,可以避免端口被限制

[root@hadoop100 ~]# systemctl status firewalld.service
● firewalld.service - firewalld - dynamic firewall daemon
   Loaded: loaded (/usr/lib/systemd/system/firewalld.service; enabled; vendor preset: enabled)
   Active: active (running) since 六 2021-01-09 08:03:31 CST; 38min ago
     Docs: man:firewalld(1)
 Main PID: 706 (firewalld)
   CGroup: /system.slice/firewalld.service
           └─706 /usr/bin/python2 -Es /usr/sbin/firewalld --nofork --nopid

1月 09 08:03:30 localhost.localdomain systemd[1]: Starting firewalld - dynamic firewall daemon...
1月 09 08:03:31 localhost.localdomain systemd[1]: Started firewalld - dynamic firewall daemon.
1月 09 08:03:32 localhost.localdomain firewalld[706]: WARNING: AllowZoneDrifting is enabled. This ...w.
Hint: Some lines were ellipsized, use -l to show in full.
[root@hadoop100 ~]# systemctl stop firewalld.service
[root@hadoop100 ~]# systemctl status firewalld.service
● firewalld.service - firewalld - dynamic firewall daemon
   Loaded: loaded (/usr/lib/systemd/system/firewalld.service; disabled; vendor preset: enabled)
   Active: inactive (dead)
     Docs: man:firewalld(1)

1月 09 08:03:30 localhost.localdomain systemd[1]: Starting firewalld - dynamic firewall daemon...
1月 09 08:03:31 localhost.localdomain systemd[1]: Started firewalld - dynamic firewall daemon.
1月 09 08:03:32 localhost.localdomain firewalld[706]: WARNING: AllowZoneDrifting is enabled. This ...w.
1月 09 08:42:23 hadoop100 systemd[1]: Stopping firewalld - dynamic firewall daemon...
1月 09 08:42:23 hadoop100 systemd[1]: Stopped firewalld - dynamic firewall daemon.
Hint: Some lines were ellipsized, use -l to show in full.

(如果是Centos6,则默认的防火墙服务名为iptables)

  1. systemctl的status可以查看现有服务的状态
  2. systemctl的stop可以临时关闭防火墙
  3. 还可以使用systemctl的disable和enable来永久禁用和启用服务,

这里为了方便禁用自动开机启动防火墙服务

[root@hadoop100 ~]# systemctl disable firewalld.service
[root@hadoop100 ~]# systemctl is-enabled firewalld.service
disabled

7、安装vim

Centos默认没有安装vim, 为了编辑时能得到高亮显示,这里安装vim,并替换原来的vi命令

# yum install vim

[root@hadoop100  ~]# vi ~/.bashrc 

# .bashrc
# User specific aliases and functions

alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'
alias vi=vim

# Source global definitions
if [ -f /etc/bashrc ]; then
        . /etc/bashrc
fi

使设置生效

[root@hadoop100 ~]# source ~/.bashrc 

8、创建操作用户

root用户具有太大操作权限,实际操作时需要对不同的用户设置不同的限制,所以需要另外创建一个一般用途的用户。

[root@hadoop100 ~]# useradd hadoop
[root@hadoop100 ~]# passwd hadoop
改用户 hadoop 的密码 。
新的 密码:123123
无效的密码: 密码少于 8 个字符
重新输入新的 密码:123123
passwd:所有的身份验证令牌已经成功更新。

修改sudoer配置文件

[root@hadoop100 ~]# source ~/.bashrc 

由于前面已经设置了vim的别名为vi,这里会直接使用vim进行编辑

  1. 在vim中,键入:set nu可以显示行号

  2. 键入100G可以快速跳转到第100行位置

  3. 键入yy复制当前行,键入p拷贝到下一行

  4. 进行如下修改

    ## Next comes the main part: which users can run what software on 
    ## which machines (the sudoers file can be shared between multiple
    ## systems).
    ## Syntax:
    ##
    ##      user    MACHINE=COMMANDS
    ##
    ## The COMMANDS section may have other options added to it.
    ##
    ## Allow root to run any commands anywhere 
    root    ALL=(ALL)       ALL
    hadoop  ALL=(ALL)       NOPASSWD:ALL
    1. 第一个配置ALL=(ALL)是让hadoop用户拥有root权限
    2. 第二个配置NOPASSWD:ALL是当hadoop用户执行sudo命令时,不需要输入hadoop用户的密码

    登录新用户进行测试

    [root@hadoop100 root]$ su hadoop
    [hadoop@hadoop100 ~]$ cd ~
    [hadoop@hadoop100 ~]$ sudo yum install -y net-tools
    [hadoop@hadoop100 ~]$ ifconfig
    ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
           inet 192.168.186.100  netmask 255.255.255.0  broadcast 192.168.186.255
           inet6 fe80::ac3e:51e8:9275:6fcd  prefixlen 64  scopeid 0x20<link>
           ether 00:0c:29:05:2a:0e  txqueuelen 1000  (Ethernet)
           RX packets 1338  bytes 431899 (421.7 KiB)
           RX errors 0  dropped 0  overruns 0  frame 0
           TX packets 920  bytes 113806 (111.1 KiB)
           TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
    
    lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
           inet 127.0.0.1  netmask 255.0.0.0
           inet6 ::1  prefixlen 128  scopeid 0x10<host>
           loop  txqueuelen 1000  (Local Loopback)
           RX packets 32  bytes 2592 (2.5 KiB)
           RX errors 0  dropped 0  overruns 0  frame 0
           TX packets 32  bytes 2592 (2.5 KiB)
           TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
    1. yum install进行软件安装是需要root权限的,因此需要加上sudo来执行
    2. 正常情况下sudo命令需要提供当前用户的密码,确认成功后才能执行命令
    3. 由于我们在/etc/sudoers为hadoop用户配置了root权限以及免sudo密码,所以无需输入密码
    4. 查看ip使用ifconfig要比ip a的方式有更好的显示和更多的选项

9、虚拟机备份

有时候虚拟机可能为因为磁盘损坏导致引导失败,建议为重要的虚拟机制作备份

  1. 可以使用VMwareStation自带的虚拟机克隆向导实现备份
  2. 拍快照
  3. 选择完整克隆
  4. 为克隆的虚拟机另起一个名字

10、远程终端连接工具

由于在VMwareStation的虚拟机界面操作十分不便,无法滚动屏幕看之前的内容,也不能粘贴复制,另外有时我们需要在虚拟机和宿主机器之间传输文件,因此这里使用NetSarang公司的Xshell和Xftp进行对虚拟机的远程ssh连接和远程sftp连接客户端。

image-20210109101251564

具体使用方法请查看官方

https://www.netsarang.com/zh/xshell/

https://www.netsarang.com/zh/xftp/

另外强烈推荐另外一款远程终端产品叫做MobaXTerm, 也是非常好用!

image-20210301005639445

[END]

Views: 82

CH-02 编程准备

为什么学习C语言

  1. 操作系统编程
    • 内存管理、服务器
  2. 操作系统设计
    • 大部分系统使用C语言实现
  3. 网络协议实现
    • 局域网、互联网数据传输
  4. 物联网和嵌入式编程
    • Linux编程
  5. 实现其他语言
    1. 解释形
      • 解析器实现大多数都是C/C++
    2. 编译型
      • 高效率编译器的实现也离不开C语言

C语言历史

为了玩游戏编写了一套操作系统

C 语言的产生,还是有一些故事的。在很久很久以前,那个时期的计算机系统还处在批处理阶段,技术不发达导致了运算速度十分缓慢,也使得程序员工作效率低下。当时他们只能在运算速度缓慢笨重的大型机器上工作,操作也十分繁琐:需要先将程序卡片装入设备,然后等一个多小时才能获取运算结果。……在知名的贝尔实验室,有一个叫 Ken Thompson 和另一个叫 Dennis M. Ritchie 的工程师,他们被共同称为“C 语言之父”。
file
他们为了在实验室角落里一台 PDP-7 小型机上写一个让自己可以不用花多少钱就可以玩的《星际旅行(Star Travel)》的游戏,用汇编语言编写了一个操作系统,并把这个系统命名成了 Unix。
file
后面又出现了B语言替代了汇编,因为B语言编写的程序无法移植到其他类型的机器上,当PDP-11出来后,为了将PDP-7上的B语言移植到PDP-11上又开发了NB语言,这就是C语言的前身,1972年NB语言改名为C语言。在1973年Dennis和Ken一起使用C语言重写了Unix系统。

file

file

后又有大神Linus

Early C

  • 1969: B created, based on BCPL, to replace PDP-7 assembler as the system programming language for Unix
  • 1971: NB ("new B") created when porting B to PDP-11
  • 1972: Language renamed to C
  • 1973: Unix re-written in C
  • 1978: The C Programming Language, 1st edition

Standard C

  • 1988: The C Programming Language, 2nd edition
  • 1989: C89, the ANSI C standard published
  • 1990: C90, the ANSI C standard accepted as ISO/IEC 9899:1990
  • 1995: C95 (ISO/IEC 9899:1990/Amd.1:1995) (online store)
  • 1999: C99 (ISO/IEC 9899:1999)
  • 2011: C11 (ISO/IEC 9899:2011) (ISO store) (ANSI store) (April 12, 2011 draft)
  • 2018: C17 (ISO/IEC 9899:2018) (ISO Store) (Final draft)
  • 2023: C23 Next major C language standard revision

环境准备

C 语言即可以在Windows、也可以在macOS(Unix内核)或者Linux(类Unix)上进行开发, 需要注意平台之间的差异, 例如Windows环境下的一些库文件和函数有些"特立独行", 除了系统的差异, 也存在编译工具的差异,例如微软平台下的宇宙第一IDE的Visual Studio与其他IDE相比就存在明显的区别。

C 语言在 Unix 上被设计出来,也被基于 Unix 的操作系统更好地支持。如果你希望更顺利地学习 C 语言,使用基于 Unix 的操作系统将会是很好的选择。同时,多数情况下你将会需要使用命令行工具来进行操作。

Windows环境配置

一般IDE会自带编译环境。你也可以选择使用自己的编译环境,一般Windows上常用的C/C++编译环境是MinGW(Minimalist GNU for Windows)。为了安装 MinGW,请访问 MinGW 的主页 ,进入 MinGW 下载页面,下载最新版本的 MinGW 安装程序,命名格式为 MinGW-.exe。

Linux环境配置

file

macOS环境配置

file

文本编辑器和IDE选择

对于程序语言的初学者来说,不要轻易地使用太复杂的IDE(integrated development environment,集成式开发环境),如:

  • Visual Studio Conmmunity(IDE)
    file

  • Visual Studio Code(IDE)
    file

  • JetBrain CLion
    file

因为IDE 会隐藏很多本来应该让你学习到的知识,如果坚持要使用 IDE,对于初学者推荐你使用 Dev C++ 或 Code::Blocks 这种相对轻量的 IDE),像VC6.0这种古老的IDE就不推荐使用了。

  • Dev-C++(IDE)
    file

  • Code Blocks(IDE)
    file

也推荐使用 Atom、Sublime、NotePad++、Gedit、Nano、Emacs、Vim 这些编辑器,其中Vim、Atom、Sublime经过适当的配置也是可以达到接近IDE的效果的。如果可能,你都可以试一试,找到你最顺手的那个。

  • Sublime
    file

  • Vim(Editor)
    file

Windows系统运行Linux

  1. 运行Linux虚拟机
    • VMwareStation/VirtualBox
    • Centos 7
  2. 运行WSL(WindowSubLinux)
    • Win10

Hello World 程序代码及说明

C语言版本

#include <stdio.h>
int main()
{
    printf("hello, world!\n");
    return 0;
}
  • #编译预处理行
  • #include文件预处理命令 - 包含文件
  • #include<stdio.h>让C语言负责标准输入输出流库的头文件包含到此处
  • main()主函数,代表程序的执行入口, 主函数有一个整数返回值
  • printf()格式化输出函数, 声明自stdio.h头文件
  • return 0;退出main()函数,向调用线程返回执行结果(返回0代表程序正常退出, 返回非0代表异常退出).

C++语言版本

C++介绍
从C++(读作 C Plus Plus)的名字看, 就知道它比标准C多出太多东西了, C几乎是C语言的超集, 但也有少量特性是C所不支持的。C++ 比 C 多了 classes、templates、exceptions 这些部分,而每个部分也有很多新增的东西。这还只是语言部分,还未谈及标准库。C 有 29 个标准库头文件,C++ 有 87 个,除了量,C++ 标准库的功能要复杂得多。是目前最难掌握也是最为复杂的语言。

我们课程学习所使用的C++只用到C++特性中最简单的部分,并且不涉及面向对象、模板、异常等复杂的部分。

编写C++的hello world程序很简单, 注意后缀使用cpp或者cc, 表示这是C++的源代码文件.

#include <iostream>
using namespace std;

int main()
{
  cout << "hello world" << endl;
  return 0;
}
  • iostream C++定义的输入输出流库, 定义了输入流对象cin和输出流对象cout对象
  • using namespace std;使用std命名空间, 命名空间用于避免命名冲突问题, std是C++自带的名称空间, 其种cout,cin和endl都位于这个名称空间下.
  • coutiosteam库中ostream类型对象,用于将<<右侧的内容输出到显示屏
  • endliostream库中定义的IO操纵符,表示换行并刷新流

GCC

GCC:GNU Compiler Collection(GUN 编译器集合),它可以编译C、C++、Java、Fortran、Pascal、Object-C、Ada等语言。

  • gcc是GCC中的GUN C Compiler(C 编译器)
  • g++是GCC中的GUN C++ Compiler(C++编译器)

gcc和g++的主要区别

  1. 对于 .c和.cpp文件,gcc分别当做c和cpp文件编译(c和cpp的语法是不一样的)
  2. 对于 .c和.cpp文件,g++则统一当做cpp文件编译
  3. 使用g++编译文件时,g++会自动链接标准库STL,而gcc不会自动链接STL

主要参数

-g - turn on debugging (so GDB gives morefriendly output)
-Wall - turns on most warnings
-O or -O2 or -O3 - turn on optimizations level 1~3
-o - name of the output file
-c - output an object file (.o)
-I - specify an includedirectory
-L - specify a libdirectory
-l - link with librarylib.a

GCC编译的4个阶段

file

  1. 预处理阶段:预处理器(cpp)

    $ gcc -E hello.c -o hello.i
    # 加上-P可以生成内容弄个更精简的文件
    $ gcc -E -P hello.c -o hello.i

    预处理阶段主要做的事情是:

    • 删注释
    • 替换宏定义
    • 替换#inlcude文件
  2. 编译阶段:编译器(ccl)

    $ gcc -S hello.i -o hello.s

    编译器将预处理的文件编译成汇编指令。

  3. 汇编阶段:汇编器(as)

    $ gcc -c hello.s -o hello.o

    汇编器将汇编指令转换为二进制目标文件。
    也可以把汇编和链接阶段合称为编译阶段,生成的.o目标文件可以使用nm命令列出所有符号,其中U表示的是未定义的符号。

    
    ➜ nm hello.o
                  U _GLOBAL_OFFSET_TABLE_
    0000000000000000  T main
                  U puts
  4. 链接阶段:链接器(ld)

    $ gcc hello.o -o hello

    也可以使用-save-tmps选项保存中间生成的临时文件,如:

    ➜ gcc -Wall -save-temps hello.c -o hello
    ➜ ls
    hello hello.c  hello.i  hello.o  hello.out  hello.s
    • -Wall 显示所有编译警告信息

如果编译成功,我们在同一个目录下可以得到一个编译后的可以执行的hello文件,可以在同一目录下使用./hello命令运行。

如果你选择了使用 IDE,你需要了解一下自己的 IDE 的编译和运行功能被设计成了什么样的菜单选项或按钮。每次先编译,确认编译通过后再运行。

file

输出流对象cout

cout 是C++中 ostream 类型的对象,该类被封装在 <iostream> 库中,该库定义的名字都在命名空间 std 中,所以 cout 全称是 std::cout

cout使用方式

  • std::cout
    std::cout << "Welcome to Tsinghua" << std::endl;
  • 使用 std 命名空间后,简写成 cout
    use namespace std;
    ......
    cout << "Welcome to Tsinghua" << endl;

file

算术运算符

C/C++支持5种常见的算术运算符(operator),分别是:

  • +
  • -
  • *
  • /
  • % 取余(取模)

除了取余运算符要求两边的操作数(operand)是整数外,其余加、减、乘、除运算要求两边操作数是实数即可。

数学函数

/*
计算 sin(20°) + cos(20°) - cos(20°) / tan(20°)
需要引入cmath库的三角函数, 参数单位为弧度
弧度与角度之间的转换公式:
弧度=角度 /180 × π
*/
#include <iostream>       // 预编译 - io流库 - 输入输出
#include <cmath>          // 预编译 - cmath库 - 三角函数
using namespace std;      // 使用命名空间

int main() {    // 主函数
    const float PI = 3.14159;
    printf("%f \n", sin(30.0 / 180 * PI));
    cout << sin(20.0 / 180 * PI) +
        cos(20.0 / 180 * PI) -
        cos(20.0 / 180 * PI) /
        tan(20.0 / 180 * PI)
        << endl;
    return 0;       // 返回0 代表程序正常结束
}

注意,上面程序30.0/180如果改成30/180则结果为0,这时因为计算时发生了隐式类型转换

其他常用数学函数

****************************************
Author: Delucia
Ver.:   0.0
Date:   2020年1月5日01:06:14
Description:
附录B - 库函数  B.1数学函数
弧度转角度 2π = 360°
正弦函数, 参数x为弧度值 - double sin (double x)
*****************************************/

#include <iostream>
#include <cmath>
#include <cstdlib>

using namespace std;

int main()
{
  // 对不同类型的 n, 计算 |n|
  cout << abs(-4294967295) << endl;
  cout << labs(-4294967296L) << endl;
  cout << fabs(-3.1415926535) << endl;

  // sin 90 = 1
  cout << sin(3.1415926/2.0) << endl;
  cout << asin(1) << endl;

  cout << cos(3.1415926535/2.0) << endl;
  cout << acos(4.48966e-011) << endl;
  cout << tan(45.0*3.1415926535/180.0) << endl;
  cout << atan(1) << endl;

  cout << exp(1) << endl;
  cout << exp(2.302585093) << endl;

  cout << log(exp(1)) << endl;
  cout << log10(10) << endl;

  cout << pow(2.0, 3.0) << endl;
  cout << sqrt(25.0) << endl;

  cout << floor(4.8) << endl;
  cout << floor(-4.8) << endl;

  return 0;
}

注释

文件头注释

//***************************************
// FileName: 2-4-1comments.cpp          *
// Author: Delucia                      *
// Date: 2020年1月4日20:13:03           *
// Description: How to use Comments     *
//***************************************

int main()
{
    return 0;
}

单行注释

// 被注释的行

多行注释

/*
被注释的行
被注释的行
/*
  • 编程初学者尽量多写注释
  • 有经验的程序员会尽量让代码达到自解释效果,只对必要的地方写上注释
  • 建议养成写注释的习惯(难)

作业

file

将上面练习改写为C++的版本,并手动编译运行.

学习资源参考

  • NIIT云课堂
  • 慕课网(imooc)
  • 网易云课堂

学习文档资源参考

  • Google/Baidu
  • StackOverFlow
  • GitHub
  • CSDN
  • 菜鸟教程
  • Cpp Reference

Views: 50

CH-01 绪论

学科介绍

软件工程

  • 2012年升级为教育部一级专业
  • 软件工程是指大型复杂计算机软件系统设计、开发、测试、维护的工程学科。

培养全栈工程师

  • 掌握软件开发过程的全链路技术
  • 注重实操
  • 不局限一门语言,重点学会编程思维
  • 使用编程解决生活中的一些问题
  • 对算法和数据结构有简单了解

教材

《程序设计基础(第四版)》 - 清华大学出版

主要章节

  1. 绪论
  2. 编程准备
  3. 代数思维与计算机解题
  4. 逻辑思维与计算机解题
  5. 函数思维与模块化设计
  6. 数据的组织与处理(1)- 数组
  7. 数据的组织与处理(2)- 结构
  8. 数据的组织与处理(3)- 文件
  9. 递归思想和相应算法

次要章节(有时间就讲的内容)

  1. 多步决策问题
  2. 宽度优先搜索
  3. 深度优先搜索
  4. 贪心法
  5. 动态规划
  6. 蒙特卡罗法

附录

A. 程序调试
B. 库函数
C. ASCII码表
D. 输入输出的格式控制

要求

  • 经过这门课程的学习,对于简单的程序,应可以做到纸笔编程, 学会快速构思程序结构, 积累经验避开常见的“坑”
  • 对于稍微复杂的程序使用IDE编程, 需要掌握一般调试手段。

考核

本门课程为理论课程,另外有实验课安排。

  • 平时成绩 40% - 考勤、答疑、测试、作业
  • 期末考试 60% - 机考 或者 试卷 (试题为标准C语言)

行业分析

IT行业岗位主要分为研发岗、市场岗、管理岗、技术支持岗。

研发岗位

对于软件开发的研发岗来说,又可细分为:

  • RD
    Research and Development engineer,研发工程师,对某种不存在的事物进行系统的研究和开发并具有一定经验的专业工作者,或者对已经存在的事物进行改进以达到优化目的的专业工作者。
  • PM
    Product Manager,产品经理,又称品牌经理。举凡产品从创意到上市,所有相关的研发、调研、生产、编预算、广告、促销活动等等,都由产品经理掌控。
  • QA
    Quality Assurance,品质保证。QA的主要职责就是质量保证工作。
  • OP
    Operator,运维操作员,服务器、数据库管理员。

file

其中由于产品经理与设计、开发工程师之间的恩恩怨怨,被编成了很多段子在网络上流传。
file

编程语言分类

最早的编程语言以Fortran为主,主要是辅助科学计算。随着技术更迭至今已经产生了上千种编程语言,
file
编程语言分为三类:

机器语言

  • 机器语言由很多0和1构成的指令组成,无需解释或编译计算机就可以直接执行
  • 编写难度大、效率低
  • 华为、Intel、高通、三星等研发芯片部门

汇编语言

  • 汇编语言的实质和机器语言是相同的,都是直接对硬件操作,只不过指令采用了英文缩写的标识符,更容易识别和记忆。下面是汇编版hello world程序,是不是十分复杂呢。
    ; hello.asm 
    section .data            ; 数据段声明
        msg db "Hello, world!", 0xA     ; 要输出的字符串
        len equ $ - msg                 ; 字串长度
    section .text            ; 代码段声明
    global _start            ; 指定入口函数
    _start:                  ; 在屏幕上显示一个字符串
        mov edx, len     ; 参数三:字符串长度
        mov ecx, msg     ; 参数二:要显示的字符串
        mov ebx, 1       ; 参数一:文件描述符(stdout) 
        mov eax, 4       ; 系统调用号(sys_write) 
        int 0x80         ; 调用内核功能
                         ; 退出程序
        mov ebx, 0       ; 参数一:退出代码
        mov eax, 1       ; 系统调用号(sys_exit) 
        int 0x80         ; 调用内核功能
  • 编写难度仍然很大,难理解和复用
  • 优点是汇编生成的可执行文件小、执行速度快
  • 芯片公司或硬件厂商

高级语言

高级语言是大多数编程者的选择。和汇编语言相比,它不但将许多相关的机器指令合成为单条指令,并且去掉了与具体操作有关但与完成工作无关的细节,例如使用堆栈、寄存器等,这样就大大简化了程序中的指令。同时,由于省略了很多细节,编程者也就不需要有太多的专业知识。

但是高级语言所编制的程序不能直接被计算机识别,必须经过转换才能被执行,按转换方式可将它们分为两类:

  1. 编译类

    是指在应用源程序执行之前,就将程序源代码“翻译”成目标代码(机器语言),因此其目标程序可以脱离其语言环境独立执行(编译后生成的可执行文件,是cpu可以理解的2进制的机器码组成的),使用比较方便、效率较高。但应用程序一旦需要修改,必须先修改源代码,再重新编译生成新的目标文件(* .obj,也就是OBJ文件)才能执行,只有目标文件而没有源代码,修改很不方便。
    编译后程序运行时不需要重新翻译,直接使用编译的结果就行了。程序执行效率高,依赖编译器,跨平台性差些。如C、C++、Delphi等

  2. 解释类

    执行方式类似于我们日常生活中的“同声翻译”,应用程序源代码一边由相应语言的解释器“翻译”成目标代码(机器语言),一边执行,因此效率比较低,而且不能生成可独立执行的可执行文件,应用程序不能脱离其解释器(想运行,必须先装上解释器,就像跟老外说话,必须有翻译在场),但这种方式比较灵活,可以动态地调整、修改应用程序。如Python、Javascript、PHP、Ruby等语言。

现在很多高级语言同时兼顾编译和解释型语言的特点。比如Java为了兼顾跨平台和运行速度,Java 源代码首先会被编译为字节码文件(.class),但并非是机器语言,而是需要在 JVM 上运行,而 .class 文件在 JVM 上是解释执行的。所以 Java 兼具编译型语言和解释型语言的特点,为了更高的效率,JVM 还引入了 JIT(just-in-time,即时编译)编译器,在 Java 程序运行时进一步编译,转换成高度优化的机器代码。由于Java更接近编译型语言的特征,个人更愿意称Java为编译型语言.

下图分别是编译型语言、解释型语言、以及基于虚拟机的语言。

file

面向过程 VS 面向对象

高级语言还可以分为面向过程和面向对象两种.

面向过程的语言

  • C / Fortran / Pascal / SQL
  • 把重复逻辑封装成函数
  • 控制结构构造: 顺序,选择,循环
  • 对于简单的程序, 流程明确, 简单粗暴高效, 但不利于编写复杂系统
  • 代码重用性低,扩展能力差,后期维护难度比较大

面向对象的语言

  • C++ / C# / Java / Python / Scala
  • 抽象共同属性和行为封装成类, 万物皆对象
  • 类与类之间关系更符合真实世界特征, 符合人类思维习惯
  • 使用类组织代码, 结构化, 模块化
  • 易扩展,代码重用率高,可继承,可覆盖,可以设计出低耦合的系统;
  • 易维护,系统低耦合的特点有利于减少程序的后期维护工作量。
  • 执行效率通常没有面向过程的语言高。

很多语言比如Javascript和PHP一开始是面向过程的, 后面也逐渐支持面向对象了.

这里所说的某个语言是面向过程或者是面向对象的, 这是通常情况下使用这个语言编写出来的代码是面向过程或者是面向对象的. 面向过程和面向对象更多是编程思想的差异, 并不是使用支持面向对象的语言比如Java写出来的就一定是面向对象的代码, 反过来使用纯C语言也不见得只能写出面向过程的代码, 只不过使用C语言编写面向过程的代码相对容易, 而使用Java语言编写面向对象代码相对容易, 因为Java是天生支持面向对象的语言.

国产编程语言

  • 易语言(中文写代码)
    file
  • 木兰(假)
    file

搞笑篇

下面这些并不算真正的编程语言, 只是好玩:

  • 文言文编程
    file w:30cm

  • 粤语编程
    w

面向交互的高级语言

  • HTML
  • Javascript
  • XML
  • ASP
  • PHP

编程语言排行榜

TIOBE编程语言排行榜是编程语言流行趋势的一个指标,每月更新,这份排行榜排名基于互联网有经验的程序员、课程和第三方厂商的数量。排名使用著名的搜索引擎(诸如Google、MSN、Yahoo!、Wikipedia、YouTube以及Baidu等)进行计算。请注意这个排行榜只是反映某个编程语言的热门程度,并不能说明一门编程语言好不好,或者一门语言所编写的代码数量多少。

这个排行榜可以用来考查你的编程技能是否与时俱进,也可以在开发新系统时作为一个语言选择依据。

C/C++如果联合起来毫无疑问会排到第一名. 只要计算机还是冯诺依曼体系, C语言将会一直永存.可以说C语言是最接近底层的高级语言, 执行效率最高.

反过来C/C++由于其复杂性不利于编写大型企业级网站项目, 因为内存必须程序员手动管理, 指针用起来也比较容易出错. 而Java语言没有指针类型, 又支持垃圾回收(GC)机制, 更容易编写出健壮的程序.

如何成为大神

  • 多敲多练多想 大神如何敲代码
  • 云笔记多总结
    • 推荐有道云笔记
    • 及时偿还技术债
  • 善用搜索引擎
    • 谷歌/百度
  • 学会提出问题
    • 切忌无脑问
    • 说明前因后果
    • 总结经验

Views: 52

JSON简介

JSON简介

JSON 是一种与开发语言无关的、轻量级的数据格式 - JavaScript Object Notation

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。 易于人阅读和编写。同时也易于机器解析和生成。 它基于JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999的一个子集。 JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, C#, Java, JavaScript, Perl, Python等)。 这些特性使JSON成为理想的数据交换语言。

优点:易于人的阅读和编写,易于程序的解析和生产
基本格式:{key:value}

标准的json数据表示
数据结构:Object、Array
基本类型:String、number、true、false、null

因此如果需要表示时间类型的时候需要使用约定格式的字符串如"YYYY-MM-DD"  
然后在后台对数据进行解析

数据结构——object
使用花括号{}包含的键值对结构,key必须是String类型,value为任何基本类型或数据结构
数据结构——Array
使用中括号[]来起始,并用逗号来分割元素

下面这个网站可以帮助你解析json字符串,并生成文件:
JSON Editor Online

{
  "array": [
    1,
    2,
    3
  ],
  "boolean": true,
  "null": null,
  "number": 123,
  "object": {
    "a": "b",
    "c": "d",
    "e": "f"
  },
  "string": "Hello World"
}
注意,JSON文件里不允许注释

具体资料可以查阅-->JSON中文官方网站
这里还有所有语言的示例代码以及相关工具等等内容,十分详尽,如

Java:
    JSON-java.
    JSONUtil.
    jsonp.
    Json-lib.
    Stringtree.
    SOJO.
    json-taglib.
    Flexjson.
    JON tools.
    Argo.
    jsonij.
    fastjson.
    mjson.
    jjson.
    json-simple.
    json-io.
    JsonMarshaller.
    google-gson.
    Json-smart.
    FOSS Nova JSON.
    Corn CONVERTER.
    Apache johnzon.
    Genson.
    JSONUtil.
    cookjson.

JavaScript:
    JSON.
    json2.js.
    clarinet.
    Oboe.js.

利用org.Json构建json数据

利用org.Json在Java中使用JSONObject对象

Json是Android SDK的官方库
适合移动端开发

例子 JSON和对象的转换,在pom.xml中添加依赖包

<!--JAVA ASON--> 
<dependency>  <groupId>org.json</groupId>
  <artifactId>json</artifactId>
  <version>20090211</version>
 </dependency>

测试类

    @Test
    public void testJSONObject(){
        BasicConfigurator.configure();
        JSONObject jsonObject = new JSONObject();
        Object nullObject= null;//使编译器跳过对null类型对检查
        try {
            jsonObject.put("name","张全蛋");
            jsonObject.put("age","25.2");
            jsonObject.put("birthday","1990-01-01");
            jsonObject.put("school","富土康");
            jsonObject.put("major", new String[]{"理发", "挖掘机"});
            jsonObject.put("hasGirlFriend",false);
            jsonObject.put("hascar",nullObject);
            jsonObject.put("comment","这是一个注释,因为json格式里是不能使用常规注释手段的");

            System.out.println(jsonObject.toString());
        } catch (JSONException e) {
            e.printStackTrace();
        }
        //output
        //{"birthday":"1990-01-01","major":["理发","挖掘机"],"school":"富土康","name":"张全蛋","comment":"这是一个注释,因为json格式里是不能使用常规注释手段的","hasGirlFriend":false,"age":"25.2"}
    }
}

输出

  {
    "birthday": "1990-01-01",
    "major": [
      "理发",
      "挖掘机"
    ],
    "school": "富土康",
    "name": "张全蛋",
    "comment": "这是一个注释,因为json格式里是不能使用常规注释手段的",
    "hasGirlFriend": false,
    "age": "25.2"
  }

Json在Java中使用字符串生成JSONObject对象

@Test
public void createJSONObjectByString(){
    JSONObject jsonObject = null;
    String jsonStr ="{\"errno\":0,\"data\":true,\"errmsg\":\"success\"}";
    try {
        jsonObject = new JSONObject(jsonStr);
    } catch (JSONException e) {
        e.printStackTrace();
    }
    if(jsonObject != null){
        System.out.println(jsonObject.toString());
        //out:
        // {"errno":0,"data":true,"errmsg":"success"}
    }
}

Json在Java中使用MAP生成JSONObject对象


    @Test
    public void createJSONObjectByMap() {
        Object nullObject = null;//使编译器跳过对null类型对检查

        Map<String, Object> jsonObject = new HashMap<String, Object>();
        jsonObject.put("name", "张全蛋");
        jsonObject.put("age", "25.2");
        jsonObject.put("birthday", "1990-01-01");
        jsonObject.put("school", "富土康");
        jsonObject.put("major", new String[]{"理发", "挖掘机"});
        jsonObject.put("hasGirlFriend", false);
        jsonObject.put("hascar", nullObject);
        jsonObject.put("comment", "这是一个注释,因为json格式里是不能使用常规注释手段的");

        System.out.println(new JSONObject(jsonObject).toString());

        //out:{"birthday":"1990-01-01","major":["理发","挖掘机"],"school":"富土康","name":"张全蛋","comment":"这是一个注释,因为json格式里是不能使用常规注释手段的","hasGirlFriend":false,"age":"25.2","hascar":null}
    }

Json在Java中使用JavaBean对象来生成JSONObject对象

    @Test
    public void createJSONObjectbyJavaBean() {
        //常用
        Message msg = new Message();
        msg.errno = 0;
        msg.errmsg = "success";
        msg.data = true;
        System.out.println(new JSONObject(msg).toString());

        //out:
        //{"errno":0,"data":true,"errmsg":"success"}
    }

从文件读取JSON数据

依赖

    <!--org.apache.commons.io.FileUtils-->
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.4</version>
    </dependency>

测试文件的内容 与测试类文件同级 名为success_message.json

    {
        "errno": 0,
        "errmsg":["智商余额不足","需发红包给刘老师@95820608进行充值!"],
        "data":true
    }

测试类中的测试方法


    @Test
    public void readJSONFile() throws IOException, JSONException {
        //从当前类的同源文件中查找特定路径的文件
        File file = new File(ReadJSONFileSample.class.getResource("/success_message.json").getFile());
        String content = FileUtils.readFileToString(file);
        JSONObject jsonObject = new JSONObject(content);
        if(!jsonObject.isNull("errno")){
            System.out.println("错误代码 --> " + jsonObject.getInt("errno"));
        }
        if(!jsonObject.isNull("errmsg")){
            System.out.println("错误信息 --> " + jsonObject.getString("errmsg").toString());
        }
        if(!jsonObject.isNull("data")){
            System.out.println("错误内容 --> " + jsonObject.getBoolean("data"));
        }
        //out
        //错误代码 --> 0
        //错误信息 --> ["智商余额不足","需发红包给刘老师@95820608进行充值!"]
        //错误内容 --> true

        //将JSONObject中的属性值为数组的值读取出来遍历
        //如果值不是数组则会导致报错!
        if(!jsonObject.isNull("errmsg")){
            JSONArray jsonArray = jsonObject.getJSONArray("errmsg");
            for (int i = 0; i < jsonArray.length(); i++) {
                String m = (String)jsonArray.get(i);
                System.out.println(m);
            }
        }
        //out
        //智商余额不足
        //需发红包给刘老师@95820608进行充值!
    }
}

使用Gson 来更加灵活的构建json数据

gson是google提供的开源api
Gson功能更强大可以在json格式和javaObject之间通过反射灵活转换
更适合服务端后台的开发

  • 优点:
  • 1.轻量, 支持将json字符串反向生成指定类的对象
  • 2.支持日期格式

使用Gson生成json

pom.xml 添加依赖

<!--GOOGLE GSON-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.4</version>
</dependency>

测试类和测试方法


    //使用GSON注解更改序列化后JSON的属性名称
    protected class Message{
        @SerializedName("ERRNO")
        int errno;
        String errmsg;
        boolean data;
    }

    @Test
    public void gsonCreate(){
        Message msg = new Message();
        msg.errno = 0;
        msg.errmsg = "success";msg.data = true;

        Gson gson = new Gson();
        String jsonStr = gson.toJson(msg);
        System.out.println(jsonStr);
        //out:{"ERRNO":0,"errmsg":"success","data":true}
    }

使用gsonBuilder定制json的转换规则

    protected class FailMessage{
        private int errno;
        private String errmsg;
        private boolean data;
        private transient String  ignor;//transient 声明的属性不会被序列化
    }

    @Test
    public void gsonCreateByGsonBuilder(){
        FailMessage msg = new FailMessage();
        msg.errno = -1;
        msg.errmsg = "fail";msg.data = false;

        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.setPrettyPrinting();//美化格式后输出!方便调试
        gsonBuilder.setFieldNamingStrategy(new FieldNamingStrategy() {
        //通过回调函数修改反射得到的属性值
            @Override
            public String translateName(Field field) {
                if(field.getName().equals("errno")){
                    return ("error-code");
                }else{
                    return field.getName();
                }
            }
        });
        Gson gsonNew = gsonBuilder.create();
        System.out.println(gsonNew.toJson(msg));////美化格式后输出!方便调试
        //out:
        //        {
        //              "error-code": -1, //errno被修改为error-code
        //              "errmsg": "fail",
        //              "data": false
        //        }
    } 

使用gson反向将json字串转换成指定类

message.json文件内容如下:

    {
        "errno": 0,
        "errmsg":"智商余额不足,需发红包给刘老师@95820608进行充值!",
        "data":true
    }

测试类

public class Message {
  public int errno;
  public String errmsg;
  public boolean data;

    public int getErrno() {
        return errno;
    }

    public void setErrno(int errno) {
        this.errno = errno;
    }

    public String getErrmsg() {
        return errmsg;
    }

    public void setErrmsg(String errmsg) {
        this.errmsg = errmsg;
    }

    public boolean getData() {
        return data;
    }

    public void setData(boolean data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "Message{" +
                "errno=" + errno +
                ", errmsg='" + errmsg + '\'' +
                ", data=" + data +
                '}';
    }
}

测试方法

    @Test
    public void GsonReadTest() throws IOException {
        File file = new File(GsonSample.class.getResource("/message.json").getFile());
        String content = FileUtils.readFileToString(file);

        Gson gson = new Gson();
        //将json文件转换成指定类
        Message msg = gson.fromJson(content, com.niit.mvcdemo.model.Message.class);
        System.out.println(msg.toString());
        //out:
        //Message{errno=0, errmsg='智商余额不足,需发红包给刘老师@95820608进行充值!', data=true}
    }

反向将json字串转换成指定带有时间属性的类

    class Logger{
        private String content;
        private Date createtime;
        @Override
        public String toString() {
            return "Logger{content='" + content + '\'' + ", createtime=" + createtime.toString() +'}';
        }
    }

    @Test
    public void GsonFromJsonToObject() throws IOException {

        Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();

        //使用gsonBuilder指定时间类型格式将json文件转换成指定类
        Logger logger = gson.fromJson("{\"content\":\"Application started...\",\"createtime\":\"1970-01-01 00:00:00\"}", json.GsonSample.Logger.class);
        System.out.println(logger.toLocalString());
        //out:Logger{content='Application started...', createtime=1970-1-1 0:00:00}
    }
小知识
yyyy-MM-dd HH:mm:ss
年-月-日 时:分:秒
大写是为了区分“月”与“分”
顺便说下HH为什么大写,是为了区分12小时制与24小时制。
小写的h是12小时制,大写的H是24小时制。
书写格式和语言规定有关,上述写法是Windows系统中的我们常见的写法,包括日期设置于办公软件在内。在其他语言中有类似的但使用符号或格式不同的写法。
有的时候我们会看到这样的格式:yyyy-M-d H:m:s
mm与m等,它们的区别为是否有前导零:H,m,s表示非零开始,HH,mm,ss表示从零开始。
比如凌晨1点2分,HH:mm显示为01:02,H:m显示为1:2。
以2014年1月1日凌晨1点1分1秒(当天是星期三)为例子介绍一下其他的:
yyyy/yyy/yy/y 显示为 2014/2014/14/4
        (3个y与4个y是一样的,为了便于理解多写成4个y)
MMMM/MMM/MM/M 显示为 一月/一月/01/1
        (4个M显示全称,3个M显示缩写,不过中文显示是一样的,英文就是January和J)
dddd/ddd/dd/d 显示为 星期三/周三(有的语言显示为“三”)/01/1
        (在英文中同M一样,4个d是全称,3个是简称;
dddd/ddd表示星期几,dd/d表示几号)
HH/H/hh/h 显示为 01/1/01 AM/1 AM
剩下的mm/m/ss/s只是前导零的问题了。
yyyy/M/d/dddd H:mm:ss 就是 2014年1月1日星期三 1:01:01

gson转换会自动将数组值映射成实体类对应的集合属性

注意Map会被映射成google自定义的Map类型..

class Product{
    private String title;
    private String[] array;
    private List list;
    private Set set;
    private Map map;
}

@Test
public void GsonMappingForCollection(){
    String jsonStr= "" +
            "{\n" +
            "  \"title\": \"product_id_1\",\n" +
            "  \"array\": [\n" +
            "    \"array1\",\n" +
            "    \"array2\"\n" +
            "  ],\n" +
            "  \"list\": [\n" +
            "    \"list1\",\n" +
            "    \"list2\"\n" +
            "  ],\n" +
            "  \"set\": [\n" +
            "    \"set2\",\n" +
            "    \"set1\"\n" +
            "  ],\n" +
            "  \"map\": {\n" +
            "    \"1\": \"map1\",\n" +
            "    \"2\": \"map2\"\n" +
            "  }\n" +
            "}"
            ;
    //通过gson将制定类的实例转化成json字符串
    Product product = new Gson().fromJson(jsonStr,GsonSample.Product.class);
    System.out.println(product.title.toString() + "\t\t" + product.title.getClass());
    //out:        product_id_1          class java.lang.String
    System.out.println(Arrays.toString(product.array) + "\t\t" + product.array.getClass());
    //out:        [array1, array2]      class [Ljava.lang.String;
    System.out.println(product.set.toString() + "\t\t" + product.set.getClass());
    //out:        [set2, set1]          class java.util.LinkedHashSet
    System.out.println(product.list.toString() + "\t\t" + product.list.getClass());
    //out:        [list1, list2]        class java.util.ArrayList
    System.out.println(product.map.toString() + "\t\t" + product.map.getClass());
    //out:        {1=map1, 2=map2}      class com.google.gson.internal.LinkedTreeMap
}

jackson实现Json序列化和反序列化

利用fasterxml.jackson实现JSON序列化和反序列化

Gradle依赖
fasterxml.jackson依赖jackson-core, jackson-databind和jackson-annotations.
示例如下:

compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.5'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.9.5'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.9.5'

序列化和反序列化实现

JSON工具类
我们构造一个JSON工具类,实现JSON序列化和反序列化, 主要用到的类为com.fasterxml.jackson.databind.ObjectMapper, 代码示例如下:

package com.notepad.util;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.HashMap;
import java.util.Map;

public class JsonSerializer {

    /**
     * JSON序列化
     *
     * @param object 对象
     * @return JSON字符串
     */
    public static String serialize(Object object) {
        ObjectMapper mapper = new ObjectMapper();
        try {
            return mapper.writeValueAsString(object);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return "";
        }
    }

    /**
     * JSON字符串反序列化
     *
     * @param jsonStr JSON字符串
     * @return a Map
     */
    public static Map deserialize(String jsonStr) {
        try {
            return deserialize(jsonStr, Map.class);
        } catch (Exception e) {
            e.printStackTrace();
            return new HashMap();
        }
    }

    public static <T> T deserialize(String jsonStr, Class<T> classType) throws Exception {
        return new ObjectMapper().readValue(jsonStr, classType);
    }
}

Entity
为测试我们编写的JSON工具类, 定义一个Entity对象。
为了构建的Entity对象被JSON工具类操作, 关于Entity有几点说明:

  • Entity对象必须有默认构造函数
  • 成员变量必须有对应的Setter方法
  • 可选: 可通过@JsonProperty自定义序列化和反序列化对应的字符的名称,如@JsonProperty(“UID”),则序列化时uid字段显示为UID,同理反序列化时找到字符串中UID对应的值,复制给uid。
  • 可选:可通过@JsonIgnore注解,过滤掉不需要进行序列化的成员变量。

示例如下:

package com.notepad.thinkingnote.domain;

import com.fasterxml.jackson.annotation.JsonProperty;

public class Entity {

    public Entity() {}

    public Entity(String uid, String name) {
        this.uid = uid;
        this.name = name;
    }

    /** 实体标识符 */
    @JsonProperty("UID")
    private String uid;

    @JsonProperty("name")
    private String name;

    public void setUid(String uid) {
        this.uid = uid;
    }

    public String getUid() {
        return uid;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

单元测试

编写单元测试,测试Entity的序列化和反序列化, 示例如下:

package com.notepad.thinkingnote.domain;

import com.notepad.util.JsonSerializer;
import org.junit.Test;

import static org.junit.Assert.*;

public class EntityTest {

    @Test
    public void testSerialize() throws Exception {
        Entity entity = new Entity("James", "James");
        System.out.println(JsonSerializer.serialize(entity));
    }

    @Test
    public void testDeserialize() throws Exception {
        String test = "{\"UID\":\"James\",\"name\":\"James\"}";
        Entity entity = JsonSerializer.deserialize(test, Entity.class);
        System.out.println(entity.getUid() + ":" + entity.getName());
    }
}

输出结果如下:
entity的序列化结果, 显示字段UID
{"UID":"James","name":"James"}

entity的序列化结果
James:James

UnrecognizedPropertyException异常解决

通过上面的介绍, 我们基本了解了JSON的序列化和反序列化,不过有时我们会遇到一种问题。
考虑这样一种情况,我们针对Http接口返回的JSON字符串,构建了一个具体的Entity对象,但是当Http接口中突然增加了一个字段type,如果还是按照原来方式解析会如何呢?

@Test
    public void testDeserialize() throws Exception {
        String test = "{\"UID\":\"James\",\"name\":\"James\", \"type\":\"entity\"}";
        Entity entity = JsonSerializer.deserialize(test, Entity.class);
        System.out.println(entity.getUid() + ":" + entity.getName());
    }

出现UnrecognizedPropertyException异常:

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "type" ....

即对于新添加的type字符无法识别。如何解决这个问题呢?我们这里提供2种方法。

  • 方法1 JsonIgnoreProperties注解
    利用fasterxml.jackson提供的@JsonIgnoreProperties注解,针对无法识别的属性进行过滤。这里主要是修改需要进行反序列化的对象Entity,示例如下:
    针对无法识别的属性进行过滤

    @JsonIgnoreProperties(ignoreUnknown = true)
    public class Entity {...}
  • 方法2 DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
    方法1的修改需要对每一个需要进行反序列化的类进行修改, 不太方便。
    方法2通过修改JSON工具类的反序列化方法,设置DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES的值为false, 可以仅一次修改就适用全部对象。
    示例如下:

    public static <T> T deserialize(String jsonStr, Class<T> classType) throws Exception {
        // 添加configure, 设置DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES为false
        // 则对于无法识别的属性直接过滤
        return new ObjectMapper()
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
                .readValue(jsonStr, classType);
    }

Views: 35