title:多线程

多线程

内容介绍

  • 多线程概述

  • 线程实现

  • 多线程安全问题产生 & 解决方案

1. 多线程概述

学习多线程之前,我们先要了解几个关于多线程有关的概念。

A:进程:进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。

B:线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

C:简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程

什么是多线程呢?即就是一个程序中有多个线程在同时执行。

2. 线程实现

2.1 实现线程一:继承Thread类

该如何创建线程呢?通过API中搜索,查到Thread类。通过阅读Thread类中的描述。Thread是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。

A:创建线程的步骤:

1.定义一个类继承Thread。

2.重写run方法。(thread非抽象类,run方法没有要求必须重写.为了开启线程,可手动重写run方法)

3.创建子类对象,就是创建线程对象。

4.调用start方法,开启线程并让线程执行,同时还会告诉jvm去调用run方法

案例代码一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}

/*
* 多线程的实现方式:
* 方式1:一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例
*
* Thread
* String getName() 返回该线程的名称。
* void setName(String name) 改变线程名称,使之与参数 name 相同。
*
*
* CPU执行程序的随机性
*/
public class ThreadDemo2 {
public static void main(String[] args) {
//创建线程实例
MyThread mt = new MyThread();
//修改线程名字
mt.setName("张三");

//启动线程
mt.start();

//创建线程实例
MyThread mt2 = new MyThread();
mt2.setName("老王");

//启动线程
mt2.start();
}
}

2.2 实现线程二:实现Runnable接口

创建线程的另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法。然后创建Runnable的子类对象,传入到某个线程的构造方法中,开启线程。

为何要实现Runnable接口,Runable是啥玩意呢?继续API搜索。

查看Runnable接口说明文档:Runnable接口用来指定每个线程要执行的任务。包含了一个 run 的无参数抽象方法,需要由接口实现类重写该方法。

创建线程的步骤。

1、定义类实现Runnable接口。

2、覆盖接口中的run方法。。

3、创建Thread类的对象

4、将Runnable接口的子类对象作为参数传递给Thread类的构造函数。

5、调用Thread类的start方法开启线程。

案例代码二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MyThread2 implements Runnable {
int num;

public MyThread2(int num) {
this.num = num;
}

@Override
public void run() {
for (int i = 0; i < 100; i++) {
//Thread t = Thread.currentThread();//获取当前线程的名称
//System.out.println(t.getName() + ":" + i);

//链式编程
System.out.println(Thread.currentThread().getName() + ":" + i + num);
}
}

}

3. 多线程安全问题产生&解决方案

3.1 多线程卖票案例

需求:用三个线程模拟三个售票窗口,共同卖100张火车票,每个线程打印出卖第几张票

案例代码三:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class TicketThread implements Runnable {
int tickets = 100;//火车票数量

@Override
public void run() {
//出售火车票
while(true) {
//当火车票小于0张,则停止售票
if(tickets > 0) {
/*
* t1,t2,t3
* 假设只剩一张票
* t1过来了,他一看有票,他就进来了,但是他突然肚子不舒服,然后他就去上卫生间了
* t2也过来了,他一看也有票,他也进来了,但是他的肚子也不舒服,他也去上卫生间了
*
* t1上完了卫生间回来了,开始售票
* tickets = 0;
* t2也上完卫生间回来了,他也进行售票
* tickets = -1;
*/
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

System.out.println(Thread.currentThread().getName() + ":" +tickets--);
}
}
}

}

3.2 多线程安全问题解决

使用同步代码块解决

​ 格式:

​ synchronized(锁对象){

​ //需要同步的代码

​ }

案例代码四:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/*
* 问题出现的原因:
* 要有多个线程
* 要有被多个线程所共享的数据
* 多个线程并发的访问共享的数据
*
* 在火车上上厕所
* 张三来了,一看门是绿的,他就进去了,把门锁上了,门就变红了
* 李四来了,一看门市红色的,他就只能憋着
* 张三用完了厕所,把锁打开了,门就变成了绿色
* 李四一看门变绿了,他就进去了,把门锁上,门就变红了
* 王五来了,一看们是红色的,他也只能憋着
* 李四用完测试了,把锁打开了,肚子又不舒服了,扭头回去了,又把门锁上了,
*
* synchronized:同步(锁),可以修饰代码块和方法,被修饰的代码块和方法一旦被某个线程访问,则直接锁住,其他的线程将无法访问
*
* 同步代码块:
* synchronized(锁对象){
*
* }
*
* 注意:锁对象需要被所有的线程所共享
*
*
* 同步:安全性高,效率低
* 非同步:效率高,但是安全性低
*
*/
public class TicketThread implements Runnable {
int tickets = 100;//火车票数量
Object obj = new Object();

@Override
public void run() {
//出售火车票
while(true) {
synchronized (obj) {//Object可为任意锁对象
if(tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" +tickets--);
}
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TicktetTest {
public static void main(String[] args) {
//创建线程对象
TicketThread tt = new TicketThread();

Thread t = new Thread(tt);
t.setName("窗口1");
Thread t2 = new Thread(tt);
t2.setName("窗口2");
Thread t3 = new Thread(tt);
t3.setName("窗口3");

//启动线程对象
t.start();
t2.start();
t3.start();
}
}

使用同步方法解决

格式:

修饰符 synchronized 返回值 方法名(){

}

案例代码五:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/*
* 同步方法:使用关键字synchronized修饰的方法,一旦被一个线程访问,则整个方法全部锁住,其他线程则无法访问
*
* synchronized
* 注意:
* 非静态同步方法的锁对象是this
* 静态的同步方法的锁对象是当前类的字节码对象//类名.class
*/
public class TicketThread implements Runnable {
static int tickets = 100;// 火车票数量
Object obj = new Object();

@Override
public void run() {
// 出售火车票
while (true) {
/*synchronized (obj) {
method();
}*/
//method();
method2();
}
}

private synchronized void method() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} System.out.println(Thread.currentThread().getName() + ":" + tickets--);
}
}

private static synchronized void method2() {

if (tickets > 0) {

try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} System.out.println(Thread.currentThread().getName() + ":" + tickets--);
}
}
}

4.线程控制常用方法

线程控制

​ 1:static sleep(long millis) 表示让当前线程对象进入休眠状态 没有执行资格 也没有执行权
​ 2:public final void join() 线程加入(需在其他线程启动前调用)
​ 3:public static void yield() 线程礼让
​ 4:public final void setDaemon(boolean on) 后台线程(随主线程结束而结束)

线程优先级:

​ A:分时调度模型
​ B:抢占式调度模型
​ 谁的优先级高 谁抢到执行权的概率就更高 如果线程之间的优先级相同 那就随机抽取
​ Max 10
​ Min 1
​ Norm 5
​ 获取优先级 getPriority() -> int
​ 设置优先级 setPriority() -> int

面向网络编程

面向网络编程

内容介绍

  • 网络编程概述

  • UDP

  • TCP

1. 网络编程概述

网络协议

通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。

网络通信协议有很多种,目前应用最广泛的是TCP/IP协议(Transmission Control Protocal/Internet Protoal传输控制协议/英特网互联协议),它是一个包括TCP协议和IP协议,UDP(User Datagram Protocol)协议和其它一些协议的协议组,在学习具体协议之前首先了解一下TCP/IP协议组的层次结构。

在进行数据传输时,要求发送的数据与收到的数据完全一样,这时,就需要在原有的数据上添加很多信息,以保证数据在传输过程中数据格式完全一致。TCP/IP协议的层次结构比较简单,共分为四层,如图所示。

1558511069275

  1. TCP/IP网络模型

上图中,TCP/IP协议中的四层分别是应用层、传输层、网络层和链路层,每层分别负责不同的通信功能,接下来针对这四层进行详细地讲解。

链路层:链路层是用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、网线提供的驱动。

网络层:网络层是整个TCP/IP协议的核心,它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。

传输层:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议。

应用层:主要负责应用程序的协议,例如HTTP协议、FTP协议等。

IP地址和端口号

要想使网络中的计算机能够进行通信,必须为每台计算机指定一个标识号,通过这个标识号来指定接受数据的计算机或者发送数据的计算机。

在TCP/IP协议中,这个标识号就是IP地址,它可以唯一标识一台计算机,目前,IP地址广泛使用的版本是IPv4,它是由4个字节大小的二进制数来表示,如:00001010000000000000000000000001。由于二进制形式表示的IP地址非常不便记忆和处理,因此通常会将IP地址写成十进制的形式,每个字节用一个十进制数字(0-255)表示,数字间用符号”.”分开,如 “192.168.1.100”。

随着计算机网络规模的不断扩大,对IP地址的需求也越来越多,IPV4这种用4个字节表示的IP地址面临枯竭,因此IPv6 便应运而生了,IPv6使用16个字节表示IP地址,它所拥有的地址容量约是IPv4的8×10^28^倍,达到2^128^个(算上全零的),这样就解决了网络地址资源数量不够的问题。

通过IP地址可以连接到指定计算机,但如果想访问目标计算机中的某个应用程序,还需要指定端口号。在计算机中,不同的应用程序是通过端口号区分的。端口号是用两个字节(16位的二进制数)表示的,它的取值范围是0~65535,其中,0~1023之间的端口号用于一些知名的网络服务和应用,用户的普通应用程序需要使用1024以上的端口号,从而避免端口号被另外一个应用或服务所占用。

接下来通过一个图例来描述IP地址和端口号的作用,如下图所示。

1558511180774

从上图中可以清楚地看到,位于网络中一台计算机可以通过IP地址去访问另一台计算机,并通过端口号访问目标计算机中的某个应用程序。

InetAddress

了解了IP地址的作用,我们看学习下JDK中提供了一个InetAdderss类,该类用于封装一个IP地址,并提供了一系列与IP地址相关的方法,下表中列出了InetAddress类的一些常用方法。

1558511214304

案例代码一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.net.InetAddress;
import java.net.UnknownHostException;

/*
* InetAddress:此类表示互联网协议 (IP) 地址。
*
*/
public class InetAddressDemo {
public static void main(String[] args) throws UnknownHostException {
//static InetAddress getByName(String host)
//InetAddress address = InetAddress.getByName("msi");
InetAddress address = InetAddress.getByName("192.168.1.107");//ip地址是唯一的

//System.out.println(address);//msi/192.168.1.107 ipconfig

String hostAddress = address.getHostAddress();//192.168.1.107 返回IP地址
String hostName = address.getHostName();//msi 返回主机名

System.out.println(hostAddress);
System.out.println(hostName);
}
}

2. UDP协议

UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。

由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。

但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。UDP的交换过程如下图所示。

1558511414342

DatagramPacket

前面介绍了UDP是一种面向无连接的协议,因此,在通信时发送端和接收端不用建立连接。UDP通信的过程就像是货运公司在两个码头间发送货物一样。在码头发送和接收货物时都需要使用集装箱来装载货物,UDP通信也是一样,发送和接收的数据也需要使用”集装箱”进行打包,为此JDK中提供了一个DatagramPacket类,该类的实例对象就相当于一个集装箱,用于封装UDP通信中发送或者接收的数据。

想要创建一个DatagramPacket对象,首先需要了解一下它的构造方法。在创建发送端和接收端的DatagramPacket对象时,使用的构造方法有所不同,接收端的构造方法只需要接收一个字节数组来存放接收到的数据,而发送端的构造方法不但要接收存放了发送数据的字节数组,还需要指定发送端IP地址和端口号。

1558511462040

使用该构造方法在创建DatagramPacket对象时,指定了封装数据的字节数组和数据的大小,没有指定IP地址和端口号。很明显,这样的对象只能用于接收端,不能用于发送端。因为发送端一定要明确指出数据的目的地(ip地址和端口号),而接收端不需要明确知道数据的来源,只需要接收到数据即可。

1558511497788

使用该构造方法在创建DatagramPacket对象时,不仅指定了封装数据的字节数组和数据的大小,还指定了数据包的目标IP地址(addr)和端口号(port)。该对象通常用于发送端,因为在发送数据时必须指定接收端的IP地址和端口号,就好像发送货物的集装箱上面必须标明接收人的地址一样。

上面我们讲解了DatagramPacket的构造方法,接下来对DatagramPacket类中的常用方法进行详细地讲解,如下表所示。

1558511555074

DatagramSocket

DatagramPacket数据包的作用就如同是”集装箱”,可以将发送端或者接收端的数据封装起来。然而运输货物只有”集装箱”是不够的,还需要有码头。在程序中需要实现通信只有DatagramPacket数据包也同样不行,为此JDK中提供的一个DatagramSocket类。DatagramSocket类的作用就类似于码头,使用这个类的实例对象就可以发送和接收DatagramPacket数据包,发送数据的过程如下图所示。

