Java自学基础讲解

    作者:课课家教育更新于: 2016-02-16 18:15:53

    大神带你学编程,欢迎选课

          举例说明同步和异步

      如果系统中存在临界资源(资源数量少于竞争资源的线程数量的资源),例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就必须进行同步存取(数据库操作中的排他锁就是最好的例子)。当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。事实上,所谓的同步就是指阻塞式操作,而异步就是非阻塞式操作。

      启动一个线程是调用run()还是start()方法

      启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM 调度并执行,这并不意味着线程就会立即运行。run()方法是线程启动后要进行回调(callback)的方法。

      什么是线程池(thread pool)

      在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁,这就是”池化资源”技术产生的原因。线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。 Java 5+中的Executor接口定义一个执行线程的工具。它的子类型即线程池接口是ExecutorService。要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,因此在工具类Executors面提供了一些静态工厂方法,生成一些常用的线程池,如下所示:

      newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

      newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

      newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

      newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

      newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。

      第60题的例子中演示了通过Executors工具类创建线程池并使用线程池执行线程的代码。如果希望在服务器上使用线程池,强烈建议使用newFixedThreadPool方法来创建线程池,这样能获得更好的性能。

      线程的基本状态以及状态之间的关系

    Java自学基础讲解 _Java基础_课课家

      说明:其中Running表示运行状态,Runnable表示就绪状态(万事俱备,只欠CPU),Blocked表示阻塞状态,阻塞状态又有多种情况,可能是因为调用wait()方法进入等待池,也可能是执行同步方法或同步代码块进入等锁池,或者是调用了sleep()方法或join()方法等待休眠或其他线程结束,或是因为发生了I/O中断。

      简述synchronized 和java.util.concurrent.locks.Lock的异同

      Lock是Java 5以后引入的新的API,和关键字synchronized相比主要相同点:Lock 能完成synchronized所实现的所有功能;主要不同点:Lock有比synchronized更精确的线程语义和更好的性能,而且不强制性的要求一定要获得锁。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且最好在finally 块中释放(这是释放外部资源的最好的地方)。

      Java中如何实现序列化,有什么意义

      序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决对象流读写操作时可能引发的问题(如果不进行序列化可能会存在数据乱序的问题)。 要实现序列化,需要让一个类实现Serializable接口,该接口是一个标识性接口,标注该类对象是可被序列化的,然后使用一个输出流来构造一个对象输出流并通过writeObject(Object)方法就可以将实现对象写出(即保存其状态);如果需要反序列化则可以用一个输入流建立对象输入流,然后通过readObject方法从流中读取对象。序列化除了能够实现对象的持久化之外,还能够用于对象的深度克隆(可以参考第29题)。

      Java中有几种类型的流

      字节流和字符流。字节流继承于InputStream、OutputStream,字符流继承于Reader、Writer。在java.io 包中还有许多其他的流,主要是为了提高性能和使用方便。关于Java的I/O需要注意的有两点:一是两种对称性(输入和输出的对称性,字节和字符的对称性);二是两种设计模式(适配器模式和装潢模式)。另外Java中的流不同于C#的是它只有一个维度一个方向。

      面试题 - 编程实现文件拷贝。(这个题目在笔试的时候经常出现,下面的代码给出了两种实现方案)

      import java.io.FileInputStream;

      import java.io.FileOutputStream;

      import java.io.IOException;

      import java.io.InputStream;

      import java.io.OutputStream;

      import java.nio.ByteBuffer;

      import java.nio.channels.FileChannel;

      public final class MyUtil {

      private MyUtil() {

      throw new AssertionError();

      }

      public static void fileCopy(String source, String target) throws IOException {

      try (InputStream in = new FileInputStream(source)) {

      try (OutputStream out = new FileOutputStream(target)) {

      byte[] buffer = new byte[4096];

      int bytesToRead;

      while((bytesToRead = in.read(buffer)) != -1) {

      out.write(buffer, 0, bytesToRead);

      public static void fileCopyNIO(String source, String target) throws IOException {

      try (FileInputStream in = new FileInputStream(source)) {

      try (FileOutputStream out = new FileOutputStream(target)) {

      FileChannel inChannel = in.getChannel();

      FileChannel outChannel = out.getChannel();

      ByteBuffer buffer = ByteBuffer.allocate(4096);

      while(inChannel.read(buffer) != -1) {

      buffer.flip();

      outChannel.write(buffer);

      buffer.clear();

      写一个方法,输入一个文件名和一个字符串,统计这个字符串在这个文件中出现的次数

      import java.io.BufferedReader;

      import java.io.FileReader;

      public final class MyUtil {

      // 工具类中的方法都是静态方式访问的因此将构造器私有不允许创建对象(绝对好习惯)

      private MyUtil() {

      throw new AssertionError();

      * 统计给定文件中给定字符串的出现次数

      * @param filename 文件名

      * @param word 字符串

      * @return 字符串在文件中出现的次数

      public static int countWordInFile(String filename, String word) {

      int counter = 0;

      try (FileReader fr = new FileReader(filename)) {

      try (BufferedReader br = new BufferedReader(fr)) {

      String line = null;

      while ((line = br.readLine()) != null) {

      int index = -1;

      while (line.length() >= word.length() && (index = line.indexOf(word)) >= 0) {

      counter++;

      line = line.substring(index + word.length());

      } catch (Exception ex) {

      ex.printStackTrace();

      return counter;

      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 stati 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");

      在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() {

      @Override

      public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)

      throws IOException {

      System.out.println(file.getFileName().toString());

      return FileVisitResult.CONTINUE;

      用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() {

      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 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允许应用程序代码把这些事件逐个拉出来,而不用提供在解析器方便时从解析器中接收事件的处理程序。

      你在项目中哪些地方用到了XML

      XML的主要作用有两个方面:数据交换和信息配置。在做数据交换时,XML将数据用标签组装成起来,然后压缩打包加密后通过网络传送给接收者,接收解密与解压缩后再从XML文件中还原相关信息进行处理,XML曾经是异构系统间交换数据的事实标准,但此项功能几乎已经被JSON(Javascript Object Notation)取而代之。当然,目前很多软件仍然使用XML来存储配置信息,我们在很多项目中通常也会将作为配置信息的硬代码写在XML文件中,Java的很多框架也是这么做的,而且这些框架都选择了dom4j作为处理XML的工具,因为Sun公司的官方API实在不怎么好用。

      补充:现在有很多时髦的软件(如Sublime)已经开始将配置文件书写成JSON格式,我们已经强烈的感受到XML的另一项功能也将逐渐被业界抛弃。

      阐述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中是可以省略的(自动从类路径中加载驱动),但是我们建议保留。

      Statement和PreparedStatement有什么区别?哪个性能更好

      与Statement相比,①PreparedStatement接口代表预编译的语句,它主要的优势在于可以减少SQL的编译错误并增加SQL的安全性(减少SQL注射攻击的可能性);②PreparedStatement中的SQL语句是可以带参数的,避免了用字符串连接拼接SQL语句的麻烦和不安全;③当批量处理SQL或频繁执行相同的查询时,PreparedStatement有明显的性能上的优势,由于数据库可以将编译优化后的SQL语句缓存起来,下次执行相同结构的语句时就会很快(不用再次编译和生成执行计划)。

      补充:为了提供对存储过程的调用,JDBC API中还提供了CallableStatement接口。存储过程(Stored Procedure)是数据库中一组为了完成特定功能的SQL语句的集合,经编译后存储在数据库中,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。虽然调用存储过程会在网络开销、安全性、性能上获得很多好处,但是存在如果底层数据库发生迁移时就会有很多麻烦,因为每种数据库的存储过程在书写上存在不少的差别。

      使用JDBC操作数据库时,如何提升读取数据的性能?如何提升更新数据的性能

      要提升读取数据的性能,可以指定通过结果集(ResultSet)对象的setFetchSize()方法指定每次抓取的记录数(典型的空间换时间策略);要提升更新数据的性能可以使用PreparedStatement语句构建批处理,将若干SQL语句置于一个批处理中执行。

      在进行数据库编程时,连接池有什么作用

      由于创建连接和释放连接都有很大的开销(尤其是数据库服务器不在本地时,每次建立连接都需要进行TCP的三次握手,释放连接需要进行TCP四次握手,造成的开销是不可忽视的),为了提升系统访问数据库的性能,可以事先创建若干连接置于连接池中,需要时直接从连接池获取,使用结束时归还连接池而不必关闭连接,从而避免频繁创建和释放连接所造成的开销,这是典型的用空间换取时间的策略(浪费了空间存储连接,但节省了创建和释放连接的时间)。池化技术在Java开发中是很常见的,在使用线程时创建线程池的道理与此相同。基于Java的开源数据库连接池主要有:C3P0、Proxool、DBCP、BoneCP、Druid等。

      补充:在计算机系统中时间和空间是不可调和的矛盾,理解这一点对设计满足性能要求的算法是至关重要的。大型网站性能优化的一个关键就是使用缓存,而缓存跟上面讲的连接池道理非常类似,也是使用空间换时间的策略。可以将热点数据置于缓存中,当用户查询这些数据时可以直接从缓存中得到,这无论如何也快过去数据库中查询。当然,缓存的置换策略等也会对系统性能产生重要影响,对于这个问题的讨论已经超出了这里要阐述的范围。

      什么是DAO模式

      DAO(Data Access Object)顾名思义是一个为数据库或其他持久化机制提供了抽象接口的对象,在不暴露底层持久化方案实现细节的前提下提供了各种数据访问操作。在实际的开发中,应该将所有对数据源的访问操作进行抽象化后封装在一个公共API中。用程序设计语言来说,就是建立一个接口,接口中定义了此应用程序中将会用到的所有事务方法。在这个应用程序中,当需要和数据源进行交互的时候则使用这个接口,并且编写一个单独的类来实现这个接口,在逻辑上该类对应一个特定的数据存储。DAO模式实际上包含了两个模式,一是Data Accessor(数据访问器),二是Data Object(数据对象),前者要解决如何访问数据的问题,而后者要解决的是如何用对象封装数据。

      事务的ACID是指什么

      原子性(Atomic):事务中各项操作,要么全做要么全不做,任何一项操作的失败都会导致整个事务的失败;

      一致性(Consistent):事务结束后系统状态是一致的;

      隔离性(Isolated):并发执行的事务彼此无法看到对方的中间状态;

      持久性(Durable):事务完成后所做的改动都会被持久化,即使发生灾难性的失败。通过日志和同步备份可以在故障发生后重建数据。

      补充:关于事务,在面试中被问到的概率是很高的,可以问的问题也是很多的。首先需要知道的是,只有存在并发数据访问时才需要事务。当多个事务访问同一数据时,可能会存在5类问题,包括3类数据读取问题(脏读、不可重复读和幻读)和2类数据更新问题(第1类丢失更新和第2类丢失更新)。

课课家教育

未登录