Big Data>HDFS讲义(5)

本文档介绍了如何使用HDFS的Java API进行文件系统操作,包括创建maven工程、导入CDH仓库的jar包、配置访问数据、使用FileSystem类进行文件操作、递归遍历文件、下载和上传文件等。同时,还讨论了HDFS的权限问题、小文件合并策略以及HDFS Web界面的使用,如Overview、Datanodes模块的功能解析。
摘要由CSDN通过智能技术生成

7.HDFS的javaAPI操作

由于cdh版本的所有的软件涉及版权的问题,所以并没有将所有的jar包托管到maven仓库当中去,而是托管在了CDH自己的服务器上面,所以我们默认去maven的仓库下载不到,需要自己手动的添加repository去CDH仓库进行下载,以下两个地址是官方文档说明,请仔细查阅

官方文档说明:CDH 5 Maven存储库

CDH仓库下载地址:Maven Artifacts for CDH 5.14.x

创建maven工程并导入jar包

<repositories>
    <repository>
        <id>cloudera</id>
        <url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
    </repository>
</repositories>
<dependencies>
    <dependency>
        <groupId>org.apache.Hadoop</groupId>
        <artifactId>Hadoop-client</artifactId>
        <version>2.6.0-mr1-cdh5.14.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.Hadoop</groupId>
        <artifactId>Hadoop-common</artifactId>
        <version>2.6.0-cdh5.14.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.Hadoop</groupId>
        <artifactId>Hadoop-hdfs</artifactId>
        <version>2.6.0-cdh5.14.0</version>
    </dependency>

    <dependency>
        <groupId>org.apache.Hadoop</groupId>
        <artifactId>Hadoop-mapreduce-client-core</artifactId>
        <version>2.6.0-cdh5.14.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/junit/junit -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>RELEASE</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.0</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <encoding>UTF-8</encoding>
                <!--    <verbal>true</verbal>-->
            </configuration>
        </plugin>

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>2.4.3</version>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>shade</goal>
                    </goals>
                    <configuration>
                        <minimizeJar>true</minimizeJar>
                    </configuration>
                </execution>
            </executions>
        </plugin>
      <!--  <plugin>
            <artifactId>maven-assembly-plugin </artifactId>
            <configuration>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <archive>
                    <manifest>
                        <mainClass>cn.itcast.Hadoop.db.DBToHdfs2</mainClass>
                    </manifest>
                </archive>
            </configuration>
            <executions>
                <execution>
                    <id>make-assembly</id>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>-->
    </plugins>
</build>
  • 使用url的方式访问数据(了解)
@Test
public void demo1()throws  Exception{
    //第一步:注册hdfs 的url,让java代码能够识别hdfs的url形式
    URL.setURLStreamHandlerFactory(new FsUrlStreamHandlerFactory());

    InputStream inputStream = null;
    FileOutputStream outputStream =null;
    //定义文件访问的url地址
    String url = "hdfs://192.168.52.100:8020/test/input/install.log";
    //打开文件输入流
    try {
        inputStream = new URL(url).openStream();
        outputStream = new FileOutputStream(new File("c:\\hello.txt"));
        IOUtils.copy(inputStream, outputStream);
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        IOUtils.closeQuietly(inputStream);
        IOUtils.closeQuietly(outputStream);
    }
}

如果执行出现以下错误,可以参见资料如何解决,也可以不用理会,不会影响程序的执行。记得配置完成环境变量之后重启开发工具
在这里插入图片描述

使用文件系统方式访问数据(掌握)

在 java 中操作 HDFS,主要涉及以下 Class:
Configuration:该类的对象封转了客户端或者服务器的配置;
FileSystem:该类的对象是一个文件系统对象,可以用该对象的一些方法来对文件进行操作,通过 FileSystem 的静态方法 get 获得该对象。
FileSystem fs = FileSystem.get(conf)
get 方法从 conf 中的一个参数 fs.defaultFS 的配置值判断具体是什么类型的文件系统。如果我们的代码中没有指定 fs.defaultFS,并且工程 classpath下也没有给定相应的配置,conf中的默认值就来自于Hadoop的jar包中的core-default.xml , 默 认 值 为 : file:/// , 则 获 取 的 将 不 是 一 个DistributedFileSystem 的实例,而是一个本地文件系统的客户端对象
在这里插入图片描述