1558511648849

在创建发送端和接收端的DatagramSocket对象时,使用的构造方法也有所不同,下面对DatagramSocket类中常用的构造方法进行讲解。

1558511665603

该构造方法用于创建发送端的DatagramSocket对象,在创建DatagramSocket对象时,并没有指定端口号,此时,系统会分配一个1558511672404

该构造方法既可用于创建接收端的DatagramSocket对象,又可以创建发送端的DatagramSocket对象,在创建接收端的DatagramSocket对象时,必须要指定一个端口号,这样就可以监听指定的端口。

上面我们讲解了DatagramSocket的构造方法,接下来对DatagramSocket类中的常用方法进行详细地讲解。

1558511683905

UDP实现

案例代码二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

/*
* 使用UDP协议发送数据
创建发送端Socket对象
创建数据并打包
发送数据
释放资源
*
* DatagramSocket:此类表示用来发送和接收数据,基于UDP协议的
*
* DatagramSocket() :创建Socket对象并随机分配端口号
* DatagramSocket(int port) :创建Socket对象并指定端口号
*/
public class SendDemo {
public static void main(String[] args) throws IOException {
//创建发送端Socket对象
DatagramSocket ds = new DatagramSocket();
//创建数据并打包
/*
* DatagramPacket :此类表示数据报包
* 数据 byte[]
* 设备的地址 ip
* 进程的地址 端口号
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
*/

String s = "hello udp,im comming!";
byte[] bys = s.getBytes();
int length = bys.length;
InetAddress address = InetAddress.getByName("msi");//发送给当前设备
int port = 8888;
//打包
DatagramPacket dp = new DatagramPacket(bys,length,address,port);
//发送数据
ds.send(dp);
//释放资源
ds.close();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/*
* 使用UDP协议接收数据
创建接收端Socket对象
接收数据
解析数据
输出数据
释放资源

*/
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
//创建接收端Socket对象
DatagramSocket ds = new DatagramSocket(8888);
//接收数据
//DatagramPacket(byte[] buf, int length)
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys,bys.length);

System.out.println(1);
ds.receive(dp);//阻塞
System.out.println(2);

//解析数据
//InetAddress getAddress() : 获取发送端的IP对象
InetAddress address = dp.getAddress();
//byte[] getData() :获取接收到的数据,也可以直接使用创建包对象时的数组
byte[] data = dp.getData();
//int getLength() :获取具体收到数据的长度
int length = dp.getLength();

//输出数据
System.out.println("sender ---> " + address.getHostAddress());
//System.out.println(new String(data,0,length));
System.out.println(new String(bys,0,length));
//释放资源
ds.close();

}
}

3. TCP协议

TCP通信同UDP通信一样,都能实现两台计算机之间的通信,通信的两端都需要创建socket对象。

区别在于,UDP中只有发送端和接收端,不区分客户端与服务器端,计算机之间可以任意地发送数据。

而TCP通信是严格区分客户端与服务器端的,在通信时,必须先由客户端去连接服务器端才能实现通信,服务器端不可以主动连接客户端,并且服务器端程序需要事先启动,等待客户端的连接。

在JDK中提供了两个类用于实现TCP程序,一个是ServerSocket类,用于表示服务器端,一个是Socket类,用于表示客户端。

通信时,首先创建代表服务器端的ServerSocket对象,该对象相当于开启一个服务,并等待客户端的连接,然后创建代表客户端的Socket对象向服务器端发出连接请求,服务器端响应请求,两者建立连接开始通信。

ServerSocket

通过前面的学习知道,在开发TCP程序时,首先需要创建服务器端程序。JDK的java.net包中提供了一个ServerSocket类,该类的实例对象可以实现一个服务器段的程序。通过查阅API文档可知,ServerSocket类提供了多种构造方法,接下来就对ServerSocket的构造方法进行逐一地讲解。

1558512099477

使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上(参数port就是端口号)。

接下来学习一下ServerSocket的常用方法,如表所示。

1558512114435

ServerSocket对象负责监听某台计算机的某个端口号,在创建ServerSocket对象后,需要继续调用该对象的accept()方法,接收来自客户端的请求。当执行了accept()方法之后,服务器端程序会发生阻塞,直到客户端发出连接请求,accept()方法才会返回一个Scoket对象用于和客户端实现通信,程序才能继续向下执行。

Socket

讲解了ServerSocket对象可以实现服务端程序,但只实现服务器端程序还不能完成通信,此时还需要一个客户端程序与之交互,为此JDK提供了一个Socket类,用于实现TCP客户端程序。

通过查阅API文档可知Socket类同样提供了多种构造方法,接下来就对Socket的常用构造方法进行详细讲解。

1558512167076

使用该构造方法在创建Socket对象时,会根据参数去连接在指定地址和端口上运行的服务器程序,其中参数host接收的是一个字符串类型的IP地址。

1558512185516

该方法在使用上与第二个构造方法类似,参数address用于接收一个InetAddress类型的对象,该对象用于封装一个IP地址。

在以上Socket的构造方法中,最常用的是第一个构造方法。

接下来学习一下Socket的常用方法,如表所示。


1558512336328在Socket类的常用方法中,getInputStream()和getOutStream()方法分别用于获取输入流和输出流。当客户端和服务端建立连接后,数据是以IO流的形式进行交互的,从而实现通信。

接下来通过一张图来描述服务器端和客户端的数据传输,如下图所示。

1558512348509

TCP协议实现

案例代码三:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;

/*
* 使用TCP协议发送数据
创建发送端Socket对象(创建连接)
获取输出流对象
发送数据
释放资源

Socket(InetAddress address, int port)
Exception in thread "main" java.net.ConnectException: Connection refused: connect

*/
public class ClientDemo {
public static void main(String[] args) throws IOException {
//创建发送端Socket对象(创建连接)
Socket s = new Socket(InetAddress.getByName("msi"),10086);
//获取输出流对象
OutputStream os = s.getOutputStream();
//发送数据
String str = "hello tcp,im comming!!!";
os.write(str.getBytes());
//释放资源
//os.close();
s.close();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

/*
* 使用TCP协议接收数据
创建接收端Socket对象
监听(阻塞)
获取输入流对象
获取数据
输出数据
释放资源

ServerSocket:接收端,服务端Socket
ServerSocket(int port)
Socket accept()

*/
public class ServerDemo {
public static void main(String[] args) throws IOException {
//创建接收端Socket对象
ServerSocket ss = new ServerSocket(10086);
//监听(阻塞)
Socket s = ss.accept();
//获取输入流对象
InputStream is = s.getInputStream();
//获取数据
byte[] bys = new byte[1024];
int len;//用于存储读到的字节个数
len = is.read(bys);
//输出数据
InetAddress address = s.getInetAddress();
System.out.println("client ---> " + address.getHostName());
System.out.println(new String(bys,0,len));
//释放资源
s.close();
//ss.close();
}
}

TCP相关案例

案例代码四:

使用TCP协议发送数据,服务端将接收到的数据转换成大写返回给客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;

/*
需求:使用TCP协议发送数据,并将接收到的数据转换成大写返回

客户端发出数据
服务端接收数据
服务端转换数据
服务端发出数据
客户端接收数据

*/
public class ClientDemo {
public static void main(String[] args) throws IOException {
//创建客户端Socket对象
Socket s = new Socket(InetAddress.getByName("msi"),10010);
//获取输出流对象
OutputStream os = s.getOutputStream();
//发出数据
os.write("tcp,im comming again!!!".getBytes());

//获取输入流对象
InputStream is = s.getInputStream();
byte[] bys = new byte[1024];
int len;//用于存储读取到的字节个数
//接收数据
len = is.read(bys);
//输出数据
System.out.println(new String(bys,0,len));

//释放资源
s.close();

}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerDemo {
public static void main(String[] args) throws IOException {
//创建服务端Socket对象
ServerSocket ss = new ServerSocket(10010);
//监听
Socket s = ss.accept();
//获取输入流对象
InputStream is = s.getInputStream();
//获取数据
byte[] bys = new byte[1024];
int len;//用于存储读取到的字节个数
len = is.read(bys);
String str = new String(bys,0,len);
//输出数据
System.out.println(str);
//转换数据
String upperStr = str.toUpperCase();
//获取输出流对象
OutputStream os = s.getOutputStream();
//返回数据(发出数据)
os.write(upperStr.getBytes());

//释放资源
s.close();
//ss.close();//服务端一般不关闭
}
}

案例代码五:

客户端:

1.提示用户输入用户名和密码,将用户输入的用户名和密码发送给服务端

2.接收服务端验证完用户名和密码的结果

服务端:

1.接收客户端发送过来的用户名和密码

2.如果用户名不是msi或者 密码不是123456,就向客户端写入”登录失败”

否则向客户端写入登录成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

/*
* 模拟用户登录
*/
public class ClientTest {
public static void main(String[] args) throws IOException {
//创建客户端Socket对象
//Socket s = new Socket(InetAddress.getByName("msi"),8888);
Socket s = new Socket("msi",8888);

//获取用户名和密码
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入用户名:");
String username = br.readLine();
System.out.println("请输入密码:");
String password = br.readLine();


//获取输出流对象
//BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
//写出数据
out.println(username);
out.println(password);

//获取输入流对象
BufferedReader serverBr = new BufferedReader(new InputStreamReader(s.getInputStream()));
//获取服务器返回的数据
String result = serverBr.readLine();
System.out.println(result);
//释放资源
s.close();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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 ServerTest {
public static void main(String[] args) throws IOException {
//创建服务器端Socket对象
ServerSocket ss = new ServerSocket(8888);
//监听
Socket s = ss.accept();
//获取输入流对象
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
//获取用户名和密码
String username = br.readLine();
String password = br.readLine();
//判断用户名和密码是否正确
boolean flag = false;
if("msi".equals(username) && "123456".equals(password)) {
flag = true;
}
//获取输出流对象
PrintWriter out = new PrintWriter(s.getOutputStream(),true);

//返回判断信息
if(flag) {
out.println("登陆成功");
}
else {
out.println("登陆失败");
}
//释放资源
s.close();
//ss.close();//服务器一般不关闭
}
}

案例代码六:

从客服端将一个文件发送给服务端,服务端接收后,返回”接收成功”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;

public class Send {
public static void main(String[] args) throws IOException {
// 客户端文本文件,服务器输出文本文件(给客户端反馈)
Socket s = new Socket("127.0.0.1", 9995);
BufferedReader br = new BufferedReader(new FileReader("网络编程"));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
s.getOutputStream()));
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
s.shutdownOutput();//禁用此套接字的输出流。表示客服端数据发送完毕,服务端终止输入流的readline()命令
BufferedReader br2 = new BufferedReader(new InputStreamReader(
s.getInputStream()));
System.out.println(br2.readLine());
s.close();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class Receive {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(9995);
Socket s = ss.accept();
BufferedReader br = new BufferedReader(new InputStreamReader(
s.getInputStream()));
BufferedWriter bw = new BufferedWriter(new FileWriter("网络编程copy"));
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
//如果客服端不终止套接字的输入流,会造成br.readline()最后处于等待状态,因为readline()始终无法为null.
BufferedWriter bw2 = new BufferedWriter(new OutputStreamWriter(
s.getOutputStream()));
bw2.write("上传完成!!");
bw2.flush();
s.close();
}
}

代理模式和注解

代理模式和注解

1.代理模式

代理模式的概念:

即Proxy Pattern,23种常用的面向对象软件的设计模式之一

代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

代理模式的组成:

抽象角色:通过接口或抽象类声明真实角色实现的业务方法。

代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。

真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。

1.1 静态代理

是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

抽象角色:

1
2
3
4
public interface Itarget {
//抽象方法
public void show ();
}

真实对象:为真正的方法执行者

1
2
3
4
5
6
7
public class Target implements Itarget {
//重写抽象对象中的方法
@Override
public void show() {
System.out.println("show...");
}
}

代理对象:

1
2
3
4
5
6
7
8
9
10
11
12
public class Proxy implements Itarget{
Itarget target;
//构造方法中传入真实对象
public Proxy(Itarget target){
this.target = target;
}
@Override
public void show() {
//实际执行的是真实对象的方法,
target.show();
}
}

客户端通过代理对象执行真实对象的方法:

1
2
3
4
5
6
7
8
9
10
11
public class proxyTest {
@Test
public void test1(){
//创建真实对象
Target target = new Target();
//将真实对象传入代理对象构造方法
Proxy proxy = new Proxy(target);
//调用代理对象的实现方法,实际执行的是真实对象的方法
proxy.show();
}
}

1.2动态代理

是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。

动态代理可以不用创建代理对象类,通过proxy.

抽象对象,真实对象同静态代理

客户端通过代理对象执行真实对象的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class targetTest {
@Test
public void test1 (){
//创建真实对象
myProxy target = new target();
//创建代理对象
myProxy proxy = (myProxy) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
//通过匿名内部类的方法
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return method.invoke(target, args);
}
});
proxy.show();
}
}

1.3 优点

(1).职责清晰

真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。

(2).代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。

(3).高扩展性

1.4 Java中动态代理

在jdk的api中提供了java.lang.reflect.Proxy它可以帮助我们完成动态代理创建

注意:在java中使用Proxy来完成动态代理对象的创建,只能为目标实现了接口的类创建代理对象。

动态代理是在内存中直接生成代理对象。

img

通过这个方法可以直接创建一个代理对象。

前两个参数帮助我们生产一个代理对象;

第三个参数控制目标行为访问的问题

img

InvocationHandler详解

它是一个接口,接口中声明了一个方法

img

Invoke方法,它是在代理对象调用行为时,会执行的方法,而invoke方法上有三个参数

img

这个方法的主要作用是,当我们通过代理对象调用行为时,来控制目标行为是否可以被调用。

img

在开发中,我们使用动态代理可以完成性能监控,权限控制,日志记录等操作


2.注解Annotation

注解概述

注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。注解是以‘@注解名’在代码中存在的

它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

你可以在编译时选择代码里的注解是否只存在于源代码级,或者它也能在class文件、或者运行时中出现(SOURCE/CLASS/RUNTIME)。

注解作用

如果要对于元数据的作用进行分类,还没有明确的定义,不过我们可以根据它所起的作用,大致可分为三类:

编写文档:通过代码里标识的元数据生成文档。

代码分析:通过代码里标识的元数据对代码进行分析。

编译检查:通过代码里标识的元数据让编译器能实现基本的编译检查

在现在开发中使用注解,一般是用于将注解替换配置文件。(Xml配置文件)

2.1 Java中基本内置注解

