HBase的微博案例

1. 实验环境说明

安装HBase前已经安装好了Hadoop完全分布式集群,和Zookeeper集群.其中Hadoop版本为2.7.2 , Zookeeper的版本为3.4.12, 虚拟机系统为centos7.5

另外,本人所有的软件都会装在/software/spath/目录下

实验用到的三台虚拟机:
在这里插入图片描述
在这里插入图片描述
Hadoop集群部署:
在这里插入图片描述

2. 实验目的

通HBase的微博模拟案例,加强对HBase的理解和应用,能够把知识用于实践,并且能熟系掌握HBase的Java API操作.

3. 实验步骤

3.1 正常启动HADOOP、ZOOKEEPER

在这里插入图片描述
在这里插入图片描述
启动Zookeeper
在这里插入图片描述
在这里插入图片描述

3.2 启动HBASE

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.3 实验步骤

3.3.1 先把虚拟机的地址映射加入到windows下的Hosts

方法一:
直接在C:\Windows\System32\drivers\etc下编辑hosts文件,然后加入虚拟机的地址映射.
在这里插入图片描述
方法二: (推荐)
实验SwitchHosts软件,启用不同的映射策略

在这里插入图片描述

3.3.2 搭建Maven项目

在这里插入图片描述

3.3.3 添加依赖并在resource目录下添加hbase-site.xml

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>red.ygs</groupId>
    <artifactId>hbase</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.hbase</groupId>
            <artifactId>hbase-server</artifactId>
            <version>1.3.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hbase</groupId>
            <artifactId>hbase-client</artifactId>
            <version>1.3.1</version>
        </dependency>
    </dependencies>
</project>

在这里插入图片描述
hbase-site.xml

<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>

<configuration>
    <property>
        <name>hbase.rootdir</name>
        <value>hdfs://YGS02:9000/HBase</value>
    </property>

    <property>
        <name>hbase.cluster.distributed</name>
        <value>true</value>
    </property>

    <!-- 0.98 后的新变动,之前版本没有.port,默认端口为 60000 -->
    <property>
        <name>hbase.master.port</name>
        <value>16000</value>
    </property>

    <property>
        <name>hbase.zookeeper.quorum</name>
        <value>YGS02,YGS03,YGS04</value>
    </property>

    <property>
        <name>hbase.zookeeper.property.dataDir</name>
        <value>/software/spath/zookeeper_3.4.12/data</value>
    </property>
</configuration>

在这里插入图片描述

3.3.4 HBase微博案例

3.3.4.1 表结构设计

在这里插入图片描述
A.创建微博内容表
表结构:
在这里插入图片描述

B.创建用户关系表
表结构
在这里插入图片描述
C. 创建微博收件箱表
表结构
在这里插入图片描述

3.3.4.2 定义常量接口Constants
package red.ygs.bigdata.weibo.constants;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;

/**
 * @author YGS---www.ygs.red
 * @create 2022-02-10 21:36
 * @description: 常量类
 */
public class Constants {

    //HBase的配置信息
    public static Configuration CONFIGURATION = HBaseConfiguration.create();

    //命名空间
    public static String NAMESPACE = "weibo";

    //微博内容表
    public static String CONTENT_TABLE = "weibo:content";
    public static String CONTENT_TABLE_CF ="info";
    public static int CONTENT_TABLE_VERSION = 1;

    //用户关系表
    public static String RELATION_TABLE = "weibo:relation";
    public static String RELATION_TABLE_CF1 = "attends";
    public static String RELATION_TABLE_CF2 = "fans";
    public static int RELATION_TABLE_VERSION = 1;

    //收件箱表
    public static String INBOX_TABLE = "weibo:inbox";
    public static String INBOX_TABLE_CF ="info";
    public static int INBOX_TABLE_VERSION = 2;

}

3.3.4.3 创建命名空间以及表名的定义
package red.ygs.bigdata.weibo.utils;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.*;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.util.Bytes;
import red.ygs.bigdata.weibo.constants.Constants;

import java.io.IOException;

/**
 * @author YGS---www.ygs.red
 * @create 2022-02-10 21:40
 * @description: HBase工具类
 */
/*
* 1.创建命名空间
* 2.判断表是否存在
* 3.创建表
*
* */
public class HBaseUtils {