获取FileSystem的几种方式

第一种方式获取FileSystem
@Test
public void getFileSystem() throws URISyntaxException, IOException {
   Configuration configuration = new Configuration();
    FileSystem fileSystem = FileSystem.get(new URI("hdfs://192.168.52.100:8020"), configuration);
    System.out.println(fileSystem.toString());
}

第二种获取FileSystem类的方式
@Test
public void getFileSystem2() throws URISyntaxException, IOException {
    Configuration configuration = new Configuration();
    configuration.set("fs.defaultFS","hdfs://192.168.52.100:8020");
    FileSystem fileSystem = FileSystem.get(new URI("/"), configuration);
    System.out.println(fileSystem.toString());
}

第三种获取FileSystem类的方式
@Test
public void getFileSystem3() throws URISyntaxException, IOException {
    Configuration configuration = new Configuration();
    FileSystem fileSystem = FileSystem.newInstance(new URI("hdfs://192.168.52.100:8020"), configuration);
    System.out.println(fileSystem.toString());
}


第四种获取FileSystem类的方式
@Test
public void getFileSystem4() throws  Exception{
    Configuration configuration = new Configuration();
    configuration.set("fs.defaultFS","hdfs://192.168.52.100:8020");
    FileSystem fileSystem = FileSystem.newInstance(configuration);
    System.out.println(fileSystem.toString());
}

递归遍历文件系统当中的所有文件

通过递归遍历hdfs文件系统

@Test
public void listFile() throws Exception{
    FileSystem fileSystem = FileSystem.get(new URI("hdfs://192.168.52.100:8020"), new Configuration());
    FileStatus[] fileStatuses = fileSystem.listStatus(new Path("/"));
    for (FileStatus fileStatus : fileStatuses) {
        if(fileStatus.isDirectory()){
            Path path = fileStatus.getPath();
            listAllFiles(fileSystem,path);
        }else{
            System.out.println("文件路径为"+fileStatus.getPath().toString());
        }
    }
}

public void listAllFiles(FileSystem fileSystem,Path path) throws  Exception{
    FileStatus[] fileStatuses = fileSystem.listStatus(path);
    for (FileStatus fileStatus : fileStatuses) {
        if(fileStatus.isDirectory()){
            listAllFiles(fileSystem,fileStatus.getPath());
        }else{
            Path path1 = fileStatus.getPath();
            System.out.println("文件路径为"+path1);
        }
    }
}

官方提供的API直接遍历

/**
 * 递归遍历官方提供的API版本
 * @throws Exception
 */
@Test
public void listMyFiles()throws Exception{
    //获取fileSystem类
    FileSystem fileSystem = FileSystem.get(new URI("hdfs://192.168.52.100:8020"), new Configuration());
    //获取RemoteIterator 得到所有的文件或者文件夹,第一个参数指定遍历的路径,第二个参数表示是否要递归遍历
    RemoteIterator<LocatedFileStatus> locatedFileStatusRemoteIterator = fileSystem.listFiles(new Path("/"), true);
    while (locatedFileStatusRemoteIterator.hasNext()){
        LocatedFileStatus next = locatedFileStatusRemoteIterator.next();
        System.out.println(next.getPath().toString());
    }
    fileSystem.close();
}
下载文件到本地
/**
 * 拷贝文件的到本地
 * @throws Exception
 */