  • @override

它的作用是对覆盖超类中方法的方法进行标记,如果被标记的方法并没有实际覆盖超类中的方法,则编译器会发出错误警告。

注意事项:

​ 对于接口中的方法重写,在jdk1.5时@Override它是会报错.

​ 在jdk1.6后的版本就可以描述接口与类之间的重写

  • @Deprecated

它的作用是对不应该再使用的方法添加注解,当编程人员使用这些方法时,将会在编译时显示提示信息

问题:什么时候方法是过时的?

当前版本中方法它存在隐患,在后续版本中对其进行了补充,这时前一个版本中的方法就会标注成过时的。

  • @SuppressWarnings

它的作用是去掉程序中的警告.

其参数有:

deprecation,使用了过时的类或方法时的警告

unchecked,执行了未检查的转换时的警告

fallthrough,当 switch 程序块直接通往下一种情况而没有 break 时的警告

path,在类路径、源文件路径等中有不存在的路径时的警告

serial,当在可序列化的类上缺少serialVersionUID 定义时的警告

finally ,任何 finally 子句不能正常完成时的警告

all,关于以上所有情况的警告

2.2 自定义注解

  • 注解声明:

要声明一个注解通过 @interface

声明一个注解格式 @interface 注解名{}

  • 注解本质分析

分析一下注解的本质:将其.class文件找到,反编译. 可以使用javap命令或反编译工具。

@interface MyAnnoation{}

反编译后的结果

interface MyAnnotation extends Annotation

{

}

结论:注解本质上就是一个接口。它扩展了java.lang.annotation.Annotation接口;

在java中所有注解都是Annotation接口的子接口。

  • 注解成员

注解本质上就是一个接口,那么它也可以有属性和方法。

但是接口中的属性是 public static final的,在注解中注解没有什么意义。

在开发中注解中经常存在的是方法。而在注解中叫做注解的属性.

  • 注解属性类型

    1.基本类型 byte short int long float double char boolean

    2.String

    3.枚举类型

    4.注解类型

    5.Class类型

    6.以上类型的一维数组类型

  • 注解属性的使用

    如果一个注解有属性,那么在使用注解时,要对属性进行赋值操作.

    也可以给属性赋默认值 double d() default 1.23;

    如果属性是数组类型

    ​ 1.可以直接使用 属性名={值1,值2,。。。}方式,例如

    ​ @MyAnnotation(st = “aaa”,i=10,sts={“a”,”b”})

    ​ 2.如果数组的值只有一个也可以写成下面方式

    ​ @MyAnnotation(st = “aaa”,i=10,sts=”a”)

    ​ 注意sts属性它是数组类型,也就是说,只有一个值时,可以省略”{}”

    对于属性名称 value的操作.

    ​ 1.如果属性名称叫value,那么在使用时,可以省略属性名称

    ​ @MyAnnotation(“hello”)

    ​ 2.如果有多个属性,都需要赋值,其中一个叫value,这时,必须写属性名称

    ​ @MyAnnotation(value=”hello”,i=10)

  • 自定义注解-元注解

    什么是元注解及其作用?

    用于修饰注解的注解,可以描述注解在什么范围及在什么阶段使用等

  • 四个元注解介绍

    • @Retention

      指定注解信息在哪个阶段存在 Source Class Runtime

    img

    SOURCE它对应着编译阶段,可以帮助我们进行检查。

    CLASS 它对应解析执行阶段

    RUNTIME

    它对应着在JVM中

  • @Target

    指定注解修饰目标对象类型 TYPE 类、接口 FIELD 成员变量 METHOD 方法

    img

  • Documented

    使用该元注解修饰,该注解的信息可以生成到javadoc 文档中

  • Inherited

    如果一个注解使用该元注解修饰,应用注解目标类的子类会自动继承该注解

@Retention @Target 是自定义注解必须使用两个元注解,并且,@Retention它的值应该是RUNTIME,因为我们会结合反射技术来使用。 @Target我们一般使用TYPE或METHOD

多表查询

多表查询

1 修改MySQL密码

​ a: 关闭mySQL服务
​ b: 打开cmd窗口, 输入mysqld –skip-grant-tables (不要关闭此页面)
​ c: 再打开一个cmd窗口,正常登陆,要输入密码时,回车跳过
​ d: 进入mysql ,use mysql数据库,update user set password = password(‘新密码’) where user = ‘root’
​ f:在任务管理器中结束掉mysqld的进程,再启动mysql服务

2 备份和还原数据库

​ 备份数据库:
​ mysqldump -u root -p 数据库名 > 存储路径(绝对路径)
​ 还原数据库
​ 外部: mysql -u root -p 存储路径< 数据库

​ 内部: use 数据库; source 本地存储路径

3 多表关系

​ 外键 foreign key

添加外键语法:    alter table 表名1 add foreign key(外键名称) references 表名2(主键名称)

4 三种表关系

一对多

​ 在多的一方建立外键 指向一方的主键

clip_image002

多对多

​ 建立中间表 存储外键分别指向其他表的主键

clip_image002-1558490610522

一对一

​ 唯一外键对应

​ 假设是一对多,在多的一方创建外键指向一的一方的主键,将外键设置为unique。
​ 主键对应
​ 将两个表的主键建立对应关系即可。

clip_image002-1558490661152

5 多表查询语法

交叉连接查询: 查询出来的是两张表的每一条记录进行匹配的结果 会出现笛卡尔积
    select * from tb1 cross join tb2;
    select * from tb1 , tb2;

内连接:
    select * from tb1 inner join tb2 on 条件; 显示内连接
    select * from tb1 , tb2 where 条件; 隐式内连接

外连接:
    左外连接 select * from tb1 left outer join tb2 on 条件;    查询左表所有和两张表的共有部分
    右外连接 select * from tb1 right outer join tb2 on 条件;    查询右表所有和两张表的共有部分

6 子查询

子查询:一条select语句的执行依赖于另一条select语句

select * from 表名 in/exists/all/any (select *...)

in(...)            在(...)范围内
exists(...)        若(...)存在查询结果,就执行select语句
all(...)        所有
any(...)        任一

7 练习题

计算企业每年营业额的增长率

1558491166063

思路:

年营业额增长率 = (本年营业额 - 上年营业额) / 上年营业额 * 100%

关键在于如何同时获取本年营业额和上年营业额

1
2
3
-- 将该表获取两次,筛选出本年营业额和上年营业额处于一行的数据
-- 使用左外连接 是由于第一年的营业额没有对应的上年营业额,它的增长率应为0%,为了保证数据的完整性,采用此方法.
SELECT * FROM income i1 LEFT JOIN income i2 ON i1.year = i2.year + 1

1558492004088

round()对数据进行取整

concat()拼接数据

ifnull() 对数据为null时,进行处理

1
SELECT i1.*,IFNULL(CONCAT(ROUND((i1.zz -i2.zz)/i2.zz*100),'%'),'0%') AS 增长率 FROM income i1 LEFT JOIN income i2 ON i1.year = i2.year + 1

1558492510616

8 事务

事务: 指的是逻辑上的一组操作,组成这组操作的各个逻辑单元,要么全都成功,要么全都失败。

事务特性:
    原子性/一致性/隔离性/持久性
    原子性: 组成事务的各个逻辑单元不可分割。
    一致性:事务执行的前后,数据完整性保持一致
    隔离性:事务执行过程中,应不受其他事务的干扰
    持久性:事务一旦结束,数据就持久化到数据库中。

如果不考虑事务的隔离级别 在进行操作时有可能出现问题:
    脏读/不可重复读/虚读

事务隔离级别的设置: 
set session isolation transaction level 事务隔离级别
查询事务级别:
    select @@tx_isolation;

事务可以设置的隔离级别:
    read uncommitted    读未提交
    read committed        读已提交
    repeatable read        可重复读
    serializable        串行化

​ 行列转置

利用聚合函数group by

max(case sname when’英语’ then score else 0 end)as “英语”

反射学习笔记

反射学习笔记


内容

反射机制的概述和字节码对象的获取方式

反射操作构造方法、成员方法、成员属性

JavaBean的概述&BeanUtils的使用


1 反射机制概述和 常用方法

1.1 反射机制的概述和字节码对象的获取方式

1.1.1 反射介绍

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

1.1.2 字节码文件获取的三种方式

对象名.getCalss(); // 次方法来自于Object 对象已经存在的情况下, 可以使用这种方式

类名.class // 类名.class这是一个静态的属性, 只要知道类名, 就可以获取

Class.forName(“com.itheima_01.Student”); // 通过Class类中的静态方法, 指定字符串, 该字符串是类的全类名(包名+类名)
// 此处将会抛出异常都系 ClassNotFoundException 防止传入错误的类名

1.2 反射操作构造方法

1.2.1 通过获取的构造创建对象

步骤:
1.获得Class对象
2获得构造
3.通过构造对象获得实例化对象

1
2
3
4
5
6
7
8
9
10
Class的成员方法:
getConstructor(Class...objs) -> Constructor
getConstructors() -> Constructor[]

getDeclaredConstructor(Class...objs) -> Constructor
getDeclaredConstructors() -> Constructor[]
newInstance() -> Object

Constructor成员方法
newInstance(Object...objs) -> Object

1.2.2 问题: 直接通过Class类中的newInstance()和获取getConstructor()有什么区别?

newInstance()方法, 只能通过空参的构造方法创建对象
getConstructor(Class… parameterTypes)方法, 方法接受一个可变参数, 可以根据传入的类型来匹配对应的构造方法

1.3 反射操作公共成员变量

1.3.1 反射public成员变量(字段)

通过反射运行public变量流程

通过反射获取该类的字节码对象
Class clazz = Class.forName(“com.heima.Person”);

创建该类对象
Object p = clazz.newInstance();

获取该类中需要操作的字段(成员变量)
getField(String name) –> 方法传入字段的名称.
注意: 此方法只能获取公共的字段
Field f = clazz.getField(“age”);

通过字段对象中的方法修改属性值
void set(Object obj, Object value) –> 参数1): 要修改那个对象中的字段, 参数2): 将字段修改为什么值.
f.set(p, 23);

1.3.2 方法总结

1
2
3
4
5
6
 通过反射获取成员变量并使用  
Field[] getFields() --> 返回该类所有(公共)的字段
Field getField(String name) --> 返回指定名称字段

Field[] getDeclaredFields() --> 暴力反射获取所有字段(包括私有)
Field getDeclaredField(String name) --> 暴力反射获取指定名称字段

1.4 反射操作私有成员变量