    // TODO 创建命名空间
    public static void  createNameSpace(String nameSpace) throws IOException {
        //1.获取connection对象
        Connection connection = ConnectionFactory.createConnection(Constants.CONFIGURATION);

        //2.获取admin对象
        Admin admin = connection.getAdmin();

        //3.构建命名空间描述器
        NamespaceDescriptor namespaceDescriptor = NamespaceDescriptor.create(nameSpace).build();

        //4.创建命名空间
        admin.createNamespace(namespaceDescriptor);

        //5.关闭资源
        admin.close();
        connection.close();

    }

    // TODO 判断表是否存在
    private static Boolean isTableExist(String tableName) throws IOException {

        //1.获取connection对象
        Connection connection = ConnectionFactory.createConnection(Constants.CONFIGURATION);

        //2.获取admin对象
        Admin admin = connection.getAdmin();

        //3.判断是否存在
        Boolean result=admin.tableExists(TableName.valueOf(tableName));

        //4..关闭资源
        admin.close();
        connection.close();

        return result;
    }

    // TODO 创建表
    public static void createTable(String tableName, int version , String... cfs) throws IOException {
        //1.判断是否存在列族信息
        if (cfs.length <= 0 ){
            System.out.println("请设置列族信息!!!");
            return;
        }
        //2.判断表是否存在
        if(isTableExist(tableName)){
            System.out.println(tableName+"表已经存在!!!");
            return;
        }

        //3.获取connection对象
        Connection connection = ConnectionFactory.createConnection(Constants.CONFIGURATION);

        //4.获取admin对象
        Admin admin = connection.getAdmin();

        //5.创建表描述器
        HTableDescriptor hTableDescriptor = new HTableDescriptor(TableName.valueOf(tableName));

        //6.循环添加列族信息
        for (String cf: cfs ) {
            //6.1.创建列族描述器
            HColumnDescriptor hColumnDescriptor = new HColumnDescriptor(cf);

            //6.2.设置版本
            hColumnDescriptor.setMaxVersions(version);

            //6.3.添加具体的列族信息
            hTableDescriptor.addFamily(hColumnDescriptor);
        }

        //7. 创建表
        admin.createTable(hTableDescriptor);

        //8.关闭资源
        connection.close();
        admin.close();
    }


}
3.3.4.4 微博操作的Dao类
package red.ygs.bigdata.weibo.dao;

import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.filter.CompareFilter;
import org.apache.hadoop.hbase.filter.RowFilter;
import org.apache.hadoop.hbase.filter.SubstringComparator;
import org.apache.hadoop.hbase.util.Bytes;
import red.ygs.bigdata.weibo.bean.Message;
import red.ygs.bigdata.weibo.constants.Constants;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * @author YGS---www.ygs.red
 * @create 2022-02-11 10:54
 * @description:
 */
/*
 * 1.发布微博
 * 2.删除微博
 * 3.关注用户
 * 4.取关用户
 * 5.获取用户的微博详情
 * 6.获取用户的初始化页面
 * */

public class HBaseDao {


    /**
     * 发布微博
     * a、微博内容表中数据+1
     * b、向微博收件箱表中加入微博的 Rowkey
     */
    // TODO 1.发布微博
    public static void publishWeiBo(String uid, String content) throws IOException {
        // 1.获取Connection对象
        Connection connection = ConnectionFactory.createConnection(Constants.CONFIGURATION);

        // 第一部分:操作微博内容表
        // 1.获取微博内容表对象
        Table conTable = connection.getTable(TableName.valueOf(Constants.CONTENT_TABLE));

        // 2.获取当前时间戳
        long ts = System.currentTimeMillis();

        // 3.获取rowkey
        String rowKey = uid + "_" + ts;

        // 4.创建Put对象
        Put conPut = new Put(Bytes.toBytes(rowKey));

        // 5.给put对象赋值
        conPut.addColumn(Bytes.toBytes(Constants.CONTENT_TABLE_CF), Bytes.toBytes("content"), Bytes.toBytes(content));

        // 6.执行插入数据的操作
        conTable.put(conPut);

        // 第二部分:操作微博收件箱表
        // 1.获取用户关系表对象
        Table relaTable = connection.getTable(TableName.valueOf(Constants.RELATION_TABLE));

        // 2.获取当前发布微博人的fans列簇数据
        Get get = new Get(Bytes.toBytes(uid));
        Result result = relaTable.get(get);

        // 3.创建一个集合,用于存放微博内容表的Put对象
        ArrayList<Put> inboxPuts = new ArrayList<Put>();
        // 4.遍历粉丝
        for (Cell cell : result.rawCells()) {
            // 5.构建微博收件箱表的Put对象
            Put inboxPut = new Put(CellUtil.cloneQualifier(cell));

            // 6.给收件箱表的Put对象赋值
            inboxPut.addColumn(Bytes.toBytes(Constants.INBOX_TABLE_CF), Bytes.toBytes(uid), Bytes.toBytes(rowKey));

            // 7.将收件箱表的Put对象存入集合
            inboxPuts.add(inboxPut);
        }

        // 8.判断是否有粉丝
        if (inboxPuts.size() > 0) {
            // 获取收件箱对象
            Table inboxTable = connection.getTable(TableName.valueOf(Constants.INBOX_TABLE));

            // 执行收件箱表数据插入操作
            inboxTable.put(inboxPuts);

            // 关闭收件箱表
            inboxTable.close();
        }

        // 关闭资源
        connection.close();
        relaTable.close();
        conTable.close();
    }

