由于文章太长,其余部分在我的其他几篇博客中!

  • 第一部分:Hadoop介绍及安装
  • 第三部分:MapReduce
  • 第四部分:项目案例实战


4、HDFS

HDFS作用:进行分布式的存储

HDFS(Hadoop Distributed File System),是一个文件系统,用于存储文件,通过目录树来定位文件;其次,它是分布式的,由很多服务器联合起来实现其功能,集中的服务器有各目的角色。

HDFS的使用场景:适合一次写入,多次读出的场景,且不支持文件的修改。适合用来做数据分析,并不适合用来做网盘应用。



4.1_HDFS的整体架构

HDFS常用命令实验心得 hdfs实例_hdfs

主从结构
Name Node

NameNode(nn):就是Master,它是一个主管、管理者。

(1)管理HDFS的名称空间;

(2)配置副本策略;

(3)管理数据块(Block)映射信息;

(4)处理客户端读写请求。

Data Node

DataNode:就是Slave。NameNode下达命令,DataNode执行实际的操作。

(1)存诸实际的数据块;

(2)执行数据块的读/写操作。

SecondaryNameNode:

主要作用:协助namenode(助理,不能替代namenode),namenode宕机的时候帮助namenode恢复, 帮助namenode做一些事情分担namenode的压力



4.2_四大机制

1. 心跳机制

  负责节点之间的通信(是否存活)。

  集群节点之间必须做时间同步:因为namenode 是集群的老大,负责集群上任务的分工,如果要进行分工,则必须知道那个节点的存活状况

namenode什么时候会判定datanode死亡?

datanode每隔3s向namenode发送一次心跳报告,当namenode连续10次没有收到datanode的心跳报告,namenode会判定datanode可能死亡,但没有完全断定datanode死亡,这个时候namenode会向datanode发送一次检查,间隔为5min,如果检查一次没有返回信息,这个时候namenode会再进行一次检查,如果还没有获的datanode信息,则判定死亡。

也就是说namenode最终判断datanode死亡需要10*3s+2*5min = 630s



2. 机架策略

  默认每个数据块有3个副本。

  如果要修改,则在 hdfs-site.xml (在安装Hadoop时修改配置文件时用过)文件中修改。

副本存放策略:

  1. 第一个副本一般存储在客户端所在的节点上
  2. 第二个副本存储在和第一个副本不同的机架上的任意一个节点上
  3. 第三个副本存储在和第一个副本相同机架上的不同节点上
    真实生产中需要手动配置机架策略
    真实生产中可以自定义机架策略: 不同节点 不同机架 不同数据中心



3. 安全模式

HDFS什么时候处于安全模式?

1、HDFS启动的时候

2、集群在维护或升级的时候,为了防止数据丢失,启动安全模式



元数据包含哪些?

  • 抽象目录树
  • 数据和块的映射关系
  • 数据块的存储的位置信息


元数据存储的位置?

  • 内存:读写快,但是一旦关机就会造成数据丢失,所以元数据既在内存存储,又在磁盘存储
  • 磁盘:元数据如果存储在磁盘,那么每次进行文件读写操作的时候,都会进行磁盘读写,必然会造成读写的性能比较低,很显然集群在正常启动之后,文件的读写元数据应该不在磁盘


集群启动时做了哪些事?

集群第一次启动的时候,首先会将磁盘元数据加载到内存中,如果磁盘元数据过大会造成加载到内存的时间过长,所以磁盘中的元数据只存储了1、2两部分,namenode的内存元数据的3是什么时候获取的,集群在启动的时候namenode会接收datanode的心跳报告,这个心跳报告还包括数据块的存储位置信息,这时候namenode就可以获取datanode数据块的存储状况。



集群启动时做了如下操作:

  • namenode启动,将元数据加载到内存中
  • datanode启动,namenode接收datanode的心跳报告
  1. 获取datanode的存活状况
  2. 获取块的存储信息
  • 启动secondarynamenode
    集群在执行这个过程的时候不允许外界对集群进行操作,这个时候处于安全模式。
    如果集群处于维护或升级的时候也可以手动将集群设置安全模式状态。


操作:

# 进入安全模式
hdfs dfsadmin -safemode enter   

# 退出安全模式
hdfs dfsadmin -safemode leave 

# 获取安全模式的状态
# 即安全模式是否开启。开启返回ON,关闭返回OFF
hdfs dfsadmin -safemode get 

# 等待自行退出安全模式
hdfs dfsadmin -safemode wait



安全模式下用户可以进行的操作:

  • 可以进行的操作:查看
  • 不可以进行的操作:修改元数据、创建目录 、上传 、修改文件名 、文件追加


4. 负载均衡

  hdfs的负载均衡:表示每一个dataNode存储的数据与其硬件相匹配,即占用率相当。

容量

存放数据量

占比

5t

2.5t

50%

2t

1t

50%

8t

4t

50%


为什么要进行负载均衡?

  根据机架策略,在上传文件的时候,会优先选择客户端所在节点。如果习惯性地使用同一个客户端,会导致客户端节点的存储的数据比较多。

  所以,集群有一个自动进行负载均衡的操作,只是这个操作比较慢,默认的带宽是1M(1048576 byte),在hdfs-default.xml中的参数balance定义了该带宽。就这个速度还是在集群空闲的情况下执行。

<property>
    <name>dfs.datanode.balance.bandwidthPerSec</name>
    <value>1048576</value> //1M
    <description>Specifies the maximum amount of bindwidth that each datanode can utilize for the balancing purpose in term of the number of bytes per second.</description>
</property>



手动进行负载均衡

  集群自动的负载均衡在小规模集群中可以,在大规模集群中来不及,所以需要手动进行负载均衡。

  进入/hadoop-2.7.7/sbin文件夹中,有两个命令start-balancer.shstop-balancer.sh

  注意:start-balancer.sh也不会立即执行,而是等集群空闲的时候才执行。



是否存在绝对的负载均衡?

  不存在

  所以在手动负载均衡的时候可以指定一个参数:start-balancer.sh -t 10%

  -t %10指的是任意两个节点之间的存储百分比不超过10%,则认为达到了负载均衡。



什么时候可能导致启动负载均衡?

  1. 有Datanode发生故障
  2. 添加新的节点


4.3_元数据管理

  • 元数据:抽象目录树、数据和块的映射、数据块的存储节点
  • 元数据中的数据分为4类:
  1. 历史日志文件:编辑完成的日志文件(记录操作信息)
  2. 正在编辑的文件:对元数据修改的操作记录的文件
  3. 镜像文件:真实的元数据信息经过序列化之后的文件 在集群启动的时候会加载这个文件
  4. seen_txid :合并点记录文件 记录的是下一次需要合并的文件

无论什么时候内存中保存的元数据永远是最新的最完整的元数据



4.4_元数据合并

如果fsimage(元数据)不和日志文件进行合并,会导致fsimage和内存元数据差别越来越大 ,所以fsimage和日志文件需要定期合并,这个合并谁在做?

因为namenode本身的主要职责是保存元数据处理客户端的请求,本身压力大,所以由secondarynamenode负责

元数据合并的过程(即为checkpoint的过程)

触发合并的条件: 时间达到3600s、元数据条数达到100000条

触发任意一个条件都会触发checkpoint过程secondarynamenode进行checkpoint后自己也会保留一份fsimage文件,原因是给namenode做备份,以防namenode宕机 元数据丢失的时候帮助namenode合并的目的即为保证内存中的元数据与硬盘中的元数据信息一致



4.5_hdfs的优缺点

优点:

  • 可构建在廉价机器上,成本低
  • 高容错性,通过多副本提高可靠性,提供了容错和恢复机制
  • 适合批处理
  • 适合大数据处理
  • 流式文件访问
    一次性写入,多次读取,保证数据一致性

缺点:

  • 不支持低延迟的数据访问
  • 不擅长存储大量的小文件 kb级别
  • 不支持文件修改


4.6_HDFS的Shell操作

4.6.1文件操作命令

(0)启动Hadoop集群(方便后续的测试)

# 启动HDFS
$ start-dfs.sh

# 关闭HDFS
$ stop-dfs.sh 

# 启动yarn
$ start-yarn.sh

(1)-help:输出某个命令参数