1
2
3
Field[] getDeclaredFields()     --> 暴力反射获取所有字段(包括私有) 
Field getDeclaredField(String name) --> 暴力反射获取指定名称字段
void setAccessible(boolean flag) --> 让jvm不检查权限

1.5 通过反射获取成员方法并使用

1
2
3
4
5
6
7
8
9
Class的成员方法:
getMethod() -> Method
getMethods() -> Method[] 获取自己的以及从父类或父接口中继承/实现过来的public修饰的成员方法对象

getDeclaredMethod() -> Method
getDeclaredMethods() -> Method[] 获取的自己的任意修饰符修饰的成员方法对象

Method的成员方法:
invoke(Object obj,Object...params) -> Object

2 JavaBean的概述、BeanUtils的使用

2.1 JavaBean的概述和规范

JavaBean的概述:
将需要操作的多个属性封装成JavaBean, 简单来说就是用于封装数据的
规范:
类使用公共进行修饰
提供私有修饰的成员变量
为成员变量提供公共getter和setter方法
提供公共无参的构造

2.2 BeanUtils

2.2.1 准备工作

  1. 导入两个jar包
    commons-beanutils.jar
    commons-logging.jar
  2. 将jar包Build path 配置到当前的classpath环境变量中

2.3 BeanUtils的常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static void    setProperty(Object bean, String name, Object value) 
static String getProperty(Object bean, String name)
static void populate(Object bean, Map properties)
setProperty 用来给对象中的属性赋值(了解)
参数1: 需要设置属性的对象
参数2: 需要修改的属性名称
参数3: 需要修改的具体元素

getProperty 用来获取对象中的属性(了解)
参数1: 要获取的javaBean对象
参数2: 对象中的哪个属性

Populate 用来给对象中的属性赋值(掌握)

参数1: 要设置属性的对象
参数2: 将属性以Map集合的形式传入
Key : 属性的名称
Value: 属性具体的值

小结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
获取字节码文件对象
1:对象名.getClass()
2:类名.class属性
3:Class.forName("包名.类名")
获取构造方法
Class的成员方法:
getConstructor(Class...objs) -> Constructor
getConstructors() -> Constructor[]

getDeclaredConstructor(Class...objs) -> Constructor
getDeclaredConstructors() -> Constructor[]
newInstance() -> Object

Constructor成员方法
newInstance(Object...objs) -> Object
获取成员变量
Class的成员方法:
getField() -> Field
getFields() -> Field[]

getDeclaredField("age") -> Field
getDeclaredFields() -> Field[]

Field的成员方法
set(Object obj,Object value)
get(Object obj) -> Object

获取成员方法
Class的成员方法:
getMethod() -> Method
getMethods() -> Method[] 获取自己的以及从父类或父接口中继承/实现过来的public修饰的成员方法对象

getDeclaredMethod() -> Method
getDeclaredMethods() -> Method[] 获取的自己的任意修饰符修饰的成员方法对象

Method的成员方法:
invoke(Object obj,Object...params) -> Object

定义Javabean规范
BeanUtils工具类的使用
setProperty(Object bean,String name,Object value)
getProperty(Object bean,String name)
populate(Object bean,Map m)

分页案例

跨域

1560486405774

$.getJSON(url,{name:value,name:value },function(){})

request获取的参数是String类型的,需转换成Integer类型

1
int pageNo = Integer.parsInt(request.getParameter("pageNo"))

金瓶梅中的代理模式

金瓶梅中的代理模式

1.什么是代理模式

代理模式的概念:

即Proxy Pattern,23种常用的面向对象软件的设计模式之一

代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

代理模式的组成:

抽象角色:通过接口或抽象类声明真实角色实现的业务方法。

代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。

真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。


2.金瓶梅中的代理模式

一日,潘金莲在门前收拾帘子时,手上叉竿一滑,不小心打到从门前经过的西门庆西门庆定神一看惊为天人。西门庆发现武大郎家隔壁刚好就是他认识的王婆家,后来便时常借故到王婆家兜转。西门庆请托王婆帮他物色女人,王婆知道他看上潘金莲,因为贪爱西门庆的钱财,所以答应帮忙撮合。

某日,王婆请潘金莲到他家中帮忙缝纫,打扮得光鲜亮丽的西门庆此时依计又来串门。西门庆不时以花言巧语讨潘金莲欢心,王婆见潘金莲已经上勾,故意出门买酒,制造他们二人独处的机会。西门庆此时更加大胆,在一次肌肤挑逗后,郎有情妹有意的二人便在王婆房里云雨。

在这故事中,真实对象就是潘金莲了,西门庆属于客户端,代理对象王婆在客户端和真实对象之间起着中介的作用.


3.代码实现

抽象角色:

1
2
3
4
public interface Itarget {
//抽象方法
public void play ();
}

真实对象:为真正的方法执行者(金莲)

1
2
3
4
5
6
7
public class JinLian implements ITarget {
//重写抽象对象中的方法
@Override
public void play() {
System.out.println("JinLin is playing...");
}
}

代理对象:在客户端和目标对象之间起到中介的作用(王婆)

1
2
3
4
5
6
7
8
9
10
11
12
public class WangPo implements ITarget{
ITarget target;
//构造方法中传入真实对象
public WangPo(ITarget target) {
this.target = target;
}
@Override
public void play() {
//实际执行的是真实对象的方法
target.play();
}
}

客户端访问:(西门庆)

1
2
3
4
5
6
7
8
9
10
11
public class ProxyTest {
@Test
public void test1(){
//创建真实对象(通过多态的方式,可传入其他实现抽象方法的对象)
ITarget target = new JinLian();
//将真实对象传入代理对象构造方法
WangPo proxy = new WangPo(target);
//调用代理对象的实现方法,实际执行的是真实对象的方法
proxy.play();
}
}

linux安装

课堂笔记

Linux安装

我们在虚拟机上来安装Linux

VMWare它是一个收费虚拟软件。

前提:需要一个Linux系统的镜像文件安装包

{width=”5.145833333333333in” height=”2.2708333333333335in”}

{width=”5.072916666666667in” height=”1.5729166666666667in”}

{width=”5.09375in” height=”3.2083333333333335in”}

我们需要指定iso文件的位置

{width=”5.768055555555556in” height=”2.349948600174978in”}

{width=”5.768055555555556in” height=”2.9073939195100613in”}

选择第一项,安装全新操作系统或升级现有操作系统

11{width=”5.763888888888889in” height=”4.320833333333334in”}

第13步:Tab键进行选择,选择Skip,退出检测

12{width=”5.763888888888889in” height=”3.207638888888889in”}

第14步:点击Next

13{width=”5.763888888888889in” height=”4.320833333333334in”}

第15步:选择语言,这里选择的是中文简体

14{width=”5.763888888888889in” height=”4.320833333333334in”}

第16步:选择键盘样式

15{width=”5.763888888888889in” height=”4.320833333333334in”}

第17步:选择存储设备

16{width=”5.763888888888889in” height=”4.320833333333334in”}

如果以前安装过虚拟机,会出现这个警告,选择是,忽略所有数据

17{width=”5.763888888888889in” height=”4.320833333333334in”}

19{width=”5.763888888888889in” height=”4.320833333333334in”}

设置时区,勾选使用UTC时间

20{width=”5.763888888888889in” height=”4.320833333333334in”}

第21步:输入根用户(root)的密码

21{width=”5.763888888888889in” height=”4.320833333333334in”}

{width=”5.768055555555556in” height=”2.865334645669291in”}

28{width=”5.763888888888889in” height=”4.320833333333334in”}

登录时,使用的用户是root,录入密码时不会显示

Linux远程访问

使用CRT

{width=”4.145833333333333in” height=”3.6770833333333335in”}

需要录入linux的ip地址及用户名密码

查看ip地址ifconfig

1560603944797

注意事项:

  1. 关于linux安装时失败问题,有可能是硬件虚拟化没有打开,需要在bios中开启虚拟化。通过 {width=”1.90625in” height=”0.3125in”}可以查看

  2. 关于CRT安装与破解(参考图片)

Linux目录结构

Linux系统它是文件系统。

它的根目录 是”/“,是以树型结构来管理。

Root用户登录后,显示时有一个~,它其实代表的就是root目录

{width=”5.465277777777778in” height=”3.3941918197725283in”}

我们可以将我们自己的文件安装在任意位置。

Linux常用命令(重点)

切换目录命令cd

cd app 切换到app目录cd .. 切换到上一层目录cd / 切换到系统根目录cd ~ 切换到用户主目录cd - 切换到上一个所在目录

列出文件列表 ls ll

ls(list)是一个非常有用的命令,用来显示当前目录下的内容。配合参数的使用,能以不同的方式显示目录内容。ls –help 可以帮助我们查看帮助信息

ls -a 显示所有文件或目录(包含隐藏的文件)

* ls -l 缩写成ll

在linux中以 . 开头的文件都是隐藏的文件

创建与删除目录

mkdir(make directory)命令可用来创建子目录。mkdir app  在当前目录下创建app目录mkdir –p app2/test  级联创建aap2以及test目

rmdir(remove directory)命令可用来删除”空”的子目录:rmdir app  删除app目录

浏览文件

cat操作

1560602191070

more与less用法类似

{width=”1.7395833333333333in” height=”0.22916666666666666in”}

{width=”1.8645833333333333in” height=”0.17708333333333334in”}

tail

tail命令是在实际使用过程中使用非常多的一个命令,它的功能是:用于显示文件后几行的内容。

用法:

tail -10 /etc/passwd 查看后10行数据

tail -f catalina.log 动态查看日志(*****)

ctrl+c 结束查看

文件操作

cp是copy操作

mv它是move相当于剪切

cp(copy)命令可以将文件从一处复制到另一处。一般在使用cp命令时将一个文件复制成另一个文件或复制到某目录时,需要指定源文件名与目标文件名或目录。

cp a.txt b.txt 将a.txt复制为b.txt文件

cp a.txt ../ 将a.txt文件复制到上一层目录中

mv 移动或者重命名

mv a.txt ../ 将a.txt文件移动到上一层目录中

mv a.txt b.txt 将a.txt文件重命名为b.txt

rm它可以帮助我们删除文件与目录

rm 删除文件

用法:rm [选项]... 文件...

rm a.txt 删除a.txt文件

删除需要用户确认,y/n

rm 删除不询问

rm -f a.txt 不询问,直接删除

rm 删除目录

rm -r a 递归删除 可以删除文件夹(会询问)

不询问递归删除(慎用)

rm -rf a 不询问递归删除 可以删除文件夹(不会询问)

rm -rf * 删除所有文件