@Test
public void getFileToLocal()throws  Exception{
    FileSystem fileSystem = FileSystem.get(new URI("hdfs://192.168.52.100:8020"), new Configuration());
    FSDataInputStream open = fileSystem.open(new Path("/test/input/install.log"));
    FileOutputStream fileOutputStream = new FileOutputStream(new File("c:\\install.log"));
    IOUtils.copy(open,fileOutputStream );
    IOUtils.closeQuietly(open);
    IOUtils.closeQuietly(fileOutputStream);
    fileSystem.close();
}
hdfs上创建文件夹
@Test
public void mkdirs() throws  Exception{
    FileSystem fileSystem = FileSystem.get(new URI("hdfs://192.168.52.100:8020"), new Configuration());
    boolean mkdirs = fileSystem.mkdirs(new Path("/hello/mydir/test"));
    fileSystem.close();
}
hdfs文件上传
@Test
public void putData() throws  Exception{
    FileSystem fileSystem = FileSystem.get(new URI("hdfs://192.168.52.100:8020"), new Configuration());
    fileSystem.copyFromLocalFile(new Path("file:///c:\\install.log"),new Path("/hello/mydir/test"));
    fileSystem.close();
}

javaAPI基本操作

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*;

import java.net.URI;
import java.net.URISyntaxException;

public class demo {
    //该类的对象封转了客户端或者服务器的配置
    static Configuration conf=new Configuration();

    //获取指定路径所有文件
    public  static void  listStatus()  throws Exception  {
        //该类的对象是一个文件系统对象
        FileSystem hdfs=FileSystem.get(new URI("hdfs://192.168.100.201:8020"),conf);
        //获取某一目录下的所有文件
        FileStatus stats[]=hdfs.listStatus(new Path("/"));

        //遍历输出
        for(int i = 0; i < stats.length; ++i)
            System.out.println(stats[i].getPath().toString());
        hdfs.close();
    }

    //重命名
    public  static void  rename()  throws Exception  {
        FileSystem hdfs=FileSystem.get(new URI("hdfs://192.168.100.201:8020"),conf);
        Path frpaht=new Path("/aaa");
        Path topath=new Path("/aaaaaaa");
        boolean isRename=hdfs.rename(frpaht, topath);
        String result=isRename?"修改成功!":"修改失败!";
        System.out.println(result);
    }

  //获取文件日期
  public  static void  GetTime()  throws Exception  {
        FileSystem hdfs=FileSystem.get(new URI("hdfs://192.168.100.201:8020"),conf);
        FileStatus fileStatus=hdfs.getFileStatus(new Path("/yarn-daemons.txt"));
        long modiTime=fileStatus.getModificationTime();
        System.out.println(modiTime);
    }

    //删除文件
    public  static void  deletefile()  throws Exception  {
        FileSystem hdfs=FileSystem.get(new URI("hdfs://192.168.100.201:8020"),conf);
        boolean isDeleted=hdfs.delete(new Path("/user/new"),true);
        System.out.println("Delete?"+isDeleted);
    }

    //创建文件夹
    public  static void  mkdir ()  throws Exception  {
      FileSystem hdfs=FileSystem.get(new URI("hdfs://192.168.100.201:8020"),conf);
      boolean bool2=hdfs.mkdirs(new Path("/user/new"));
      if (bool2)
      {
          System.out.println("创建成功!!");
      }
      else
      {
          System.out.println("创建失败!!");
      }
    }

     //创建数据
     public  static void  AddFile()  throws Exception  {
        FileSystem hdfs=FileSystem.get(new URI("hdfs://192.168.100.201:8020"),conf);
        byte[] buff="hello hadoop world!\r\n hadoop ".getBytes();
        FSDataOutputStream outputStream=hdfs.create(new Path("/tmp/file.txt"));
        outputStream.write(buff,0,buff.length);
        outputStream.close();
    }

    //上传数据
    public  static void  put()  throws Exception  {
       FileSystem hdfs=FileSystem.get(new URI("hdfs://192.168.100.201:8020"),conf);
       Path src =new Path("C:/123.py");
       Path dst =new Path("/");
       hdfs.copyFromLocalFile(src, dst);
    }