# 这里以rm命令为例
$ hadoop fs -help rm

(2)-ls: 显示目录信息

$ hadoop fs -ls /

(3)-mkdir:在HDFS上创建目录

$ hadoop fs -mkdir -p /test
# 加-p递归创建,只创建一个目录就可以不要

(4)-moveFromLocal:从本地剪切粘贴到HDFS

# touch - 修改文件或者目录的时间属性,如果文件不存在则建立
# 修改文件kongming.txt的时间属性为当前时间
$ touch kongming.txt

$ hadoop fs -moveFromLocal ./kongming.txt /test

(5)-appendToFile:追加一个文件到已经存在的文件末尾

$ touch liubei.txt

$ vi liubei.txt

输入

san gu mao lu

$ hadoop fs -appendToFile liubei.txt /sanguo/shuguo/kongming.txt

(6)-cat:显示文件内容

$ hadoop fs -cat /sanguo/shuguo/kongming.txt

(7)-chgrp 、-chmod、-chown:Linux文件系统中的用法一样,修改文件所属权限

$ hadoop fs -chmod 666 /sanguo/shuguo/kongming.txt

$ hadoop fs -chown atguigu:atguigu  /sanguo/shuguo/kongming.txt

(8)-copyFromLocal:从本地文件系统中拷贝文件到HDFS路径去(只能传一个文件)

$ hadoop fs -copyFromLocal README.txt /

(9)-put:和-copyFromLocal一样

$ hadoop fs -put ./zaiyiqi.txt /user/atguigu/test/

(10)-get:等同于copyToLocal,就是从HDFS下载文件到本地

$ hadoop fs -get /sanguo/shuguo/kongming.txt ./

(11)-copyToLocal:从HDFS拷贝到本地

$ hadoop fs -copyToLocal /sanguo/shuguo/kongming.txt ./

(12)-cp :从HDFS的一个路径拷贝到HDFS的另一个路径

$ hadoop fs -cp /sanguo/shuguo/kongming.txt /zhuge.txt

(13)-mv:在HDFS目录中移动文件

$ hadoop fs -mv /zhuge.txt /sanguo/shuguo/

(14)-getmerge:合并下载多个文件,比如HDFS的目录 /user/atguigu/test下有多个文件:log.1, log.2,log.3,…