rm -rf /* 自杀

打包压缩与解压

tar命令位于/bin目录下,它能够将用户所指定的文件或目录打包成一个文件,但不做压缩。一般Linux上常用的压缩方式是选用tar将许多文件打包成一个文件,再以gzip压缩命令压缩成xxx.tar.gz(或称为xxx.tgz)的文件。

常用参数:

-c:创建一个新tar文件

-v:显示运行过程的信息

-f:指定文件名

-z:调用gzip压缩命令进行压缩

-t:查看压缩文件的内容

-x:解开tar文件

打包:

tar –cvf xxx.tar ./*

打包并且压缩:

tar –zcvf xxx.tar.gz ./*

解压

tar –xvf xxx.tar

tar -xvf xxx.tar.gz -C /usr/aaa

文件查找

查找符合条件的文件find

查找文件中符合条件的字符串grep

find指令用于查找符合条件的文件

示例:

find / -name “ins*“ 查找文件名称是以ins开头的文件

find / -name “ins*“ –ls

find / –user itcast –ls 查找用户itcast的文件

find / –user itcast –type d –ls 查找用户itcast的目录

find /-perm -777 –type d-ls 查找权限是777的文件

查找文件里符合条件的字符串。

用法: grep [选项]... PATTERN [FILE]...

示例:

grep lang anaconda-ks.cfg 在文件中查找lang

grep lang anaconda-ks.cfg –color 高亮显示

{width=”4.695833333333334in” height=”0.2in”}

{width=”5.009027777777778in” height=”0.2in”}

{width=”5.382638888888889in” height=”0.1909722222222222in”}

其它常用命令

【pwd】

显示当前所在目录

【touch】

创建一个空文件

* touch a.txt

【ll -h】

友好显示文件大小

【wget】

下载资料

* wget http://nginx.org/download/nginx-1.9.12.tar.gz

VI与VIM编辑器

有三种模式 命令行 插入 底行

通过 vi(vim) 文件名 就可以对文件进行操作

当操作时,开始是命令行模式 按I o a 切换到插入模式

按esc 可以在重新切换到命令行模式

在命令行模式下按 “:” 就可以切换到底行模式。

在命令行模式下可以使用一些快捷键

在Linux下一般使用vi编辑器来编辑文件。vi既可以查看文件也可以编辑文件。三种模式:命令行、插入、底行模式。

切换到命令行模式:按Esc键;

切换到插入模式:按 i 、o、a键;

i 在当前位置前插入

I 在当前行首插入

a 在当前位置后插入

A 在当前行尾插入

o 在当前行之后插入一行

O 在当前行之前插入一行

切换到底行模式:按 :(冒号);更多详细用法,查询文档《Vim命令合集.docx》和《vi使用方法详细介绍.docx》

打开文件:vim file

退出:esc :q

修改文件:输入i进入插入模式

保存并退出:esc:wq

不保存退出:esc:q!

三种进入插入模式:

i:在当前的光标所在处插入

o:在当前光标所在的行的下一行插入

a:在光标所在的下一个字符插入

快捷键:命令行模式下

dd – 快速删除一行

yy - 复制当前行

nyy - 从当前行向后复制几行

p - 粘贴

R – 替换

重定向

> 重定向输出,覆盖原有内容;>> 重定向输出,又追加功能;示例:

cat /etc/passwd > a.txt 将输出定向到a.txt中

cat /etc/passwd >> a.txt 输出并且追加

ifconfig > ifconfig.txt

管道

管道是Linux命令中重要的一个概念,其作用是将一个命令的输出用作另一个命令的输入。示例

ls --help | more 分页查询帮助信息

ps –ef | grep java 查询名称中包含java的进程

ifconfig | more

cat index.html | more

ps –ef | grep aio

&&命令执行控制

命令之间使用 && 连接,实现逻辑与的功能。

只有在 && 左边的命令返回真(命令返回值 $? == 0),&& 右边的命令才会被执行。

只要有一个命令返回假(命令返回值 $? == 1),后面的命令就不会被执行。

mkdir test && cd test

系统常用命令

date 显示或设置系统时间

date 显示当前系统时间

date -s “2014-01-01 10:10:10” 设置系统时间df 显示磁盘信息

df –h 友好显示大小free 显示内存状态

free –m 以mb单位显示内存组昂头top 显示,管理执行中的程序

clear 清屏幕

ps 正在运行的某个进程的状态

ps –ef 查看所有进程

ps –ef | grep ssh 查找某一进程kill 杀掉某一进程

kill 2868 杀掉2868编号的进程

kill -9 2868 强制杀死进程

du 显示目录或文件的大小。

du –h 显示当前目录的大小

who 显示目前登入系统的用户信息。

uname 显示系统信息。

uname -a 显示本机详细信息。依次为:内核名称(类别),主机名,内核版本号,内核版本,内核编译日期,硬件名,处理器类型,硬件平台类型,操作系统名称

Linux下用户与组管理

用户管理

useradd 添加一个用户

useradd test 添加test用户

useradd test -d /home/t1 指定用户home目录

passwd 设置、修改密码

passwd test 为test用户设置密码

切换登录:

ssh -l test -p 22 192.168.19.128

su – 用户名

userdel 删除一个用户 一定要在root用户下才能删除

userdel test 删除test用户(不会删除home目录)

userdel –r test 删除用户以及home目录

组管理

当在创建一个新用户user时,若没有指定他所属于的组,就建立一个和该用户同名的私有组

创建用户时也可以指定所在组

groupadd 创建组

groupadd public 创建一个名为public的组

useradd u1 –g public 创建用户指定组groupdel 删除组,如果该组有用户成员,必须先删除用户才能删除组。

groupdel public

id,su命令

【id命令】

功能:查看一个用户的UID和GID用法:id [选项]... [用户名]

直接使用id

直接使用id 用户名

【su命令】

功能:切换用户。用法:su [选项]... [-] [用户 [参数]... ]示例:

su u1 切换到u1用户

su - u1 切换到u1用户,并且将环境也切换到u1用户的环境(推荐使用)

【账户文件】

/etc/passwd 用户文件/etc/shadow 密码文件/etc/group 组信息文件

【用户文件】

root:x:0:0:root:/root:/bin/bash账号名称: 在系统中是唯一的用户密码: 此字段存放加密口令用户标识码(User ID): 系统内部用它来标示用户组标识码(Group ID): 系统内部用它来标识用户属性用户相关信息: 例如用户全名等用户目录: 用户登录系统后所进入的目录用户环境: 用户工作的环境

【密码文件】

shadow文件中每条记录用冒号间隔的9个字段组成.用户名:用户登录到系统时使用的名字,而且是惟一的口令: 存放加密的口令最后一次修改时间: 标识从某一时刻起到用户最后一次修改时间最大时间间隔: 口令保持有效的最大天数,即多少天后必须修改口令最小时间间隔: 再次修改口令之间的最小天数警告时间:从系统开始警告到口令正式失效的天数不活动时间: 口令过期少天后,该账号被禁用失效时间:指示口令失效的绝对天数(从1970年1月1日开始计算)标志:未使用

【组文件】

root:x:0:组名:用户所属组组口令:一般不用GID:组ID用户列表:属于该组的所有用户

文件权限管理

文件权限

{width=”5.095833333333333in” height=”2.173611111111111in”}


属主(user) 属组(group) 其他用户
r w x r w x r w x
4 2 1 4 2 1 4 2 1


r:对文件是指可读取内容 对目录是可以ls

w:对文件是指可修改文件内容,对目录 是指可以在其中创建或删除子节点(目录或文件)

x:对文件是指是否可以运行这个文件,对目录是指是否可以cd进入这个目录

Linux三种文件类型:

普通文件: 包括文本文件、数据文件、可执行的二进制程序文件等。

目录文件: Linux系统把目录看成是一种特殊的文件,利用它构成文件系统的树型结构。

设备文件: Linux系统把每一个设备都看成是一个文件

文件类型标识

普通文件(-)目录(d)符号链接(l)

* 进入etc可以查看,相当于快捷方式字符设备文件(c)块设备文件(s)套接字(s)命名管道(p)

文件权限管理:

chmod 变更文件或目录的权限。

chmod 755 a.txt -rwxr-xr-x

chmod u=rwx,g=rx,o=rx a.txt

chmod 000 a.txt / chmod 777 a.txtchown 变更文件或目录改文件所属用户和组

chown u1:public aa :变更当前的目录或文件的所属用户和组

chown -R u1:public aa :变更目录中的所有的子目录及文件的所属用户和组

例如: aa 属于 root 用户 root组 假设aa下面有一个a.txt文件 root用户 root 组

chown u1:public aa aa属于u1用户 public组 a.txt root用户 root组

chown -R u1:public aa aa属于u1用户 public组 a.txt u1用户 public组

文件的权限:

R,W,X---------------(4,2,1)

文件权限的更改

Chmod 777 a.txt;

chmod u=rwx,g=rx,o=rx a.txt

文件所属用户或者组的一个更改

常用网络操作

主机名配置

hostname 查看主机名

hostname xxx 修改主机名 重启后无效

如果想要永久生效,可以修改/etc/sysconfig/network文件

IP地址配置

Setup设置ip地址

ifconfig 查看(修改)ip地址(重启后无效)

ifconfig eth0 192.168.12.22 修改ip地址

如果想要永久生效

修改 /etc/sysconfig/network-scripts/ifcfg-eth0文件

{width=”3.4166666666666665in” height=”2.2708333333333335in”}

域名映射

/etc/hosts文件用于在通过主机名进行访问时做ip地址解析之用

{width=”5.768055555555556in” height=”0.586819772528434in”}

192.168.42.43 www.baidu.com

网络服务管理

service network status 查看指定服务的状态

service network stop 停止指定服务

service network start 启动指定服务

service network restart 重启指定服务

service ---status–all 查看系统中所有后台服务

netstat –nltp 查看系统中网络进程的端口监听情况

防火墙设置

防火墙根据配置文件/etc/sysconfig/iptables来控制本机的”出”、”入”网络访问行为。

service iptables status 查看防火墙状态

service iptables stop 关闭防火墙

service iptables start 启动防火墙

chkconfig iptables off 禁止防火墙自启

Linux上软件安装介绍

  • Linux上的软件安装有以下几种常见方式介绍
  1. 二进制发布包

软件已经针对具体平台编译打包发布,只要解压,修改配置即可

  1. RPM包

软件已经按照redhat的包管理工具规范RPM进行打包发布,需要获取到相应的软件RPM发布包,然后用RPM命令进行安装

  1. Yum在线安装

软件已经以RPM规范打包,但发布在了网络上的一些服务器上,可用yum在线安装服务器上的rpm软件,并且会自动解决软件安装过程中的库依赖问题

  1. 源码编译安装

软件以源码工程的形式发布,需要获取到源码工程后用相应开发工具进行编译打包部署。

  • 上传与下载工具介绍
  1. FileZilla

{width=”2.0625in” height=”0.2916666666666667in”}

{width=”5.768055555555556in” height=”2.2918678915135606in”}

  1. lrzsz

我们可以使用yum安装方式安装 yum install lrzsz

Yum remove lrzsz

注意:必须有网络

可以在crt中设置上传与下载目录

{width=”5.572916666666667in” height=”3.71875in”}

上传:

1560602710805

下载

sz 文件名

在Linux上安装JDK:

【步骤一】:上传JDK到Linux的服务器.

* 上传JDK

* 卸载open-JDK

java –version

rpm -qa | grep java

rpm -e --nodeps java-1.6.0-openjdk-1.6.0.35-1.13.7.1.el6_6.i686

rpm -e --nodeps java-1.7.0-openjdk-1.7.0.79-2.5.5.4.el6.i686

【步骤二】:在Linux服务器上安装JDK.

* 通常将软件安装到/usr/local

* 直接解压就可以

tar –xvf jdk.tar.gz -C 目标路径

{width=”4.322916666666667in” height=”0.20833333333333334in”}

【步骤三】:配置JDK的环境变量.

配置环境变量:

① vi /etc/profile

② 在末尾行添加

#set java environment

JAVA_HOME=/usr/local/jdk/jdk1.7.0_71

CLASSPATH=.:$JAVA_HOME/lib.tools.jar

PATH=$JAVA_HOME/bin:$PATH

export JAVA_HOME CLASSPATH PATH

保存退出

③source /etc/profile 使更改的配置立即生效

总结:

  1. 进入local目录 cd /usr/local

  2. 上传你需要安装的jdk压缩文件(注意jdk要和centOS版本一致)

  3. 查询当前centos系统中默认的jdk(rpm -qa | grep java)

  4. 删除系统中默认的jdk (rpm -e –nodeps 查询到的jdk文件)

  5. 在local目录下创建一个jdk文件夹,然后把jdk压缩文件解压到jdk文件夹里面

  6. 配置JDK的环境变量(vi /etc/profile)

在末尾行添加

#set java environment

JAVA_HOME=jdk安装的绝对路径

CLASSPATH=.:$JAVA_HOME/lib.tools.jar

PATH=$JAVA_HOME/bin:$PATH

export JAVA_HOME CLASSPATH PATH

  1. source /etc/profile 使更改的配置立即生效

在Linux上安装Mysql:

【步骤一】:将mysql的安装文件上传到Linux的服务器.

将mysql的tar解压

将系统自带的mysql卸载

先查看

rpm -qa |grep mysql

rpm -e –nodes 查询到的名称

【步骤二】:安装MYSQL服务端

{width=”4.0in” height=”0.1875in”}

下面的提示是告诉我们root用户的密码第一次是随机生成的,它保存在/root/.mysql_secret中,第一次登录需要修改root密码

{width=”5.216418416447944in” height=”2.0in”}

【步骤三】:安装MYSQL客户端

{width=”3.9895833333333335in” height=”0.1875in”}

查看生成的root密码

cat /root/.mysql_secret

mysql -u root -p 密码

报错:原因是没有启动mysql服务

需要开启mysql服务

service mysql start

{width=”4.083333333333333in” height=”0.34375in”}

执行下面操作报错,原因是第一次操作mysql必须修改root用户的密码

{width=”5.768055555555556in” height=”0.3638418635170604in”}

设置root用户的密码

{width=”2.6979166666666665in” height=”0.22916666666666666in”}

总结:

  1. 上传mysql安装压缩文件到local目录

  2. 创建一个mysql目录

  3. 把mysql的压缩文件解压到mysql目录中

  4. 将系统自带的mysql卸载

  5. 安装mysql的服务器(有可能系统缺少依赖) rpm -ivh MySQL-server-5.6.22-1.el6.i686.rpm

  6. 安装mysql的客户端

  7. 开启mysql服务

  8. 登录mysql mysql -uroot -p原始密码(保存在/root/.mysql_secret)

  9. 使用mysql必须修改root用户的密码

  10. 正常使用了

  • Mysql服务加入到系统服务并自动启动操作:

chkconfig --add mysql

自动启动:

chkconfig mysql on

查询列表:

chkconfig

  • 关于mysql远程访问设置

  • grant all privileges on *.* to 'root' @'%' identified by 'root';

  • flush privileges;

{width=”5.768055555555556in” height=”0.8278226159230097in”}

在linux中很多软件的端口都被”防火墙”限止,我们需要将防火墙关闭

防火墙打开3306端口

/sbin/iptables -I INPUT -p tcp --dport 3306 -j ACCEPT

/etc/rc.d/init.d/iptables save

/etc/init.d/iptables status

学习阶段也可以直接将防火墙关闭

service iptables stop;

在Linux上安装tomcat:

1.Tomcat上传到linux上

2.将上传的tomcat解压

3.在tomcat/bin目录下执行 ./startup.sh(注意防火墙)

4.查看目标 tomcat/logs/catalina.out

nginx,redis,gossip_spider

nginx

什么是nginx?

Nginx是一款高性能的http 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。由俄罗斯的程序设计师Igor Sysoev所开发,官方测试nginx能够支支撑5万并发链接,并且cpu、内存等资源消耗却非常低,运行非常稳定。

为什么要用nginx?

  • 1) http服务器。Nginx是一个http服务可以独立提供http服务。可以做网页静态服务器。
  • 2) 虚拟主机。可以实现在一台服务器虚拟出多个网站。例如个人网站使用的虚拟主机。
  • 3) 反向代理,负载均衡。当网站的访问量达到一定程度后,单台服务器不能满足用户的请求时,需要用多台服务器集群可以使用nginx做反向代理。并且多台服务器可以平均分担负载,不会因为某台服务器负载高宕机而某台服务器闲置的情况。

nginx的高可用

​ 负载均衡高可用:为了避免出现nginx出现宕机,可对nginx进行高可用,为nginx提供备用机,使用高可用监控程序(keepalived)来处理,一般的处理模式是,两台同时运行,实际提供服务的只有一台,监控中心与这台服务器之间会传输诸如’I am alive’这样得信息来监控对方的运行状况,一旦发现对方挂掉,就马上切换到另一台,如果挂掉的主服务器重新发送激活的状态 ,监控中心就会将请求交由主服务器


redis

Redis的概念

​ Redis是一款由C语言编写, 基于内存的持久化的数据库, 其中数据是以KEY-VALUE的形式存储的, Redis提供了丰富的数据类型

Redis的特点

  • 1) Redis将数据存储到内存当中, 所以Redis的读写效率非常高: 读 11w/s 写 8w/s
  • 2) redis提供了丰富的数据类型: string , hash , list ,set , sortedSet
    • 注意: redis中数据类型, 主要是描述的key-value中value的数据类型, 而key只有string
  • 3) Redis数据的移植非常快的
  • 4) redis中所有的操作都是原子性的, 保证数据的完整性的

redis数据类型的使用场景和特点

  • string: 可以使用json转换对象, 存储
    • 特点: 和 java中 string是类似的, 表示的就是字符串
    • 使用场景: 做缓存
  • hash: 存储对象是比较方便的
    • 特点: 和 java中 hashMap是类似的
    • 使用场景: 做缓存 (hash使用较少)
  • list:
    • 特点: 和 java中 linkedList是类似, 可以看做是队列(FIFO)先进先出
    • 使用场景: 任务队列
  • set :
    • 特点: 和 java中set集合是类似的 去重 无序
    • 使用场景: 去重业务
  • sortedSet
    • 特点: 和 java中 sortedSet(TreeSet)类型 有序 去重
    • 使用场景: 排序操作(排行榜)

jedis连接池

jedis看做是一个连接对象, 频繁的创建一个连接, 比较耗时耗资源的 通常情况, 采用连接池的技术, 提前的创建好一部分的连接对象, 放置到容器中, 反复的使用即可

  • jedis的连接池基本使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class JedisUtils {
private static JedisPool jedisPool;
// 什么加载: 随着类的加载而加载, 一般只会加载一次
static {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(100); //最大连接数
config.setMaxIdle(50); // 最大闲时的数量
config.setMinIdle(25); // 最小闲时的数量

jedisPool = new JedisPool(config,"192.168.72.142",6379);
}

// 获取连接的方法

public static Jedis getJedis(){


return jedisPool.getResource() ;
}
}

娱乐爬虫

163娱乐爬虫流程

1563178753672

1.确定首页url

1
2
3
4
5
6
7
8
9
10
11
12
13
public class News163Spider2 {
private static IdWorker idWorker = new IdWorker(0, 3);
private static NewsDao newsDao = new NewsDao();

public static void main(String[] args) throws Exception {
//1.确定首页url
List<String> indexUrlList = new ArrayList<String>();
indexUrlList.add("https://ent.163.com/special/000380VU/newsdata_index.js?callback=data_callback");
indexUrlList.add("https://ent.163.com/special/000380VU/newsdata_music.js?callback=data_callback");
indexUrlList.add("https://ent.163.com/special/000380VU/newsdata_show.js?callback=data_callback");
indexUrlList.add("https://ent.163.com/special/000380VU/newsdata_tv.js?callback=data_callback");
indexUrlList.add("https://ent.163.com/special/000380VU/newsdata_star.js?callback=data_callback");
indexUrlList.add("https://ent.163.com/special/000380VU/newsdata_movie.js?callback=data_callback");

2.发送请求,获取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private static void page(String indexUrl) throws Exception {
String page = "02";
while (true) {
//2.发送请求,获取数据
String jsonStr = HttpClientUtils.doGet(indexUrl);
if (jsonStr == null){
break;
}
//将jsonStr转换为标准json格式
jsonStr = jsonStr.substring(jsonStr.indexOf("(") + 1, jsonStr.lastIndexOf(")"));
parseJson(jsonStr);
//获取下一页url

indexUrl = indexUrl.replaceAll("_[0-9]+.js", ".js");
String[] split = indexUrl.split(".js");
indexUrl = split[0] + "_" + page + ".js" + split[1];

int pagenum = Integer.parseInt(page);
pagenum ++;
if (pagenum < 10){
page = "0"+pagenum;
}else{
page = pagenum + "";
}
}
}

3.解析数据,封装javabean

4.保存数据(去重)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private static void parseJson(String jsonStr) throws Exception {
//3.解析数据
Gson gson = new Gson();
List<Map<String, Object>> newsList = gson.fromJson(jsonStr, List.class);
for (Map<String, Object> newsObject : newsList) {
String docUrl = (String) newsObject.get("docurl");
System.out.println(docUrl);
//过滤网页
if (!docUrl.contains("ent.163.com") || docUrl.contains("photoview")) {
continue;
}

//#########去重########
Jedis jedis = JedisUtils.getJedis();
Boolean flag = jedis.sismember("bigData:spider:163News:docUrl02", docUrl);
jedis.close();
if (flag){
continue;
}
//#########去重########

News news = parseNewsItem(docUrl);
//4.保存数据
newsDao.saveNews(news);

//#########去重########
jedis = JedisUtils.getJedis();
jedis.sadd("bigData:spider:163News:docUrl02",news.getDocurl());
jedis.close();
//#########去重########
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//解析新闻详情页  
private static News parseNewsItem(String docUrl) throws Exception {
String html = HttpClientUtils.doGet(docUrl);
Document document = Jsoup.parse(html);
News news = new News();
//获取title
String title = document.select("#epContentLeft>h1").text();
news.setTitle(title);
//获取time
String timeAndSource = document.select(".post_time_source").text();

String[] split = timeAndSource.split(" 来源: ");
String time = split[0];

news.setTime(time);
//获取source
String source = split[1].substring(0, split[1].indexOf("举"));
news.setSource(source);
//获取content
String content = document.select("#endText p").text();
news.setContent(content);
//获取editor
String editor = document.select(".ep-editor").text();
editor = editor.substring(editor.indexOf(":") + 1, editor.indexOf("_"));
news.setEditor(editor);
//获取id
long id = idWorker.nextId();
news.setId(id + "");
//设置docurl
news.setDocurl(docUrl);
// System.out.println(news);

//返回news
return news;
}

5.分页获取爬取

1
2
3
4
5
6
7
//5.分页获取数据
while (!indexUrlList.isEmpty()) {
String indexUrl = indexUrlList.remove(0);

page(indexUrl);
System.out.println("本栏目爬取完毕!");
}

整体爬虫代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
public class News163Spider2 {
private static IdWorker idWorker = new IdWorker(0, 3);
private static NewsDao newsDao = new NewsDao();

public static void main(String[] args) throws Exception {
//1.确定首页url
List<String> indexUrlList = new ArrayList<String>();
indexUrlList.add("https://ent.163.com/special/000380VU/newsdata_index.js?callback=data_callback");
indexUrlList.add("https://ent.163.com/special/000380VU/newsdata_music.js?callback=data_callback");
indexUrlList.add("https://ent.163.com/special/000380VU/newsdata_show.js?callback=data_callback");
indexUrlList.add("https://ent.163.com/special/000380VU/newsdata_tv.js?callback=data_callback");
indexUrlList.add("https://ent.163.com/special/000380VU/newsdata_star.js?callback=data_callback");
indexUrlList.add("https://ent.163.com/special/000380VU/newsdata_movie.js?callback=data_callback");
//5.分页获取数据
while (!indexUrlList.isEmpty()) {
String indexUrl = indexUrlList.remove(0);

page(indexUrl);
System.out.println("本栏目爬取完毕!");
}


}

private static void page(String indexUrl) throws Exception {
String page = "02";
while (true) {
//2.发送请求,获取数据
String jsonStr = HttpClientUtils.doGet(indexUrl);
if (jsonStr == null){
break;
}
//将jsonStr转换为标准json格式
jsonStr = jsonStr.substring(jsonStr.indexOf("(") + 1, jsonStr.lastIndexOf(")"));
parseJson(jsonStr);
//获取下一页url
indexUrl = indexUrl.replaceAll("_[0-9]+.js", ".js");
String[] split = indexUrl.split(".js");
indexUrl = split[0] + "_" + page + ".js" + split[1];
int pagenum = Integer.parseInt(page);
pagenum ++;
if (pagenum < 10){
page = "0"+pagenum;
}else{
page = pagenum + "";
}
}
}

private static void parseJson(String jsonStr) throws Exception {
//3.解析数据
Gson gson = new Gson();
List<Map<String, Object>> newsList = gson.fromJson(jsonStr, List.class);
for (Map<String, Object> newsObject : newsList) {
String docUrl = (String) newsObject.get("docurl");
System.out.println(docUrl);
//过滤网页
if (!docUrl.contains("ent.163.com") || docUrl.contains("photoview")) {
continue;
}

//#########去重########
Jedis jedis = JedisUtils.getJedis();
Boolean flag = jedis.sismember("bigData:spider:163News:docUrl02", docUrl);
jedis.close();
if (flag){
continue;
}
//#########去重########

News news = parseNewsItem(docUrl);
// System.out.println(news);
//4.保存数据
newsDao.saveNews(news);

//#########去重########
jedis = JedisUtils.getJedis();
jedis.sadd("bigData:spider:163News:docUrl02",news.getDocurl());
jedis.close();
//#########去重########
}
}

private static News parseNewsItem(String docUrl) throws Exception {
String html = HttpClientUtils.doGet(docUrl);
Document document = Jsoup.parse(html);
News news = new News();
//获取title
String title = document.select("#epContentLeft>h1").text();
news.setTitle(title);
//获取time
String timeAndSource = document.select(".post_time_source").text();

String[] split = timeAndSource.split(" 来源: ");
String time = split[0];

news.setTime(time);
//获取source
String source = split[1].substring(0, split[1].indexOf("举"));
news.setSource(source);
//获取content
String content = document.select("#endText p").text();
news.setContent(content);
//获取editor
String editor = document.select(".ep-editor").text();
editor = editor.substring(editor.indexOf(":") + 1, editor.indexOf("_"));
news.setEditor(editor);
//获取id
long id = idWorker.nextId();
news.setId(id + "");
//设置docurl
news.setDocurl(docUrl);

//返回news
return news;
}
}

分布式爬虫开发

1. 什么是分布式, 分布式和集群的区别

  • 分布式: 分布式指的就是某一个模块, 或者某个系统, 拆分成不同的业务,并进行分开部署,
  • 集群: 集群更多强调的是将相同的模块或者是系统, 重复部署多次

一般来说, 在大多数的情况下, 集群和分布式是同时存在, 共同作用于整个项目

  • 通俗描述:

    • 小饭店原来只有一个厨师,切菜洗菜备料炒菜全干。

      后来客人多了,厨房一个厨师忙不过来,又请了个厨师,两个厨师都能炒一样的菜,这两个厨师的关系是集群。

      为了让厨师专心炒菜,把菜做到极致,又请了个配菜师负责切菜,备菜,备料,厨师和配菜师的关系是分布式。

      一个配菜师也忙不过来了,又请了个配菜师,两个配菜师关系是集群。

  • 从刚才的案例中, 请分析出, 集群和分布式能解决什么样的问题?

    • 1) 主要解决单节点压力过大
    • 2) 提高代码的复用性 :
    • 3) 降低模块间或者各个子系统的耦合性

缺点: 提高开发难度

2. 进行分布式爬虫编写

2.1 为什么要进行分布式爬虫的改写

1538301333207

  • 主要是由于程序的执行效率过低, 可以进行优化提升

1) 专门用来获取163新闻详情页url程序

2) 专门用来解析163新闻详情页的程序

3) 专门用来保存数据的程序 (公共的程序)

4) 专门用来获取腾讯新闻数据的程序

2.2 分布式爬虫的架构

1538573149803

1
2
3
4
5
6
1) 用来执行去重的公共的key:  set
bigData:spider:docurl
2) 用来保存详情页docurl的key: list
bigData:spider:163itemUrl:docurl
3) 用来保存news对象key : list
bigData:spider:newsJson

2.3 分布式爬虫开发:

2.3.1 163分布式爬虫改进

说明: 163爬虫一共要拆分成三个子工程, 目前将第一个工程命名为News163Master 第二个工程命名为 News163Slave 第三个工程命名为 PublicDaoNode

  • 1) News163Master 开发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130

//需求: 获取详情页的url

/**
* 1) 确定首页url 2) 发送请求, 获取数据 3) 解析数据 4) 去重判断 5) 将docurl保存到redis中 6) 获取下一页
*/
public class News163Master {

public static void main(String[] args) throws Exception {

//1. 确定首页url:
List<String> urlList = new ArrayList<String>();
urlList.add("https://ent.163.com/special/000380VU/newsdata_index.js?callback=data_callback");
urlList.add("https://ent.163.com/special/000380VU/newsdata_star.js?callback=data_callback");
urlList.add("https://ent.163.com/special/000380VU/newsdata_movie.js?callback=data_callback");
urlList.add("https://ent.163.com/special/000380VU/newsdata_tv.js?callback=data_callback");
urlList.add("https://ent.163.com/special/000380VU/newsdata_show.js?callback=data_callback");
urlList.add("https://ent.163.com/special/000380VU/newsdata_music.js?callback=data_callback");

//5. 分页获取数据
while(!urlList.isEmpty()) {
String indexUrl = urlList.remove(0);
System.out.println("获取了下一个栏目的数据#######################################" );
page(indexUrl);
}
}
// 执行分页的方法

public static void page(String indexUrl) throws Exception{
String page = "02";
while(true) {
//1. 发送请求获取数据
// 此处获取的json的数据, 并不是一个非标准的json

String jsonStr = HttpClientUtils.doGet(indexUrl);
if(jsonStr==null){
System.out.println("数据获取完成");
break;
}
// 转换为标准json方法
jsonStr = splitJson(jsonStr);

//2. 解析数据, 3 保存数据
parseJson(jsonStr);

//4. 获取下一页的url
if(indexUrl.contains("newsdata_index")){
indexUrl = "https://ent.163.com/special/000380VU/newsdata_index_" + page + ".js?callback=data_callback";
}
if(indexUrl.contains("newsdata_star")){
indexUrl = "https://ent.163.com/special/000380VU/newsdata_star_" + page + ".js?callback=data_callback";
}
if(indexUrl.contains("newsdata_movie")){
indexUrl = "https://ent.163.com/special/000380VU/newsdata_movie_" + page + ".js?callback=data_callback";
}
if(indexUrl.contains("newsdata_tv")){
indexUrl = "https://ent.163.com/special/000380VU/newsdata_tv_" + page + ".js?callback=data_callback";
}
if(indexUrl.contains("newsdata_show")){
indexUrl = "https://ent.163.com/special/000380VU/newsdata_show_" + page + ".js?callback=data_callback";
}
if(indexUrl.contains("newsdata_music")){
indexUrl = "https://ent.163.com/special/000380VU/newsdata_music_" + page + ".js?callback=data_callback";
}


System.out.println(indexUrl);

//5. page ++
int pageNum = Integer.parseInt(page);
pageNum++;

if(pageNum <10){
page = "0"+pageNum;
}else{
page = pageNum+"";
}
}

}

// 解析json的方法
private static void parseJson(String jsonStr) throws Exception{
//3.1 将json字符串转换成 指定的对象
Gson gson = new Gson();

List<Map<String, Object>> newsList = gson.fromJson(jsonStr, List.class);
// 3.2 遍历整个新闻的结合, 获取每一个新闻的对象
for (Map<String, Object> newsObj : newsList) {
// 新闻 : 标题, 时间,来源 , 内容 , 新闻编辑 , 新闻的url
//3.2.1 获取新闻的url , 需要根据url, 获取详情页中新闻数据
String docUrl = (String) newsObj.get("docurl");
// 过滤掉一些不是新闻数据的url
if(docUrl.contains("photoview")){
continue;
}
if(docUrl.contains("v.163.com")){
continue;
}
if(docUrl.contains("c.m.163.com")){
continue;
}
if(docUrl.contains("dy.163.com")){
continue;
}
// ###################去重处理代码######################
Jedis jedis = JedisUtils.getJedis();
Boolean flag = jedis.sismember("bigData:spider:docurl", docUrl);
jedis.close();//一定一定一定不要忘记关闭, 否则用着用着没了, 导致程序卡死不动
if(flag){
// 代表存在, 表示已经爬取过了
continue;
}
// ###################去重处理代码######################
// 将docurl存储到redis的list集合中
jedis = JedisUtils.getJedis();
jedis.lpush("bigData:spider:163itemUrl:docurl",docUrl);
jedis.close();

}
}
// 将非标准的json转换为标准的json字符串
private static String splitJson(String jsonStr) {
int firstIndex = jsonStr.indexOf("(");
int lastIndex = jsonStr.lastIndexOf(")");

return jsonStr.substring(firstIndex + 1, lastIndex);

}
}
  • 2) News163Slave 开发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