     //检查目录是否存在
     public  static void  check()  throws Exception  {
            FileSystem hdfs=FileSystem.get(new URI("hdfs://192.168.100.201:8020"),conf);
            Path findf=new Path("/abc");
            boolean isExists=hdfs.exists(findf);
            System.out.println("Exist?"+isExists);
    }

HDFS权限问题以及伪造用户

首先停止hdfs集群,在node01机器上执行以下命令
[root@node01 ~]# cd /export/servers/hadoop-2.6.0-cdh5.14.0
[root@node01 hadoop-2.6.0-cdh5.14.0]# sbin/stop-dfs.sh


修改node01机器上的hdfs-site.xml当中的配置文件
[root@node01 ~]# cd /export/servers/hadoop-2.6.0-cdh5.14.0/etc/hadoop

[root@node01 hadoop]# vim hdfs-site.xml
在hdfs-site.xml添加如下配置  开启权限认证 (每个节点都要配置)
<property>
   <name>dfs.permissions</name>
   <value>true</value>
</property>

	有权限,必然有管理员
​	linux  超级管理员是    root
​	HDFS  超级管理员是    hdfs(部分大数据平台)

修改完成之后配置文件发送到其他机器上面去
	scp hdfs-site.xml node02:$PWD
	scp hdfs-site.xml node03:$PWD


重启hdfs集群
[root@node01 ~]# cd /export/servers/hadoop-2.6.0-cdh5.14.0
[root@node01 hadoop-2.6.0-cdh5.14.0]# sbin/start-dfs.sh


随意上传一些文件到我们Hadoop集群当中准备测试使用
[root@node01 ~]# cd /export/servers/hadoop-2.6.0-cdh5.14.0/etc/hadoop
[root@node01 hadoop]# hdfs dfs -mkdir /config
[root@node01 hadoop]# hdfs dfs -put *.xml /config
[root@node01 hadoop]# hdfs dfs -chmod 600 /config/core-site.xml 
  • 使用代码准备下载文件
@Test
public void getConfig()throws  Exception{
    FileSystem fileSystem = FileSystem.get(new URI("hdfs://192.168.52.100:8020"), new Configuration(),"root");
    fileSystem.copyToLocalFile(new Path("/config/core-site.xml"),new Path("file:///c:/core-site.xml"));
    fileSystem.close();
}

HDFS的小文件合并

由于Hadoop擅长存储大文件,因为大文件的元数据信息比较少,如果Hadoop集群当中有大量的小文件,那么每个小文件都需要维护一份元数据信息,会大大的增加集群管理元数据的内存压力,所以在实际工作当中,如果有必要一定要将小文件合并成大文件进行一起处理。
在我们的hdfs 的shell命令模式下,可以通过命令行将很多的hdfs文件合并成一个大文件下载到本地,命令如下

[root@node01 ~]# cd /export/servers
[root@node01 servers]# hdfs dfs -getmerge /config/*.xml  ./hello.xml

既然可以在下载的时候将这些小文件合并成一个大文件一起下载,那么肯定就可以在上传的时候将小文件合并到一个大文件里面去
代码如下:

/**
 * 将多个本地系统文件,上传到hdfs,并合并成一个大的文件
 * @throws Exception
 */
@Test
public void mergeFile() throws  Exception{
    //获取分布式文件系统
    FileSystem fileSystem = FileSystem.get(new URI("hdfs://192.168.52.100:8020"), new Configuration(),"root");
    FSDataOutputStream outputStream = fileSystem.create(new Path("/bigfile.xml"));
    //获取本地文件系统
    LocalFileSystem local = FileSystem.getLocal(new Configuration());
    //通过本地文件系统获取文件列表,为一个集合
    FileStatus[] fileStatuses = local.listStatus(new Path("file:///F:\\上传小文件合并"));
    for (FileStatus fileStatus : fileStatuses) {
        FSDataInputStream inputStream = local.open(fileStatus.getPath());
       IOUtils.copy(inputStream,outputStream);
        IOUtils.closeQuietly(inputStream);
    }
    IOUtils.closeQuietly(outputStream);
    local.close();
    fileSystem.close();
}

在这里插入图片描述
总结
HDFS -> Local 使用HDFS提供的-getmerge 方法
Local -> HDFS 遍历每个小文件追加到一个文件,在进行上传(这个文件不在HDFS)

HDFS-Web界面介绍

当我们启动HDFS集群后,然后通过http://master:50070/去访问HDFS WEB UI的时候,我们会经常使用Utilities下的Browse the file system去查看HDFS中的文件,如下:
在这里插入图片描述
然后就会出现HDFS中的根目录下所有的文件:
在这里插入图片描述
上面的方式是我们常见的访问HDFS文件的方式之一,这种使用的方式也是很方便的。

当我们启动HDFS集群后,我们可以通过http://master:50070来访问HDFS集群,其中,master是NameNode所在机器的名称。下面的就是HDFS WEB UI的七个大模块:
在这里插入图片描述
这篇文章,我们重点分别来详细看一下Overview、Datanodes以及Utilities三个模块

Overview

在这里插入图片描述

