Java面试系列之二

Java中的线程
  • 线程类创建的两种方式:让一个类称为线程类的方式有两种,一个是实现java.lang.Runnable接口,另一个是继承自java.lang.Thread类。
  • 解释Runnable接口与Thread类的区别:
    • 线程继承自Thread则不能继承自其他类,而Runnable接口可以。
    • 线程类继承自Thread相对于Runnable来说。使用线程的方法更方便一些。
    • 实现Runnable接口的线程类的多线程,可以更方便的访问同一变量,而Thread类则需要内部类来进行替代。
  • synchronized关键字代表要为某一段代码加上一个同步锁,这样的锁是绑定在某一个对象上边的。如果是同步代码块,需要为该synchronized关键字提供一个对象的引用;如果是同步方法,只需要加一个synchronized关键字的修饰。synchronizedz为某一段代码加上锁以后,某个线程进入该段代码之前,首先需要检查该锁是否被占用,如果没有被占用则继续执行;如果已经被占用,则需要等到该锁被释放以后才能继续执行。其中,该线程执行完该段代码就是释放锁的标志。
  • 使用Java的线程池,往线程池添加任务时:
    • 如果此时线程池中的数量小于corePoolSize,即使缓冲队列workQueue未满,那么任务被放入缓冲队列。
    • 如果此时线程池中的数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列。
    • 如果此时线程池中的数量大于corePoolSize,但是缓冲队列workQueue已满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
    • 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue已满,并且线程池中的数量等于maximumPoolSize,那么通过handler所指定的策略来出来此任务
常用的线程池
  1. Executors.newCacheThreadPool():可缓存线程池,先查看池中有没有以前建立的线程,如果有,就直接使用。如果没有,就建一个新的线程加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务
  2. Executors.newFixedThreadPool(int n):创建一个可重用固定个数的线程池,以共享的无界队列方式来运行这些线程。
  3. Executors.newScheduledThreadPool(int n):创建一个定长线程池,支持定时及周期性任务执行
  4. Executors.newSingleThreadExecutor():创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