// 需求 : 解析新闻详情页的数据
// 步骤: 1) 从redis中获取docurl 2) 根据url解析商品的详情页 3) 封装成news对象 4) 将news对象转换为json数据
// 5) 将newsJson存储到redis的list中 6) 循环
public class News163Slave {
private static IdWorker idWorker = new IdWorker(0,1);
public static void main(String[] args) throws Exception {
while (true) {
//1.从redis中获取docurl
Jedis jedis = JedisUtils.getJedis();

//String docurl = jedis.rpop("bigData:spider:163itemUrl:docurl"); // 取不到的时候出来
// 第一个参数: 阻塞的时间 如果list中没有数据了, 就会进行阻塞, 最多阻塞20s. 如果在23s之内有数据进来, 马上解除阻塞
// 返回值: list 在这个list中只会有两个元素, 第一个元素为key值 第二个元素为弹出的元素
List<String> list = jedis.brpop(20, "bigData:spider:163itemUrl:docurl");
jedis.close();
if(list == null || list.size()==0 ){
break;
}
String docurl = list.get(1);
//2. 根据url解析商品的详情页 封装成news对象
News news = parseNewsItem(docurl);

//3. 将news对象转换为json数据
Gson gson = new Gson();
String newsJson = gson.toJson(news);

//4. 将newsJson存储到redis中
jedis = JedisUtils.getJedis();
jedis.lpush("bigData:spider:newsJson", newsJson);
jedis.close();
}
}

// 根据url 解析新闻详情页:
private static News parseNewsItem(String docUrl) throws Exception {
System.out.println(docUrl);
// 3.3.1 发送请求, 获取新闻详情页数据
String html = HttpClientUtils.doGet(docUrl);

//3.3.2 解析新闻详情页:
Document document = Jsoup.parse(html);

//3.3.2.1 : 解析新闻的标题:
News news = new News();
Elements h1El = document.select("#epContentLeft h1");
String title = h1El.text();
news.setTitle(title);

//3.3.2.2 : 解析新闻的时间:
Elements timeAndSourceEl = document.select(".post_time_source");

String timeAndSource = timeAndSourceEl.text();

String[] split = timeAndSource.split(" 来源: ");// 请各位一定一定一定要复制, 否则会切割失败
news.setTime(split[0]);
//3.3.2.3 : 解析新闻的来源:
news.setSource(split[1]);
//3.3.2.4 : 解析新闻的正文:
Elements ps = document.select("#endText p");
String content = ps.text();
news.setContent(content);
//3.3.2.5 : 解析新闻的编辑:
Elements spanEl = document.select(".ep-editor");
// 责任编辑:陈少杰_b6952
String editor = spanEl.text();
// 一定要接收返回值, 否则白写了
editor = editor.substring(editor.indexOf(":") + 1, editor.lastIndexOf("_"));
news.setEditor(editor);
//3.3.2.6 : 解析新闻的url:
news.setDocurl(docUrl);
//3.3.2.7: id
long id = idWorker.nextId();
news.setId(id + "");

return news;
}
}
  • 3) PublicDaoNode 开发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