  • 第1处的master:9999表示当前HDFS集群的基本路径。这个值是从配置core-site.xml中的fs.defaultFS获取到的。
  • 第2处的Started表示集群启动的时间
  • 第3处的Version表示我们使用的Hadoop的版本,我们使用的是2.7.5的Hadoop
  • 第4处的Compiled表示Hadoop的安装包(hadoop-2.7.5.tar.gz)编译打包的时间,以及编译的作者等信息
  • 第5处的Cluster ID表示当前HDFS集群的唯一ID
  • 第6处的Block Pool ID表示当前HDFS的当前的NameNode的ID,我们知道通过HDFS Federation (联盟)的配置,我们可以为一个HDFS集群配置多个NameNode,每一个NameNode都会分配一个Block Pool ID

Summary

在这里插入图片描述

  • 第1处的Security is off表示当前的HDFS集群没有启动安全机制
  • 第2处的Safemode is off表示当前的HDFS集群不在安全模式,如果显示的是Safemode is on的话,则表示集群处于安全模式,那么这个时候的HDFS集群是不能用的
  • 第3处表示当前HDFS集群包含了3846个文件或者目录,以及1452个数据块,那么在NameNode的内存中肯定有3846 + 1452 = 5298个文件系统的对象存在
  • 第4处表示NameNode的堆内存(Heap Memory)是312MB,已经使用了287.3MB,堆内存最大为889MB,对
  • 第5处表示NameNode的非堆内存的使用情况,有效的非堆内存是61.44MB,已经使用了60.36MB。没有限制最大的非堆内存,但是非堆内存加上堆内存不能大于虚拟机申请的最大内存(默认是1000M)
  • 第6处的Configured Capacity表示当前HDFS集群的磁盘总容量。这个值是通过:Total Disk Space - Reserved Space计算出来的。Total Disk Space表示所在机器所在磁盘的总大小,而Reserved Space表示一个预留给操作系统层面操作的空间。Reserved space空间可以通过dfs.datanode.du.reserved(默认值是0)在hdfs-site.xml文件中进行配置。

我们这边的总容量为什么是:33.97GB呢,我们可以通过du -h看一下两个slave的磁盘使用情况,如下:
在这里插入图片描述
上面 17GB + 17GB = 34GB,而且我们没有配置Reserved Space,所以HDFS总容量就是33.97GB(有一点点的误差可以忽略)

  • 第7处DFS Used表示HDFS已经使用的磁盘容量,说白了就是HDFS文件系统上文件的总大小(包含了每一个数据块的副本的大小)
  • 第8处Non DFS Used表示在任何DataNodes节点上,不在配置的dfs.datanode.data.dir里面的数据所占的磁盘容量。其实就是非HDFS文件占用的磁盘容量

配置dfs.datanode.data.dir就是DataNode数据存储的文件目录

