本文是對(duì)HDFS的JAVA API操作的一個(gè)學(xué)習(xí)總結(jié),包括如下章節(jié)的內(nèi)容:
- 概述
- 目錄和文件操作
- 文件上傳和下載
- 讀寫(xiě)數(shù)據(jù)操作
- 本地文件系統(tǒng)支持
參考資料:
1卿捎、本文介紹的內(nèi)容依賴hadoop環(huán)境仲智,關(guān)于hadoop運(yùn)行環(huán)境的搭建可參見(jiàn)《Hadoop運(yùn)行環(huán)境搭建》娘汞。
2烈钞、如果想了解下HDFS的基本概念漫仆,可先閱讀《HDFS學(xué)習(xí)筆記》捎拯。
一、概述
我們除了通過(guò)命令行接口訪問(wèn)HDFS系統(tǒng)外盲厌,還可以通過(guò)hadoop類庫(kù)提供的Java API編寫(xiě)java程序來(lái)訪問(wèn)HDFS系統(tǒng)署照,如進(jìn)行文件的上傳、下載吗浩、目錄的創(chuàng)建建芙、文件的刪除等各種文件操作。
hadoop類庫(kù)中提供的HDFS操作的核心API是FileSystem抽象類懂扼,該類提供了一系列方法來(lái)進(jìn)行相關(guān)的操作禁荸。
FileSystem是一個(gè)通用的文件系統(tǒng)API右蒲,它是一個(gè)抽象類,可以通過(guò)其提供的靜態(tài)工廠方法來(lái)獲取該類的實(shí)例赶熟。獲取HDFS文件系統(tǒng)FileSystem的實(shí)例最常用的靜態(tài)方法是:
static FileSystem get(Configuration conf);
參數(shù)Configuration對(duì)象是對(duì)文件系統(tǒng)中屬性信息的封裝瑰妄,默認(rèn)的屬性來(lái)自hadoop配置文件core-site.xml中的配置,當(dāng)然也可以通過(guò)代碼進(jìn)行屬性的設(shè)置映砖。進(jìn)行文件操作的基本流程是:
1间坐、創(chuàng)建Configuration對(duì)象
2、利用FileSystem 的靜態(tài)get方法獲取FileSystem 實(shí)例
3邑退、調(diào)用FileSystem 的方法進(jìn)行實(shí)際的文件操作
下面我們通過(guò)實(shí)際的例子來(lái)進(jìn)行學(xué)習(xí)竹宋。
二、目錄和文件操作API
1地技、創(chuàng)建目錄
利用FileSystem的mkdirs方法可以創(chuàng)建文件或目錄蜈七,其定義如下:
public boolean mkdirs(Path f) throws IOException;
該方法的參數(shù)用于指定待創(chuàng)建的目錄路徑,需要說(shuō)明的是乓土,mkdirs命令可以級(jí)聯(lián)創(chuàng)建目錄宪潮,即如果指定待創(chuàng)建的目錄的上級(jí)目錄不存在溯警,則會(huì)一次幫創(chuàng)建趣苏。
例子代碼如下:
package com.hdfs;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class CreateDir {
public static void main(String[] args) {
Configuration conf=new Configuration();
conf.set("fs.defaultFS", "hdfs://192.168.1.3:8020");
try {
FileSystem HDFS = FileSystem.get(conf);
HDFS.mkdirs(new Path(args[0]));
System.out.println("cerate success");
} catch (IOException e) {
System.out.println("cerate error");
e.printStackTrace();
}
}
}
上面代碼是一個(gè)普通的帶main方法的java類。main方法中第二行代碼如下:
conf.set("fs.defaultFS", "hdfs://192.168.1.3:8020");
上面代碼的目的是設(shè)置本程序(也就是HDFS客戶端)所訪問(wèn)的HDFS服務(wù)的地址梯轻。如果不通過(guò)代碼進(jìn)行設(shè)置食磕,則默認(rèn)會(huì)取hadoop安裝環(huán)境下core-site.xml文件中配置的"fs.defaultFS"的屬性值。所以喳挑,如果程序是運(yùn)行在服務(wù)器上(即name節(jié)點(diǎn)所在機(jī)器)則不用通過(guò)代碼進(jìn)行顯示設(shè)置彬伦。
代碼有了,那我們?cè)趺淳幾g和執(zhí)行程序呢伊诵?這個(gè)和處理mapreduce程序類似单绑,可以參考《mapreduce學(xué)習(xí)筆記》一文中的介紹。這里再簡(jiǎn)單介紹下曹宴。
我們?cè)贗DE中編寫(xiě)上面程序搂橙,要想代碼可以編譯成功,只需要引入hadoop-common-2.7.6.jar笛坦。但運(yùn)行的時(shí)候区转,需要依賴更多的Jar包。最簡(jiǎn)單的運(yùn)行方式是版扩,將程序編譯后的class打成jar包废离,然后在命令行下利用hadoop jar命令來(lái)執(zhí)行,該命令會(huì)自動(dòng)引入執(zhí)行程序需要的jar包礁芦。
假設(shè)上面代碼打成jar包的名稱為testhdfs.jar蜻韭,則在命令行下運(yùn)行如下命令(待創(chuàng)建的目錄路徑是通過(guò)參數(shù)傳入):
hadoop jar testhdfs.jar com.hdfs.CreateDir /testdir
執(zhí)行后,根目錄下就會(huì)生成一個(gè)testdir目錄。
下面介紹的例子可以利用同樣的方法進(jìn)行編譯和運(yùn)行湘捎,后續(xù)不會(huì)再重復(fù)介紹诀豁。
2、刪除目錄或文件
利用FileSystem的delete方法可以刪除文件或目錄窥妇,其定義如下:
public boolean delete(Path f, boolean recursive) throws IOException
如果參數(shù)f指定的是文件或空目錄舷胜,則參數(shù)recursive的值被忽略;
如果參數(shù)f指定的目錄不未空活翩,則如果參數(shù)recursive的值為false烹骨,則執(zhí)行會(huì)拋異常,只有值為true材泄,才會(huì)將該目錄及其下內(nèi)容全部刪除沮焕。
例子代碼如下:
package com.hdfs;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class DeleteFile {
public static void main(String[] args) {
Configuration conf=new Configuration();
conf.set("fs.defaultFS", "hdfs://192.168.1.3:8020");
try {
FileSystem HDFS = FileSystem.get(conf);
boolean re = HDFS.delete(new Path(args[0]), true);
System.out.println("delete:"+re);
} catch (IOException e) {
e.printStackTrace();
}
}
}
3、重命名(移動(dòng))文件或目錄
利用FileSystem的rename方法可以重命名文件或目錄拉宗,如果被移動(dòng)的文件或目錄其父目錄發(fā)生變化峦树,則相當(dāng)于移動(dòng)文件或目錄。該方法定義如下:
public boolean rename(Path src,Path dest) throws IOException
該方法有兩個(gè)參數(shù)旦事,第一個(gè)參數(shù)是被操作的文件或目錄的路徑魁巩,第二參數(shù)是待修改后的路徑。如果操作成功姐浮,方法的返回值為true,否則為false谷遂。
例子代碼如下:
package com.hdfs;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class ReName {
public static void main(String[] args) {
Configuration conf=new Configuration();
conf.set("fs.defaultFS", "hdfs://192.168.1.3:8020");
try {
FileSystem HDFS = FileSystem.get(conf);
boolean re = HDFS.rename(new Path(args[0]), new Path(args[1]));
System.out.println("rename:"+re);
} catch (IOException e) {
e.printStackTrace();
}
}
}
4、判斷文件或目錄是否存在
利用FileSystem的exists方法可以判斷指定的文件或目錄是否存在卖鲤,其定義如下:
public boolean exists(Path f) throws IOException;
如果存在肾扰,方法返回值為true,否則為false蛋逾。
例子代碼如下:
package com.hdfs;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class CheckExist {
public static void main(String[] args) {
Configuration conf=new Configuration();
conf.set("fs.defaultFS", "hdfs://192.168.1.3:8020");
try {
FileSystem HDFS = FileSystem.get(conf);
boolean re = HDFS.exists(new Path(args[0]));
System.out.println("is exist:"+re);
} catch (IOException e) {
e.printStackTrace();
}
}
}
5集晚、判斷是文件還是目錄
利用FileSystem的isFile方法可以判斷指定的路徑是不是文件;利用isDirectory方法可以判斷指定的路徑是不是目錄区匣。兩個(gè)方法的定義如下:
public boolean isFile(Path f) throws IOException;
public boolean isDirectory(Path f) throws IOException;
如果存在指定的路徑不存在偷拔,則上面兩個(gè)方法都返回false。
例子代碼如下:
package com.hdfs;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class IsFile {
public static void main(String[] args) {
Configuration conf=new Configuration();
conf.set("fs.defaultFS", "hdfs://192.168.1.3:8020");
try {
FileSystem HDFS = FileSystem.get(conf);
boolean re = HDFS.isDirectory(new Path(args[0]));
System.out.println("isDirectory:"+re);
re = HDFS.isFile(new Path(args[0]));
System.out.println("isFile:"+re);
} catch (IOException e) {
e.printStackTrace();
}
}
}
6沉颂、查看目錄和文件的內(nèi)容
利用FileSystem的listStatus方法可以獲取指定的文件的信息或目錄下內(nèi)容的信息条摸,其定義如下:
public FileStatus[] listStatus(Path arg0)
throws FileNotFoundException, IOException
該方法返回一個(gè)FileStatus類型的對(duì)象數(shù)組,F(xiàn)ileStatus對(duì)象中封裝了文件(或目錄的)各種信息铸屉,如名稱钉蒲,訪問(wèn)時(shí)間,大小等彻坛。方法的參數(shù)是一個(gè)路徑顷啼,如果路徑指向一個(gè)文件踏枣,則返回的是數(shù)組就一個(gè)元素,代表該文件的信息钙蒙;如果路徑指向的是是一個(gè)目錄茵瀑,則返回的是該目錄下子目錄和文件信息。
例子代碼如下:
package com.hdfs;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class ShowFile {
public static void main(String[] args) throws IOException {
Configuration conf = null;
FileSystem fs = null;
conf = new Configuration();
conf.set("fs.defaultFS", "hdfs://192.168.1.3:8020");
fs = FileSystem.get(conf);
FileStatus[] listStatus = fs.listStatus(new Path(args[0]));
for(FileStatus fileStatus:listStatus){
String fileName = fileStatus.getPath().getName();
String type = fileStatus.isFile()?"file":"dir";
long size = fileStatus.getLen();
System.out.println(fileName+","+type+","+size);
}
}
}
7躬厌、復(fù)制文件或目錄
上面的例子都是利用FileSystem中的方法進(jìn)行文件和目錄的操作马昨,在文件操作中,還有一個(gè)常見(jiàn)的操作就是文件和目錄的復(fù)制扛施。但是FileSystem沒(méi)有提供接口來(lái)進(jìn)行復(fù)制操作鸿捧。這時(shí)可以利用FileContext接口提供的操作來(lái)實(shí)現(xiàn)。具體我們看一個(gè)例子疙渣,代碼如下:
package com.hdfs;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileContext;
import org.apache.hadoop.fs.FileContext.Util;
import org.apache.hadoop.fs.Path;
public class CopyFile {
public static void main(String[] args) {
Configuration conf=new Configuration();
conf.set("fs.defaultFS", "hdfs://192.168.1.3:8020");
try {
FileContext fileContext = FileContext.getFileContext(conf);
Util util = fileContext.util();
boolean re=util.copy(new Path(args[0]), new Path(args[1]));
System.out.println("copy:"+re);
} catch (IOException e) {
e.printStackTrace();
}
}
}
上面代碼先利用FileContext 的靜態(tài)方法獲取到一個(gè)FileContext 對(duì)象匙奴,類似獲取FileSystem方法,需要傳入一個(gè)Configuration 對(duì)象的參數(shù)妄荔。然后再獲取一個(gè)Util對(duì)象泼菌。Util對(duì)象提供的copy方法可以完成復(fù)制操作。copy方法有兩個(gè)參數(shù)啦租,第1個(gè)參數(shù)是待復(fù)制的文件或目錄哗伯,第2個(gè)參數(shù)是復(fù)制后的路徑(含文件名或目錄名),要求文件或目錄名不存在刷钢。如果復(fù)制成功笋颤,copy方法返回true乳附。
Util對(duì)象中也有些方法内地,類似FileSystem中的方法可以完成相同功能的一些文件操作。
三赋除、文件上傳和下載操作API
1阱缓、上傳本地文件
利用FileSystem的copyFromLocalFile方法可以將本地文件或目錄(及目錄下所有內(nèi)容)上傳到HDFS系統(tǒng)上,其定義如下:
public void copyFromLocalFile(Path src, Path dst) throws IOException;
1)該方法有兩個(gè)參數(shù)举农,第一個(gè)參數(shù)是待上傳的文件或目錄的本地路徑荆针,第二個(gè)參數(shù)是HDFS系統(tǒng)上的目的路徑。
2)如果待上傳的是一個(gè)目錄颁糟,則會(huì)把整個(gè)目錄及目錄下的所有內(nèi)容上傳航背。
3)如果目的路徑是一個(gè)目錄,則會(huì)把本地文和目錄上傳到該目的目錄下棱貌,被上傳的文件或目錄名不變玖媚。如果目的路徑是一個(gè)不存在的路徑,則會(huì)把本地文和目錄上傳到該目的路徑的上級(jí)目錄下婚脱,被上傳的文件或目錄名變?yōu)槟康穆窂街付ǖ拿Q今魔。
4)copyFromLocalFile還有多個(gè)重載方法勺像,可以選擇上傳后是否同時(shí)刪除本地文件或目錄,以及是否覆蓋目的文件或目錄错森。
例子代碼如下:
package com.hdfs;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class PutFile {
public static void main(String[] args) {
Configuration conf=new Configuration();
conf.set("fs.defaultFS", "hdfs://192.168.1.3:8020");
try {
FileSystem HDFS = FileSystem.get(conf);
HDFS.copyFromLocalFile(new Path(args[0]), new Path(args[1]));
System.out.println("action success");
} catch (IOException e) {
e.printStackTrace();
}
}
}
2吟宦、下載文件到本地
利用FileSystem的copyToLocalFile方法可以將HDFS系統(tǒng)上的文件或目錄(及目錄下所有內(nèi)容)下載到本地系統(tǒng)上,其定義如下:
public void copyToLocalFile(Path src, Path dst) throws IOException;
該方法的使用與上面介紹的上傳本地文件或目錄到HDFS系統(tǒng)上的copyFromLocalFile方法非常類似涩维,區(qū)別只是源路徑和目的路徑正好相反殃姓,這里不再詳細(xì)介紹。直接看一個(gè)例子:
package com.hdfs;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class PutFile {
public static void main(String[] args) {
Configuration conf=new Configuration();
conf.set("fs.defaultFS", "hdfs://192.168.1.3:8020");
try {
FileSystem HDFS = FileSystem.get(conf);
HDFS.copyToLocalFile(new Path(args[0]), new Path(args[1]));
System.out.println("action success");
} catch (IOException e) {
e.printStackTrace();
}
}
}
四瓦阐、讀寫(xiě)數(shù)據(jù)操作API
1辰狡、讀取數(shù)據(jù)
調(diào)用FileSystem的open方法(參數(shù)是HDFS系統(tǒng)上的一個(gè)文件)會(huì)返回一個(gè)FSDataInputStream對(duì)象,利用該對(duì)象可以讀取文件中的數(shù)據(jù)垄分。
我們先看一個(gè)例子代碼宛篇,該例子是讀取一個(gè)文本文件中的內(nèi)容:
package com.hdfs;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class ReadData {
public static void main(String[] args) {
Configuration conf=new Configuration();
conf.set("fs.defaultFS", "hdfs://192.168.1.3:8020");
BufferedReader reader = null;
String line = null;
try {
FileSystem HDFS = FileSystem.get(conf);
FSDataInputStream in = HDFS.open(new Path(args[0]));
reader = new BufferedReader(new InputStreamReader(in));
while ((line = reader.readLine()) != null){
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}finally{
if(reader!=null){
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
上面程序,首先調(diào)用FileSystem的open方法獲取到一個(gè)FSDataInputStream對(duì)象薄湿,是一個(gè)數(shù)據(jù)流對(duì)象叫倍,通過(guò)它提供的方法可以讀取流中的數(shù)據(jù),但該類的方法都是一些級(jí)別較低的底層方法豺瘤,不適合用來(lái)讀取文本文件吆倦,對(duì)于文本文件,我們一般習(xí)慣逐行讀取數(shù)據(jù)坐求,所以使用了JAVA IO 中的BufferedReader來(lái)包裝FSDataInputStream對(duì)象來(lái)讀取數(shù)據(jù)蚕泽。最后要在finally語(yǔ)句塊中關(guān)閉BufferedReader對(duì)象。
如果要讀取二進(jìn)制文件桥嗤,可利用工具類IOUtils中的方法须妻,如下面例子:
package com.hdfs;
import java.io.IOException;
import java.io.OutputStream;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
public class ReadData2 {
public static void main(String[] args) {
Configuration conf=new Configuration();
conf.set("fs.defaultFS", "hdfs://192.168.1.3:8020");
FSDataInputStream in = null;
try {
FileSystem HDFS = FileSystem.get(conf);
in = HDFS.open(new Path(args[0]));
OutputStream out = System.out;
IOUtils.copyBytes(in, out, 4096, true);
} catch (IOException e) {
e.printStackTrace();
}finally{
IOUtils.closeStream(in);
}
}
}
上面程序使用的IOUtils的copyBytes方法(它有多個(gè)重載的方法),該方法是個(gè)通用的方法泛领,用于將一個(gè)數(shù)據(jù)流中的數(shù)據(jù)寫(xiě)入另外一個(gè)數(shù)據(jù)流荒吏,它有4個(gè)參數(shù),第1個(gè)參數(shù)是InputStream類型渊鞋,即被讀取出來(lái)的數(shù)據(jù)流绰更,在這個(gè)例子中就是FSDataInputStream 對(duì)象,第2個(gè)參數(shù)是OutputStream類型锡宋,即要寫(xiě)入的數(shù)據(jù)流對(duì)象儡湾,這里為了演示簡(jiǎn)單,直接使用了系統(tǒng)輸出對(duì)象System.out,第3個(gè)參數(shù)表示本次讀寫(xiě)操作數(shù)據(jù)的規(guī)模(這里是4096個(gè)字節(jié))执俩,第4個(gè)參數(shù)是個(gè)布爾值徐钠,為true,表示調(diào)用完畢后關(guān)閉流對(duì)象奠滑。
需要說(shuō)明的是丹皱,如果第4個(gè)參數(shù)設(shè)置為true妒穴,則因?yàn)檎{(diào)用后文件流被關(guān)閉了,不能再次讀取摊崭。如果要能連續(xù)多次調(diào)用IOUtils的copyBytes方法讼油,則第4個(gè)參數(shù)不能設(shè)置為true,以為在finally語(yǔ)句塊中也進(jìn)行了關(guān)閉操作呢簸,所以不會(huì)導(dǎo)致文件流不會(huì)被關(guān)閉矮台。FSDataInputStream 對(duì)象內(nèi)部有一個(gè)位置指針,被讀取一次后根时,指針會(huì)指向讀取數(shù)據(jù)后的位置瘦赫,再次讀取,會(huì)從新的位置往后讀取蛤迎。
同時(shí)FSDataInputStream 對(duì)象支持隨機(jī)訪問(wèn)确虱,可以調(diào)用其seek方法定位到文件指定的位置開(kāi)始讀取,注意不能超過(guò)文件的長(zhǎng)度替裆。
我們看下面一個(gè)例子校辩,利用seek方法多次讀取數(shù)據(jù),代碼如下:
package com.hdfs;
import java.io.IOException;
import java.io.OutputStream;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
public class ReadData2 {
public static void main(String[] args) {
Configuration conf=new Configuration();
conf.set("fs.defaultFS", "hdfs://192.168.1.3:8020");
FSDataInputStream in = null;
try {
FileSystem HDFS = FileSystem.get(conf);
in = HDFS.open(new Path(args[0]));
OutputStream out = System.out;
IOUtils.copyBytes(in, out, 4096,false);
System.out.println("read again");
in.seek(0);
IOUtils.copyBytes(in, out, 4096, false);
} catch (IOException e) {
e.printStackTrace();
}finally{
IOUtils.closeStream(in);
}
}
}
需要注意的是辆童,seek方法是相對(duì)高開(kāi)銷的操作宜咒,需要慎重使用。對(duì)于大數(shù)據(jù)把鉴,建議采用順序讀取的方式(可采用Mapreduce來(lái)進(jìn)行讀取數(shù)據(jù))故黑。
2、創(chuàng)建文件寫(xiě)入數(shù)據(jù)
調(diào)用FileSystem的create方法(該方法有多個(gè)重載方法)會(huì)創(chuàng)建一個(gè)空的文件庭砍,同時(shí)該方法會(huì)返回一個(gè)FSDataOutputStream對(duì)象场晶,利用該對(duì)象可以往文件中寫(xiě)入數(shù)據(jù)。如果create方法指定的新文件所在的上級(jí)目錄不存在逗威,會(huì)自動(dòng)幫創(chuàng)建峰搪。
下面我們先看一個(gè)例子岔冀,該例子創(chuàng)建一個(gè)文件凯旭,寫(xiě)入文本信息:
package com.hdfs;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class WriteData {
public static void main(String[] args) {
Configuration conf=new Configuration();
conf.set("fs.defaultFS", "hdfs://192.168.1.3:8020");
BufferedWriter writer = null;
try {
FileSystem HDFS = FileSystem.get(conf);
FSDataOutputStream out = HDFS.create(new Path(args[0]));
writer = new BufferedWriter(new OutputStreamWriter(out));
writer.write("hello1");
writer.newLine();
writer.write("hello2");
} catch (IOException e) {
e.printStackTrace();
}finally{
if(writer!=null){
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
上面代碼先調(diào)用FileSystem的create方法創(chuàng)建一個(gè)空文件(如果文件已存在,則會(huì)被覆蓋)使套,該方法返回一個(gè)FSDataOutputStream 對(duì)象罐呼,F(xiàn)SDataOutputStream 對(duì)象中有很多寫(xiě)數(shù)據(jù)的方法,不過(guò)直接使用不太方便侦高。我們這里是準(zhǔn)備下寫(xiě)入文本信息嫉柴,因此利用了java io中的 BufferedWriter 來(lái)寫(xiě)入文本信息。
如果要寫(xiě)入二進(jìn)制數(shù)據(jù)奉呛,可利用工具類IOUtils中的方法计螺,如下面例子:
package com.hdfs;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
public class WriteData2 {
public static void main(String[] args) {
Configuration conf=new Configuration();
conf.set("fs.defaultFS", "hdfs://192.168.1.3:8020");
FSDataOutputStream out=null;
try {
FileSystem HDFS = FileSystem.get(conf);
out = HDFS.create(new Path(args[0]));
InputStream in = buildInputBuffer();
IOUtils.copyBytes(in , out, 4096, true);
} catch (IOException e) {
e.printStackTrace();
}finally{
IOUtils.closeStream(out);
}
}
private static InputStream buildInputBuffer() {
byte[] byt = new byte[1024];
for(int i=0;i<1024;i++){
byt[i]=66;
}
InputStream in = new ByteArrayInputStream(byt);
return in;
}
}
上面程序使用的IOUtils的copyBytes方法與上面從HDFS文件中讀取數(shù)據(jù)例子中使用的是一樣的方法夯尽。只是我們這里是從一個(gè)字節(jié)流(為了簡(jiǎn)化,自己構(gòu)建的)中讀取數(shù)據(jù)寫(xiě)入到create方法返回的FSDataOutputStream 對(duì)象中登馒。
一般來(lái)說(shuō)匙握,往HDFS中寫(xiě)入數(shù)據(jù)都是數(shù)據(jù)量比較大的,整個(gè)過(guò)程所需時(shí)間較長(zhǎng)陈轿。為了讓客戶端能獲取寫(xiě)入過(guò)程中的狀態(tài)圈纺,F(xiàn)ileSystem的create另一個(gè)重載方法提供了一個(gè)參數(shù)(Progressable接口)可以獲取數(shù)據(jù)寫(xiě)入的進(jìn)度。具體我們看一個(gè)例子麦射,例子代碼如下:
package com.hdfs;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.util.Progressable;
public class WriteData3 {
public static void main(String[] args) {
Configuration conf=new Configuration();
conf.set("fs.defaultFS", "hdfs://192.168.1.3:8020");
FSDataOutputStream out=null;
try {
FileSystem HDFS = FileSystem.get(conf);
out = HDFS.create(new Path(args[0]),new Progressable() {
@Override
public void progress() {
System.out.print(".");
}
});
InputStream in = buildInputBuffer();
IOUtils.copyBytes(in , out, 4096, true);
} catch (IOException e) {
e.printStackTrace();
}finally{
IOUtils.closeStream(out);
}
}
private static InputStream buildInputBuffer() {
int size = 1024*10000;
byte[] byt = new byte[size];
for(int i=0;i<size;i++){
byt[i]=66;
}
InputStream in = new ByteArrayInputStream(byt);
return in;
}
}
相比前面的例子蛾娶,在本例子中,FileSystem的create方法多了一個(gè)參數(shù),是一個(gè)Progressable接口對(duì)象潜秋,該接口有一個(gè)progress方法蛔琅,HDFS在寫(xiě)入數(shù)據(jù)的過(guò)程中,會(huì)根據(jù)進(jìn)度不斷調(diào)用progress方法峻呛,這樣我們實(shí)現(xiàn)progress方法就能知道數(shù)據(jù)寫(xiě)入的進(jìn)度了揍愁。
3、追加數(shù)據(jù)到已有的文件中
在某些場(chǎng)景下杀饵,我們需要往已經(jīng)存在文件的末尾追加寫(xiě)入數(shù)據(jù)莽囤。這時(shí)我們可以調(diào)用FileSystem的append方法來(lái)打開(kāi)一個(gè)文件,并返回FSDataOutputStream對(duì)象切距,然后就可以寫(xiě)入數(shù)據(jù)朽缎。除了使用append方法替換create方法,其它操作都一樣谜悟,這里不再重復(fù)介紹话肖。可以看一個(gè)簡(jiǎn)單例子葡幸,代碼如下:
package com.hdfs;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class AppendData {
public static void main(String[] args) {
Configuration conf=new Configuration();
conf.set("fs.defaultFS", "hdfs://192.168.1.3:8020");
BufferedWriter writer = null;
try {
FileSystem HDFS = FileSystem.get(conf);
FSDataOutputStream out = HDFS.append(new Path(args[0]));
writer = new BufferedWriter(new OutputStreamWriter(out));
writer.write("newdata");
writer.newLine();
} catch (IOException e) {
e.printStackTrace();
}finally{
if(writer!=null){
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
可以看出最筒,往已存在文件中添加數(shù)據(jù),只需將create方法換成append方法蔚叨。需要注意的是床蜘,append方法指定的文件必須已經(jīng)存在,如果不存在蔑水,h不會(huì)自動(dòng)創(chuàng)建邢锯,而是會(huì)報(bào)異常。
五搀别、本地文件系統(tǒng)支持
在本文中丹擎,我們通過(guò)多個(gè)例子介紹了如何使用hadoop提供的JAVA API來(lái)操作HDFS系統(tǒng)。可以看出蒂培,核心就是FileSystem類中提供的各種方法的使用再愈。在最上面我們也提到,F(xiàn)ileSystem類是一個(gè)通用的文件系統(tǒng)操作API护戳,不僅可以用來(lái)操作HDFS系統(tǒng)践磅,也可以用來(lái)訪問(wèn)其它文件系統(tǒng),比如本地文件系統(tǒng)灸异。
這就帶來(lái)一個(gè)好處府适,我們?cè)诩涵h(huán)境下開(kāi)發(fā)和調(diào)試代碼比較麻煩,這樣我們可以先訪問(wèn)本地的文件系統(tǒng)來(lái)進(jìn)行代碼邏輯的驗(yàn)證肺樟,如果代碼沒(méi)問(wèn)題檐春,我們可以再到集群環(huán)境下去驗(yàn)證。這有點(diǎn)和mapreduce程序也可以在本地運(yùn)行類似么伯。
我們把上面寫(xiě)數(shù)據(jù)的例子改成到本地環(huán)境下運(yùn)行疟暖,代碼如下:
package com.hdfs;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class WriteData {
public static void main(String[] args) {
Configuration conf=new Configuration();
conf.set("fs.defaultFS", "file:///");
BufferedWriter writer = null;
try {
FileSystem HDFS = FileSystem.get(conf);
FSDataOutputStream out = HDFS.create(new Path(args[0]));
writer = new BufferedWriter(new OutputStreamWriter(out));
writer.write("hello1");
writer.newLine();
writer.write("hello2");
} catch (IOException e) {
e.printStackTrace();
}finally{
if(writer!=null){
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
可以看出,與前面例子相比田柔,我們只是改了一句代碼俐巴,將
conf.set("fs.defaultFS", "hdfs://192.168.1.3:8020");
語(yǔ)句改為
conf.set("fs.defaultFS", "file:///");
設(shè)置"fs.defaultFS"屬性為"file:///",意思就是訪問(wèn)的是本地文件系統(tǒng)硬爆。上面程序無(wú)論是在linux或海上windows下都可以運(yùn)行欣舵。如在linux下運(yùn)行:
hadoop jar testhdfs.jar /home/hadoop/test.txt
在windows下運(yùn)行:
hadoop jar testhdfs.jar d:/test.txt
需要特別注意的是,F(xiàn)ileSystem中的方法并不是全部能夠在各種文件系統(tǒng)中運(yùn)行缀磕,比如append方法經(jīng)過(guò)測(cè)試發(fā)現(xiàn)本地文件系統(tǒng)就不支持缘圈。所以在開(kāi)發(fā)調(diào)試時(shí)需要特別注意這點(diǎn)。