package com.cy.thread;
import java.io.Serializable;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolTest {
    private static int produceTaskSleepTime = 2000;
    public static void main(String[] args){
        /**
         * 构造一个线程池
         * @corePoolSize:核心线程
         * @maximumPoolSize:最大线程
         * @keepAliveTime:存活时间
         * @timeUnit:时间单位
         * @workQueue:工作队列
         * @handler:采取策略
         */
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 0,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), new ThreadPoolExecutor.DiscardOldestPolicy());
        //每隔produceTaskSleepTime的时间向线程池派送一个任务
        int i = 1;
        while(true){
            try {
                Thread.sleep(produceTaskSleepTime); //休息一定的时间
                String task = "task@" + i; //设置任务名字
                System.out.println("put " + task);
                //用execute方法启动一个线程
                threadPoolExecutor.execute(new ThreadPoolTask(task));
                i++;
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}
class ThreadPoolTask implements Runnable, Serializable{ //线程类
    private static final long serialVersionUID = 0;

    private static int consumeTaskSleepTime = 2000; //时间间歇,毫秒
    private String threadPoolTaskData; //存储任务名的变量

    ThreadPoolTask(String tasks){
        threadPoolTaskData = tasks;
    }
    //每个任务的执行过程,现在是什么都没做,处理print和sleep
    @Override
    public void run() {
        System.out.println("start.."+threadPoolTaskData);
        try{
            Thread.sleep(consumeTaskSleepTime);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        threadPoolTaskData = null;
    }
}

Java中的反射

反射是为了能够动态地加载一个类,动态地调用一个方法,动态地访问一个属性等动态要求而设计的。它出发点就在于JVM会为每一个类创建一个java.lang.Class类的实例,通过该对象可以获取这个类的信息,然后通过使用java.lang.reflect包下的API以达到到各种动态需求。

Class类的含义和作用

每一个Class类的对象就代表了一种被加载进入JVM的类,它代表了该类的一种信息映射。开发者可以通过一下3种途径获取到Class对象。

  • Class类的forName()方法的返回值。
  • 访问所有类都会拥有的静态的Class属性。类名.class
  • 调用所有对象都会有的getClass()方法。实例.getClass()

在Class类中,定义许多关于类信息的方法。例如,getName()、getMethod()、getConstructor()和newInstance()等可以用于反射开发,还有isInstance()和isInterface()等一些关于类的功能方法。

反射创建实例以及常用方法
  • 创建实例即获取构造方法,由构造方法创建实例
//第一种:通过实例直接调用newInstance()方法创建实例
Class<String> clazz = (Class<String>) Class.forName("java.lang.String");
Object obj = clazz.newInstance();
String strObj = (String) obj;

//第二种:通过实例获取构造函数,然后用构造函数创建实例
Constructor<?> constructor = clazz.getConstructor(String.class);
Object ins = constructor.newInstance("张三");
String two = (String) ins;

//第三种:可以通过getConstructors获取到所有的构造方法
Constructor<?>[] constructors = clazz.getConstructors();
for (Constructor<?> constructor1 : constructors) {
    System.out.println(constructor1);
}
  • 根据反射对象获取方法,并进行调用
//获取方法并进行调用
Method split = clazz.getDeclaredMethod("split", String.class);
String[] strs = (String[]) split.invoke("1,2,3",",");
for (String str : strs) {
    System.out.println(str);
}
//根据反射对象获取所有可见的对象
Method[] declaredMethods = clazz.getDeclaredMethods();
  • 根据反射对象获取属性
//已知属性名,通过属性名获取反射对象该属性的值
Field serialVersionUID = clazz.getDeclaredField("hash");
//获取所有的可见属性
Field[] declaredFields = clazz.getDeclaredFields();
反射机制来访问一个类的私有成员

在使用反射机制访问私有成员的时候,它们的可访问性是为false的。需要调用setAccessible(true)方法,把原本不可访问的私有成员变为可以访问以后,才能进行成功的访问或调用。

Java的网络编程
TCP/IP协议的理解

TCP/IP定义了电子设备(如计算机)连入因特网标准,以及数据如何在它们之间传输的标准。它既是互联网中的基本通信语言或协议,也是局域网的通信协议。
TCP/IP是一组包括TCP协议、IP协议,UDP协议、ICMP协议和其他一些协议的协议组。需要进行网络通信的计算需要提供符合这些协议标准的程序以后,才能进行网络通信。

TCP协议的通信特点

TCP协议主要拥有如下的通信特点:

  1. 面向连接的传输。
  2. 端到端的通信。
  3. 可靠性,确保传输数据的正确性,不出现丢失或乱序。
  4. 采用字节流方式,即以字节为单位传输字节序列。
Java的TCP编程模型

编写Java的TCP网络应用程序需要分为服务器端和客户端两个部分:
服务器端:

  • 创建一个服务器端的Socket,指定一个端口号。
  • 开始监听来自客户端的请求要求。
  • 获得输出流或输入流
  • 调用输入流/输出流的read()或write()方法,进行数据的传输。
  • 释放资源,关闭输出流/输入流、Socket和ServerSocket对象
ServerSocket ss = new ServerSocket(8880);//用端口号创建一个serverSoket对象
Socket s = ss.accept();//开始监听来自客户端的请求
OutputStream os = s.getOutputStream(); //获得输出流
PrintWriter pw = new PrintWriter(os); //创建PrintWriter对象
pw.write("now time = " + new Date()); //向输出流中写出当前的时间
pw.flush(); //清空缓存
pw.close(); //关闭流资源
s.close();
ss.close();

客户端:

  • 创建Socket对象,建立与服务器的连接。
  • 获得输出流或输入流
  • 调用输入流/输出流的read()或write()方法,进行数据的传输。
  • 释放资源,关闭输出流/输入流、socket对象
s = new Socket("localhost", 8880); //用IP地址和端口创建Socket对象
InputStream is = s.getInputStream(); //获得输入流
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr); //创建BufferReader对象
String str = br.readLine(); //读取一行
System.out.println(str); //打印从服务器端来的结果
br.close(); //关闭流资源
isr.close();
is.close();
s.close();
UDP协议的通信特点
  1. UDP是一个无连接协议,传输数据之前源端和终端不建立连接,当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。
  2. 不需要维护连接状态,包括收发状态等。
  3. 字节开销很小。
  4. 吞吐量主要受应用软件生成数据的速率、传输宽带、源端和终端主机性能等因素的限制。
Java访问Web站点
  1. 用URL类创建一个资源定位的对象。
  2. 调用URL的openConnection()方法得到HttpURLConnection对象。
  3. 调用HttpURLConnection的open()方法打开连接。
  4. 用getHeaderFields()方法得到响应结果的头部信息。
  5. 用getInputStream()方法得到输入流对象,得到响应内容。
//创建URL对象
URL url = new URL("https://music.91q.com/player");
//用URL创建HttpURLConnection对象
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.connect(); //打开连接
Map<String, List<String>> header = conn.getHeaderFields();
for (String key : header.keySet()){
    System.out.println(key + ":" +header.get(key));
}
//打印响应内容
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
String str = null;
while ((str = br.readLine()) != null){
    System.out.println(str);
}
conn.disconnect();
Java对数据库操作
JDBC操作数据库步骤
  1. 注册驱动程序
  2. 获取数据库连接
  3. 创建会话
  4. 执行SQL语句
  5. 处理结果集
  6. 关闭连接
Connection connection = null;
try {
    Class.forName("com.mysql.jdbc.Driver"); //加载驱动
     connection = DriverManager //获取连接
            .getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
    Statement statement = connection.createStatement(); //创建会话
    connection.setAutoCommit(false); //将自动提交设置为false
    boolean flag =  //创建表
            statement.execute("create table student( id int primary key auto_increment, name varchar(30),age int,gender varchar(10))");
    //向表中插入数据
    statement.execute("insert into student(name,age,gender) values ('liubei','30','M')");
    //执行查询语句
    ResultSet resultSet = statement.executeQuery("select * from student");
    //获取结果
    while (resultSet.next()){
        String name = resultSet.getString("name");
        int age = resultSet.getInt("age");
        String gender = resultSet.getString("gender");
        System.out.println("姓名:"+name+";年龄:"+age+";性别:"+gender);
    }
    conn.commit(); //提交事务
} catch (Exception e) {
    e.printStackTrace();
    conn.rollback(); //事务回滚
}finally {
    try {
        if(statement != null){statement.close();}
        if (connection != null){ connection.close(); }
    } catch (SQLException e) { e.printStackTrace(); }
}
使用JDBC事务

事务的四大特性ACID

  • 原子性:Atomicity
  • 一致性:Consistency
  • 隔离性:Isolation
  • 持久性:Durability
    JDBC的事务主要是在代码中控制的,关键点在于:关闭自动提交事务(connection.setAutoCommit(false);)、调用commit()方法提交事务和调用rollback()方法回滚事务。一般来说,JDBC中使用事务服务大致有以下步骤。
  • 关闭自动提交事务,设置连接的自动提交事务属性为false.
  • 捕获(try catch)执行代码。如果执行过程顺利,提交事务,一旦发生异常,回滚(rollback)事务。
  • 关闭连接。
JavaEE相关知识
简单的web目录结构
  • projectName:项目名称
    • src :放java代码
    • WebContent
      • META-INF
      • WEB-INF
        • lib : 存放jar包
        • web.xml :项目配置项
        • classes :存放编译后的代码
      • pages :放静态页面
servlet的概念及配置

Servlet在Java Web服务器中就充当了信息资源的最小表示单位,代表了一个用户可以通过浏览器获取的资源。Servlet可以进行无限的扩展,它可以使用Java的所有类库资源,为用户返回文本、图片、音频、视频等各类信息资源。

从编程角度开看,Servlet是一个Java类,这个类需要实现Servlet接口,提供一个公开的无参数的构造方法。由Web容器来控制它的创建、初始化、提供服务、销毁等。它的各种行为方式通过在web.xml文件中的配置类决定。

web.xml需要配置下面信息
<!-- 首先定义一个名为MyTestServlet的Servlet,指定好它的完整类名 -->
<servlet>
    <!-- servlet的名字 -->
    <servlet-name>MyTestServlet</servlet-name> 
    <!-- Servlet的完整类名 -->
    <servlet-class>MyTestServlet</servlet-class>
    <!-- 初始化参数定义 可选 -->
    <init-param>
        <!-- 初始化参数的名字 -->
        <param-name>myparam</param-name>
        <!-- 初始化参数的值 -->
        <param-value>100</param-value>
    </init-param>
</servlet>
<!-- 再指定该Servlet的URL -->
<servlet-mapping>
    <!-- Servlet的名字,须同上面一样 -->
    <servlet-name>MyTestServlet</servlet-name>
    <!-- URL,往往以“/”开头 -->
    <url-pattern>/MyTestServlet</url-pattern>
</servlet-mapping>
Servlet的生命周期

servlet的生命周期分为4个阶段:加载、初始化、提供服务和销毁,这些过程都是由Web容器来掌控。开发者关注最多的是初始化和提供服务两个阶段,在init()方法中,开发者可以获取配置在web.xml中的初始化参数;service()方法中的代码,会在Servlet的请求来到时被调用。

JavaEE中提供的Servlet接口实现类

在JavaEE的SDK中,一共提供了一下3个Servlet接口的实现类

  • javax.faces.webapp.FacesServlet;
  • javax.servlet.GenericServlet;
  • javax.servlet.http.HttpServlet;
Servlet中获取请求参数的值

在Servlet中,任何负责做出响应的方法(例如,service()、doPost()、和doGet())都会包含一个ServletRequest对象参数,不管是post还是get的请求方式,Servlet都可以通过ServletRequest接口的getParameter()或getParameterValues()方法获取到。前者适用于只有一个值的参数,后者多用于有多值的参数,列如,复选框(checkbox)

Servlet中Forward和Redirect的区别

Forward和Redirec代表了两种请求转发方式:直接请求转发和间接请求转发。对应到代码里,分别是RequestDispatcher类的froward方法和HttpServletResponse类的sendRedirect()方法。

对于间接转发方式,服务器端在响应第一次请求的时候,让浏览器再向另外一个URL发出请求,从而达到转发的目的。它本质上是两次HTTP请求,对应两个request对象。

对于直接转发方式,客户端浏览器只发出一次请求,Servlet把请求转发给Servlet、html、jsp或其他信息资源,由第二个信息资源响应该请求,两个信息资源共享同一个request对象。

过滤器的作用和工作原理

对于Web应用程序来说,过滤器是处于Web容器内的一个组件,它会过滤特定请求资源请求信息和响应信息。一个请求来到的时候,Web容器会判断是否有过滤器与该信息资源关联,如果有,则交给过滤器一一处理,然后再交给目标资源,响应的时候则以相反的顺序交给过滤器处理,最后再返回给用户浏览器。过滤器对应Filter接口,开发者一般需要实现doFiler()方法,并在web.xml文件夹中提供相应的配置。

监听器的作用和工作原理

对于Web应用程序来说,监听器是处于Web容器内的一个组件,它会对Web容器中的3种范围对象进行监听:request、session和application。当这些范围对象在创建或销毁的时候,Web容器会主动的调用它们的初始化或销毁的回调方法,从而达到时间响应的效果。根据范围不同,Java EE为开发者提供如下一些监听器接口。

  • Request事件监听器接口ServletRequestListener;
  • Session事件监听器接口HttpSessionListener;
  • Application事件监听器接口ServletContextListener;
JSP动态语言
Jsp运行机制

当客户端发出一次对某个JSP的请求,Web容器处理该请求的过程如下:

  1. Web容器会检验JSP的语法是否正确。
  2. 将JSP文件转换成Servlet的源码文件。
  3. 编译该源码文件成为Class文件。
  4. 创建一个该Servlet类的对象实例,以Servlet的方式为请求提供服务。
JSP的内置对象及其用途

JSP包含9个内置对象,分别是application、session、request、response、out、page、pageContext、exception、config.
pageContext代表的是JSP页面上下文,也就是一个运行环境。为开发者提供了访问其他内置对象的同一入口。

pageContext.getRequest(); //获取请求对象
pageContext.getSession(); //获取会话对象
pageContext.getServletContext(); //获取Servlet上下文对象
pageContext.getResponse(); //获取响应对象
pageContext.getOut(); //获取输出流对象
JSP使用JavaBean

JSP使用JavaBean有两种方式:JSP的脚本中使用纯粹的Java代码和一些JavaBean的动作标签。其中使用jsp:useBean等动作标签会更加简洁一些,使用得也更多。用动作标签来使用JavaBean的时候,用jsp:useBean标签在特定范围内声明或创建一个JavaBean,用jsp:setProperty标签来设置一个JavaBean的属性的值,用jsp:getProperty标签来获取一个JavaBean的属性的值。

使用迭代标签<c:forEach>循环显示数据

<c:forEach>标签的使用方法如下:

  1. 把需要循环的内容放在<c:forEach>与</c:forEach>之间。
  2. 为<c:forEach>提供items属性,它指定的是集合对象。
  3. 为<c:forEach>提供var属性,它指定的是每次循环得到的集合元素。
  4. 如果需要记录循环状态,则指定varStatus属性。
  5. 在循环体中,使用表达式语言,访问var属性指定的变量的各种属性。
<c:forEach begin="20" end="50" step="2" var="i">
    偶数:<c:out value="${i}"/><br/>
</c:forEach>
<%
    List<User> users = new ArrayList<>();
    for (int i = 0; i < 5;i++){
        User user = new User("wang" + i, 25 + i, i % 2 == 0 ? "male" : "female");
        users.add(user);
    }
    request.setAttribute("users",users);
%>
<table>
    <tr>
        <th>编号</th>
        <th>用户名</th>
        <th>年龄</th>
        <th>性别</th>
    </tr>
    <c:forEach items="${users}" var="user" varStatus="status">
        <tr>
            <td>${status.count}</td>
            <td>${user.name}</td>
            <td>${user.age}</td>
            <td>${user.gender}</td>
        </tr>
    </c:forEach>
</table>
JSTL逻辑判断标签

JSTL主要提供了两种标签来处理条件表达式,它们是:<c:if>或<c:choose>。

  1. 标签的条件表达式一般放在它的test属性中,如果返回为true则打印标签体的内容。
  2. 一般需要结合<c:when>和<c:otherwise>来使用,条件表达式写在<c:when>标签的test属性中,它类似于Java的switch语句。
<ul>
    <c:if test="${ 3 > 5}">
        <li><a href="www.baidu.com">百度</a></li>
    </c:if>
    <c:if test="${3 < 5}">
        <li><a href="www.jd.com">京东</a></li>
    </c:if>
</ul>
<br/><br/><br/>
<c:choose>
    <c:when test="${5 > 3}">
        男
    </c:when>
    <c:when test="${5 < 3}">
        女
    </c:when>
    <c:otherwise>
        未知性别
    </c:otherwise>
</c:choose>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值