    /**
     * 关注用户逻辑
     * a、在微博用户关系表中,对当前主动操作的用户添加新的关注的好友
     * b、在微博用户关系表中,对被关注的用户添加粉丝(当前操作的用户)
     * c、当前操作用户的微博收件箱添加所关注的用户发布的微博 rowkey
     */
    // TODO 3.关注用户
    public static void addAttends(String uid, String... attends) throws IOException {

        //校验是否添加了待关注的人
        if (attends.length <= 0) {
            System.out.println("请选择待关注的人!");
            return;
        }

        // 获取Connection对象
        Connection connection = ConnectionFactory.createConnection(Constants.CONFIGURATION);

        // 第一部分:操作用户关系表
        // 1.获取用户关系表对象
        Table relaTable = connection.getTable(TableName.valueOf(Constants.RELATION_TABLE));

        // 2.创建一个集合,用于存放用户关系表的Put对象
        ArrayList<Put> relaPuts = new ArrayList<Put>();

        // 3.创建操作着的Put对象
        Put uidPut = new Put(Bytes.toBytes(uid));

        // 4.循环创建被关注的着Put对象
        for (String attend : attends) {
            // 5.给操作者的Put对象赋值
            uidPut.addColumn(Bytes.toBytes(Constants.RELATION_TABLE_CF1), Bytes.toBytes(attend), Bytes.toBytes(attend));

            // 6.创建被关注着的Put对象
            Put attendPut = new Put(Bytes.toBytes(attend));

            // 7.给被关注着的Put对象赋值
            attendPut.addColumn(Bytes.toBytes(Constants.RELATION_TABLE_CF2), Bytes.toBytes(uid), Bytes.toBytes(uid));

            // 8.将被关注着的Put对象放入集合
            relaPuts.add(attendPut);

        }
        // 9.将操作者的Put对象添加至集合
        relaPuts.add(uidPut);

        // 10.执行用户关系表的插入数据操作
        relaTable.put(relaPuts);

        // 第二部分:操作收件箱表
        // 1.获取微博内容表的对象
        Table conTable = connection.getTable(TableName.valueOf(Constants.CONTENT_TABLE));

        // 2.创建收件箱表的Put对象
        Put inboxPut = new Put(Bytes.toBytes(uid));

        // 3.循环attends,获取每个被关注着的近期发布的微博
        for (String attend : attends) {

            // 4.获取当前被关注着的近期发布的微博scan
            Scan scan = new Scan(Bytes.toBytes(attend + "_"), Bytes.toBytes(attend + "|"));
            ResultScanner resultScanner = conTable.getScanner(scan);
            // 5.对获取的值进行遍历

            // 定义一个时间戳
            long ts = System.currentTimeMillis();

            for (Result result : resultScanner) {
                // 6.给收件箱的Put对象赋值
                inboxPut.addColumn(Bytes.toBytes(Constants.INBOX_TABLE_CF), Bytes.toBytes(attend), ts++, result.getRow());
            }

        }
        // 7.判断当前的Put对象是否为空
        if (!inboxPut.isEmpty()) {


            // 获取收件箱表对象
            Table inboxTable = connection.getTable(TableName.valueOf(Constants.INBOX_TABLE));

            // 插入数据
            inboxTable.put(inboxPut);

            // 关闭收件箱表连接
            inboxTable.close();
        }

        // 关闭资源
        relaTable.close();
        conTable.close();
        connection.close();
    }

