Java面試題全集(8)

Java面試題全集(8)

白玉 IT哈哈

71、如何用Java代碼列出一個目錄下所有的文件?

答:
如果只要求列出當前文件夾下的文件,代碼如下所示:


import java.io.File;
class Test12 {
    public static void main(String[] args) {
        File f = new File("/Users/Hao/Downloads");
        for(File temp : f.listFiles()) {
            if(temp.isFile()) {
                System.out.println(temp.getName());
            }
        }
    }
}

如果需要對文件夾繼續展開,代碼如下所示:


import java.io.File;
class Test12 {
    public static void main(String[] args) {
        showDirectory(new File("/Users/Hao/Downloads"));
    }
    public static void showDirectory(File f) {
        _walkDirectory(f, 0);
    }
    private static void _walkDirectory(File f, int level) {
        if(f.isDirectory()) {
            for(File temp : f.listFiles()) {
                _walkDirectory(temp, level + 1);
            }
        }
        else {
            for(int i = 0; i < level - 1; i++) {
                System.out.print("\t");
            }
            System.out.println(f.getName());
        }
    }
}

在Java 7中可以使用NIO.2的API來做同樣的事情,代碼如下所示:


class ShowFileTest {
    public static void main(String[] args) throws IOException {
        Path initPath = Paths.get("/Users/Hao/Downloads");
        Files.walkFileTree(initPath, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 
                    throws IOException {
                System.out.println(file.getFileName().toString());
                return FileVisitResult.CONTINUE;
            }
        });
    }
}

72、用Java的套接字編程實現一個多線程的回顯(echo)服務器。

