目录
通过java.net.URL读取数据
通过FlieSystem API读写数据
读取数据
写入数据
新建目录
新建文件并写入
追加写入
一致模型
修改配置信息
通过FileStatus查询文件系统
查看文件系统信息
查看块信息
查看datanode信息
让java程序能够识别hadoop的hdfs URL需要先通过FsUrlStreamHandlerFactory实例调用java.net.URL对象的setURLStreamHandlerFactory()方法,每个java虚拟机只能调用一次此方法,所以一般在静态方法中调用;也就是说程序的其他部件已经声明了一个URLStreamHandlerFactory实例就无法使用这种方法读取数据。所以一般很少使用这种方法读取数据
以下方法类似于Linux中的cat命令显示hadoop文件系统中的文件内容:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import org.apache.hadoop.fs.FsUrlStreamHandlerFactory;
import org.apache.hadoop.io.IOUtils;
public class CatByURL {
static{
URL.setURLStreamHandlerFactory(new FsUrlStreamHandlerFactory());
}
public static void main(String[] args) {
InputStream in = null;
try {
//通过URL对象获取流
in = new URL("hdfs://master1:9000/d1/file").openStream();
// static void copyBytes(InputStream in, OutputStream out, int buffSize, boolean close)
// 最后一个参数指复制结束后是否关闭数据流,因为System.out不必关闭输入流
IOUtils.copyBytes(in, System.out, 10, false);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
//关闭数据流
IOUtils.closeStream(in);
}
}
}
Hadoop文件系统通过Hadoop Path对象来代表文件,可以将路径视为一个Hadoop文件系统URI。FileSystem是一个通用的文件系统API
获取FileSystem实例的静态工厂方法之一:
public static FileSystem get(Configuration conf) throws IOException
第一步需要检索HDFS,Configuration对象封装了客户端或服务器的配置。通过设置配置文件读取类路径来实现;此方法返回的是默认文件系统(core-site.xml,core-default.xml)
同样在获取了FileSystem实例后,调用open()方法可以获取文件的输入流
public FSDataInputStream open(Path f) throws IOException //默认缓冲区为4kb
public abstract FSDataInputStream open(Path f, int bufferSize) throws IOException
我们可以重写上面的CatByURL
@Test
public void catbyFS() throws IOException{
/* 创建配置对象conf,加载配置文件信息:
* 比如hadoop-common-2.7.3.jar里的core-default和src下的core-site.xml*/
Configuration conf = new Configuration();
/* 通过FileSystem提供的静态方法get(Configuration conf)
* 获取FileSystem的具体实例,DistributedFileSystem
* 同时加载了剩下的六个配置文件 */
FileSystem fs = FileSystem.get(conf);
/*使用Path来描述HDFS系统上的一个文件*/
Path path = new Path("hdfs://master1:9000/d1/file");
/*通过fs.open(Path path)方法 来进行一些权限,路径等校验,如果校验通过,就返回一个输入流*/
FSDataInputStream input = fs.open(path);
IOUtils.copyBytes(input, System.out, 10, false);
IOUtils.closeStream(input);
}
FSDataInputStream对象
FSDataInputStream并不是标准的java.io对象,支持随机访问
public class FSDataInputStream extends DataInputStream
implements Seekable, PositionedReadable//...
{
}
FSDataInputStream是继承了DataInputStream的一个特殊类,并且实现了Seekable接口,支持在文件中找到指定位置,并提供了一个查询当前文职相对于文件起始位置偏移量的查询方法:
public interface Seekable {
/* 从文件开始查找给定的偏移量。下一个read()将来自该位置。不能查找文件末尾后面的内容*/
void seek(long pos) throws IOException;
/* 返回从文件开始的当前偏移量*/
long getPos() throws IOException;
@Override
int read(ByteBuffer buf)
//...
}
seek()与InputStream的skip()方法不同,seek()可以移到文件中任意一个绝对位置,skip()只能相对于当前位置定位到另一个新位置
@Test
public void testSeek() throws Exception{
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);
Path path = new Path("hdfs://master1:9000/d1/file");
FSDataInputStream in = fs.open(path);
Long pos = in.getPos();
System.out.println("起始位置:"+pos);
IOUtils.copyBytes(in, System.out, 10, false);
pos = in.getPos();
System.out.println("结束位置(文件字节数)"+pos);
//将指针移动到文件的第10个字节之后,重新往后读取文件
in.seek(10);
pos = in.getPos();
System.out.println(pos);
//一次只能读一个块的信息
byte[] bs = new byte[10];
in.read(bs);
System.out.println(new String(bs));
IOUtils.closeStream(in);
}
运行结果
起始位置:0
asdasd
asdadsa
结束位置(文件字节数):15
10
adsa
与IOUtils.copyBytes()不同的是,通过bytes数组读取数据只能读取到一个块的数据,比如块大小为10b,文件大小为15b,则文件分为2个块存储在datanode上,即使通过15b的byte数组读取数据,也只能获取到第一个块内的10b数据;
另外seek()方法是一个相对高开小的操作,需要慎重使用
通过FileSystem的mkdir(Path f)方法,并且可以创建多级目录,该方法是返回boolean类型;相当于shell接口的hadoop fs -mkdir -p
//@Test
public void testMkdir() throws Exception{
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);
Path path = new Path("hdfs://master1:9000/d2");
fs.mkdirs(path);
}
默认加载客户端上的默认配置信息,或者可以在src目录下创建hdfs-site.xml设置自定义属性,,create方法写入文件时会自动创建父目录,不需要显示创建一个目录
相当于shell 接口的hadoop fs -put localfile dst
@Test
public void testWrite() throws Exception{
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);
Path path = new Path("hdfs://master1:9000/d2/f3");
FSDataOutputStream out = fs.create(path);
out.write("asdasd".getBytes());
out.close();
}
与新建文件不同的是,FileSystem的实例需要调用append(Path f)获得FSDataOutputStream类对象,即可对已经存在的文件进行写入
注意:当默认副本数大于节点数时,可以在hdfs-site.xml中设置属性:dfs.client.block.write.replace-datanode-on-failure.policy为NEVER
dfs.client.block.write.replace-datanode-on-failure.policy属性仅在dfs.client.block.write.replace-datanode-on-failure为true时生效,属性值:
FSDataOutputStream对象
FileSystem实例的create()方法返回FSDataOutputStream对象,它也有一个查询文件当前位置的方法getPos()
public class FSDataOutputStream extends DataOutputStream implements Syncable{
public void write(int b) throws IOException {}
public void write(byte b[], int off, int len) throws IOException {}
public long getPos() throws IOException {}
public void close() throws IOException {}
//...
}
与FSDataInputStream不同的是,FSDataOutputStream类不允许在文件中定位,因为HDFS只允许对一个一打开的文件进行顺序写入,或在现有文件的末尾追加数据
文件系统的一致模型描述了文件读写的数据可见性
通过新建一个文件之后,即FileSystem实例调用create方法后,文件在文件系统的命名空间中立即可见;但是,写入文件的内容并不保证能立即可见,即使数据块已经刷新并存储(调用flush()方法),文件长度显示仍为0;只有当写的数据超过一个块后,之前的数据块是可见的,当前正在写入的块是不可见的
FSDataOutputStream提供了hdfs()方法,可以强行将客户端内缓存刷新到datanode中的内存中,若成功执行hdfs能保存文件中到目前为止写入的数据均到达所有datanode的写入管道,并且可见;但hflush不能保证datanode已经将数据写到磁盘上,如果数据中心断电,数据会丢失
public void hflush() throws IOException
hsync()方法能保证数据持久化到磁盘上
public void hsync() throws IOException
在以下这两行代码中,客户端获取了hadoop的相关配置信息
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);
第一行代码读取了core-default和core-site.xml;
第二行读取了 hdfs-default.xml,hdfs-site.xml,mapred-default.xml,mapred-site.xml,yarn-default.xml,yarn-site.xml;
而在对每一个配置文件的读取上存在着前后顺序,客户端会先读取集群上(即namenode)的配置文件,然后读取客户端(src目录)下的配置文件(若存在);所以我们可以在src下新建配置文件,这样就不需要去集群中修改;但是有一些属性只能在集群中修改,在客户端修改会无效,比如像hdfs-site.xml中的dfs.namenode.fs-limits.min-block-size(最小块大小)这种名字中含有"namenode"的属性一般都需要在集群中修改
另外也可以通过调用Configuration实例的set(String name, String value)方法设置属性值,但注意需要在FileSystem.get(conf)之前设置,比如:
Configuration conf = new Configuration();
conf.set("dfs.blocksize", "1500000");
FileSystem fs = FileSystem.get(conf);
dfs.blocksize为hdfs-site.xml中的属性,而构造Configuration的实例的过程仅读取了core有关属性;其实这是一个设置属性的过程,而不是覆盖原来属性值,当运行到FileSystem.get(conf)时,读取属性若客户端中已经有了相关属性就不会去覆盖了,这也就是为什么set访问写在get后无法修改属性值的原因
FileStatus类封装了文件系统中的文件和目录的元数据,包括文件长度,块大小,复本,修改时间,所有者以及权限信息
FileSystem的getFileStatus()方法可以获取文件或目录的FileStatus对象,相当于shell接口的hadoop fs -ls
//@Test
public void testStatus() throws Exception{
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);
Path path = new Path("hdfs://master1:9000/d1/file");
FileStatus status = fs.getFileStatus(path);
System.out.println(status.toString());
}
运行结果
FileStatus{path=hdfs://master1:9000/d1/file; isDirectory=false; length=15; replication=3; blocksize=134217728; modification_time=1555219406730; access_time=1555510861507; owner=jinge; group=supergroup; permission=rw-r--r--; isSymlink=false}
获取单一属性方法:
FileSystem下有获取所有块位置信息的方法:
public BlockLocation[] getFileBlockLocations(FileStatus file, long start, long len)
@Test
public void testLocation() throws IOException {
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);
Path path = new Path("hdfs://master1:9000/d1/f1");
FileStatus stat = fs.getFileStatus(path);
//stat.getLen()为文件系统中块个数,f1仅有1块
BlockLocation[] bls = fs.getFileBlockLocations(stat, 0, stat.getLen());
for(BlockLocation b:bls) {
//获取块所在主机列表
String[] hosts = b.getHosts();
for(String host:hosts) {
System.out.println("host:"+host+" ");
}
//获取该块的名称(ip:port)列表
String[] names = b.getNames();
for(String name:names) {
System.out.println("name:"+name+" ");
}
//获取块所在网络拓扑列表
String[] topos = b.getTopologyPaths();
for(String topo:topos) {
System.out.println("topo:"+topo+" ");
}
//...
}
}
DistributedFileSystem类下有获取所有datanode节点的方法:
public DatanodeInfo[] getDataNodeStats() throws IOException
@Test
public void testDatanodeInfo() throws IOException {
Configuration conf = new Configuration();
DistributedFileSystem fs = (DistributedFileSystem) FileSystem.get(conf);
DatanodeInfo[] infos = fs.getDataNodeStats();
for(DatanodeInfo info:infos) {
System.out.println("BlockPoolUsed:"+info.getBlockPoolUsed());
System.out.println("CacheCapacity:"+info.getCacheCapacity());
System.out.println("DatanodeReport:"+info.getDatanodeReport());
System.out.println("DatanodeUuid:"+info.getDatanodeUuid());
System.out.println("HostName:"+info.getHostName());
System.out.println("InfoPort:"+info.getInfoPort());
System.out.println("Name:"+info.getName());
//...
}
}