目录
第六章 HDFS的命令操作
HDFS实质就是一个文件系统,有两种操作方式:HDFS命令方式和Java API的方式。HDFS的命令方式有些类似于Linux的操作方式。本章主要针对HDFS的命令方式进行说明。
6.1 HDFS命令方式
在使用命令方式操作HDFS之前,应先开启Hadoop或HDFS客户端。特别地,HDFS中访问文件只有一种方式,即只能通过绝对路径进行访问。其中,/ 代表的抽象根目录,其具体表示为:hdfs://master:9000/xxx
6.1.1 HDFS常用指令
hadoop fs :开启原生文件系统客户端(里面有指令提示)
hadoop fs -ls :查看根目录下的文件
hadoop fs -mkdir -p /aa/bb/cc/dd :循环建立文件夹
HDFS的核心功能—上传和下载,其两个核心的命令分别为:
(1)文件上传:从本地上传至HDFS,这里的本地文件目录是指Linux下的文件目录
hadoop fs -put [-f] [-p] [-l] <localsrc> ... <dst>
hadoop fs -put 本地文件目录 HDFS上的目录
例如:hadoop fs -put 1.txt 2.txt /ss :上传时,会把最后一个路径之前的目录看做全部要上传的文件。
(2)文件下载:从HDFS下载到本地,即下载到Linux下的目录中
hadoop fs -get [-p] [-ignoreCrc] [-crc] <src> ... <localdst>
hadoop fs -get HDFS路径 本地路径
(3)合并下载:HDFS下载到本地目录,并将两个文件合并成一个文件
hadoop fs -getmerge /ss/1.txt /ss/2.txt /home/hadoop/cc.txt
该指令会把最后一个路径之前的文件当做需要合并的文件,最后一个路径的文件就是合并后的文件。
查看文件:hadoop fs -cat
删除文件:hadoop fs -rm -r(递归删除)-f(强制,不提示)
移动文件:hadoop fs -mv(修改名字或移动文件)
复制文件:hadoop fs -cp
以上这些命令,在集群中的任意一个节点都可以使用。因为在HDFS中我们所看到的目录结构只是一个抽象目录树,实际存储是在集群中的节点上。
如:现将一个大小为150M,名字为aa.txt的文件上传至HDFS的根目录下,执行hadoop fs -put aa.txt / 后,在根目录下会看到/aa.txt。
但是aa.txt在真实存储的时候会先进行分块(分两块),再进行存储。假设集群中有5个DataNode,这两个块由NameNode分配存储在哪个节点中。
以上都是常用命令
6.1.2 其他命令
文件追加:hadoop fs -appendToFile 本地文件 HDFS文件
(追加方式:在文件的末尾追加;若追加后文件大小超过128M,会自动进行切分成块)
文件显示末尾:hadoop fs -tail
Linux用到的监听命令:tail -f 文件路径 (-f 代表跟踪)
修改权限:就比Linux系统多了个fs
修改文件或目录的权限:hadoop fs -chomd -R 777 hdfs目录/..
修改文件或目录的所属租:hadoop fs -chown -R 用户名:组名 hdfs目录/文件
(p.s)Linux下的权限管理命令:
(1)修改文件、目录的权限:chmod 文件权限
文件权限包括3种:可读、可写、可执行。对应的字母分别为:r、w、x;对应的数字分别为:4、2、1
文件的最大权限是:可读可写可执行;即rwx或7
例如:
红框中-rw-r--r--,一共10位。
第1位:代表文件属性,d为目录,-为文件,l为连接
其余9位,三个为一组。分别代表本用户,本组用户和其他用户的权限。
结合图,rw- 本用户,6;rw- 本组用户,4;r-- 其他用户,4
修改目录或文件的权限
Chmod 711 文件名
改一个文件夹下的所有的文件权限都是711(可读可写,可运行,可运行)
(2)修改文件所属用户和组:
chown -R 用户名:组名 文件/目录
sodu chown -R root:root /ss
6.2 Java API的方式(HDFS的API)
使用API操作HDFS的前提是:Hadoop集群可用;然后安装配置eclipse插件,通过eclipse插件连接HDFS。
6.2.1 导入Jar包
导入Jar包通常有三种方式:
(1)在工程中建立一个lib文件夹,添加Jar包后build to path
优点:代码移动时比较方便,不用重新导包
缺点:不好解决Jar包冲突,易造成工程过于臃肿
(2)使用maven管理Jar包(企业常用)
优点:解决了Jar包冲突问题
缺点:代码移动时,需要重构工程
(3)手动建立一个依赖库
以上三种方式自己在开发中都可以使用,可根据不同特点进行选择。
本人在学习中使用的Hadoop版本是Hadoop2.7.5,找到该文件的安装目录-->D:\hadoop\hadoop-2.7.5;各模块的Jar包都存放在一个share文件夹中:D:\hadoop\hadoop-2.7.5\share\hadoop;
操作HDFS需要使用的Jar包在common和hdfs文件夹中,我们分别将这两个文件夹中的所有Jar包导入工程,如下图所示:
6.2.2 使用Java API
(1)FileSystem对象
该对象是HDFS抽象目录树的一个实例,如果用户想要操作HDFS,首先需要获取到这个实例。
(ps:Java中有5种方式获取实例:new、利用反射、利用工厂类、单例设计模式的静态方法和克隆。)
FileSystem对象包括单机模式的FileSystem对象和分布式模式的FileSystem对象两种,接下来分别进行说明:
1)单机模式的FileSystem
单机模式下的FileSystem对象是本地文件系统抽象目录树的一个实例,这个本地指代码的运行地;因为代码运行在Windows中,所以本地文件系统的根目录是windows下,Hadoop安装目录中磁盘的根目录。若此时使用HDFS中的上传功能,将文件进行上传。开发者本想将程序上传至HDFS,实际上却上传到了本地(即windows中的磁盘)。因为本人将Hadoop安装在了D盘中,所以单机模式FileSystem的根目录则是D盘。
下面是具体测试代码:
package com.ethan.hdfs;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
/**
* 单机模式下的FileSystem
* @author Ethan
*
*/
public class HdfsLocalTest {
public static void main(String[] args) {
// FileSystem:这个对象是HDFS抽象目录树的一个实例,如果用户想要操作HDFS,首先需要获取到这个实例
// 获取配置文件:哪里的?
Configuration conf = new Configuration();
try {
// 单机模式的文件系统抽象目录树实例
// 根目录 --> file:\\\D:.....
FileSystem fs = FileSystem.get(conf); // 获取本地文件系统
// 这个本地指的是代码的运行地
System.out.println(fs);
// org.apache.hadoop.fs.LocalFileSystem@31368b99 本地文件系统
// Path hdfs内置对象 代表文件路径对象
Path src = new Path("磁盘中文件的绝对路径。。。");
Path dst = new Path("/"); // 根目录。。。希望是HDFS上的根目录,但却是本地系统的根目录
// 想上传至HDFS,实际是本地
fs.copyFromLocalFile(src, dst);
} catch (IOException e) {
e.printStackTrace();
}
}
}
2)分布式模式的FileSystem对象
在获取FileSystem实例时,选择带URI参数的get方法即可。注意:最好选择get(URI uri, Configuration conf, String user)方法,URI:HDFS的IP和端口号;conf:配置文件实例;user:集群用户名。若不选择该方法,容易报windows用户无权限错误。
下面是具体测试代码:
package com.ethan.hdfs;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hdfs.DistributedFileSystem;
/**
* 分布式模式下的FileSystem
* @author Ethan
*
*/
public class HdfsClusterTest {
public static void main(String[] args) {
Configuration conf = new Configuration();
// 这时候的根目录代表HDFS,根目录--> hdfs://master:9000
try {
// 以指定获取文件系统的url连接,完全分布式的url;core-site.xml中fs.default
// 分布式模式的文件系统抽象目录树实例
FileSystem fs = FileSystem.get(new URI("hdfs://xxx.xxx.xxx.xx:9000"), conf, "root");
if (fs instanceof DistributedFileSystem) {
System.out.println("fs是分布式模式下抽象目录树的实例。。。");
}else {
System.out.println("fs不是分布式模式下抽象目录树的实例。。。");
}
Path src = new Path("想上传文件的目录。。。");
Path dst = new Path("/");
fs.copyFromLocalFile(src, dst);
} catch (IOException e) {
e.printStackTrace();
} catch (URISyntaxException e) {
e.printStackTrace();
}catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
(2)Configuration对象
该对象是加载配置文件的对象,即该对象用于读取配置文件。
读取的配置文件包括:core-default.xml、hdfs-default.xml、mappred-default.xml和yarn-default.xml
配置文件加载(读取)的顺序:
a)首先加载Jar包中的xxx-default.xml
b) 然后加载工程classpath(src)下的配置文件
并且只识别两种名字,hdfs-default.xml和hdfs-site.xml,其他名字识别不了
c) 通过代码去设置配置项
例如:
Configuration conf = new Configuration();
conf.set("dfs.replication", "5");
如果这这三种方式全都配置,应该以哪个配置文件为准呢?实际上,根据加载(读取)的优先级:Jar > src > 代码配置即可得出答案:哪个是最后一次设置或加载的,哪个配置就是生效的。
6.2.3 HDFS中常用的API
下面列举一些常用的FileSystem操作HDFS的方法:
package com.ethan.hdfs;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.BlockLocation;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.LocatedFileStatus;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.RemoteIterator;
/**
* HDFS中常用的API
* @author Ethan
*
*/
public class CommonApi {
public static void main(String[] args) {
Configuration conf = new Configuration();
try {
FileSystem fs = FileSystem.get(new URI("hdfs://xxx.xxx.xx.xxx:9000"), conf, "root");
Path path = new Path("/api_test/aa/bb");
// 创建文件夹,可递归创建文件夹
fs.mkdirs(path); // 创建成功返回true,失败返回false
// 可以设定是否递归删除
fs.delete(new Path("/api_test/aa/bb"), false); // true就是递归删除
// 上传文件
fs.copyFromLocalFile(src, dst);
//下载文件
fs.copyToLocalFile(src, dst);
// 判断目录是否存在
fs.exists();
// 对文件进行重命名
fs.rename(src, dst);
// 获取指定目录下的文件列表(只能获取文件列表)
RemoteIterator<LocatedFileStatus> listFiles = fs.listFiles(new Path("/hdfs_test"), false); // 文件的迭代器
// 循环遍历迭代器
while(listFiles.hasNext()) {
// 获取下一个
LocatedFileStatus next = listFiles.next();
System.out.println(next.getPath());
System.out.println(next.getReplication());
System.out.println(next.getBlockSize());
// 返回每一个文件的块信息,结果封装在数组中,数据长度代表块的个数,每一个块代表数组中的一个元素
BlockLocation[] blockLocations = next.getBlockLocations();
System.out.println(blockLocations.length);
// bl代表每一个文件的每一个块的信息
for (BlockLocation bl : blockLocations) {
// 0,36,master,slave2,slave1 :起始偏移量,末尾偏移量,块的存储位置
System.out.println(bl + "\t");
// 返回的是获取每一个块的存储节点
String[] hosts = bl.getHosts();
long offset = bl.getOffset();
for (String host : hosts) {
System.out.println(host);
}
}
}
// 返回指定目录下的文件或目录状态信息
FileStatus[] fileStatus = fs.listStatus(new Path("/"));
for (FileStatus f : fileStatus) {
// 判断是文件还是文件夹
System.out.println(f.isDirectory());
System.out.println(f.isFile());
}
// 关闭流
fs.close();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
}
在此具体说明一下由FileSystem上传文件至HDFS时,文件名的问题。
(1)在上传文件时,若没有指定文件名,并且上传的父目录存在,则以原文件名存储;若指定了文件名,则上传的文件名为指定的文件名。测试如下:
package com.ethan.hdfs;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class HdfsParctise {
public static void main(String[] args) {
Configuration conf = new Configuration();
try {
FileSystem fs = FileSystem.get(new URI("hdfs://192.168.56.110:9000"), conf, "root");
Path localPath = new Path("D:\\z_hadooptest\\20200114.txt");
Path dirPath = new Path("/hdfs_test");
Path dirPathUseName = new Path("/hdfs_test/20200114useName.txt");
// 未指定文件名上传
fs.copyFromLocalFile(localPath, dirPath);
// 指定文件名上传
fs.copyFromLocalFile(localPath, dirPathUseName);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
}
上传后的结果:
(2)上传文件时,如果上传的路径已经存在,则文件最终上传到该目录下;如果不存在,并未指定文件名,则直接以上传目录的最后一级目录作为文件名;若指定文件名,则自动在HDFS中递归建立文件夹,并以指定的文件名上传文件;
package com.ethan.hdfs;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class HdfsParctise {
public static void main(String[] args) {
Configuration conf = new Configuration();
try {
FileSystem fs = FileSystem.get(new URI("hdfs://xxx.xxx.xx.xxx:9000"), conf, "root");
Path localPath = new Path("D:\\z_hadooptest\\20200114.txt");
Path dirNoExistNoName = new Path("/ethan/shadow_knight");
Path dirNoExistByName = new Path("/bond/007/20200114NoExist.txt");
// 路径不存在,上传
fs.copyFromLocalFile(localPath, dirNoExistNoName);
// 路径不存在,但指定了文件名,上传
fs.copyFromLocalFile(localPath, dirNoExistByName);
// 上传结果:由于/ethan/shadow_knight路径不存在,最终文件名为shadow_knight
// 由于/bond/007/20200114NoExist.txt路径不存在,但指定了文件名,最终文件名为20200114NoExist.txt
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
}
上传后的结果:
6.2.4 I/O流操作HDFS
开发人员也可以用I/O流操作HDFS,进行文件的上传和下载。
(1)文件上传
从本地上传至HDFS,实际上是从本地读取文件,写到HDFS。对应的I/O流为:本地(输入流)、HDFS(输出流);具体来说,本地的输入流是普通的输入流,HDFS的输出流为HDFS专有的输出流。
文件上传步骤如下:
1)创建本地输入流:FileInputStream in = new FileInputStream(new File("D:\\xxxx.txt"));
2)创建HDFS输出流:FSDataOutputStream out = fs.create(new Path("/hdfs_test/xxx.txt"));
ps:创建流时必须指定一个文件名。此处与使用API方式操作HDFS不一样了。
3)关闭流:由HDFS的IOUtils工具帮助关闭,不用手动关闭。
(2)文件下载
文件下载与上传正好相反,即从HDFS读取文件,本地写文件。对应I/O流分别为HDFS(输入流),本地(输出流);
文件下载步骤如下:
1)创建HDFS输入流:FSDataInputStream fin = fs.open(new Path("/hdfs_test/xxx.txt"))
2)创建本地输出流: FileOutputStream fout = new FileOutputStream(new File("C:\\xxx.txt"));
3)关闭流:由HDFS的IOUtils工具帮助关闭,不用手动关闭。
文件上传、下载测试代码如下:
package com.ethan.hdfs;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
/**
* I/O流操作HDFS
* 完成文件上传和下载
* @author Ethan
*
*/
public class HdfsIOTest {
public static void main(String[] args) {
Configuration conf = new Configuration();
try {
FileSystem fs = FileSystem.get(new URI("hdfs://xxx.xxx.xx.xxx:9000"), conf, "root");
// 上传 (本地(输入流)-->HDFS(输出流))从本地读取,写到HDFS
// 本地的输入流:普通输入流
// HDFS输出流:HDFS专有的
// 1.创建输入流
FileInputStream in = new FileInputStream(new File("C:\\xxx.txt"));
// 2.创建HDFS输出流
// 流的方式必须指定一个文件名,这与Api方式不一样了
FSDataOutputStream out = fs.create(new Path("/hdfs_test/xxxx.txt"));
IOUtils.copyBytes(in, out, 4396);
// IOUtils帮助关闭流 --> 这与fs操作不同
// in.close(); out.close();
// 文件下载
// 从HDFS(读 输入流)-->本地(写 输出流)
// 创建HDFS输入流
FSDataInputStream fin = fs.open(new Path("/hdfs_test/a"));
// 创建本地输出流
FileOutputStream fout = new FileOutputStream(new File("D:\\xxx.txt"));
IOUtils.copyBytes(fin, fout, 4396);
} catch (IOException | InterruptedException | URISyntaxException e) {
e.printStackTrace();
}
}
}
综上,就是通过指令、API和I/O操作HDFS的常用方法。