// 需求 : 公共的保存数据库的程序:
// 步骤: 1) 从redis中获取newsJson数据 2) 将newsJson转换成news对象 3) 去重判断 4) 保存数据
// 5) 将docurl存储到redis的去重的set集合中 6) 循环
public class PublicDaoNode {
private static NewsDao newsDao = new NewsDao();

public static void main(String[] args) {

while(true) {
//1) 从redis中获取newsJson数据
Jedis jedis = JedisUtils.getJedis();
List<String> list = jedis.brpop(20, "bigData:spider:newsJson");
jedis.close();
if (list == null || list.size() == 0) {
break;
}
String newsJson = list.get(1);
System.out.println(newsJson);
//2. 将newsJson转换成news对象
Gson gson = new Gson();
News news = gson.fromJson(newsJson, News.class);

//3) 去重判断
jedis = JedisUtils.getJedis();
Boolean flag = jedis.sismember("bigData:spider:docurl", news.getDocurl());
jedis.close();

if (flag) {
continue;
}
//4) 保存数据

newsDao.saveNews(news);

// 5) 将docurl存储到redis的去重的set集合中
jedis = JedisUtils.getJedis();
jedis.sadd("bigData:spider:docurl", news.getDocurl());
jedis.close();
}

}

}
2.2.2.2 腾讯娱乐分布式爬虫改进

说明: 腾讯娱乐爬虫需要拆分成二个子工程, 一个子工程用于获取数据,封装news对象, 一个公共的子工程用于保存数据, 其中公共的已经开发完毕, 只需要拆分另一个子工程名为NewsTencentMaster即可

  • 1) NewsTencentMaster 开发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

