Thinking in Java from Chapter 21
From Thinking in Java 4th Edition
并发
线程可以驱动任务,因此你需要一种描述任务的方式,这可由Runnable接口来提供。
要想定义任务,只需要实现Runnable接口,并编写run()方法,使得该任务可以执行你的命令。
public class LiftOff implements Runnable {
protected int countDown = 10; // Default
private static int taskCount = 0;
private final int id = taskCount++; public LiftOff() {}
public LiftOff(int countDown){
this.countDown = countDown;
} public String status(){
return "#" + id + "(" + (countDown > 0 ? countDown : "LiftOff!") + "). ";
} public void run(){
while(countDown-- > 0){
System.out.println(status());
Thread.yield();
}
}
}
从Runnable导出一个类时,它必须具有run()方法,但是它不会产生任何内在的线程能力。要实现线程行为,你必须显式地将一个任务附着到线程上。
将Runnable对象转变为工作任务的传统方式是把它提交给一个Thread构造器:
public class BasicThread {
public static void main(String[] args){
Thread t = new Thread(new LiftOff());
t.start();
System.out.println("Waiting for LiftOff");
}
} /* Output:
Waiting for LiftOff
#0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(LiftOff!),
*/
Thread构造器只需要一个Runnable对象。
1. 调用Thread对象的start()方法为该线程执行必要的初始化操作。
2. 调用Runnable对象的run()方法,以便在这个新线程中启动该任务。
从输出可以看出start()方法迅速返回了,因为"Waiting for LiftOff"消息在倒计时完成之前就出现了。
实际上你产生的是对LiftOff.run()方法的调用,并且这个方法还没完成,但是因为LiftOff.run()是由不同的线程执行的,因此你仍旧可以执行main()线程中的其他操作。
如果添加更多的线程去驱动更多的任务,就可以看到所有任务彼此之间是如何呼应的:
public class MoreBasicThreads {
public static void main(String[] args){
for(int i = 0; i < 5; ++i)
new Thread(new LiftOff()).start();
System.out.println("Waiting for LiftOff");
}
} /* Output:
Waiting for LiftOff
#3(9), #1(9), #3(8), #1(8), #3(7), #1(7), #1(6), #1(5),
#1(4), #1(3), #1(2), #1(1), #1(LiftOff!), #4(9), #4(8),
#4(7), #4(6), #2(9), #2(8), #2(7), #2(6), #2(5), #2(4),
#2(3), #2(2), #2(1), #2(LiftOff!), #0(9), #4(5), #3(6),
#4(4), #3(5), #4(3), #3(4), #4(2), #3(3), #4(1), #3(2),
#4(LiftOff!), #0(8), #3(1), #0(7), #3(LiftOff!), #0(6),
#0(5), #0(4), #0(3), #0(2), #0(1), #0(LiftOff!),
*/
使用Executor
Executor在客户端和任务执行之间提供了一个间接层;与客户端直接执行任务不同,这个中介对象将执行任务。
Executor用来代替MoreBasicThreads.java中显式创建Thread对象。
ExecutorService知道如何构建上下文来执行Runnable对象。
import java.util.concurrent.*; public class CachedThreadPool {
public static void main(String[] args){
ExecutorService exec = Executors.newCachedThreadPool(); // No space between new and Cached
for(int i = 0; i < 5; ++i)
exec.execute(new LiftOff());
exec.shutdown();
}
} /* Output:
#0(9), #2(9), #0(8), #1(9), #0(7), #0(6), #0(5), #0(4),
#0(3), #0(2), #0(1), #0(LiftOff!), #2(8), #1(8), #3(9),
#2(7), #1(7), #3(8), #2(6), #1(6), #3(7), #2(5), #2(4),
#4(9), #3(6), #1(5), #3(5), #1(4), #1(3), #1(2), #1(1),
#1(LiftOff!), #4(8), #2(3), #4(7), #4(6), #3(4), #4(5),
#3(3), #4(4), #3(2), #4(3), #3(1), #4(2), #2(2), #4(1),
#4(LiftOff!), #3(LiftOff!), #2(1), #2(LiftOff!),
*/
上例中,CachedThreadPool将为每个任务都创建一个线程。
ExecutorService对象是使用静态的Executor方法创建的,这个方法可以确定其Executor类型。
常见的情况是,Executor被用来创建和管理系统中的所有的任务。
对shutdown()方法的调用可以防止新任务被提交给这个Executor,当前任务(本例中为main的线程)将继续运行在shutdown()被调用之前提交的所有任务。
下面的程序展示了CachedThreadPool替换为不同类型的Executor。FixedThreadPool使用了有限的线程集来执行所提交的任务
import java.util.concurrent.*; public class FixedThreadPool {
public static void main(String[] args){
// Constructor argument is number of threads:
ExecutorService exec = Executors.newFixedThreadPool(5);
for(int i = 0; i < 5; ++i)
exec.execute(new LiftOff());
exec.shutdown();
}
} /* Output:
#0(9), #2(9), #4(9), #0(8), #2(8), #3(9), #1(9), #1(8),
#0(7), #4(8), #0(6), #4(7), #0(5), #4(6), #4(5), #4(4),
#4(3), #4(2), #4(1), #4(LiftOff!), #1(7), #1(6), #1(5),
#1(4), #1(3), #1(2), #1(1), #1(LiftOff!), #3(8), #2(7),
#0(4), #3(7), #2(6), #0(3), #3(6), #2(5), #0(2), #3(5),
#0(1), #3(4), #2(4), #0(LiftOff!), #3(3), #2(3), #3(2),
#2(2), #3(1), #2(1), #3(LiftOff!), #2(LiftOff!),
*/
有了FixedThreadPool,你就可以一次性预先执行代价高昂的线程分配。这可以节省时间,因为你不用为每个任务都固定地付出创建线程的开销。
SingleThreadExecutor就像是线程数量为1的FixedThreadPool。如果向SingleThreadExecutor提交了多个任务,那么这些任务将排队,每个任务都会在下一个任务开始之前结束,所有的任务将使用相同的线程。
下例中可以看到每个任务都是按照它们被提交的顺序、并且是在下一个任务开始之前完成的。因此SingleThreadExecutor会序列化所有提交给它们的任务,并会维护它自己(隐藏)的悬挂任务。
import java.util.concurrent.*; public class SingleThreadExecutor {
public static void main(String[] args){
ExecutorService exec = Executors.newSingleThreadExecutor(); for(int i = 0; i < 5; ++i)
exec.execute(new LiftOff());
exec.shutdown();
}
} /* Output:
#0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(LiftOff!),
#1(9), #1(8), #1(7), #1(6), #1(5), #1(4), #1(3), #1(2), #1(1), #1(LiftOff!),
#2(9), #2(8), #2(7), #2(6), #2(5), #2(4), #2(3), #2(2), #2(1), #2(LiftOff!),
#3(9), #3(8), #3(7), #3(6), #3(5), #3(4), #3(3), #3(2), #3(1), #3(LiftOff!),
#4(9), #4(8), #4(7), #4(6), #4(5), #4(4), #4(3), #4(2), #4(1), #4(LiftOff!),
*/
从任务中返回值
Runnable是执行工作的独立任务,但是它不返回任何值。如果希望能够返回值,则必须实现Callable接口而不是Runnable接口。
Callable是一种具有类型参数的泛型,它的类型参数表示的是从方法call()中返回的值,并且必须使用ExecutorService.submit()方法调用它:
import java.util.concurrent.*;
import java.util.*; class TaskWithResult implements Callable<String> {
private int id;
public TaskWithResult(int id){
this.id = id;
} public String call() {
return "result of TaskWithResult " + id;
}
} public class CallableDemo {
public static void main(String[] args){
ExecutorService exec = Executors.newCachedThreadPool();
ArrayList<Future<String>> results = new ArrayList<Future<String>>(); for(int i = 0; i < 10; ++i)
results.add(exec.submit(new TaskWithResult(i)));
for(Future<String> fs : results)
try {
// get() blocks until completion:
System.out.println(fs.get());
} catch(InterruptedException e){
System.out.println(e);
return;
} catch(ExecutionException e) {
System.out.println(e);
} finally {
exec.shutdown();
}
}
} /* Output:
result of TaskWithResult 0
result of TaskWithResult 1
result of TaskWithResult 2
result of TaskWithResult 3
result of TaskWithResult 4
result of TaskWithResult 5
result of TaskWithResult 6
result of TaskWithResult 7
result of TaskWithResult 8
result of TaskWithResult 9
*/
submit()方法会产生Future对象,它用Callable返回结果的特定类型进行了参数化 。
1. 可以用isDone()方法来查看Future是否完成
2. 任务完成时,可以调用get()方法来获取该结果
也可以不用isDone()进行检查就直接调用get(),这种情况下,get()将阻塞直至结果准备就绪。
休眠
影响任务行为的一种简单方法是调用sleep(),这将使任务中止执行给定的时间。
在LiftOff类中,把yield()的调用换成sleep()将得到:
import java.util.concurrent.*; public class SleepingTask extends LiftOff {
public void run(){
try {
while(countDown-- > 0){
System.out.print(status());
// Old-style
// Thread.sleep(100);
// Java SE5/6-style:
TimeUnit.MILLISECONDS.sleep(100);
}
} catch(InterruptedException e) {
System.err.println("Interrupted");
}
} public static void main(String[] args){
ExecutorService exec = Executors.newCachedThreadPool();
for(int i = 0; i < 5; ++i)
exec.execute(new SleepingTask());
exec.shutdown();
}
} /* Output:
#0(9), #3(9), #1(9), #4(9), #2(9),
#0(8), #3(8), #1(8), #2(8), #4(8),
#0(7), #4(7), #2(7), #3(7), #1(7),
#4(6), #0(6), #3(6), #1(6), #2(6),
#4(5), #3(5), #2(5), #0(5), #1(5),
#4(4), #3(4), #2(4), #1(4), #0(4),
#4(3), #2(3), #3(3), #0(3), #1(3),
#4(2), #2(2), #0(2), #3(2), #1(2),
#4(1), #2(1), #0(1), #3(1), #1(1),
#4(LiftOff!), #2(LiftOff!), #0(LiftOff!), #3(LiftOff!), #1(LiftOff!),
*/
sleep()调用可以抛出InterruptedException异常,并可以看到,它在run()中捕获。因为异常不能跨线程传播回main(),所以你必须在本地处理所有在任务内部产生的异常。
优先级
调度器将倾向于让优先权最高的线程先执行,然而,这并不意味着优先权较低的线程将得不到执行(也就是说,优先权不会导致死锁)。优先级较低的线程仅仅是执行的频率较低。
绝大多数时间里,所有线程都应该按照默认的优先级运行。试图操纵线程的优先级通常是一种错误。
可以用getPriority()来读取现有线程的优先级,并且在任何时候都可以通过setPriority()来修改它:
import java.util.concurrent.*; public class SimplePriorities implements Runnable {
private int countDown = 5;
private volatile double d; // No optimization
private int priority; public SimplePriorities(int priority){
this.priority = priority;
} public String toString(){
return Thread.currentThread() + ": " + countDown;
} public void run(){
Thread.currentThread().setPriority(priority);
while(true){
// An expensive, interruptable operation:
for(int i = 1; i < 100000; ++i){
d += (Math.PI + Math.E) / (double)i;
if(0 == i % 1000)
Thread.yield();
} System.out.println(this);
if(0 == --countDown) return;
}
} public static void main(String[] args){
ExecutorService exec = Executors.newCachedThreadPool(); for(int i = 0; i < 5; ++i)
exec.execute(new SimplePriorities(Thread.MIN_PRIORITY));
exec.execute(new SimplePriorities(Thread.MAX_PRIORITY)); exec.shutdown();
}
}
让步
如果知道已经完成了在run()方法的循环的一次迭代过程中所需的工作,就可以给线程调度机制一个暗示:你的工作已经做得差不多了,可以让别的线程使用CPU了。这个暗示将通过yield()方法做出(不过这只是一个暗示,没有任何机制保证它将会被采纳)。
当调用yield()时,你也在建议具有相同优先级的其他线程可以运行。
后台线程
后台(daemon)线程,是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并不属于程序中不可缺少的部分。
当所有非后台线程结束时,程序也就终止了,同时会杀死进程中的所有后台线程。
反过来,只要有非后台线程还在运行,程序就不会终止:
import java.util.concurrent.*;
import static net.mindview.util.Print.*; public class SimpleDaemons implements Runnable {
public void run(){
try {
while(true) {
TimeUnit.MILLISECONDS.sleep(100);
print(Thread.currentThread() + " " + this);
}
} catch(InterruptedException e) {
print("sleep() interrupted");
}
} public static void main(String[] args) throws Exception {
for(int i = 0; i < 10; ++i){
Thread daemon = new Thread(new SimpleDaemons());
daemon.setDaemon(true); // Must call before start()
daemon.start();
} print("All daemons started");
TimeUnit.MILLISECONDS.sleep(175);
}
} /* Output:
All daemons started
Thread[Thread-4,5,main] SimpleDaemons@187a0b5
Thread[Thread-2,5,main] SimpleDaemons@153c85c
Thread[Thread-3,5,main] SimpleDaemons@23fc9c
Thread[Thread-8,5,main] SimpleDaemons@b211be
Thread[Thread-0,5,main] SimpleDaemons@11f65e4
Thread[Thread-1,5,main] SimpleDaemons@15332f2
Thread[Thread-9,5,main] SimpleDaemons@1da14a7
Thread[Thread-7,5,main] SimpleDaemons@16b9e5b
Thread[Thread-5,5,main] SimpleDaemons@1bbee70
Thread[Thread-6,5,main] SimpleDaemons@1cbca8d
*/
SimpleDaemons.java创建了显示的线程,以便可以设置它们的后台标志。通过编写定制的ThreadFactory可以定制由Executor创建的线程的属性(后台、优先级、名称):
package net.mindview.util;
import java.util.concurrent.*; public class DaemonThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
}
现在可以用一个新的DaemonThreadFactory作为参数传递给Executor.newCachedThreadPool():
package net.mindview.util;
import java.util.concurrent.*; public class DaemonThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
} // Using a Thread Factory to create daemons.
import java.util.concurrent.*;
import net.mindview.util.*;
import static net.mindview.util.Print.*; public class DaemonFromFactory implements Runnable {
public void run(){
try {
while(true){
TimeUnit.MILLISECONDS.sleep(100);
print(Thread.currentThread() + " " + this);
}
} catch(InterruptedException e){
print("Interrupted");
}
} public static void main(String[] args) throws Exception {
ExecutorService exec = Executor.newCachedThreadPool(new DaemonThreadFactory());
for(int i = 0; i < 10; ++i)
exec.execute(new DaemonFromFactory());
print("All daemons started");
TimeUnit.MILLISECONDS.sleep(500); // Run for a while
}
} /* Output
All daemons started
Thread[Thread-0,5,main] DaemonFromFactory@c01009
Thread[Thread-9,5,main] DaemonFromFactory@1bd343
Thread[Thread-7,5,main] DaemonFromFactory@7c321a
Thread[Thread-5,5,main] DaemonFromFactory@c79e29
Thread[Thread-3,5,main] DaemonFromFactory@279922
Thread[Thread-1,5,main] DaemonFromFactory@1fcb7ef
Thread[Thread-8,5,main] DaemonFromFactory@7e3dfb
Thread[Thread-2,5,main] DaemonFromFactory@1415cb9
Thread[Thread-4,5,main] DaemonFromFactory@334426
Thread[Thread-6,5,main] DaemonFromFactory@15226d3
Thread[Thread-0,5,main] DaemonFromFactory@c01009
Thread[Thread-9,5,main] DaemonFromFactory@1bd343
....
*/
每个静态的ExecutorService创建方法都被重载为接受一个ThreadFactory对象,而这个对象将被用来创建新的线程:
package net.mindview.util;
import java.util.concurrent.*; public class DaemonThreadPoolExecutor extends ThreadPoolExecutor {
public DaemonThreadPoolExecutor() {
super(0, Integer.MAX_VALUE, 60L, TimeUnit, SECONDS, new SynchronousQueue<Runnable>(), new DaemonThradFactory());
}
}
可以通过iDaemon()方法来确定线程是否是一个后台线程。如果是一个后台线程,那么它创建的任何线程将自动被设置成后台线程。
// Daemon threads spawn other daemon threads.
import java.util.concurrent.*;
import static net.mindview.util.Print.*; class Daemon implements Runnable {
private Thread[] t = new Thread[10]; public void run() {
for(int i = 0; i < t.length; ++i){
t[i] = new Thread(new DaemonSpawn());
t[i].start();
printnb("DaemonSpawn " + i + " started, ");
} for(int i = 0; i < t.length; ++i)
printnb("t[" + i + "].isDaemon() = " + t[i].isDaemon() + ", "); while(true)
Thread.yield();
}
} class DaemonSpawn implements Runnable {
public void run(){
while(true)
Thread.yield();
}
} public class Daemons {
public static void main(String[] args) throws Exception {
Thread d = new Thread(new Daemon());
d.setDaemon(true);
d.start();
printnb("d.isDaemon() = " + d.isDaemon() + ", "); // Allow the daemon threads to
// finish their startup processes:
TimeUnit.SECONDS.sleep(1);
}
} /* Output:
d.isDaemon() = true,
DaemonSpawn 0 started,
DaemonSpawn 1 started,
DaemonSpawn 2 started,
DaemonSpawn 3 started,
DaemonSpawn 4 started,
DaemonSpawn 5 started,
DaemonSpawn 6 started,
DaemonSpawn 7 started,
DaemonSpawn 8 started,
DaemonSpawn 9 started,
t[0].isDaemon() = true,
t[1].isDaemon() = true,
t[2].isDaemon() = true,
t[3].isDaemon() = true,
t[4].isDaemon() = true,
t[5].isDaemon() = true,
t[6].isDaemon() = true,
t[7].isDaemon() = true,
t[8].isDaemon() = true,
t[9].isDaemon() = true,
*/
应该意识到后台进程在不执行finally子句的情况下就会终止其run()方法:
// Daemon threads don't run the finally clause
import java.util.concurrent.*;
import static net.mindview.util.Print.*; class ADaemon implements Runnable {
public void run() {
try {
print("Starting ADaemon");
TimeUnit.SECONDS.sleep(1);
} catch(InterruptedException e){
print("Exiting via InterruptedException");
} finally {
print("This should always run?");
}
}
} public class DaemonsDontRunFinally {
public static void main(String[] args) throws Exception {
Thread t = new Thread(new ADaemon());
t.setDaemon(true);
t.start();
}
} /* Output:
Starting ADaemon
*/
目前的示例中,都是实现了Runnable。在非常简单的情况下,你可能会希望使用直接从Thread继承这种可替换的方式:
public class SimpleThread extends Thread {
private int countDown = 5;
private static int threadCount = 0; public SimpleThread() {
// Store the thread name:
super(Integer.toString(++threadCount));
start();
} public String toString() {
return "#" + getName() + "(" + countDown + "), ";
} public void run() {
while(true){
System.out.print(this);
if(0 == --countDown)
return;
}
} public static void main(String[] args){
for(int i = 0; i < 5; ++i)
new SimpleThread();
}
} /* Output:
#2(5), #2(4), #2(3), #2(2), #2(1),
#4(5), #4(4), #4(3), #4(2), #4(1),
#5(5), #5(4), #5(3), #5(2), #5(1),
#3(5), #3(4), #3(3), #3(2), #3(1),
#1(5), #1(4), #1(3), #1(2), #1(1),
*/
你可以通过适当的Thread构造器为Thread对象赋予具体的名称,这个名称可以通过使用getName()从toString()中获得。
另一种可能会看到的惯用法是自管理的Runnable:
// A Runnable containing its own driver Thread.
public class SelfManaged implements Runnable {
private int countDown = 5;
private Thread t = new Thread(this); public SelfManaged() {t.start();} public String toString() {
return Thread.currentThread().getName() + "(" + countDown + "), ";
} public void run() {
while(true){
System.out.print(this);
if(0 == --countDown)
return;
}
} public static void main(String[] args){
for(int i = 0; i < 5; ++i)
new SelfManaged();
}
} /* Output:
Thread-3(5), Thread-3(4), Thread-3(3), Thread-3(2), Thread-3(1),
Thread-5(5), Thread-5(4), Thread-5(3), Thread-5(2), Thread-5(1),
Thread-6(5), Thread-6(4), Thread-6(3), Thread-6(2), Thread-6(1),
Thread-4(5), Thread-4(4), Thread-4(3), Thread-4(2), Thread-4(1),
Thread-7(5), Thread-7(4), Thread-7(3), Thread-7(2), Thread-7(1),
*/
这与从Thread继承并没有什么特别的差异,只是语法稍微晦涩一些。但是,实现接口使得你可以继承另一个不同的类,而从Thread继承将不行。
注意,这个示例中的start()是在构造器中调用的。应该意识到,在构造器中启动线程可能会变得很有问题,因为另一个任务可能会在构造器结束之前开始执行,这意味着该任务能够访问处于不稳定状态的对象。这是优选Executor而不是显式地创建Thread对象的另一个原因。
有时,通过使用内部类来讲线程代码隐藏在类中将会很有用:
Thinking in Java from Chapter 21的更多相关文章
- 《Think in Java》20 21(并发)
chapter 20 注解 三种标准注解和四种元注解: 编写注解处理器 chapter 21 并发 基本的线程机制 定义任务 package cn.test; public class LiftOff ...
- JAVA自学笔记21
JAVA自学笔记21 1.转换流 由于字节流操作中文不是非常方便,因此java提供了转换流 字符流=字节流+编码表 1)编码表 由字符及其对应的数值组成的一张表 图解: 2)String类的编码和解码 ...
- Java设计模式(21)访问模式(Visitor者模式)
Visitor定义:作用于某个对象群中各个对象的操作.它可以使你在不改变这些对象本身的情况下,定义作用于这些对象的新操作. 在Java中,Visitor模式实际上是分离了collection结构中的元 ...
- 零元学Expression Blend 4 – Chapter 21 以实作案例学习MouseDragElementBehavior
原文:零元学Expression Blend 4 – Chapter 21 以实作案例学习MouseDragElementBehavior 本章将教大家如何运用Blend 4内建的行为注入元件「Mou ...
- Thinking in Java from Chapter 15
From Thinking in Java 4th Edition. 泛型实现了:参数化类型的概念,使代码可以应用于多种类型.“泛型”这个术语的意思是:“适用于许多许多的类型”. 如果你了解其他语言( ...
- Java 学习(21):Java 实例
Java 实例 本章节我们将为大家介绍 Java 常用的实例,通过实例学习我们可以更快的掌握 Java 的应用. Java 环境设置实例 //HelloWorld.java 文件 public cla ...
- 5000字 | 24张图带你彻底理解Java中的21种锁
本篇主要内容如下: 本篇文章已收纳到我的Java在线文档. Github 我的SpringCloud实战项目持续更新中 帮你总结好的锁: 序号 锁名称 应用 1 乐观锁 CAS 2 悲观锁 synch ...
- 【Java EE 学习 21 下】【使用java实现邮件发送、邮件验证】
一.邮件发送 1.邮件发送使用SMTP协议或者IMAP协议,这里使用SMTP协议演示. SMTP协议使用的端口号:25 rfc821详细记载了该协议的相关信息 (1)使用telnet发送邮件(使用12 ...
- 【Java EE 学习 21 下】【 使用易宝支付接口实现java网上支付功能】
一.网上支付分为两种情况,一种方法是使用直接和银行的支付接口,另外一种方法是使用第三方支付平台和银行对接完成支付. 1.直接和银行对接. 2.使用第三方支付平台 3.常见的第三方支付平台 二.使用易宝 ...
随机推荐
- JDK1.7 的 HashMap
HashMap是一个用于存储key-value的键值对集合,每个键值对都是一个Entry.这些键值对分散存储在一个数组中,这个数组就是HashMap的主干. HashMap每个初始值都为null. 1 ...
- leetcode494
public class Solution { public int FindTargetSumWays(int[] nums, int S) { Queue<int> Q = new Q ...
- element-ui table 嵌套
嵌套的时时候用template,数据 scope.row.xxx <template> <div> <el-table :data="urls" st ...
- Redis内存模型总结
一.Redis内存统计 在客户端通过redis-cli连接服务器后,通过info命令可以查看内存使用情况: info memory 返回结果中比较重要的几个说明如下: (1)used_memory:R ...
- json介绍和使用
最近在开发时需要用到json,所以在各种寻找json相关的博客,恰巧在博客园里就有一篇写的很不错的,在这里推荐下:http://www.cnblogs.com/Truly/archive/2006/1 ...
- vue 关键词模糊查询
页面html,绑定的列表数据为datas,关键词为 select_words,如下图 其中d.accounts和d.roleName是需要进行搜索的字段,也可以进行大小写都可以
- jstl标准标签库 其他标签
url操作标签 import 将另一个页面的内容引入到这个页面上来, 与include指令的区别: 这个标签可以引入其他项目中甚至网络上的资源 <c:import url="被导入的路 ...
- Django的rest_framework的分页组件源码分析
前言: 分页大家应该都很清楚,今天我来给大家做一下Django的rest_framework的分页组件的分析:我的讲解的思路是这样的,分别使用APIview的视图类和基于ModelViewSet的视图 ...
- 386. Lexicographical Numbers 输出1到n之间按lexico排列的数字序列
[抄题]: Given an integer n, return 1 - n in lexicographical order. For example, given 13, return: [1,1 ...
- Python学习积累:使用help();打印多个变量;fileno()
1.使用篇: 1.1如何从help()退出: 直接回车即可! 2.技能篇: 2.1 如何一次性打印多个变量? 多个变量中间使用逗号隔开,且引用变量为%(变量1,变量2,变量3), 2.2fileno( ...