$ hadoop fs -getmerge /user/atguigu/test/* ./zaiyiqi.txt

(15)-tail:显示一个文件的末尾

$ hadoop fs -tail /sanguo/shuguo/kongming.txt

(16)-rm:删除文件

$ hadoop fs -rm /user/atguigu/test/jinlian2.txt

(17)-rmdir:删除空目录

# 只能删除空目录
$ hadoop fs -rmdir /test

# 递归删除所有目录
$ hadoop fs -rmr dir

(18)-du 统计文件夹的大小信息

$ hadoop fs -du -s -h /user/atguigu/test

2.7 K /user/atguigu/test

 
$ hadoop fs -du -h /user/atguigu/test

1.3 K /user/atguigu/test/README.txt

15   /user/atguigu/test/jinlian.txt

1.4 K /user/atguigu/test/zaiyiqi.txt

(19)-setrep:设置HDFS中文件的副本数量

$ hadoop fs -setrep 10 /sanguo/shuguo/kongming.txt


图3-3 HDFS副本数量

这里设置的副本数只是记录在NameNode的元数据中,是否真的会有这么多副本,还得看DataNode的数量。因为目前只有3台设备,最多也就3个副本,只有节点数的增加到10台时,副本数才能达到10。

4.6.2 管理员命令

显示所有dataNode

hdfs dfsadmin -report


4.7_HDFS的客户端操作

4.7.1 HDFS的API操作
HDFSUtils.java
package com.atSchool.utils;

import java.io.IOException;
import java.nio.file.ClosedFileSystemException;

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

public class HDFSUtils {
1、获取FileSystem对象
/**
	 * 获取FileSystem对象
	 * 
	 * @return 返回FileSystem的一个实例
	 */
	public static FileSystem getFileSystem() {
		// 连接HDFS
		Configuration configuration = new Configuration(); // 获取配置文件对象
		configuration.set("fs.defaultFS", "hdfs://192.168.232.129:9000"); // name和value在/usr/soft/hadoop/etc/hadoop目录下的core-site.xml文件下找
        configuration.set("dfs.replication", "1");	// 将在windows上传文件时的备份数设置为1
        
		// 调用HDFS下的对象:FileSystem - 操作HDFS的对象
		FileSystem fileSystem = null;
		try {
			if (fileSystem == null) {
				fileSystem = FileSystem.get(configuration);
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return fileSystem;
	}
2、释放FileSystem对象
/**
	 * 释放FileSystem对象
	 * 
	 * @param fileSystem
	 *            FileSystem对象
	 */
	public static void ClosedFileSystem(FileSystem fileSystem) {
		try {
			if (fileSystem != null) {
				fileSystem.close();
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
HDFSDemo.java
package com.atSchool.HDFS;

import java.io.IOException;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hdfs.DistributedFileSystem;
import org.apache.hadoop.hdfs.protocol.DatanodeInfo;

import com.atSchool.utils.HDFSUtils;

public class HDFSDemo {
	private static final FileSystem DistributedFileSystem = null;

	public static void main(String[] args) throws IOException {
		// mkdir(); // 创建目录
		// deletFile(); // 删除文件
		// getListFile(); // 获取目录下所有文件
		// upload(); // 上传文件(非IO流)
		// downloadFile(); // 下载文件(非IO流)
		// getNodeInfo(); // 获取节点信息
		// readFile(); // 读取文件
		writeFile();// 写入
	}
1、创建目录
// 创建目录
	private static void mkdir() {
		FileSystem fileSystem = HDFSUtils.getFileSystem();
		Path path = new Path("/test_0324");
		try {
			boolean mkdirs = fileSystem.mkdirs(path);
			System.out.println(mkdirs == true ? "创建成功" : "创建失败");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			HDFSUtils.ClosedFileSystem(fileSystem);
		}
	}
2、删除文件/目录(不关心是否为空)
// 删除文件或者目录(不关心目录是否为空)
	private static void deletFile() {
		FileSystem fileSystem = HDFSUtils.getFileSystem();
		/**
		 * 第一个参数:需要删除的文件的路径 第二个参数:是否递归删除,true/false
		 */
		try {
			boolean delete = fileSystem.delete(new Path("/123"), true);
			System.out.println(delete == true ? "删除成功" : "删除失败");
		} catch (IllegalArgumentException | IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			HDFSUtils.ClosedFileSystem(fileSystem);
		}
	}
3、获取目录下所有文件
// 获取目录下所有文件
	private static void getListFile() {
		FileSystem fileSystem = HDFSUtils.getFileSystem();
		try {
			// 返回一个FileStatus数组,存储的是 ‘存储着目录下的所有文件的所有信息’ 的对象
			FileStatus[] listStatus = fileSystem.listStatus(new Path("/"));
			for (FileStatus fileStatus : listStatus) {
				System.out.println(fileStatus);
				System.out.println(fileStatus.getOwner()); // 可以指定获取某一项的信息
			}
		} catch (IllegalArgumentException | IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			HDFSUtils.ClosedFileSystem(fileSystem);
		}
	}
4、上传文件
// 上传文件
	private static void upload() {
		FileSystem fileSystem = HDFSUtils.getFileSystem();
		// 第一个参数:本地文件的路径 第二个参数:目标文件路径
		try {
			fileSystem.copyFromLocalFile(
					new Path("D:\\------ Learning materials ------\\Linux\\尚硅谷linux资料\\金庸-射雕英雄传txt精校版 _167MB.txt"),
					new Path("/"));
			System.out.println("上传成功");
		} catch (IllegalArgumentException | IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			HDFSUtils.ClosedFileSystem(fileSystem);
		}
	}
5、下载文件
// 下载文件
	private static void downloadFile() {
		FileSystem fileSystem = HDFSUtils.getFileSystem();
		// 参数1:需要下载的文件路径 参数2:目标存储路径
		try {
			fileSystem.copyToLocalFile(new Path("/C盘各个文件说明.txt"), new Path("D:\\hadoop_download"));
			System.out.println("下载成功");
		} catch (IllegalArgumentException | IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			HDFSUtils.ClosedFileSystem(fileSystem);
		}
	}
6、获取HDFS集群节点信息
// 获取HDFS集群节点信息
	private static void getNodeInfo() {
		FileSystem fileSystem = HDFSUtils.getFileSystem();
		// 获取分布式文件系统
		DistributedFileSystem dfs = (DistributedFileSystem) fileSystem;
		// 获取所有的节点
		try {
			DatanodeInfo[] dataNodeStats = dfs.getDataNodeStats();
			for (int i = 0; i < dataNodeStats.length; i++) {
				System.out.println("DateNote_" + i + "_Name:" + dataNodeStats[i]);
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			HDFSUtils.ClosedFileSystem(fileSystem);
		}
	}
7、读取文件
// 读取文件
	public static void readFile() {
		FileSystem fileSystem = HDFSUtils.getFileSystem();
		// 读取文件
		FSDataInputStream open = null;
		try {
			// 打开目标文件,得到一个输入流
			open = fileSystem.open(new Path("/C盘各个文件说明.txt"));
			byte[] b = new byte[1024];
			int len = 0;
			while ((len = open.read(b)) != -1) { // 当读不到数据的是否会返回-1
				String string = new String(b, 0, len, "utf-8"); // 读取适当的内容,并设置指定的编码方式
				System.out.println(string);
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (open != null) {
				try {
					open.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			HDFSUtils.ClosedFileSystem(fileSystem);
		}
	}
8、写入文件(覆盖/追加)
// 写入文件
	public static void writeFile() {
		FileSystem fileSystem = HDFSUtils.getFileSystem();
		FSDataOutputStream fsdos = null;

		try {
			Path path = new Path("/C盘各个文件说明.txt");
            /**
             * 方式一:打开一个文件覆盖写入
			 * public FSDataOutputStream create(Path f) throws IOException
			 * 在指定的路径上创建FSDataOutputStream。默认情况下,文件将被覆盖。
			 * 参数:
			 *		f-要创建的文件
			 */
			// fsdos = fileSystem.create(path);
            
			/**
			 * 方式二:打开一个文件追加写入
			 * public FSDataOutputStream append(Path f) throws IOException
			 * 附加到现有文件(可选操作)。
			 * 参数:
			 * 		f-所附的现有档案。
			 *
			 * 注意:要使用该方法,必须在hdfs-site.xml中添加:
			 * <property>
			 * 		<name>dfs.support.append</name>
			 * 		<value>true</value>
			 * </property>
			 */
			fsdos = fileSystem.append(path);
			
			/**
			 * public void write(int b) throws IOException
			 * 覆盖的方式写入指定的字节(参数的低八位)。b)到基础输出流。如果没有异常抛出,则计数器written增加为1.
			 * 实现write方法OutputStream.
			 */
			fsdos.write("hello ,insert\n".getBytes("UTF-8"));
			System.out.println("写入成功");
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (fsdos != null) {
					fsdos.close();
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			HDFSUtils.ClosedFileSystem(fileSystem);
		}
	}
}



文件上传/下载的时候会生成一个crc文件,crc文件是干什么的?

数据真实存储目录:datanode负责

真实数据的存储目录配置文件的目录: /home/hadoop/data/

data/current:存储真实数据

data/in_use.lock:是一个锁文件,作用用于标识进程,锁定进程

文件下载的是侯会产生一个.crc结尾的文件,这个文件的作用用于校验下载的文件和上传的文件是否为同一个文件,根据文件的起始偏移量和结尾偏移量,如果中间的内容不发生改变则校验通过。

例如:末尾追加是可以校验通过的,如果起始偏移量和结尾偏移量中间的内容发生了改变,则校验不通过 。

HomeWork
package com.atSchool.homeWork;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Random;

import org.apache.hadoop.fs.BlockLocation;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;

import com.atSchool.utils.HDFSUtils;

public class HomeWork_1 {
	public static void main(String[] args) throws FileNotFoundException, IllegalArgumentException, IOException {
		// DelSpecifiedFile(new Path("/")); // 递归删除指定路径下特定类型的文件
		// DelEmptyFile(new Path("/")); // 删除某路径下所有的空文件
		// getSpecifiedBlock(); // 手动拷贝某个特定的数据块
		InputRandom();
	}
1、递归删除指定路径下特定类型的文件
/**
	 * 递归删除指定路径下特定类型的文件,比如class类型文件,比如txt类型文件 
	 * 1、删除文件文件的方法
     * 2、遍历文件 判断是文件夹还是文件
	 * 3、执行删除的操作
	 */
	private static final String SUFFIX = "txt";

	private static void DelSpecifiedFile(Path path) throws IOException {
		// 获取连接
		FileSystem fileSystem = HDFSUtils.getFileSystem();
		/**
		 * public abstract FileStatus[] listStatus(Path f) throws FileNotFoundException,IOException 
		 * 如果路径是目录,则列出给定路径中文件/目录的状态。 不保证按排序顺序返回文件/目录状态列表。
		 * 参数: f-给定路径
		 */
		// 查看path目录下一级子目录的子文件信息
		FileStatus[] listStatus = fileSystem.listStatus(path);
		// 遍历listStatus
		for (FileStatus fileStatus : listStatus) {
			// 如果是文件,则交给DelFile判断
			if (fileStatus.isFile()) {
				DelFile(fileStatus.getPath());
			} else {
				// 如果是目录,则进入递归
				DelSpecifiedFile(fileStatus.getPath());
			}
		}
		// HDFSUtils.ClosedFileSystem(fileSystem);
	}

	// 判断删除文件的方法
	private static void DelFile(Path path) throws IOException {
		FileSystem fileSystem = HDFSUtils.getFileSystem();
		// 获取路径下文件的名称
		String name = path.getName();

		// 判断是不是以.txt结尾
		String[] split = name.split("\\.");

		// 如果是指定的类型文件就删除
		if (split[split.length - 1].equals(SUFFIX)) {
			/**
			 * public abstract boolean delete(Path f, boolean recursive) throws IOException
			 * 删除文件。 
			 * 参数:
             *		f-删除的路径。
			 * 		recursive-如果PATH是一个目录并设置为true,则删除该目录,否则会引发异常。
			 *		对于文件,可以将递归设置为true或false。 
			 * 返回:
			 * 		如果删除成功,则为true,否则为false。
			 */
			fileSystem.delete(path, true);
			System.out.println("删除成功");
		}
		// HDFSUtils.ClosedFileSystem(fileSystem);
	}
2、删除某路径下所有的空文件
/**
	 * 删除某路径下所有的空文件
	 * 
	 * @throws IOException
	 * @throws FileNotFoundException
	 */
	private static void DelEmptyFile(Path path) throws FileNotFoundException, IOException {
		FileSystem fs = HDFSUtils.getFileSystem();
		// 获取文件信息
		FileStatus[] listStatus = fs.listStatus(path);
		// 当根目录为空时则直接删除
		if (listStatus.length == 0) {
			fs.delete(path, true);
			System.out.println(path + "删除成功");
			// HDFSUtils.ClosedFileSystem(fs);
			return;
		}

		// 遍历传进来的路径path下的所有文件
		for (FileStatus fileStatus : listStatus) {
			// 如果是文件,且长度为0
			/**
			 * public long getLen() 获取此文件的长度(以字节为单位)。
			 */
			Path p = fileStatus.getPath(); // 获取当前被遍历的文件的路径
			if (fileStatus.isFile()) {
				// 如果该文件为空
				if (fileStatus.getLen() == 0) {
					fs.delete(p, true);
					System.out.println(p + "删除成功");
				}
			} else { // 如果是目录
				DelEmptyFile(p);
			}
		}
		// 当递归完了后回来的时候再判断一下当前目录是否为空,为空就删除
		if (fs.listStatus(path).length == 0) {
			fs.delete(path, true);
			System.out.println(path + "删除成功");
		}
		// HDFSUtils.ClosedFileSystem(fs);
	}
3、从随机地方开始读,读任意长度
// 从随机地方开始读,读任意长度
	private static void InputRandom() throws IOException {
		FileSystem fs = HDFSUtils.getFileSystem();
		Random rand = new Random(); // 随机数类
		// 创建输入流
		FSDataInputStream open = fs.open(new Path("/122/123/124/book.txt"));
		open.seek(rand.nextInt(100)); // 随机定位到[0,100)的位置
		// 创建输出流
		FSDataOutputStream create = fs.create(new Path("/t"));
		int i = rand.nextInt(1000000);
		IOUtils.copyBytes(open, create, new Long(i), true);
		System.out.println("读取成功");
		// 关闭输入输出流
		if (open != null) {
			open.close();
		}
		if (create != null) {
			create.close();
		}
	}
4、手动拷贝某个特定的数据块
// 手动拷贝某个特定的数据块(比如某个文件的第二个数据块)
	private static void getSpecifiedBlock() throws IOException {
		FileSystem fileSystem = HDFSUtils.getFileSystem();
		FileOutputStream os = null;
		FSDataInputStream inputStream = null;
		// 1、在指定路径上打开 FSDataInputStream
		inputStream = fileSystem.open(new Path("/金庸-射雕英雄传txt精校版 _167MB.txt"));

		// 2、获取文件/目录的信息 这里只有一个文件,所以该数组也只会有一个元素
		FileStatus[] listStatus = fileSystem.listStatus(new Path("/金庸-射雕英雄传txt精校版 _167MB.txt"));

		// 3、获取块信息
		// getFileBlockLocations:返回一个包含主机名、偏移量和给定文件部分大小的数组。对于不存在的文件或区域,将返回NULL。
		// 第一个参数:一个path,这里传的是前面获取到的文件信息 第二个参数:偏移量 第三个参数:长度
		// FileStatus类中的方法:getLen() 获取此文件的长度(以字节为单位)。
		BlockLocation[] fileBlockLocations = fileSystem.getFileBlockLocations(listStatus[0], 0, listStatus[0].getLen());

		// 4、获取指定块的偏移量 + 长度
		long length = fileBlockLocations[1].getLength(); // 获取第二个块的长度
		long offset = fileBlockLocations[1].getOffset(); // 获取第二个快的起始偏移量

		// 5、拷贝指定块
		/**
		 * public void seek(long desired) throws IOException 寻求给定的偏移量。
		 */
		inputStream.seek(offset); // 寻找起始的偏移量
		os = new FileOutputStream(new File("D:/block2.txt")); // 输出流
		/**
		 * IOUtils:Hadoop下用于I/O相关功能的实用程序类。 
		 * public static void copyBytes(InputStream in,OutputStream out, long count, boolean close) throws IOException
		 * 复制从一个流到另一个流的字节数。 
		 * 参数:
         * 		in-要阅读的InputStream 
         * 		out-输出流要写入 
         * 		count-要复制的字节数
		 * 		close-使用完后是否关闭该流
		 */
		IOUtils.copyBytes(inputStream, os, length, true);
		System.out.println("拷贝成功");
		// 关闭输入输出流
		if (os != null) {
			os.close();
		}
		if (inputStream != null) {
			inputStream.close();
		}
		// HDFSUtils.ClosedFileSystem(fileSystem);
	}
}



4.7.2 HDFS的IO流操作
package com.atSchool.HDFS;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

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 com.atSchool.utils.HDFSUtils;

public class HDFSIOOperate {
	public static void main(String[] args) throws IllegalArgumentException, IOException {
//		 UploadFile();
		DownloadFile();
	}

	// 上传文件
	public static void UploadFile() throws IllegalArgumentException, IOException {
		// 获取上传目标对象
		File file = new File("C:\\C盘各个文件说明.txt");

		// 获取文件系统对象
		FileSystem fs = HDFSUtils.getFileSystem();

		// 在指定路径下打开输出流
		FSDataOutputStream out = fs.create(new Path("/说明.txt"));

		// 获取输入流
		FileInputStream in = new FileInputStream(file);

		// 将数据写入到目标文件中
		int len = 0;
		byte[] b = new byte[1024];
		while ((len = in.read(b, 0, b.length)) > 0) {
			 out.write(b, 0, len);
		}
		// 清空缓冲中的数据
		out.flush();

		// 关闭输出,输入流
		out.close();
		in.close();
		System.out.print("上传成功");
	}

	// 下载文件
	private static void DownloadFile() throws IllegalArgumentException, IOException {
		// 获取FileSystem对象
		FileSystem fs = HDFSUtils.getFileSystem();

		// 获取 需要下载的 目标文件的输入流
		FSDataInputStream in = fs.open(new Path("/说明.txt"));

		// 指定下载后保存的路径及文件名
		File file = new File("D:\\download_from_HDFS.txt");
		file.createNewFile(); // 创建文件,如果存在这样的文件就不创建了,返回false。
		// 在指定路径下打开输出流
		FileOutputStream out = new FileOutputStream(file);

		// 将数据写入到目标文件中
		int len = 0;
		byte[] b = new byte[1024];
		while ((len = in.read(b, 0, b.length)) > 0) {
			out.write(b, 0, len);
		}
		// 清空缓冲中的数据
		out.flush();

		// 关闭输出,输入流
		out.close();
		in.close();
		System.out.print("下载成功");
	}
}



注意

hadoop hdfs文件系统对象FileSystem get和newInstance区别:

FileSystem对于文件系统类的实例做了缓存,如果是来自同一个文件系统,它会返回同一个实例。

所以get方法不是每次都创建FileSystem对象,会从缓存中获取FileSystem对象,而newInstance方法则会每次都创建新对象。所以在使用该对象的API编程时,推荐使用get方法。

不要去close我们拿到的FileSystem, 那么会不会有“泄漏“呢,FileSystem的设计者其实考虑到了这个问题,它有一个shutdown的hook会负责最后关掉这些FileSystem

注意:用get不能close,否则多线程报错(所以我用static),而用newInstance必须每次close.



常用方法总结
FileSystem类中的方法

FileSystem类中的方法

说明

参数

public static FileSystem get(Configuration conf) throws IOException

返回配置的文件系统实现。

public boolean mkdirs(Path f) throws IOException

创建文件

public abstract boolean delete(Path f, boolean recursive) throws IOException

删除文件。

f-删除的路径。recursive-如果PATH是一个目录并设置为true,则删除该目录,否则会引发异常。对于文件,可以将递归设置为true或false。

public abstract FileStatus[] listStatus(Path f) throws FileNotFoundException, IOException

如果路径是目录,则列出给定路径中文件/目录的状态。不保证按排序顺序返回文件/目录状态列表。

f-给定路径

public void copyFromLocalFile(Path src, Path dst) throws IOException

Src文件位于本地磁盘上。以给定的dst名称将其添加到FS中,之后源将保持原样。

public void copyToLocalFile(Path src, Path dst) throws IOException

Src文件位于FS下,DST位于本地磁盘上。将它从FS控件复制到本地DST名称。

public FSDataInputStream open(Path f) throws IOException

在指定路径上打开FSDataInputStream输入流

public FSDataOutputStream create(Path f) throws IOException

在指定的路径上创建FSDataOutputStream输出流。默认情况下,文件将被覆盖

f-要创建的文件

public FSDataOutputStream append(Path f) throws IOException

附加的方式在指定路径上打开一个输出流(可选操作)。

如果要使用则需要在hdfs-site.xml文件中设置

f-所附的现有档案。

public abstract FileStatus[] listStatus(Path f) throws FileNotFoundException,IOException

如果路径是目录,则列出给定路径中文件/目录的状态。 不保证按排序顺序返回文件/目录状态列表。

f-给定路径


IOUtils中的方法

IOUtils:Hadoop下用于I/O相关功能的实用程序类。

IOUtils中的方法

说明

参数

public static void copyBytes(InputStream in,OutputStream out, long count, boolean close) throws IOException

复制从一个流到另一个流的字节数。

in-要阅读的InputStream

out-输出流要写入

count-要复制的字节数

close-使用完后是否关闭该流

public static void copyBytes(InputStream in,OutputStream out,int buffSize,boolean close) throws IOException

从一个流复制到另一个流。

in-InputStrem阅读

out-输出流要写入

buffSize-缓冲区的大小

close-是否在最后关闭InputStream和OutputStream。流在Final子句中关闭。