答:


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class EchoServer {
    private static final int ECHO_SERVER_PORT = 6789;
    public static void main(String[] args) {        
        try(ServerSocket server = new ServerSocket(ECHO_SERVER_PORT)) {
            System.out.println("服務器已經啓動...");
            while(true) {
                Socket client = server.accept();
                new Thread(new ClientHandler(client)).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private static class ClientHandler implements Runnable {
        private Socket client;
        public ClientHandler(Socket client) {
            this.client = client;
        }
        @Override
        public void run() {
            try(BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
                    PrintWriter pw = new PrintWriter(client.getOutputStream())) {
                String msg = br.readLine();
                System.out.println("收到" + client.getInetAddress() + "發送的: " + msg);
                pw.println(msg);
                pw.flush();
            } catch(Exception ex) {
                ex.printStackTrace();
            } finally {
                try {
                    client.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

注意:上面的代碼使用了Java 7的TWR語法,由於很多外部資源類都間接的實現了AutoCloseable接口(單方法回調接口),因此可以利用TWR語法在try結束的時候通過回調的方式自動調用外部資源類的close()方法,避免書寫冗長的finally代碼塊。此外,上面的代碼用一個靜態內部類實現線程的功能,使用多線程可以避免一個用戶I/O操作所產生的中斷影響其他用戶對服務器的訪問,簡單的說就是一個用戶的輸入操作不會造成其他用戶的阻塞。當然,上面的代碼使用線程池可以獲得更好的性能,因爲頻繁的創建和銷燬線程所造成的開銷也是不可忽視的。

下面是一段回顯客戶端測試代碼:


import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class EchoClient {
    public static void main(String[] args) throws Exception {
        Socket client = new Socket("localhost", 6789);
        Scanner sc = new Scanner(System.in);
        System.out.print("請輸入內容: ");
        String msg = sc.nextLine();
        sc.close();
        PrintWriter pw = new PrintWriter(client.getOutputStream());
        pw.println(msg);
        pw.flush();
        BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
        System.out.println(br.readLine());
        client.close();
    }
}

如果希望用NIO的多路複用套接字實現服務器,代碼如下所示。NIO的操作雖然帶來了更好的性能,但是有些操作是比較底層的,對於初學者來說還是有些難於理解。


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
public class EchoServerNIO {
    private static final int ECHO_SERVER_PORT = 6789;
    private static final int ECHO_SERVER_TIMEOUT = 5000;
    private static final int BUFFER_SIZE = 1024;
    private static ServerSocketChannel serverChannel = null;
    private static Selector selector = null;    // 多路複用選擇器
    private static ByteBuffer buffer = null;    // 緩衝區
    public static void main(String[] args) {
        init();
        listen();
    }
    private static void init() {
        try {
            serverChannel = ServerSocketChannel.open();
            buffer = ByteBuffer.allocate(BUFFER_SIZE);
            serverChannel.socket().bind(new InetSocketAddress(ECHO_SERVER_PORT));
            serverChannel.configureBlocking(false);
            selector = Selector.open();
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    private static void listen() {
        while (true) {
            try {
                if (selector.select(ECHO_SERVER_TIMEOUT) != 0) {
                    Iterator<SelectionKey> it = selector.selectedKeys().iterator();
                    while (it.hasNext()) {
                        SelectionKey key = it.next();
                        it.remove();
                        handleKey(key);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    private static void handleKey(SelectionKey key) throws IOException {
        SocketChannel channel = null;
        try {
            if (key.isAcceptable()) {
                ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
                channel = serverChannel.accept();
                channel.configureBlocking(false);
                channel.register(selector, SelectionKey.OP_READ);
            } else if (key.isReadable()) {
                channel = (SocketChannel) key.channel();
                buffer.clear();
                if (channel.read(buffer) > 0) {
                    buffer.flip();
                    CharBuffer charBuffer = CharsetHelper.decode(buffer);
                    String msg = charBuffer.toString();
                    System.out.println("收到" + channel.getRemoteAddress() + "的消息:" + msg);
                    channel.write(CharsetHelper.encode(CharBuffer.wrap(msg)));
                } else {
                    channel.close();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            if (channel != null) {
                channel.close();
            }
        }
    }
}

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
public final class CharsetHelper {
    private static final String UTF_8 = "UTF-8";
    private static CharsetEncoder encoder = Charset.forName(UTF_8).newEncoder();
    private static CharsetDecoder decoder = Charset.forName(UTF_8).newDecoder();    private CharsetHelper() {
    }
    public static ByteBuffer encode(CharBuffer in) throws CharacterCodingException{
        return encoder.encode(in);
    }
    public static CharBuffer decode(ByteBuffer in) throws CharacterCodingException{
        return decoder.decode(in);
    }
}

73、XML文檔定義有幾種形式?它們之間有何本質區別?解析XML文檔有哪幾種方式?

答:XML文檔定義分爲DTD和Schema兩種形式,二者都是對XML語法的約束,其本質區別在於Schema本身也是一個XML文件,可以被XML解析器解析,而且可以爲XML承載的數據定義類型,約束能力較之DTD更強大。對XML的解析主要有DOM(文檔對象模型,Document Object Model)、SAX(Simple API for XML)和StAX(Java 6中引入的新的解析XML的方式,Streaming API for XML),其中DOM處理大型文件時其性能下降的非常厲害,這個問題是由DOM樹結構佔用的內存較多造成的,而且DOM解析方式必須在解析文件之前把整個文檔裝入內存,適合對XML的隨機訪問(典型的用空間換取時間的策略);SAX是事件驅動型的XML解析方式,它順序讀取XML文件,不需要一次全部裝載整個文件。當遇到像文件開頭,文檔結束,或者標籤開頭與標籤結束時,它會觸發一個事件,用戶通過事件回調代碼來處理XML文件,適合對XML的順序訪問;顧名思義,StAX把重點放在流上,實際上StAX與其他解析方式的本質區別就在於應用程序能夠把XML作爲一個事件流來處理。將XML作爲一組事件來處理的想法並不新穎(SAX就是這樣做的),但不同之處在於StAX允許應用程序代碼把這些事件逐個拉出來,而不用提供在解析器方便時從解析器中接收事件的處理程序。

74、你在項目中哪些地方用到了XML?

答:XML的主要作用有兩個方面:數據交換和信息配置。在做數據交換時,XML將數據用標籤組裝成起來,然後壓縮打包加密後通過網絡傳送給接收者,接收解密與解壓縮後再從XML文件中還原相關信息進行處理,XML曾經是異構系統間交換數據的事實標準,但此項功能幾乎已經被JSON(JavaScript Object Notation)取而代之。當然,目前很多軟件仍然使用XML來存儲配置信息,我們在很多項目中通常也會將作爲配置信息的硬代碼寫在XML文件中,Java的很多框架也是這麼做的,而且這些框架都選擇了dom4j作爲處理XML的工具,因爲Sun公司的官方API實在不怎麼好用。

補充:現在有很多時髦的軟件(如Sublime)已經開始將配置文件書寫成JSON格式,我們已經強烈的感受到XML的另一項功能也將逐漸被業界拋棄。

75、闡述JDBC操作數據庫的步驟。

答:下面的代碼以連接本機的Oracle數據庫爲例,演示JDBC操作數據庫的步驟。

  • 加載驅動。
    Class.forName("oracle.jdbc.driver.OracleDriver");
  • 創建連接。
    Connection con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl", "scott", "tiger");
  • 創建語句。
    PreparedStatement ps = con.prepareStatement("select * from emp where sal between ? and ?");
    ps.setInt(1, 1000);
    ps.setInt(2, 3000);


  • 執行語句。
    ResultSet rs = ps.executeQuery();
  • 處理結果。
    while(rs.next()) {
    System.out.println(rs.getInt("empno") + " - " + rs.getString("ename"));
    }


  • 關閉資源。

    finally {
            if(con != null) {
                        try {
                con.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

提示:關閉外部資源的順序應該和打開的順序相反,也就是說先關閉ResultSet、再關閉Statement、在關閉Connection。上面的代碼只關閉了Connection(連接),雖然通常情況下在關閉連接時,連接上創建的語句和打開的遊標也會關閉,但不能保證總是如此,因此應該按照剛纔說的順序分別關閉。此外,第一步加載驅動在JDBC 4.0中是可以省略的(自動從類路徑中加載驅動),但是我們建議保留。

76、Statement和PreparedStatement有什麼區別?哪個性能更好?

答:與Statement相比,①PreparedStatement接口代表預編譯的語句,它主要的優勢在於可以減少SQL的編譯錯誤並增加SQL的安全性(減少SQL注射***的可能性);②PreparedStatement中的SQL語句是可以帶參數的,避免了用字符串連接拼接SQL語句的麻煩和不安全;③當批量處理SQL或頻繁執行相同的查詢時,PreparedStatement有明顯的性能上的優勢,由於數據庫可以將編譯優化後的SQL語句緩存起來,下次執行相同結構的語句時就會很快(不用再次編譯和生成執行計劃)。

補充:爲了提供對存儲過程的調用,JDBC API中還提供了CallableStatement接口。存儲過程(Stored Procedure)是數據庫中一組爲了完成特定功能的SQL語句的集合,經編譯後存儲在數據庫中,用戶通過指定存儲過程的名字並給出參數(如果該存儲過程帶有參數)來執行它。雖然調用存儲過程會在網絡開銷、安全性、性能上獲得很多好處,但是存在如果底層數據庫發生遷移時就會有很多麻煩,因爲每種數據庫的存儲過程在書寫上存在不少的差別。

77、使用JDBC操作數據庫時,如何提升讀取數據的性能?如何提升更新數據的性能?

答:要提升讀取數據的性能,可以指定通過結果集(ResultSet)對象的setFetchSize()方法指定每次抓取的記錄數(典型的空間換時間策略);要提升更新數據的性能可以使用PreparedStatement語句構建批處理,將若干SQL語句置於一個批處理中執行。

78、在進行數據庫編程時,連接池有什麼作用?

答:由於創建連接和釋放連接都有很大的開銷(尤其是數據庫服務器不在本地時,每次建立連接都需要進行TCP的三次握手,釋放連接需要進行TCP四次握手,造成的開銷是不可忽視的),爲了提升系統訪問數據庫的性能,可以事先創建若干連接置於連接池中,需要時直接從連接池獲取,使用結束時歸還連接池而不必關閉連接,從而避免頻繁創建和釋放連接所造成的開銷,這是典型的用空間換取時間的策略(浪費了空間存儲連接,但節省了創建和釋放連接的時間)。池化技術在Java開發中是很常見的,在使用線程時創建線程池的道理與此相同。基於Java的開源數據庫連接池主要有:C3P0、Proxool、DBCP、BoneCP、Druid等。

79、什麼是DAO模式?

答:DAO(Data Access Object)顧名思義是一個爲數據庫或其他持久化機制提供了抽象接口的對象,在不暴露底層持久化方案實現細節的前提下提供了各種數據訪問操作。在實際的開發中,應該將所有對數據源的訪問操作進行抽象化後封裝在一個公共API中。用程序設計語言來說,就是建立一個接口,接口中定義了此應用程序中將會用到的所有事務方法。在這個應用程序中,當需要和數據源進行交互的時候則使用這個接口,並且編寫一個單獨的類來實現這個接口,在邏輯上該類對應一個特定的數據存儲。DAO模式實際上包含了兩個模式,一是Data Accessor(數據訪問器),二是Data Object(數據對象),前者要解決如何訪問數據的問題,而後者要解決的是如何用對象封裝數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章