    /**
     * 取消关注(remove)
     * a、在微博用户关系表中,对当前主动操作的用户删除对应取关的好友
     * b、在微博用户关系表中,对被取消关注的人删除粉丝(当前操作人)
     * c、从收件箱中,删除取关的人的微博的 rowkey
     */
    // TODO 4.取关用户
    public static void deleteAttends(String uid, String... dels) throws IOException {

        if (dels.length <= 0) {
            System.out.println("请添加待取关的用户!");
            return;
        }

        // 1.获取Connection对象
        Connection connection = ConnectionFactory.createConnection(Constants.CONFIGURATION);

        // 第一部分:操作用户关系表
        // 1.获取用户关系表对象
        Table relaTable = connection.getTable(TableName.valueOf(Constants.RELATION_TABLE));

        // 2.创建一个集合,用于存放用户关系表的Delete对象
        ArrayList<Delete> relaDeletes = new ArrayList<Delete>();

        // 3.创建操作者的Delete对象
        Delete uidDelete = new Delete(Bytes.toBytes(uid));

        // 4.循环创建被取关的Delete对象
        for (String del : dels) {
            // 5.给操作者的Delete对象赋值
            uidDelete.addColumns(Bytes.toBytes(Constants.RELATION_TABLE_CF1), Bytes.toBytes(del));
            // 6.创建被取关的Delete对象
            Delete delDelete = new Delete(Bytes.toBytes(del));
            // 7.给被取关者的Delete对象赋值
            delDelete.addColumns(Bytes.toBytes(Constants.RELATION_TABLE_CF2), Bytes.toBytes(uid));
            // 8.将被取关者的Delete对象添加至集合
            relaDeletes.add(delDelete);
        }
        // 9.将操作者的Delete对象添加至集合
        relaDeletes.add(uidDelete);

        // 10.执行用户关系表的删除操作
        relaTable.delete(relaDeletes);

        // 第二部分:操作收件箱表

        // 1.获取收件箱表对象
        Table inboxTable = connection.getTable(TableName.valueOf(Constants.INBOX_TABLE));

        // 2.创建操作者的Delete对象
        Delete inboxDelete = new Delete(Bytes.toBytes(uid));

        // 3.给操作者的Delete赋值
        for (String del : dels) {
            inboxDelete.addColumns(Bytes.toBytes(Constants.INBOX_TABLE_CF), Bytes.toBytes(del));
        }
        // 4.执行收件箱表的删除操作
        inboxTable.delete(inboxDelete);

        // 关闭资源
        relaTable.close();
        inboxTable.close();
        connection.close();
    }

    /**
     * 获取微博实际内容
     * a、从微博收件箱中获取所有关注的人的发布的微博的 rowkey
     * b、根据得到的 rowkey 去微博内容表中得到数据
     * c、将得到的数据封装到 Message 对象中
     */
    // TODO 5.获取用户的微博详情
    public static void getWeiBO(String uid) throws IOException {
        // 1.获取Connection对象
        Connection connection = ConnectionFactory.createConnection(Constants.CONFIGURATION);
        // 2.获取微博内容表对象
        Table conTable = connection.getTable(TableName.valueOf(Constants.CONTENT_TABLE));

        // 3.构建Scan对象
        Scan scan = new Scan();
        // 构建过滤器
        RowFilter rowFilter = new RowFilter(CompareFilter.CompareOp.EQUAL,new SubstringComparator(uid + "_"));

        scan.setFilter(rowFilter);
        // 4.获取数据
        ResultScanner resultScanner = conTable.getScanner(scan);

        // 5.解析数据并打印
        for (Result result : resultScanner) {
            for (Cell conCell : result.rawCells()) {
                System.out.println("RK="+Bytes.toString(CellUtil.cloneRow(conCell)));
                System.out.println("CF="+Bytes.toString(CellUtil.cloneFamily(conCell)));
                System.out.println("CN="+Bytes.toString(CellUtil.cloneQualifier(conCell)));
                System.out.println("Value="+Bytes.toString(CellUtil.cloneValue(conCell)));
            }
        }

        // 6.关闭资源
        conTable.close();
        connection.close();
    }