  • 第9处DFS Remaining = Configured Capacity - DFS Used - Non DFS Used。这是HDFS上实际可以使用的总容量
  • 第10处Block Pool Used表示当前的Block Pool使用的磁盘容量
  • 第11处DataNodes usages%表示所有的DataNode的磁盘使用情况(最小/平均/最大/方差)
  • 第12处Live Nodes表示存活的DataNode的数量。Decommissioned表示已经下线的DataNode
  • 第13处Dead Nodes表示已经死了的DataNode的数量。Decommissioned表示已经下线的DataNode
  • 第14处Decommissioning Nodes表示正在下线的DataNode的数量。
  • 第15处Total Datanode Volume Failures表示DataNode上数据块的损坏大小
  • 第16处Number of Under-Replicated Blocks表示没有达到备份数要求的数据块的数量
  • 第17处Number of Blocks Pending Deletion表示正要被删除的数据块
  • 第18处Block Deletion Start Time表示可以删除数据块的时间。这个值等于集群启动的时间加上配置dfs.namenode.startup.delay.block.deletion.sec的时间,其中配置dfs.namenode.startup.delay.block.deletion.sec默认是0秒

Datanodes

在这里插入图片描述
上面有一个Admin State我们有必要说明下,Admin State可以取如下的值:

  • 1.In Service,表示这个DataNode正常
  • 2.Decommission In Progress,表示这个DataNode正在下线
  • 3.Decommissioned,表示这个DataNode已经下线
  • 4.Entering Maintenance,表示这个DataNode正进入维护状态
  • 5.In Maintenance,表示这个DataNode已经在维护状态
    在这里插入图片描述
    我们这里详细总结下Browse the file system,对于Logs我们在HDFS日志的查看总结中讲解
    当我们点击Browse the file system时,我么会进入到如下的界面:
    在这里插入图片描述
    上图每一个字段的解释如下:
  • Permission:表示该文件或者目录的权限,和Linux的文件权限规则是一样的
  • Owner:表示该文件或者目录的所有者
  • Group:表示该文件或者目录的所有者属于的组
  • Size:表示该文件或者目录的大小,如果是目录的话则一直显示0B
  • Last Modified:表示该文件或者目录的最后修改时间
  • Replication:表示该文件或者目录的备份数,如果是目录的话则一直显示0
  • Block Size:表示该文件的数据块的大小,如果是目录的话则一直显示0B
  • Name:表示文件或者目录的名字

我们可以通过鼠标点击Name来访问对应的文件目录或者文件:
当我们访问的是目录的时候,则是去查看该目录下有哪些子文件或者子目录。
当我们访问的是文件的时候,我们查看的是文件的详细信息,比如,我们访问文件/user/omneo.csv文件:
在这里插入图片描述
在这里插入图片描述
Journal Manager:Journal Node 存储EditLog数据的路径
State: Journal Node 存储EditLog数据的文件名
在这里插入图片描述
NameNode 存储数据的路径
NameNode存储edits的路径
/export/servers/hadoop-2.6.0-cdh5.14.0/hadoopDatas/dfs/nn/edits
NameNode存储fsimage的路径
/export/servers/hadoop-2.6.0-cdh5.14.0/hadoopDatas/namenodeDatas
在这里插入图片描述
Storage Type : 集群存储类型 DISK(磁盘)
Configured Capacity: 配置容量 135.01 GB
Capacity Used: 使用的容量 355.01 MB (0.26%)
Capacity Remaining:剩余容量 117.49 GB (87.02%)
Block Pool Used: 使用的块池 355.01 MB
Nodes In Service:服务中的节点 3
在这里插入图片描述
Datanode usage histogram: 数据节点使用率柱状图
Disk usage of each DataNode (%):每个数据节点的磁盘使用率(%)
In operation:运行中的节点
在这里插入图片描述
Entering Maintenance: 进入维护的节点列表
Decommissioning: 退役的节点列表
在这里插入图片描述
Snapshot Summary:快照摘要
Snapshottable directories : 快照目录列表:2
Snapshotted directories: 已创建的快照目录:4
在这里插入图片描述
Startup Progress:集群启动时加载的fsimage和edits
启动时加载的fsimage: fsimage_0000000000000000537
启动时加载的edits:edits_0000000000000000538-0000000000000000538
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值