42.用Java的套接字编程实现一个多线程的回显(echo)服务器
- 套接字(socket)为两台计算机之间的通信提供了一种机制。
原码:
package com.anran.socket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author anran
* @version 创建时间:2017年9月9日 上午11:22:16
* 类说明 :分别为每一个创建本地对应端口的客户端创建一个线程,并获取每一个客户端的输入
*/
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 = null;
while (null != (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();
}
}
}
}
}
package com.anran.socket;
import java.io.PrintWriter;
import java.net.Socket;
/**
* @author anran
* @version 创建时间:2017年9月9日 上午11:22:56
* 类说明 : 创建多个线程,向一个端口发送信息(这个端口必须是要已经被其它程序监听)
*/
public class EchoClient {
public static void main(String[] args) throws Exception {
String ip = "localhost";
int port = 6789;
for(int i=0; i<5; i++){
new Thread(new CreateSocket(ip, port)).start();
}
}
private static class CreateSocket implements Runnable {
private String ip;
private int port;
CreateSocket(String ip, int port){
this.ip = ip;
this.port = port;
}
@Override
public void run() {
try (Socket client = new Socket(ip, port)){
PrintWriter pw = new PrintWriter(client.getOutputStream());
for (int i=0; i<2; i++){
pw.println("{" + Thread.currentThread() + "}" + i);
//flush()方法可以使得监听端口读取一次
pw.flush();
Thread.sleep(2000);
}
}catch (Exception e){
e.printStackTrace();
}
//当该客户端close()的时候,对应监听线程也会执行完成(这里不写是因为Java 7的TWR会自己关闭)
// client.close();
}
}
}
输出:
服务器已经启动...
收到/127.0.0.1发送的: {Thread[Thread-1,5,main]}0
收到/127.0.0.1发送的: {Thread[Thread-2,5,main]}0
收到/127.0.0.1发送的: {Thread[Thread-4,5,main]}0
收到/127.0.0.1发送的: {Thread[Thread-0,5,main]}0
收到/127.0.0.1发送的: {Thread[Thread-3,5,main]}0
收到/127.0.0.1发送的: {Thread[Thread-1,5,main]}1
收到/127.0.0.1发送的: {Thread[Thread-3,5,main]}1
收到/127.0.0.1发送的: {Thread[Thread-2,5,main]}1
收到/127.0.0.1发送的: {Thread[Thread-4,5,main]}1
收到/127.0.0.1发送的: {Thread[Thread-0,5,main]}1
如果希望用NIO的多路复用套接字实现服务器,代码如下所示。NIO的操作虽然带来了更好的性能,但是有些操作是比较底层的,对于初学者来说还是有些难于理解(我也没有理解,待学习!!!!!!)。
package com.anran.socket;
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;
/**
* @author anran
* @version 创建时间:2017年9月9日 下午12:19:32 类说明 :
*/
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();
}
}
}
}
package com.anran.socket;
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;
/**
* @author anran
* @version 创建时间:2017年9月9日 下午12:20:16 类说明 :
*/
public 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);
}
}
43.创建数据库连接
代码:
package com.anran.sql;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @author anran
* @version 创建时间:2017年9月9日 下午4:05:32 类说明 :
*/
public class ConnectionSql {
public static void main(String[] args) throws ClassNotFoundException,
SQLException {
String driver = "org.postgresql.Driver";
String url = "jdbc:postgresql://localhost:5432/messagepush_db";
String user = "postgres";
String password = "ajl930919";
// 加载数据库驱动
Class.forName(driver);
// 获取数据库连接
Connection conn = DriverManager.getConnection(url, user, password);
try {
// 创建语句
PreparedStatement ps = conn
.prepareStatement("select * from rule where 3=?");
try {
// 填写语句中却是的参数
ps.setInt(1, 3);
// 执行语句
ResultSet set = ps.executeQuery();
try {
// 读取所有行
while (set.next()) {
// 获取某一行中列的值
System.out.println(set.getInt("rid") + " "
+ set.getString("rname"));
}
} finally {
set.close();
}
} finally {
ps.close();
}
} finally {
conn.close();
}
}
}
输出:
1 系统推送
2 微信推送规则
3 支付宝推送规则
4 1
8 无短信推送
9 NoSMS
10 无短信
44.Statement和PreparedStatement有什么区别
- PreparedStatement中的sql可以使用?然后后续在对数据进行填充(ps:43中),但是Statement只能通过拼接sql。
- PreparedStatement中的sql是预编译的,可以减少sql的编译错误并增加sql的安全。(ps:因为PreparedStatement中后续设置参数是有具体类型,从而在编译的时候就能够进行防范,但是Statement只是进行拼接)
- 当批量处理SQL或频繁执行相同的查询时,PreparedStatement有明显的性能上的优势,由于数据库可以将编译优化后的SQL语句缓存起来,下次执行相同结构的语句时就会很快(不用再次编译和生成执行计划)。但是Statement每次都需要重新编译。
45.事务的ACID
- 原子性(Atomic):事务中各项操作,要么全做要么全不做,任何一项操作的失败都会导致整个事务的失败;
- 一致性(Consistent):事务结束后系统状态是一致的;
- 隔离性(Isolated):并发执行的事务彼此无法看到对方的中间状态;
- 持久性(Durable):事务完成后所做的改动都会被持久化,即使发生灾难性的失败。通过日志和同步备份可以在故障发生后重建数据。
事务中数据读取常见问题:
- 脏读(Dirty Read):A事务读取B事务尚未提交的数据并在此基础上操作,而B事务执行回滚,那么A读取到的数据就是脏数据。
时间 | 转账事务A | 取款事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 取出500元余额修改为500元 | |
T5 | 查询账户余额为500元(脏读) | |
T6 | 撤销事务余额恢复为1000元 | |
T7 | 汇入100元把余额修改为600元 | |
T8 | 提交事务 |
- 不可重复读(Unrepeatable Read):事务A重新读取前面读取过的数据,发现该数据已经被另一个已提交的事务B修改过了。
时间 | 转账事务A | 取款事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额为1000元 | |
T5 | 取出100元修改余额为900元 | |
T6 | 提交事务 | |
T7 | 查询账户余额为900元(不可重复读) |
- 幻读(Phantom Read):事务A重新执行一个查询,返回一系列符合查询条件的行,发现其中插入了被事务B提交的行。
时间 | 转账事务A | 取款事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 统计总存款为10000元 | |
T4 | 新增一个存款账户存入100元 | |
T5 | 提交事务 | |
T6 | 再次统计总存款为10100元(幻读) |
- 第1类丢失更新:事务A撤销时,把已经提交的事务B的更新数据覆盖了。
时间 | 取款事务A | 转账事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额为1000元 | |
T5 | 汇入100元修改余额为1100元 | |
T6 | 提交事务 | |
T7 | 取出100元将余额修改为900元) | |
T8 | 撤销事务 | |
T9 | 余额恢复为1000元(丢失更新) |
- 第2类丢失更新:事务A覆盖事务B已经提交的数据,造成事务B所做的操作丢失。
时间 | 转账事务A | 取款事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额为1000元 | |
T5 | 取出100元将余额修改为900元 | |
T6 | 提交事务 | |
T7 | 汇入100元将余额修改为1100元 | |
T8 | 提交事务 | |
T9 | 查询账户余额为1100元(丢失更新) |
46.正则表达式(比较高深)
- http://www.jb51.net/tools/zhengze.html(真的是会研究的人!!!!!!)
47.获取一个类对象的方法(注意不是类的对象)
- 类型.class; String.class
- 对象.getClass(); “Hello”.getClass();
- Class.forName(); Class.forName(“java.lang.String”);
48.通过反射创建对象
- 通过类对象调用newInstance()方法,例如:String.class.newInstance()
- 通过类对象的getConstructor()或getDeclaredConstructor()方法获得构造器(Constructor)对象并调用其newInstance()方法创建对象,例如:String.class.getConstructor(String.class).newInstance(“Hello”);
原码:
package com.anran.object;
/**
* @author anran
* @version 创建时间:2017年9月9日 下午8:20:22 类说明 :
*/
public class Object {
public Object() {
// TODO Auto-generated constructor stub
}
public Object(String str) {
}
}
package com.anran.object;
import java.lang.reflect.InvocationTargetException;
/**
* @author anran
* @version 创建时间:2017年9月9日 下午8:20:37 类说明 :
*/
public class ObjectTest {
public static void main(String[] args) throws InstantiationException,
IllegalAccessException, IllegalArgumentException,
InvocationTargetException, NoSuchMethodException, SecurityException {
// 类的反射创建对象
Object object = Object.class.newInstance();
// 获取默认构造函数构建对象
Object object1 = Object.class.getDeclaredConstructor().newInstance(
"Hello");
// 获取其它构造函数构建对象
Object object2 = Object.class.getConstructor(String.class).newInstance(
"anran");
}
}
49.通过反射获取对象的私有变量和方法
原码:
package com.anran.object;
/**
* @author anran
* @version 创建时间:2017年9月9日 下午8:20:22 类说明 :
*/
public class Object {
private int num = 99;
public Object() {
// TODO Auto-generated constructor stub
}
public Object(String str) {
}
private int save(String str, Integer i){
System.out.println(str + " " + i);
return 0;
}
}
package com.anran.object;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author anran
* @version 创建时间:2017年9月9日 下午8:20:37 类说明 :
*/
public class ObjectTest {
public static void main(String[] args) throws InstantiationException,
IllegalAccessException, IllegalArgumentException,
InvocationTargetException, NoSuchMethodException, SecurityException, NoSuchFieldException {
Object object = new Object();
//获取对象中私有的变量
//getDeclaredFields()获取所有私有变量
Field f = object.getClass().getDeclaredField("num");
//设置属性是可以使用
f.setAccessible(true);
//获取私有变量名称
System.out.println(f.getName());
//获取私有变量的类型
System.out.println(f.getType());
//获取对象私有变量的值
System.out.println(f.get(object));
//修改对象私有变量的值
f.set(object, 1);
System.out.println(f.get(object));
//获取对象中私有方法
//获取所有私有方法getDeclaredMethods()
Method m = object.getClass().getDeclaredMethod("save", String.class, Integer.class);
m.setAccessible(true);
//获取方法名称
System.out.println(m.getName());
//获取返回值类型
System.out.println(m.getReturnType());
//获取参数类型
Class<?>[] clasz = m.getParameterTypes();
for(int i=0; i< clasz.length; i++){
System.out.println(clasz[i]);
}
//调用方法
System.out.println(m.invoke(object, "anran", 1));
}
}
输出:
num
int
99
1
save
int
class java.lang.String
class java.lang.Integer
anran 1
0
50.面向对象的”六原则一法则”
- 单一职责原则:一个类只做它该做的事情,从而达到”高内聚、低耦合”,继而使得该类可以轻易方到别的项目中使用(代码复用)。
- 开闭原则:对扩展开放对修改关闭。(在理想的状态下,当我们需要为一个软件系统增加新功能时,只需要从原来的系统派生出一些新类就可以,不需要修改原来的任何一行代码。要做到开闭有两个要点:①抽象是关键,一个系统中如果没有抽象类或接口系统就没有扩展点;②封装可变性,将系统中的各种可变因素封装到一个继承结构中,如果多个可变因素混杂在一起,系统将变得复杂而换乱)
- 依赖倒转原则:面向接口编程。(该原则说得直白和具体一些就是声明方法的参数类型、方法的返回类型、变量的引用类型时,尽可能使用抽象类型而不用具体类型,因为抽象类型可以被它的任何一个子类型所替代,请参考下面的里氏替换原则。)
- 里氏替换原则:任何时候都可以用子类型替换掉父类型。
- 接口隔离原则:接口要小而专,绝不能大而全。(臃肿的接口是对接口的污染,既然接口表示能力,那么一个接口只应该描述一种能力,接口也应该是高度内聚的。)
- 合成聚合复用原则:优先使用聚合或合成关系复用代码。(类与类之间简单的说有三种关系,Is-A关系、Has-A关系、Use-A关系,分别代表继承、关联和依赖。其中,关联关系根据其关联的强度又可以进一步划分为关联、聚合和合成,但说白了都是Has-A关系,合成聚合复用原则想表达的是优先考虑Has-A关系而不是Is-A关系复用代码)
- 迪米特法则:迪米特法则又叫最少知识原则,一个对象应当对其他对象有尽可能少的了解。(迪米特法则简单的说就是如何做到”低耦合”,门面模式和调停者模式就是对迪米特法则的践行。)
50.Is-A关系、Has-A关系、Use-A关系
- Is-A继承关系:“表示类与类之间的继承关系、接口与接口之间的继承的关系以及类对接口实现的关系”。如:
public abstract class A{
……
}
public class B extends A{
……
}
B类继承自A类,那么B类和A类的关系就是Is-A的关系。
- Has-A合成关系:“是关联关系的一种,是整体和部分(通常为一个私有的变量)之间的关系”,如:
public class Heart{
……
}
public class Man{
private Heart heart = new Heart();
……
}
- Use-A依赖关系:“是类与类(通常为函数的参数)之间的连接,依赖总是单向的”。如
public abstract class Course{
……
}
public class Student{
public void Learn(Course course){
……
}
}
Student类和Course的关系就是Use-A关系,Student类总是单向指向Course,学生知道自己学的是什么课程,而课程根本不关心它被哪个学生学习。
51.UML是统一建模语言
UML定义了多种图形化的符号来描述软件系统部分或全部的静态结构和动态结构,包括:用例图(use case diagram)、类图(class diagram)、时序图(sequence diagram)、协作图(collaboration diagram)、状态图(statechart diagram)、活动图(activity diagram)、构件图(component diagram)、部署图(deployment diagram)等。
1.用例图(用来捕获需求,描述系统的功能,通过该图可以迅速的了解系统的功能模块及其关系)
2.类图(描述类以及类与类之间的关系,通过该图可以快速了解系统)
3.时序图(描述执行特定任务时对象之间的交互关系以及执行顺序,通过该图可以了解对象能接收的消息也就是说对象能够向外界提供的服务)