    // TODO 6.获取用户的的初始化页面
    public static void getInit(String uid) throws IOException {
        // 1.获取Connection对象
        Connection connection = ConnectionFactory.createConnection(Constants.CONFIGURATION);
        // 2.获取收件箱表对象
        Table inboxTable = connection.getTable(TableName.valueOf(Constants.INBOX_TABLE));

        // 3.获取微博内容表对象
        Table conTable = connection.getTable(TableName.valueOf(Constants.CONTENT_TABLE));

        // 4.创建收件箱表Get对象,并获取数据(设置最大版本)
        Get inboxGet = new Get(Bytes.toBytes(uid));
        inboxGet.setMaxVersions();
        Result result = inboxTable.get(inboxGet);

        // 5.遍历获取的数据
        for (Cell cell : result.rawCells()) {
            // 6.构建微博内容表Get对象
            Get contGet = new Get(CellUtil.cloneValue(cell));

            // 7.获取该Get对象的数据内容
            Result contResult = conTable.get(contGet);
            // 8.解析内容并打印
            for (Cell conCell : contResult.rawCells()) {
                System.out.println("RK:"+Bytes.toString(CellUtil.cloneRow(conCell)));
                System.out.println("CF:"+Bytes.toString(CellUtil.cloneFamily(conCell)));
                System.out.println("CN:"+Bytes.toString(CellUtil.cloneQualifier(conCell)));
                System.out.println("Value:"+Bytes.toString(CellUtil.cloneValue(conCell)));
            }

        }

        // 9.关闭资源
        inboxTable.close();
        conTable.close();
        connection.close();
    }
}

3.3.4.5 信息对象JavaBean类
package red.ygs.bigdata.weibo.bean;

/**
 * @author YGS---www.ygs.red
 * @create 2022-02-11 18:47
 * @description:
 */
public class Message {
    private String uid;
    private String timestamp;
    private String content;

    public String getUid() {
        return uid;
    }

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

    public String getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(String timestamp) {
        this.timestamp = timestamp;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return "Message [uid=" + uid + ", timestamp=" + timestamp +
                ", content=" + content + "]";
    }
}
3.3.4.6 测试类
package red.ygs.bigdata.weibo.test;

import red.ygs.bigdata.weibo.constants.Constants;
import red.ygs.bigdata.weibo.dao.HBaseDao;
import red.ygs.bigdata.weibo.utils.HBaseUtils;

import java.io.IOException;

/**
 * @author YGS---www.ygs.red
 * @create 2022-04-17 20:26
 * @description:
 */
public class WeiBoTest {
    public static void init(){
        try {
            // 创建命名空间
            HBaseUtils.createNameSpace(Constants.NAMESPACE);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            // 创建微博内容表
            HBaseUtils.createTable(Constants.CONTENT_TABLE,Constants.CONTENT_TABLE_VERSION,Constants.CONTENT_TABLE_CF);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 创建用户关系表
        try {
            HBaseUtils.createTable(Constants.RELATION_TABLE,Constants.RELATION_TABLE_VERSION,Constants.RELATION_TABLE_CF1,Constants.RELATION_TABLE_CF2);
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 创建收件箱表
        try {
            HBaseUtils.createTable(Constants.INBOX_TABLE,Constants.INBOX_TABLE_VERSION,Constants.INBOX_TABLE_CF);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        // 初始化
        init();
        // 1001发微博
        HBaseDao.publishWeiBo("1001","赶紧下课ba!");
        // 1002关注1001和1003
        HBaseDao.addAttends("1001","1002","1003");
        // 获取1002初始化页面
        HBaseDao.getInit("1002");

        System.out.println("----------------------------------------------");
        // 1003发布3条微博,同时1001发布2条微博
        HBaseDao.publishWeiBo("1003","谁说的赶紧下课!");
        Thread.sleep(10);
        HBaseDao.publishWeiBo("1001","我没说话!");
        Thread.sleep(10);
        HBaseDao.publishWeiBo("1003","那谁说的!");
        Thread.sleep(10);
        HBaseDao.publishWeiBo("1002","就是不下课!");

        HBaseDao.publishWeiBo("1002","爱咋咋地!");
        System.out.println("发送消息完毕!!!");

        // 获取1002初始化页面
        HBaseDao.getInit("1002");
        // 1002取关1003
        HBaseDao.deleteAttends("1002","1003");
        // 获取1002初始化页面
        HBaseDao.getInit("1002");
        // 1002再次关注1003
        HBaseDao.addAttends("1002","1003");
        // 获取1002初始化页面
        HBaseDao.getInit("1002");
    }
}

3.3.4.7 测试结果

在这里插入图片描述
在这里插入图片描述

4.总结

通本次HBase的微博模拟案例,加强对HBase的理解和应用,能够把知识用于实践,并且能熟系掌握HBase的Java API操作。

原文链接地址:https://www.ygs.red/article/543

访问原文即可获取源码项目!!!
访问原文即可获取源码项目!!!
访问原文即可获取源码项目!!!

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YGSBlog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值