Java多线程编程核心技术---线程间通信(二)
通过管道进行线程间通信:字节流
Java提供了各种各样的输入/输出流Stream可以很方便地对数据进行操作,其中管道流(pipeStream)是一种特殊的流,用于在不同线程间直接传送数据,一个线程发送数据到输出管道,另一个线程从输入管道中读数据。通过使用管道,实现不同线程间的通信,无需借助于类似临时文件之类的东西。
JDK提供了4个类来使线程间可以进行通信:
- PipedInputStream和PipedOutputStream
- PipedReader和PipedWriter
public class WriteData {
	public void writeMethod(PipedOutputStream out) {
		try {
			System.out.println("write:");
			for (int i = 0; i < 100; i++) {
				String outData = "" + (i + 1);
				out.write(outData.getBytes());
				//System.out.print(outData);
			}
			//System.out.println();
			out.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
public class ReadData {
	public void readMethod(PipedInputStream input) {
		try {
			System.out.println("read:");
			byte[] byteArray = new byte[20];
			int readLength = input.read(byteArray);
			while (readLength != -1) {
				String newData = new String(byteArray, 0, readLength);
				System.out.println(newData);
				readLength = input.read(byteArray);
			}
			System.out.println();
			input.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
public class ThreadWrite extends Thread {
	private WriteData writeData;
	private PipedOutputStream out;
	public ThreadWrite(WriteData writeData, PipedOutputStream out) {
		super();
		this.out = out;
		this.writeData = writeData;
	}
	@Override
	public void run() {
		writeData.writeMethod(out);
	}
}
public class ThreadRead extends Thread {
	private ReadData readData;
	private PipedInputStream input;
	public ThreadRead(ReadData readData, PipedInputStream input) {
		super();
		this.input = input;
		this.readData = readData;
	}
	@Override
	public void run() {
		readData.readMethod(input);
	}
}
public class Main {
	public static void main(String[] args) {
		try {
			WriteData writeData = new WriteData();
			ReadData readData = new ReadData();
			PipedInputStream inputStream = new PipedInputStream();
			PipedOutputStream outputStream = new PipedOutputStream();
			outputStream.connect(inputStream);
			//inputStream.connect(outputStream);
			ThreadRead threadRead = new ThreadRead(readData, inputStream);
			ThreadWrite threadWrite = new ThreadWrite(writeData, outputStream);
			threadRead.start();//先启动读线程
			Thread.sleep(1000);
			threadWrite.start();//后启动写线程
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
控制台打印结果如下:
read:
write:
123456
78910111213141516171
81920212223242526272
8293031323334353637
383940414243
44454647484950
51525354555657
58596061626364
65666768697071
72737475767778
798081828384
858687888990
91929394959697
9899100
先启动读线程,由于没有数据被写入,读线程阻塞在readLength = input.read(byteArray),直到有数据被写入才继续往下运行。
通过管道进行线程间通信:字符流
public class WriteData {
	public void writeMethod(PipedWriter writer) {
		try {
			System.out.println("write:");
			for (int i = 0; i < 100; i++) {
				String outData = "" + (i + 1);
				writer.write(outData);
				//System.out.print(outData);
			}
			//System.out.println();
			writer.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
public class ReadData {
	public void readMethod(PipedReader reader) {
		try {
			System.out.println("read:");
			char[] array = new char[20];
			int readLength = reader.read(array);
			while (readLength != -1) {
				String newData = new String(array, 0, readLength);
				System.out.println(newData);
				readLength = reader.read(array);
			}
			System.out.println();
			reader.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
public class ThreadWrite extends Thread {
	private WriteData writeData;
	private PipedWriter writer;
	public ThreadWrite(WriteData writeData, PipedWriter writer) {
		super();
		this.writer = writer;
		this.writeData = writeData;
	}
	@Override
	public void run() {
		writeData.writeMethod(writer);
	}
}
public class ThreadRead extends Thread {
	private ReadData readData;
	private PipedReader reader;
	public ThreadRead(ReadData readData, PipedReader reader) {
		super();
		this.reader = reader;
		this.readData = readData;
	}
	@Override
	public void run() {
		readData.readMethod(reader);
	}
}
public class Main {
	public static void main(String[] args) {
		try {
			WriteData writeData = new WriteData();
			ReadData readData = new ReadData();
			PipedReader reader = new PipedReader();
			PipedWriter writer = new PipedWriter();
//			writer.connect(reader);
			reader.connect(writer);
			ThreadRead threadRead = new ThreadRead(readData, reader);
			ThreadWrite threadWrite = new ThreadWrite(writeData, writer);
			threadRead.start();
			Thread.sleep(1000);
			threadWrite.start();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
控制台打印结果如下:
read:
write:
12345678910111213141
51617181920212223242
52627282930313233343
53637383940414243444
54647484950515253545
55657585960616263646
56667686970717273747
57677787980818283848
58687888990919293949
596979899100
打印结果和前一个例子基本一样。此实验是在两个线程中通过管道流进行字符数据的传输。
等待/通知:交叉备份
创建20个线程,10个线程将数据备份到A数据库中,10个线程将数据备份到B数据库中,并且备份A数据库和备份B数据库是交叉进行的。
public class DBTools {
	volatile private boolean prevIsA = false;
	synchronized public void backupA(){
		try {
			while (prevIsA) {
				wait();
			}
			for (int i = 0; i < 5; i++) {
				System.out.println("☆☆☆☆☆");
			}
			prevIsA = true;
			notifyAll();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	synchronized public void backupB(){
		try {
			while (!prevIsA) {
				wait();
			}
			for (int i = 0; i < 5; i++) {
				System.out.println("★★★★★");
			}
			prevIsA = false;
			notifyAll();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
public class ThreadA extends Thread {
	private DBTools dbTools;
	public ThreadA(DBTools dbTools) {
		super();
		this.dbTools = dbTools;
	}
	@Override
	public void run() {
		dbTools.backupA();
	}
}
public class ThreadB extends Thread {
	private DBTools dbTools;
	public ThreadB(DBTools dbTools) {
		super();
		this.dbTools = dbTools;
	}
	@Override
	public void run() {
		dbTools.backupB();
	}
}
public class Main {
	public static void main(String[] args) {
		DBTools dbTools = new DBTools();
		for (int i = 0; i < 20; i++) {
			ThreadA threadA = new ThreadA(dbTools);
			threadA.start();
			ThreadB threadB = new ThreadB(dbTools);
			threadB.start();
		}
	}
}
控制台打印结果如下:
......
★★★★★
★★★★★
★★★★★
★★★★★
★★★★★
☆☆☆☆☆
☆☆☆☆☆
☆☆☆☆☆
☆☆☆☆☆
☆☆☆☆☆
★★★★★
★★★★★
★★★★★
★★★★★
★★★★★
☆☆☆☆☆
☆☆☆☆☆
☆☆☆☆☆
☆☆☆☆☆
☆☆☆☆☆
......
交替打印是使用prevIsA作为标记来实现的。
方法join的使用
在很多情况下,主线程创建并启动子线程,如果子线程要进行大量运算,主线程往往比子线程先运行完毕。如果主线程想要等待子线程执行完之后再结束,就需要用到join方法。
先看一个实验。
public class MyThread extends Thread {
	@Override
	public void run() {
		try {
			int secondValue = (int)(Math.random() * 10000);
			System.out.println(secondValue);
			Thread.sleep(secondValue);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args) {
		MyThread thread = new MyThread();
		thread.start();
//		Thread.sleep(?);
		System.out.println("我想在thread对象执行完毕之后再执行");
		System.out.println("但是上面sleep()中的值写多少呢?");
		System.out.println("答案是:不能确定!!!");
	}
}
用join()方法来解决上面的问题
public class MyThread extends Thread {
	@Override
	public void run() {
		try {
			int secondValue = (int)(Math.random() * 10000);
			System.out.println(secondValue);
			Thread.sleep(secondValue);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args) throws InterruptedException {
		MyThread thread = new MyThread();
		thread.start();
//		Thread.sleep(?);
		thread.join();
		System.out.println("我想在thread对象执行完毕之后再执行");
	}
}
控制台打印结果如下:
9230
我想在thread对象执行完毕之后再执行
方法join()的作用是使所属线程对象x正常执行run()方法中的任务,而使当前线程进行无限期的阻塞。等线程x执行完毕之后再执行当前线程后续的代码。
方法join()与异常
在join()过程中,如果当前线程对象被中断,则当前线程出现异常。
public class ThreadA extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < Integer.MAX_VALUE; i++) {
			String newString = new String();
			Math.random();
		}
	}
}
public class ThreadB extends Thread {
	@Override
	public void run() {
		try {
			ThreadA a = new ThreadA();
			a.start();
			a.join();
			System.out.println("线程B在run end处打印");
		} catch (Exception e) {
			System.out.println("线程B在catch处打印");
			e.printStackTrace();
		}
	}
}
public class ThreadC extends Thread {
	private ThreadB threadB;
	public ThreadC(ThreadB threadB) {
		super();
		this.threadB = threadB;
	}
	@Override
	public void run() {
		threadB.interrupt();
	}
}
public class Main {
	public static void main(String[] args) {
		try {
			ThreadB b = new ThreadB();
			b.start();
			Thread.sleep(2000);
			ThreadC c = new ThreadC(b);
			c.start();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
控制台打印结果如下:
线程B在catch处打印
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Thread.join(Unknown Source)
	at java.lang.Thread.join(Unknown Source)
	at com.umgsai.thread.thread38.ThreadB.run(ThreadB.java:9)
此时程序不结束,a线程扔在运行。
方法join(long)的使用
public class MyThread extends Thread {
	@Override
	public void run() {
		try {
			System.out.println("MyThread开始,time = " + System.currentTimeMillis());
			Thread.sleep(5000);
			System.out.println("MyThread结束,time = " + System.currentTimeMillis());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args) {
		try {
			MyThread thread = new MyThread();
			thread.start();
			thread.join(2000);
			System.out.println("main线程结束 time = " + System.currentTimeMillis());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
控制台打印结果如下:
MyThread开始,time = 1466909154102
main线程结束 time = 1466909156103
MyThread结束,time = 1466909159102
如果将main中的join(2000)改成Thread.sleep(2000),运行效果是一样的,main线程都是等待2秒。主要区别来自于这两个方法对同步的处理上。
方法join(long)与sleep(long)的区别
方法join(long)的功能在内部是使用wait(long)方法来实现的,所以join(long)方法具有释放锁的特点。
方法join(long)源代码如下:
public final synchronized void join(long millis) throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);//释放锁
            now = System.currentTimeMillis() - base;
        }
    }
}
当执行wait(long)方法后,当前线程的锁被释放,其他线程就可以调用此线程中的同步方法了。
public class ThreadA extends Thread {
	private ThreadB b;
	public ThreadA(ThreadB b) {
		super();
		this.b = b;
	}
	@Override
	public void run() {
		try {
			synchronized (b) {
				b.start();
				Thread.sleep(6000);////不释放b对象锁
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
public class ThreadB extends Thread {
	@Override
	public void run() {
		try {
			System.out.println("B begin time = " + System.currentTimeMillis());
			Thread.sleep(5000);
			System.out.println("B end time = " + System.currentTimeMillis());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	synchronized public void bService(){
		System.out.println("bService time = " + System.currentTimeMillis());
	}
}
public class ThreadC extends Thread {
	private ThreadB threadB;
	public ThreadC(ThreadB threadB) {
		super();
		this.threadB = threadB;
	}
	@Override
	public void run() {
		threadB.bService();
	}
}
public class Main {
	public static void main(String[] args) {
		try {
			ThreadB b = new ThreadB();
			ThreadA a = new ThreadA(b);
			a.start();
			Thread.sleep(1000);
			ThreadC c = new ThreadC(b);
			c.start();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
控制台打印结果如下:
B begin time = 1466927915044
B end time = 1466927920045
bService time = 1466927921044
将线程A做如下修改:
public class ThreadA extends Thread {
	private ThreadB b;
	public ThreadA(ThreadB b) {
		super();
		this.b = b;
	}
	@Override
	public void run() {
		try {
			synchronized (b) {
				b.start();
				b.join();//释放b对象锁
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
此时控制台打印结果如下:
B begin time = 1466927989797
bService time = 1466927990801
B end time = 1466927994798
方法join()后面的代码提前运行:出现意外
public class ThreadA extends Thread {
	private ThreadB b;
	public ThreadA(ThreadB b) {
		super();
		this.b = b;
	}
	@Override
	public void run() {
		try {
			synchronized (b) {
				System.out.println(Thread.currentThread().getName() + " A begin time = " + System.currentTimeMillis());
				Thread.sleep(5000);
				System.out.println(Thread.currentThread().getName() + " A end time = " + System.currentTimeMillis());
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
public class ThreadB extends Thread {
	@Override
	synchronized public void run() {
		try {
			System.out.println(Thread.currentThread().getName() + " B begin time = " + System.currentTimeMillis());
			Thread.sleep(5000);
			System.out.println(Thread.currentThread().getName() + " B end time = " + System.currentTimeMillis());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
public class Main {
	public static void main(String[] args) {
		try {
			ThreadB b = new ThreadB();
			ThreadA a = new ThreadA(b);
			a.start();
			b.start();
			b.join(2000);
			System.out.println("main end " + System.currentTimeMillis());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
P187 此节未完待续...
类ThreadLocal的使用
变量值的共享可以使用public static变量的形式,所有的线程都使用同一个public static变量。类ThreadLocal为每个线程绑定自己的值。
每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保存到其中。ThreadLocalMap针对每个thread保留一个entry,如果对应的thread不存在则会调用initValue。
方法get()与null
public class Run {
	public static ThreadLocal t1 = new ThreadLocal<>();
	public static void main(String[] args) {
		if (t1.get() == null) {
			System.out.println("从未放过值");
			t1.set("我的值");
		}
		System.out.println(t1.get());
		System.out.println(t1.get());
	}
}
控制台打印结果如下:
从未放过值
我的值
我的值
类ThreadLocal解决的是变量在不同线程间的隔离性,也就是不同线程拥有自己的值,不同线程中的值是可以放入ThreadLocal类中进行保存的。
验证线程变量的隔离性
public class ThreadA extends Thread {
	@Override
	public void run() {
		try {
			for (int i = 0; i < 100; i++) {
				Tools.t1.set("ThreadA " + (i + 1));
				System.out.println("ThreadA getValue=" + Tools.t1.get());
				Thread.sleep(200);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
public class ThreadB extends Thread {
	@Override
	public void run() {
		try {
			for (int i = 0; i < 100; i++) {
				Tools.t1.set("ThreadB " + (i + 1));
				System.out.println("ThreadB getValue=" + Tools.t1.get());
				Thread.sleep(200);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
public class Main {
	public static void main(String[] args) {
		try {
			ThreadA a = new ThreadA();
			ThreadB b = new ThreadB();
			a.start();
			b.start();
			for (int i = 0; i < 100; i++) {
				Tools.t1.set("Thread main " + (i + 1));
				System.out.println("Thread main getValue=" + Tools.t1.get());
				Thread.sleep(200);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
控制台打印结果如下:
......
Thread main getValue=Thread main 99
ThreadB getValue=ThreadB 99
ThreadA getValue=ThreadA 99
Thread main getValue=Thread main 100
ThreadA getValue=ThreadA 100
ThreadB getValue=ThreadB 100
虽然三个线程都向t1对象中set()数据值,但每个线程还是能取出自己的数据。
第一次调用ThreadLocal类的get()方法返回值是null。可以设置默认值。
public class ThreadLocalExt extends ThreadLocal {
	public static ThreadLocalExt t1 = new ThreadLocalExt();
	@Override
	protected Object initialValue() {
		return "This is the default value";
	}
	public static void main(String[] args) {
		if (t1.get() == null) {
			System.out.println("从未放过值");
			t1.set("我的值");
		}
		System.out.println(t1.get());
		System.out.println(t1.get());
	}
}
控制台打印结果如下:
This is the default value
This is the default value
子线程和父线程各自拥有自己的值
public class ThreadLocalExt extends ThreadLocal {
	@Override
	protected Object initialValue() {
		return new Date().getTime();
	}
}
public class Tools {
	public static ThreadLocalExt t1 = new ThreadLocalExt();
}
public class ThreadA extends Thread {
	@Override
	public void run() {
		try {
			for (int i = 0; i < 5; i++) {
				System.out.println("ThreadA取值:" + Tools.t1.get());
				Thread.sleep(100);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
public class Main {
	public static void main(String[] args) {
		try {
			for (int i = 0; i < 5; i++) {
				System.out.println("Main取值:" + Tools.t1.get());
				Thread.sleep(100);
			}
			Thread.sleep(5000);
			ThreadA a = new ThreadA();
			a.start();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
控制台打印结果如下:
Main取值:1467118837063
Main取值:1467118837063
Main取值:1467118837063
Main取值:1467118837063
Main取值:1467118837063
ThreadA取值:1467118842566
ThreadA取值:1467118842566
ThreadA取值:1467118842566
ThreadA取值:1467118842566
ThreadA取值:1467118842566
类InheritableThreadLocal的使用
使用类InheritableThreadLocal可以在子线程中获得父线程继承下来的值。
public class InheritableThreadLocalExt extends InheritableThreadLocal {
	@Override
	protected Object initialValue() {
		return new Date().getTime();
	}
}
public class Tools {
	public static InheritableThreadLocalExt t1 = new InheritableThreadLocalExt();
}
public class ThreadA extends Thread {
	@Override
	public void run() {
		try {
			for (int i = 0; i < 5; i++) {
				System.out.println("ThreadA取值:" + Tools.t1.get());
				Thread.sleep(100);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
public class Main {
	public static void main(String[] args) {
		try {
			for (int i = 0; i < 5; i++) {
				System.out.println("Main取值:" + Tools.t1.get());
				Thread.sleep(100);
			}
			Thread.sleep(5000);
			ThreadA a = new ThreadA();
			a.start();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
控制台打印结果如下:
Main取值:1467119046785
Main取值:1467119046785
Main取值:1467119046785
Main取值:1467119046785
Main取值:1467119046785
ThreadA取值:1467119046785
ThreadA取值:1467119046785
ThreadA取值:1467119046785
ThreadA取值:1467119046785
ThreadA取值:1467119046785
值继承再修改
将上面带做如下修改:
public class InheritableThreadLocalExt extends InheritableThreadLocal {
	@Override
	protected Object initialValue() {
		return new Date().getTime();
	}
	@Override
	protected Object childValue(Object parentValue) {
		return parentValue + "我是在子线程中加的~~~";
	}
}
重新运行程序,控制台打印结果如下:
Main取值:1467119256537
Main取值:1467119256537
Main取值:1467119256537
Main取值:1467119256537
Main取值:1467119256537
ThreadA取值:1467119256537我是在子线程中加的~~~
ThreadA取值:1467119256537我是在子线程中加的~~~
ThreadA取值:1467119256537我是在子线程中加的~~~
ThreadA取值:1467119256537我是在子线程中加的~~~
ThreadA取值:1467119256537我是在子线程中加的~~~
需要注意的是,如果子线程在取得值的同时,主线程将InheritableThreadLocal中的值进行更改,那么子线程取到的值还是旧值。
Java多线程编程核心技术---线程间通信(二)的更多相关文章
- Java多线程编程核心技术---线程间通信(一)
		线程是操作系统中独立的个体,但这些个体如果不经过特殊处理就不能成为一个整体.线程间的通信就是成为整体的必用方案之一.线程间通信可以使系统之间的交互性更强大,在大大提高CPU利用率的同时还会使程序员对各 ... 
- Java多线程编程(6)--线程间通信(下)
		因为本文的内容大部分是以生产者/消费者模式来进行讲解和举例的,所以在开始学习本文介绍的几种线程间的通信方式之前,我们先来熟悉一下生产者/消费者模式. 在实际的软件开发过程中,经常会碰到如下场景 ... 
- Java多线程编程(5)--线程间通信
		一.等待与通知 某些情况下,程序要执行的操作需要满足一定的条件(下文统一将其称之为保护条件)才能执行.在单线程编程中,我们可以使用轮询的方式来实现,即频繁地判断是否满足保护条件,若不满足则继续判断 ... 
- java多线程同步以及线程间通信详解&消费者生产者模式&死锁&Thread.join()(多线程编程之二)
		本篇我们将讨论以下知识点: 1.线程同步问题的产生 什么是线程同步问题,我们先来看一段卖票系统的代码,然后再分析这个问题: package com.zejian.test; /** * @author ... 
- 《Java多线程编程核心技术》读后感(二)
		方法内的变量为线程安全 package Second; public class HasSelfPrivateNum { public void addI(String username) { try ... 
- 《java多线程编程核心技术》不使用等待通知机制 实现线程间通信的 疑问分析
		不使用等待通知机制 实现线程间通信的 疑问分析 2018年04月03日 17:15:08 ayf 阅读数:33 编辑 <java多线程编程核心技术>一书第三章开头,有如下案例: ... 
- Java多线程编程核心技术(三)多线程通信
		线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体.线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时 ... 
- Java多线程编程核心技术(二)对象及变量的并发访问
		本文主要介绍Java多线程中的同步,也就是如何在Java语言中写出线程安全的程序,如何在Java语言中解决非线程安全的相关问题.阅读本文应该着重掌握如下技术点: synchronized对象监视器为O ... 
- java多线程5:线程间的通信
		在多线程系统中,彼此之间的通信协作非常重要,下面来聊聊线程间通信的几种方式. wait/notify 想像一个场景,A.B两个线程操作一个共享List对象,A对List进行add操作,B线程等待Lis ... 
随机推荐
- Linux的vim三种模式及命令
			一般模式:在Linux终端中输入"vim 文件名"就进入了一般模式,但不能输入文字.编辑模式:在一般模式下按i就会进入编辑模式,此时就可以写程式,按Esc可回到一般模式. 命令模式 ... 
- 让人又爱又恨的char(字符型)
			今天来总结一下char型,平常写算法的时候对这个东西感觉都有一点绕着走,说到底还是对这部分的知识不熟悉所以有点怕他,不过以后不要怕,今天来总结一下 首先,说到字符型数据类型,char型,恩它是一种数据 ... 
- bzoj 3172 AC自动机
			初学AC自动机,要先对于每一个模式串求出来trie树,在此基础上构建fail指针,然后在trie树加上失配边构建出整张trie图. AC自动机的原理和KMP差不多,一个节点的fail指针就是指向tri ... 
- Linux下创建文本文件(vi/vim命令使用详解)
			vi test.txt 或者 vim test.txt vim是vi的升级版,指令更多,功能更强. 下面是收集的vim用法,当在vim里面要实现退出,首先要做的是按[Esc],然后再输入[:wq] 一 ... 
- dedecms /include/uploadsafe.inc.php SQL Injection Via Local Variable Overriding Vul
			catalog . 漏洞描述 . 漏洞触发条件 . 漏洞影响范围 . 漏洞代码分析 . 防御方法 . 攻防思考 1. 漏洞描述 . dedecms原生提供一个"本地变量注册"的模拟 ... 
- JSONArray的使用
			用的是fastjson.jar包 1. jsonarray,jsonobject 使用正确代码如下:(这种getJSONObject 函数形成jsonobj 的方式非常好,可以避免循环引用或jsono ... 
- 【Beta版本】冲刺计划及安排
			目录 一.Beta的初步完善 二.团队分工的改进 三.工具流程的改进 四.冲刺阶段的计划与安排 五.关于组长是否重选 六.附录 队伍:606notconnected 成员:031401433 张斯巍 ... 
- mainBundle和CustomBundle
			iOS中NSBundle类 An NSBundle object represents a location in the file system that groups code and resou ... 
- 点亮第一个LED灯
			1.代码: #include <reg52.h> //<reg51.h> 包含52单片机寄存器库sbit led = P1^0; //只有地址可以被8整除的 才可以用s ... 
- js017-错误处理与调试
			js017-错误处理与调试 本章内容 理解浏览器报告的错误 处理错误 调试JS代码 17.2 错误处理 17.2.1 try-catch语句 try{ //possible error code }c ... 
