最近有一項工作,保存 Excel 格式的電子表格至 postgresql 的數(shù)據(jù)表,要求使用 Java 編寫一段代碼把這事辦了。
如果 Java 不是問題,那么關(guān)于這事的一個簡單想法是:首先,Excel 格式有點復(fù)雜了,改成 CSV 格式。之后,第一步就是打開CSV文件;第二步讀取指定列;第三步寫數(shù)據(jù)庫。
但 java 真的是個問題,無他只因不會。關(guān)于 Java ,有個網(wǎng)站 Java Tutorials Learning Paths 內(nèi)容很好,只是有點老 Java 8 版本。另外 Head First 系列有 Java 版,這個更老 Java 5. 這本我有,可以留言。
我看 Java 的主要特性,如虛擬機、一次編譯到處運行什么的都無所謂。重點是 Java 是基于類的面向?qū)ο笳Z言,這點與 C++ 類似,與 Js 不同。但是 Java 比 C++ 徹底,或者說更純粹,濃郁的90年代現(xiàn)代化的感覺。
Unix 一切皆文件,Windows 一切皆視窗,Java 一切皆對象。
對象 Object 是什么?
==對象是類的實例或數(shù)組。== 但要注意的是 Java 中,所有類的基類名為 class Object,即 java.lang.Object ,此對象非彼對象。
好,知道了什么是對象,約等于我們學(xué)會了 Java,接下來的問題就簡單了。
怎么打開 CSV 文件?
JDK 中沒有原生庫支持 CSV 操作。習(xí)慣 Python 的,可能有點不爽了。但這也不是什么大事,Apache Commons CSV 庫也很標(biāo)準(zhǔn)。
下面是一個解析 Excel CSV File 文件的例子:
Reader in = new FileReader("path/to/file.csv");
Iterable<CSVRecord> records = CSVFormat.EXCEL.parse(in);
for (CSVRecord record : records) {
String lastName = record.get("Last Name");
String firstName = record.get("First Name");
}
這個例子很簡單,對于只學(xué)習(xí)了 5 分鐘 Java 的人,也能看懂。
但我還是想問一下 FileReader 類到底是什么?
FileReader 類是為了方便讀取字符文件而存在的。這種便利來自于構(gòu)造器假設(shè)了字符編碼與字節(jié)緩沖器的尺寸。若要自定義這些參數(shù),需在 FileInputStream 上構(gòu)造 InputStreamReader 。是不是很神奇?剛才還是完全理解的,現(xiàn)在又好像什么都不懂了。
這需要先了解一下 Java 是怎么處理 IO 的。這里,Java 引入了 I/O Stream 這一概念,用來抽象描述 IO 操作。所以,流可以表示不同的源與目的,包括磁盤上的文件,設(shè)備,其它程序,內(nèi)存數(shù)組等,同時支持多種數(shù)據(jù)類型。這有點 Unix 中文件的意思。流的本質(zhì)是數(shù)據(jù)序列。關(guān)于數(shù)據(jù)序列是如何而來這樣的細節(jié)對于消費者是不應(yīng)該去關(guān)心的。
字節(jié)流是最基礎(chǔ)的一類流。程序使用字節(jié)流完成 8-比特字節(jié)的輸入與輸出。所有字節(jié)流類始于 InputStream 與 OutputStream,它們同為抽象類。
這里又有了新問題,什么是抽象類?
抽象類是被 abstract 修飾聲明的類。
- 抽象類可以不包含抽象方法。
- 包含抽象方法的類必為抽象類。
- 抽象類可繼承不可實例。
當(dāng)一個抽象類被繼承時,其子類通常提供父類所有抽象方法的實現(xiàn)。若不如此,子類也需用 abstract 修飾聲明。
接口中的方法,若匪經(jīng) default 或 static 修飾聲明,則意為抽象,因此 abstract 未見于接口方法。
雖然 Python 中所有類的基類也名為 class Object ,但它確實沒有抽象類,也沒有接口。而 python 的 metaclasses java 似乎也沒有。也許應(yīng)該用 C++ 或 C# 來類比??墒钦l又會那些呢。
參見 python java 類定義對比。
一段字節(jié)流的示例代碼:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyBytes {
public static void main (String[] args) throws IOException {
FileInputStream in = null;
FileOutputStream out = null;
try {
in = new FileInputStream("xanadu.txt");
out = new FileOutputStream("outagain.txt");
int c;
while ((c = in.read()) != -1) {
out.write(c);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
這個例子很簡單,對于只學(xué)習(xí)了 5 分鐘 Java 的人,真的能看懂。
這段示例中,花費最多時間的部分目的在于一次一字節(jié)的讀取輸入流寫入輸出流。
這段示例中,花費最多代碼的部分目的在于幫助避免嚴(yán)重的資源泄漏,因為當(dāng)不在需要時正確的關(guān)閉流是非常重要的。
這段代碼看起來像是一個正常的程序,但它實際代表著一種低階 I/O ,這是應(yīng)該避免的。因為 xanadu.txt 文件包含的是字符數(shù)據(jù),因此最好的方法是使用字符流 character streams ,這是一種為更復(fù)雜的數(shù)據(jù)類型而準(zhǔn)備的流。字節(jié)流僅應(yīng)用于最基礎(chǔ)的 I/O 。
既然如此,為何還要講 byte streams ?因為,其它的所有流類型都構(gòu)建在字節(jié)流之上。
所有字符流類始于 Reader 與 Writer,它們同為抽象類。這兩個名字挺有意思,是因為對于人類來說,字符與字節(jié)相比有了意義,才稱為讀者與作者嗎?
一段字符流的示例代碼:
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class CopyCharacters {
public static void main(String[] args) throws IOException {
FileReader inputStream = null;
FileWriter outputStream = null;
try {
inputStream = new FileReader("xanadu.txt");
outputStream = new FileWriter("characteroutput.txt");
int c;
while ((c = inputStream.read()) != -1) {
outputStream.write(c);
}
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
}
}
這個例子很簡單,對于只學(xué)習(xí)了 5 分鐘 Java 的人,真的能看懂。
有沒有既視感? Character Streams 與 Byte Streams 的示例代碼相似度很高。找茬是個有意思的游戲。
字符流使用字節(jié)流完成物理 I/O ,字符流處理字符與字節(jié)之間的轉(zhuǎn)換。
前文提到的 InputStreamReader 與 OutputStreamWriter 是兩個通用目的 byte-to-character 的橋接流。也是 FileReader 與 FileReader 的父類。