// 需求 : 解析数据, 封装成news对象, 将news对象保存到redis中
// 1) 确定首页url 2) 发送请求, 获取数据 3) 解析数据 4) 去重判断 5) 封装news对象 6) 将news对象转换为newsJson
// 7) 将newsJson保存到Redis中 8) 分页获取
public class NewsTencentMaster {
private static IdWorker idWorker = new IdWorker(0,2);

public static void main(String[] args) throws Exception {
//1. 确定首页url
String topNewsUrl = "https://pacaio.match.qq.com/irs/rcd?cid=137&token=d0f13d594edfc180f5bf6b845456f3ea&ext=ent&num=60";
String noTopNewsUrl = "https://pacaio.match.qq.com/irs/rcd?cid=146&token=49cbb2154853ef1a74ff4e53723372ce&ext=ent&page=0";

//2. 执行分页:
page(topNewsUrl, noTopNewsUrl);

}

// 执行分页的方法
public static void page(String topNewsUrl, String noTopNewsUrl) throws Exception {
//1. 热点新闻数据的获取: 只有一页数据
//1.1 发送请求, 获取数据
String topNewsJsonStr = HttpClientUtils.doGet(topNewsUrl);
//1.2 解析数据
List<News> topNewsList = parseJson(topNewsJsonStr);
//1.3 保存数据
saveNews(topNewsList);

//2. 处理非热点数据
int page = 1;
while (true) {

//2.1 发送请求, 获取数据
String noTopNewsJsonStr = HttpClientUtils.doGet(noTopNewsUrl);
//2.2 解析数据
List<News> noTopNewsList = parseJson(noTopNewsJsonStr);

if (noTopNewsList == null) {
break;
}
//2.3 保存数据
saveNews(noTopNewsList);
//2.4 获取下一页url
noTopNewsUrl = "https://pacaio.match.qq.com/irs/rcd?cid=146&token=49cbb2154853ef1a74ff4e53723372ce&ext=ent&page=" + page;

//2.5 自增 +1
page++;

System.out.println(page);
}


}

// 保存数据的操作 : 腾讯返回数据的时候, 就会有重复的数据
public static void saveNews(List<News> newsList) {
Jedis jedis = JedisUtils.getJedis();
Gson gson = new Gson();
for (News news : newsList) {
// 需要将news对象转换为newsJson
String newsJson = gson.toJson(news);

// 将newsJson存储到redis的list集合中
jedis.lpush("bigData:spider:newsJson",newsJson);
}
jedis.close();

}

// 解析新闻数据
private static List<News> parseJson(String newsJsonStr) {
//3.1 将字符串json数据转换为指定的类型: map
Gson gson = new Gson();
Map<String, Object> map = gson.fromJson(newsJsonStr, Map.class);
//获取一下, 本次获取了多少条数据
Double datanum = (Double) map.get("datanum");
if (datanum.intValue() == 0) {
return null;
}
//3.2 获取data中数据 : 列表页中数据
List<Map<String, Object>> newsList = (List<Map<String, Object>>) map.get("data");
//3.3 遍历这个列表, 获取每一个新闻的数据
List<News> tencentNewList = new ArrayList<News>();
for (Map<String, Object> newsMap : newsList) {
String docurl = (String) newsMap.get("vurl");
if (docurl.contains("video")) {
continue;
}
//######################去重处理############################33
Jedis jedis = JedisUtils.getJedis();
Boolean flag = jedis.sismember("bigData:spider:docurl", docurl);
jedis.close();
if (flag) {
// 如果为true, 表示已经存在, 已经爬取过了
continue;
}

//######################去重处理############################33

//3.3.1 封装news对象
News news = new News();

news.setTitle((String) newsMap.get("title"));
news.setTime((String) newsMap.get("update_time"));
news.setSource((String) newsMap.get("source"));
news.setContent((String) newsMap.get("intro"));
news.setEditor((String) newsMap.get("source"));
news.setDocurl(docurl);

news.setId(idWorker.nextId() + "");

tencentNewList.add(news);

System.out.println(docurl);
}

return tencentNewList;


}
}

注意: 开发完成以后., 一定要进行测试:如果能够在本地全部跑通, 才可以进行部署, 否则不要进行部署

  • 测试:
    • 1) 清空数据: redis 和 mysql
    • 2) 修改一个dao连接内容(修改本地连接)
    • 3) 分别启动四个程序即可: 可以不分先后顺序

3. 进行分布式爬虫的部署

3.1 部署方案

说明: 目前一共有四个子项目, 其中二个master各占用一台虚拟机, 其中一个用来解析新闻详情页, 可以部署一个两台的集群, 其中一个用来保存数据的, 可以部署三台,构建一个集群, 一共为七台, 外加一台mysql和一台redis, 共需要九台服务器

1538574257790

  • 本次仅仅是模拟部署, 故采用三台服务器来模拟九台服务器:(实际中真实的九台服务器)
    • 1) 三台服务器都可以上网, 并都安装有jdk1.8以上
    • 2) 三台服务器的防火墙均已关闭(实际中开放端口号)
    • 3) 其中有一台需要安装 MySQL 其中一台安装 Redis,并均已正常开启,mysql必须开启远程登录

3.2 部署准备工作

  • 1) 检测linux环境:

    • 1.1) 使用 ping 命令检测三台是否可以联网,并都安装有jdk1.8
      • 注意: Windows电脑上的jdk 和 linux上的jdk需要保持一致
    • 1.2) 使用 service iptables status 查看是否关闭防火墙
    • 1.3) 检测MySQL:
      • 1.3.1) 在linux中能否正常连接mysql
      • 1.3.2) 在sqlyog中远程连接mysql是否正常
      • 1.3.3) linux和sqlyog连接MySQL的密码需要保持一致, 以免出现存储不进去的问题
    • 1.4) 使用 ps -ef | grep redis 检测redis是否正常启动
  • 将项目进行打包:一共有四个子项目, 共需要打包四个jar

    • 0) 修改NewsDao中连接数据库相关设置

    1544080592257

  • 1) 添加打包插件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<plugins>
<!--这是jdk编译的插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
<!--打包的插件-->
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest> <!-- 注意 此为设置程序的主入口-->
<mainClass>com.cyannote.jdSpider.JdSlave</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
</plugins>
  • 2) 修改打包插件中的主入口:

    1538574857793

  • 3) 将项目进行编译

    1538574959362

  • 4) 进行打包操作:

    1538575008618

  • 5) 打包成功后, 会在打包过程中提示打包的位置

    1538575060321

  • 6) 打开此位置, 查看此jar包中主入口是否是刚才是设置的主入口类

    1538575219745

  • 7) 将其进行更该成对应的jar包的名称后, 放置一个适当的位置等待上传

  • 8) 重复执行以上2~ 7 步, 将四个jar包都要打包出来

    • 特别注意: 不要忘记修改主入口类(程序的入口)和 打包前进行编译

注意: 需要单独对163slave程序再次进行打包操作. 在导包的时候, 需要修改idwork中编号,否则在部署的时候, 会出现id冲突的问题

1
因为, IDwork如果编号都一致了, 那么在同一时刻产生的id值是一样的

3.3 进行分布式部署

  • 注意, 在开启这三个服务器连接窗口的时候, 一定要三个一起开

1544081473781

使用三台虚拟机模拟9台服务器, 其本质上就是使用xshell 或者 CRT 将三台虚拟机的连接窗口开启各开启三次即可, 然后自己进行分配, 那一台是MySQL, 那一台是Redis, 那一台是master….

1
2
3
4
5
统一jar包上传至 ; /export/servers/spider 目录下

mkdir -p /export/servers/spider
rm -rf /export/servers/spider/*
cd /export/servers/spider
  • 1) 将对应的jar包上传到 服务器中, 推荐使用 rz进行上传
    • 将jar包上传至每个服务器的: /export/servers/spider
1
2
3
4
1) 安装 rz 命令:
安装命令: yum -y install lrzsz
2) 上传命令: rz
注意: 上传的位置和输入rz命令的位置是一样的
  • 2) 启动各个jar包即可: 推荐先启动获取数据的集群, 后启动 解析数据的集群, 最后启动两个master
1
启动命令:  java -jar  xxx.jar

将爬虫设置为定时执行获取数据的操作: 目前采用的shell脚本的方式来操作

需求: 每二十分钟执行一次爬虫程序, 用于爬取最新的新闻信息

实现步骤:

  • 1) 更改hosts文件(此步骤需要在每一个centos中配置)
1
2
3
4
5
6
7
8
9
vi /etc/hosts

修改如下内容:
192.168.72.141 node01
192.168.72.142 node02
192.168.72.143 node03


三台服务器必须要配置免密登录
  • 2) 编写shell脚本:

    注意: 在进行修改shell脚本的时候, 一定要明确, 那台机子上部署了什么jar包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
vi startSpider.sh
i
#脚本内容: 以下脚本为示例内容, 只提供大体逻辑, 根据实际进行局部修改

#!/bin/sh
echo "开始执行"

for host in node01 node02 node03
do
ssh -q $host "source /etc/profile; nohup java -jar /export/servers/spider/PublicDaoNode.jar >>/dev/daoLog.log 2>&1 &"
if [ $host == node01 ]
then
ssh -q $host "source /etc/profile; nohup java -jar /export/servers/spider/News163Slave.jar >>/dev/163Slave.log 2>&1 &"
fi
if [ $host == node02 ]
then
ssh -q $host "source /etc/profile; nohup java -jar /export/servers/spider/News163Slave.jar >>/dev/163Slave.log 2>&1 &"
fi
if [ $host == node03 ]
then
ssh -q $host "source /etc/profile; nohup java -jar /export/servers/spider/News163Master.jar >>/dev/163Master.log 2>&1 &"
ssh -q $host "source /etc/profile; nohup java -jar /export/servers/spider/NewsTencentMaster.jar >>/dev/tencent.log 2>&1 &"
fi
done


echo "结束了"
  • 3) 编写定时任务: 推荐定时任务和当前shell脚本在同一台虚拟机中
1
2
3
4
crontab -e          // 设置定时, 输入完成后会自动进入一个设置文档中
输入 i 进入编辑模式
*/10 * * * * sh /export/servers/spider/startSpider.sh // 表示每隔10分钟执行一次 启动爬虫的脚本
输入 esc 退出命令行模式, 输入 :wq 保存退出即可

必须修改脚本文件的权限,否则会出现无法启动脚本的情况.

4. 使用代理ip

http://hc.apache.org/httpcomponents-client-4.5.x/tutorial/html/connmgmt.html#d5e485

1
2
3
4
5
HttpHost proxy = new HttpHost("someproxy", 8080);
DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);
CloseableHttpClient httpclient = HttpClients.custom()
.setRoutePlanner(routePlanner)
.build();

5.3.1 集成在代码中

  • 将代理的ip存放到redis中,存储类型list。
1
2
3
4
5
6
7
8
9
10
11
public void testInitIP() throws Exception {
Jedis conn = JedisUtil.getConn();
BufferedReader bufferedReader = new BufferedReader(
new FileReader(new File("C:\\Users\\maoxiangyi\\Desktop\\Proxies2018-06-06.txt")));
String line = null;
while ((line=bufferedReader.readLine())!=null) {
conn.lpush("spider:ip", line);
}
bufferedReader.close();
conn.close();
}
  • 重构后的httpclient
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
private static String execute(HttpRequestBase request) {

RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(5000)// 设置创建连接的最长时间
.setConnectionRequestTimeout(5000)// 设置获取连接的最长时间
.setSocketTimeout(10 * 1000)// 设置数据传输的最长时间
.build();
request.setConfig(requestConfig);

String html = null;

// 从redis中获取代理IP
Jedis conn = JedisUtil.getConn();
// 从右边弹出一个元素之后,从新放回左边
List<String> ipkv = conn.brpop(0, "spider:ip");
// CloseableHttpClient httpClient = getHttpClient();
CloseableHttpClient httpClient = getProxyHttpClient(ipkv.get(1));
try {
CloseableHttpResponse res = httpClient.execute(request);
if (200 == res.getStatusLine().getStatusCode()) {
html = EntityUtils.toString(res.getEntity(), Charset.forName("utf-8"));
//请求成功之后,将代理IP放回去,下次继续使用
conn.lpush("spider:ip", ipkv.get(1));
conn.close();
}
} catch (Exception e) {
System.out.println("请求失败");
// TODO 需要开发自动重试功能
throw new RuntimeException(e);
}
return html;
}
private static PoolingHttpClientConnectionManager cm;
private static CloseableHttpClient getProxyHttpClient(String ipkv) {

String[] vals = ipkv.split(":");
System.out.println(vals);
HttpHost proxy = new HttpHost(vals[0], Integer.parseInt(vals[1]));
DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);
return HttpClients.custom().setConnectionManager(connectionManager).setRoutePlanner(routePlanner).build();
}

在linux安装JDk

在Linux 上安装JDK

【步骤一】:上传或下载JDK到Linux的服务器:

* 上传/下载JDK

* 卸载linux自带的open-JDK

查询linux自带的jdk

​ java –version

​ rpm -qa | grep java

卸载linux自带的jdk

​ rpm -e -nodeps 查询到的java版本号

​ 例如:

​ rpm -e –nodeps java-1.6.0-openjdk-1.6.0.35-1.13.7.1.el6_6.i686

​ rpm -e –nodeps java-1.7.0-openjdk-1.7.0.79-2.5.5.4.el6.i686

【步骤二】:在Linux服务器上安装JDK

* 通常将软件安装到/usr/local

* 直接解压就可以

tar –xvf  jdk.tar.gz  -C (目标路径)

1561106276271

【步骤三】:配置JDK的环境变量

配置环境变量:

1
2
3
4
5
6
7
8
9
10
① vi /etc/profile

② 在末尾行添加
#set java environment
JAVA_HOME=/usr/local/jdk/jdk1.7.0_71
CLASSPATH=.:$JAVA_HOME/lib.tools.jar
PATH=$JAVA_HOME/bin:$PATH
export JAVA_HOME CLASSPATH PATH
保存退出
③source /etc/profile 使更改的配置立即生效