赞
踩
基础&进阶篇
1.什么是Java
Java是一门面向对象的高级编程语言,不仅吸收了C++语言的各种优点,比如继承了C++语言面向对象的
技术核心。还摒弃了C++里难以理解的多继承、指针等概念,,同时也增加了垃圾回收机制,释放掉不
被使用的内存空间,解决了管理内存空间的烦恼。
因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地
实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程 。
2. Java的特点有哪些
Java 语言是一种分布式的面向对象语言,具有面向对象、平台无关性、简单性、解释执行、多线程、安
全性等很多特点,下面针对这些特点进行逐一介绍。
Oracle JDK 比 OpenJDK 更稳定。OpenJDK和Oracle JDK的代码几乎相同,但Oracle JDK有更多的
类和一些错误修复。因此,如果您想开发企业/商业软件,我建议您选择Oracle JDK,因为它经过了
彻底的测试和稳定。某些情况下,有些人提到在使用OpenJDK 可能会遇到了许多应用程序崩溃的
问题,但是,只需切换到Oracle JDK就可以解决问题;
在响应性和JVM性能方面,Oracle JDK与OpenJDK相比提供了更好的性能;
Oracle JDK不会为即将发布的版本提供长期支持,用户每次都必须通过更新到最新版本获得支持来
获取最新版本;
Oracle JDK根据二进制代码许可协议获得许可,而OpenJDK根据GPL v2许可获得许可。
5. Java有哪些数据类型
Java中有 8 种基本数据类型,分别为:
6 种数字类型 (四个整数形,两个浮点型):byte、short、int、long、float、double
1 种字符类型:char
1 种布尔型:boolean。
byte:
byte 数据类型是8位、有符号的,以二进制补码表示的整数;
最小值是 -128(-2^7);
最大值是 127(2^7-1);
默认值是 0;
byte 类型用在大型数组中节约空间,主要代替整数,因为 byte 变量占用的空间只有 int 类型的四
分之一;
例子:byte a = 100,byte b = -50。
short:
short 数据类型是 16 位、有符号的以二进制补码表示的整数
最小值是 -32768(-2^15);
最大值是 32767(2^15 - 1);
Short 数据类型也可以像 byte 那样节省空间。一个short变量是int型变量所占空间的二分之一;
默认值是 0;
例子:short s = 1000,short r = -20000。
int:
int 数据类型是32位、有符号的以二进制补码表示的整数;
最小值是 -2,147,483,648(-2^31);
最大值是 2,147,483,647(2^31 - 1);
一般地整型变量默认为 int 类型;
默认值是 0 ;
例子:int a = 100000, int b = -200000。
long:
注意:Java 里使用 long 类型的数据一定要在数值后面加上 L,否则将作为整型解析
long 数据类型是 64 位、有符号的以二进制补码表示的整数;
最小值是 -9,223,372,036,854,775,808(-2^63);
最大值是 9,223,372,036,854,775,807(2^63 -1);
这种类型主要使用在需要比较大整数的系统上;
默认值是 0L;
例子: long a = 100000L,Long b = -200000L。
"L"理论上不分大小写,但是若写成"l"容易与数字"1"混淆,不容易分辩。所以最好大写。
float:
float 数据类型是单精度、32位、符合IEEE 754标准的浮点数;
float 在储存大型浮点数组的时候可节省内存空间;
默认值是 0.0f;
浮点数不能用来表示精确的值,如货币;
例子:float f1 = 234.5f。
double:
double 数据类型是双精度、64 位、符合IEEE 754标准的浮点数;
浮点数的默认类型为double类型;
double类型同样不能表示精确的值,如货币;
默认值是 0.0d;
例子:double d1 = 123.4。
char:
char类型是一个单一的 16 位 Unicode 字符;
最小值是 \u0000(即为 0);
最大值是 \uffff(即为 65535);
char 数据类型可以储存任何字符;
例子:char letter = ‘A’;(单引号)
boolean:
boolean数据类型表示一位的信息;
只有两个取值:true 和 false;
这种类型只作为一种标志来记录 true/false 情况;
默认值是 false;
例子:boolean one = true。
这八种基本类型都有对应的包装类分别为:Byte、Short、Integer、Long、Float、Double、
Character、Boolean
类型名 称 字节、位数 最小值 最大值 默认值 例子 byte字 节 1字节,8位 -128(-2^7) 127(2^7-1) 0 byte a = 100,byte b = -50 short短 整型 2字节,16位 -32768(-2^15) 32767(2^15 - 1) 0 short s = 1000, short r = -20000 int整形 4字节,32位 -2,147,483,648(-2^31) 2,147,483,647(2^31 - 1) 0 int a = 100000, int b = -200000 lang长 整型 8字节,64位 -9,223,372,036,854,775,808(-2^63) 9,223,372,036,854,775,807(2^63 -1) 0L long a = 100000L, Long b = -200000L double 双精度 8字节,64位 double类型同样不能表示精确的 值,如货币 0.0d double d1 = 123.4 float单 精度 4字节,32位 在储存大型浮点数组的时候可节省内存 空间 不同统计精准的货币值 0.0f float f1 = 234.5f
char字 符 2字节,16位 \u0000(即为0) \uffff(即为65,535) 可以储存任何字符
char letter = ‘A’; boolean 布尔 返回true和 false两个值 这种类型只作为一种标志来记录 true/false 情况; 只有两个取值:true 和 false; false boolean one = true
6. Java中引用数据类型有哪些,它们与基本数据类型有什么区别?
引用数据类型分3种:类,接口,数组;
简单来说,只要不是基本数据类型.都是引用数据类型。 那他们有什么不同呢?
1、从概念方面来说
1,基本数据类型:变量名指向具体的数值
2,引用数据类型:变量名不是指向具体的数值,而是指向存数据的内存地址,.也及时hash值 2、从内存的构建方面来说(内存中,有堆内存和栈内存两者)
1,基本数据类型:被创建时,在栈内存中会被划分出一定的内存,并将数值存储在该内存中.
2,引用数据类型:被创建时,首先会在栈内存中分配一块空间,然后在堆内存中也会分配一块具体的空间用来
存储数据的具体信息,即hash值,然后由栈中引用指向堆中的对象地址.
举个例子
由上图可知,基本数据类型中会存在两个相同的1,而引用型类型就不会存在相同的数据。
假如"hello"的引用地址是xxxxx1,声明str变量并其赋值"hello"实际上就是让str变量引用了"hello"的内
存地址,这个内存地址就存储在堆内存中,是不会改变的,当再次声明变量str1也是赋值为"hello"时,
此时就会在堆内存中查询是否有"hello"这个地址,如果堆内存中已经存在这个地址了,就不会再次创建
了,而是让str1变量也指向xxxxx1这个地址,如果没有的话,就会重新创建一个地址给str1变量。
7. 从使用方面来说
1,基本数据类型:判断数据是否相等,用和!=判断。
2,引用数据类型:判断数据是否相等,用equals()方法,和!=是比较数值的。而equals()方法是比较内存
地址的。
补充:数据类型选择的原则
如果要表示整数就使用int,表示小数就使用double;
如果要描述日期时间数字或者表示文件(或内存)大小用long;
如果要实现内容传递或者编码转换使用byte;
如果要实现逻辑的控制,可以使用booleam;
如果要使用中文,使用char避免中文乱码;
如果按照保存范围:byte < int < long < double;
//基本数据类型作为方法参数被调用 public class Main{ public static void main(String[] args){ //基本数据类型 int i = 1; int j = 1; double d = 1.2; //引用数据类型 String str = “Hello”; String str1= “Hello”; } }
8. Java中的自动装箱与拆箱
什么是自动装箱拆箱?
从下面的代码中就可以看到装箱和拆箱的过程
装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。
在Java SE5之前,自动装箱要这样写:Integer i = newInteger( 10``);
对于Java的自动装箱和拆箱,我们看看源码编译后的class文件,其实装箱调用包装类的valueOf方法,
拆箱调用的是Integer.Value方法,下面就是变编译后的代码:
常见面试一:
这段代码输出什么?
//自动装箱 Integer total = 99; //自定拆箱 int totalprim = total;
public class Main { public static void main(String[] args) { Integer i1 = 100; Integer i2 = 100; Integer i3 = 200; Integer i4 = 200; System.out.println(i1i2); System.out.println(i3i4); } }
答案是: true false
为什么会出现这样的结果?输出结果表明i1和i2指向的是同一个对象,而i3和i4指向的是不同的对象。此
时只需一看源码便知究竟,下面这段代码是Integer的valueOf方法的具体实现:
public static Integer valueOf(int i) { if(i >= -128 && i <= IntegerCache.high) return IntegerCache.cache[i + 128]; elsereturn new Integer(i); } private static class IntegerCache { static final int high; static final Integer cache[]; static { final int low = -128; // high value may be configured by property int h = 127; if (integerCacheHighPropValue != null) { // Use Long.decode here to avoid invoking methods that // require Integer’s autoboxing cache to be initialized int i = Long.decode(integerCacheHighPropValue).intValue(); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - -low); }high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); }
基本类型 包装器类型
boolean Boolean
char Character
int Integer
byte Byte
short Short
long Long
float Float
double Double
从这2段代码可以看出,在通过valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,便返
回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。
上面的代码中i1和i2的数值为100,因此会直接从cache中取已经存在的对象,所以i1和i2指向的是同一
个对象,而i3和i4则是分别指向不同的对象。
常见面试二:
输出结果为:
原因很简单,在某个范围内的整型数值的个数是有限的,而浮点数却不是。
9. 为什么要有包装类型?
让基本数据类型也具有对象的特征
private IntegerCache() {} } public class Main { public static void main(String[] args) { Double i1 = 100.0; Double i2 = 100.0; Double i3 = 200.0; Double i4 = 200.0; System.out.println(i1i2); System.out.println(i3i4); } }false false
为了让基本类型也具有对象的特征,就出现了包装类型(如我们在使用集合类型Collection时就一定要使
用包装类型而非基本类型)因为容器都是装object的,这是就需要这些基本类型的包装器类了。
自动装箱: new Integer(6); ,底层调用: Integer.valueOf(6)
自动拆箱: int i = new Integer(6); ,底层调用 i.intValue(); 方法实现。
答案在下面这段代码中找:
二者的区别:
run
method of this thread. * * The result is that two threads are running concurrently: the * current thread (which returns from the call to the * start
method) and the other thread (which executes its * run
method). *
* It is never legal to start a thread more than once. * In particular, a thread may not be restarted once it has completed * execution. ** @exception IllegalThreadStateException if the thread was already * started. * @see #run() * @see #stop() */ public synchronized void start() { … }
当一个线程抛出了异常并且没有被捕获时(这种情况只可能是运行时异常),JVM检查这个线程是否被
预置了未捕获异常处理器。如果找到,JVM将调用线程对象的这个方法,并将线程对象和异常作为传入
参数。
Thread类还有另一个方法可以处理未捕获到的异常,即静态方法
setDefaultUncaughtExceptionHandler()。这个方法在应用程序中为所有的线程对象创建了一个异常处
理器。
当线程抛出一个未捕获到的异常时,JVM将为异常寻找以下三种可能的处理器。
首先,它查找线程对象的未捕获异常处理器。
如果找不到,JVM继续查找线程对象所在的线程组(ThreadGroup)的未捕获异常处理器。
如果还是找不到,如同本节所讲的,JVM将继续查找默认的未捕获异常处理器。
如果没有一个处理器存在,JVM则将堆栈异常记录打印到控制台,并退出程序。
30. 同步方法和同步块,哪个是更好的选择
同步块,这意味着同步块之外的代码是异步执行的,这比同步整个方法更提升代码的效率。请知道一条
原则:同步的范围越小越好。
借着这一条,我额外提一点,虽说同步的范围越少越好,但是在Java虚拟机中还是存在着一种叫做锁粗
化的优化方法,这种方法就是把同步范围变大。这是有用的,比方说StringBuffer,它是一个线程安全
的类,自然最常用的append()方法是一个同步方法,我们写代码的时候会反复append字符串,这意味
着要进行反复的加锁->解锁,这对性能不利,因为这意味着Java虚拟机在这条线程上要反复地在内核态
和用户态之间进行切换,因此Java虚拟机会将多次append方法调用的代码进行一个锁粗化的操作,将多
次的append的操作扩展到append方法的头尾,变成一个大的同步块,这样就减少了加锁–>解锁的次
数,有效地提升了代码执行的效率。
Task task = new Task(); Thread thread = new Thread(task); thread.setUncaughtExceptionHandler(new ExceptionHandler()); thread.start(); } }class Task implements Runnable{ @Override public void run() { int numero = Integer.parseInt(“TTT”); } }class ExceptionHandler implements UncaughtExceptionHandler{ @Override public void uncaughtException(Thread t, Throwable e) { System.out.printf(“An exception has been captured\n”); System.out.printf(“Thread: %s\n”, t.getId()); System.out.printf(“Exception: %s: %s\n”, e.getClass().getName(),e.getMessage()); System.out.printf(“Stack Trace: \n”); e.printStackTrace(System.out); System.out.printf(“Thread status: %s\n”,t.getState()); } }
有三个线程T1,T2,T3,如何保证顺序执行?
在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的join()方法在一个线程中启动另一个线
程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,
T2调用T1),这样T1就会先完成而T3最后完成。
实际上先启动三个线程中哪一个都行, 因为在每个线程的run方法中用join方法限定了三个线程的执行
顺序。
public class JoinTest2 { // 1.现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行 public static void main(String[] args) { final Thread t1 = new Thread(new Runnable() { @Override public void run() { System.out.println(“t1”); } }); final Thread t2 = new Thread(new Runnable() { @Override public void run() { try {// 引用t1线程,等待t1线程执行完 t1.join(); } catch (InterruptedException e) { e.printStackTrace(); }System.out.println(“t2”); } }); Thread t3 = new Thread(new Runnable() { @Override public void run() { try {// 引用t2线程,等待t2线程执行完 t2.join(); } catch (InterruptedException e) { e.printStackTrace(); }System.out.println(“t3”); } }); t3.start();//这里三个线程的启动顺序可以任意,大家可以试下! t2.start(); t1.start(); } }
什么是CAS
CAS,全称为Compare and Swap,即比较-替换。
假设有三个操作数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,才会
将内存值修改为B并返回true,否则什么都不做并返回false。当然CAS一定要volatile变量配合,这样才
能保证每次拿到的变量是主内存中最新的那个值,否则旧的预期值A对某条线程来说,永远是一个不会
变的值A,只要某次CAS操作失败,永远都不可能成功。
CAS?CAS 有什么缺陷,如何解决?
CAS 涉及3个操作数,内存地址值V,预期原值A,新值B;如果内存位置的值V与预期原A值相匹
配,就更新为新值B,否则不更新
CAS有什么缺陷?.
ABA 问题
并发环境下,假设初始条件是A,去修改数据时,发现是A就会执行修改。但是看到的虽然是A,中间可
能发生了A变B,B又变回A的情况。此时A已经非彼A,数据即使成功修改,也可能有问题。
可以通过AtomicStampedReference「解决ABA问题」,它,一个带有标记的原子引用类,通过控制变
量值的版本来保证CAS的正确性。
循环时间长开销
自旋CAS,如果一直循环执行,一直不成功,会给CPU带来非常大的执行开销。
很多时候,CAS思想体现,是有个自旋次数的,就是为了避开这个耗时问题~
只能保证一个变量的原子操作。
CAS 保证的是对一个变量执行操作的原子性,如果对多个变量操作时,CAS 目前无法直接保证操作的原
子性的。
可以通过这两个方式解决这个问题:
使用互斥锁来保证原子性;
将多个变量封装成对象,通过AtomicReference来保证原子性。
34. 什么是AQS
简单说一下AQS,AQS全称为AbstractQueuedSychronizer,翻译过来应该是抽象队列同步器。
如果说java.util.concurrent的基础是CAS的话,那么AQS就是整个Java并发包的核心了,
ReentrantLock、CountDownLatch、Semaphore等等都用到了它。
AQS实际上以双向队列的形式连接所有的Entry,比方说ReentrantLock,所有等待的线程都被放在一个
Entry中并连成双向队列,前面一个线程使用ReentrantLock好了,则双向队列实际上的第一个Entry开
始运行。
AQS定义了对双向队列所有的操作,而只开放了tryLock和tryRelease方法给开发者使用,开发者可以根
据自己的实现重写tryLock和tryRelease方法,以实现自己的并发功能。
35. 线程池作用
(如果问到了这样的问题,可以展开的说一下线程池如何用、线程池的好处、线程池的启动策略)合理
利用线程池能够带来三个好处。
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系
统的稳定性,使用线程池可以进行统一的分配,调优和监控。
36. ThreadLocal是什么
从名字我们就可以看到ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该
变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可
以访问自己内部的副本变量。
从字面意思来看非常容易理解,但是从实际使用的角度来看,就没那么容易了,作为一个面试常问的
点,使用场景那也是相当的丰富:
1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2、线程间数据隔离
3、进行事务操作,用于存储线程事务信息。
4、数据库连接,Session会话管理。
37. ThreadLocal有什么用
简单说ThreadLocal就是一种以空间换时间的做法,在每个Thread里面维护了一个以开地址法实现的
ThreadLocal.ThreadLocalMap,把数据进行隔离,数据不共享,自然就没有线程安全方面的问题了
public class Thread implements Runnable { //ThreadLocal.ThreadLocalMap是Thread的属性 ThreadLocal.ThreadLocalMap threadLocals = null; }
ThreadLocal中的关键方法set()和get()
public void set(T value) { Thread t = Thread.currentThread(); //获取当前线程t ThreadLocalMap map = getMap(t); //根据当前线程获取到ThreadLocalMap if (map != null) map.set(this, value); //K,V设置到ThreadLocalMap中 elsecreateMap(t, value); //创建一个新的ThreadLocalMap }public T get() { Thread t = Thread.currentThread();//获取当前线程t ThreadLocalMap map = getMap(t);//根据当前线程获取到ThreadLocalMap if (map != null) { //由this(即ThreadLoca对象)得到对应的Value,即ThreadLocal的泛型值 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings(“unchecked”) T result = (T)e.value; return result; } }return setInitialValue(); }
ThreadLocalMap的Entry数组
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } }
所以怎么回答「ThreadLocal的实现原理」?如下,最好是能结合以上结构图一起说明哈~
Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,即每个线程都有
一个属于自己的ThreadLocalMap。
ThreadLocalMap内部维护着Entry数组,每个Entry代表一个完整的对象,key是ThreadLocal本
身,value是ThreadLocal的泛型值。
每个线程在往ThreadLocal里设置值的时候,都是往自己的ThreadLocalMap里存,读也是以某个
ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。
ThreadLocal 内存泄露问题
先看看一下的TreadLocal的引用示意图哈,
ThreadLocalMap中使用的 key 为 ThreadLocal 的弱引用,如下
弱引用:只要垃圾回收机制一运行,不管JVM的内存空间是否充足,都会回收该对象占用的内存。
弱引用比较容易被回收。因此,如果ThreadLocal(ThreadLocalMap的Key)被垃圾回收器回收了,但
是因为ThreadLocalMap生命周期和Thread是一样的,它这时候如果不被回收,就会出现这种情况:
ThreadLocalMap的key没了,value还在,这就会「造成了内存泄漏问题」。
如何「解决内存泄漏问题」?使用完ThreadLocal后,及时调用remove()方法释放内存空间。
ThreadLocal的应用场景
数据库连接池
会话管理中使用
39. notify()和notifyAll()有什么区别?
notify()和notifyAll()都是Object对象用于通知处在等待该对象的线程的方法。
void notify(): 唤醒一个正在等待该对象的线程。
void notifyAll(): 唤醒所有正在等待该对象的线程。
notify可能会导致死锁,而notifyAll则不会
任何时候只有一个线程可以获得锁,也就是说只有一个线程可以运行synchronized 中的代码
使用notifyall,可以唤醒 所有处于wait状态的线程,使其重新进入锁的争夺队列中,而notify只能唤醒一
个。
wait() 应配合while循环使用,不应使用if,务必在wait()调用前后都检查条件,如果不满足,必须调用
notify()唤醒另外的线程来处理,自己继续wait()直至条件满足再往下执行。
notify() 是对notifyAll()的一个优化,但它有很精确的应用场景,并且要求正确使用。不然可能导致死
锁。正确的场景应该是 WaitSet中等待的是相同的条件,唤醒任一个都能正确处理接下来的事项,如果
唤醒的线程无法正确处理,务必确保继续notify()下一个线程,并且自身需要重新回到WaitSet中.
40. 为什么wait()方法和notify()/notifyAll()方法要在同步块中被调
用
这是JDK强制的,wait()方法和notify()/notifyAll()方法在调用前都必须先获得对象的锁
41. wait()方法和notify()/notifyAll()方法在放弃对象监视器时有什
么区别
wait()方法和notify()/notifyAll()方法在放弃对象监视器的时候的区别在于:wait()方法立即释放对象监视
器,notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃对象监视器。
42. wait()方法和notify()/notifyAll()方法在放弃对象监视器时有什
么区别
wait()方法和notify()/notifyAll()方法在放弃对象监视器的时候的区别在于:wait()方法立即释放对象监视
器,notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃对象监视器。
43. 线程中断是否能直接调用stop,为什么?
Java提供的终止方法只有一个stop,但是不建议使用此方法,因为它有以下三个问题: 1. stop方法是过时的 从Java编码规则来说,已经过时的方式不建议采用. 2. stop方法会导致代码逻辑不完整 stop方法是一种"恶意"的中断,一旦执行stop方法,即终止当前正在
运行的线程,不管线程逻辑是否完整,这是非常危险的.
44. 什么是阻塞(Blocking)和非阻塞(Non-Blocking)?
阻塞和非阻塞通常用来形容多线程间的相互影响。比如一个线程占用了临界区资源,那么其他所有需要
这个而资源的线程就必须在这个临界区中进行等待。等待会导致线程挂起,这种情况就是阻塞。此时,
如果占用资源的线程一直不愿意释放资源,那么其他所有阻塞在这个临界区上的线程都不能工作。
非阻塞的意思与之相反,它强调没有一个线程可以妨碍其他线程执行。所有的线程都会尝试不断前向执
行。
1).工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE),这样可灵活的
往线程池中添加线程。
2).如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工
作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
3、newSingleThreadExecutor创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任
务,如果这个线程异常结束,会有另一个取代它,保证顺序执行(我觉得这点是它的特色)。
单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动
的。
4、newScheduleThreadPool 创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于
Timer。
49. 在Java中Executor、ExecutorService、Executors的区别?
Executor 和 ExecutorService 这两个接口主要的区别是:
ExecutorService 接口继承了 Executor 接口,是 Executor 的子接口
Executor 和 ExecutorService 第二个区别是:Executor 接口定义了 execute()方法用来接收一个
Runnable接口的对象,而 ExecutorService 接口中的 submit()方法可以接受Runnable和Callable
接口的对象。
Executor 和 ExecutorService 接口第三个区别是 Executor 中的 execute() 方法不返回任何结果,
而 ExecutorService 中的 submit()方法可以通过一个 Future 对象返回运算结果。
Executor 和 ExecutorService 接口第四个区别是除了允许客户端提交一个任务,ExecutorService
还提供用来控制线程池的方法。比如:调用 shutDown() 方法终止线程池。
Executors 类提供工厂方法用来创建不同类型的线程池。
比如: newSingleThreadExecutor() 创建一个只有一个线程的线程池,newFixedThreadPool(int
numOfThreads)来创建固定线程数的线程池,newCachedThreadPool()可以根据需要创建新的线程,
但如果已有线程是空闲的会重用已有线程。
50. 请说出与线程同步以及线程调度相关的方法。
wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理
InterruptedException异常;
notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待
状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞
争,只有获得锁的线程才能进入就绪状态;
举例说明同步和异步。
如果系统中存在临界资源(资源数量少于竞争资源的线程数量的资源),例如正在写的数据以后可能被
另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就必须进行同步存取
(数据库操作中的排他锁就是最好的例子)。当应用程序在对象上调用了一个需要花费很长时间来执行
的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往
更有效率。事实上,所谓的同步就是指阻塞式操作,而异步就是非阻塞式操作。
不使用stop停止线程?
当run() 或者 call() 方法执行完的时候线程会自动结束,如果要手动结束一个线程,你可以用volatile 布尔
变量来退出run()方法的循环或者是取消任务来中断线程。
使用自定义的标志位决定线程的执行情况
public class SafeStopThread implements Runnable{ private volatile boolean stop=false;//此变量必须加上volatile int a=0; @Override public void run() { // TODO Auto-generated method stub while(!stop){ synchronized ("") { a++; try {Thread.sleep(100); } catch (Exception e) { // TODO: handle exception }a–; String tn=Thread.currentThread().getName(); System.out.println(tn+":a="+a); } } //线程终止 public void terminate(){ stop=true; } public static void main(String[] args) { SafeStopThread t=new SafeStopThread(); Thread t1=new Thread(t); t1.start(); for(int i=0;i<5;i++){ new Thread(t).start(); } t.terminate(); } }
如何控制某个方法允许并发访问线程的大小?
Semaphore两个重要的方法就是semaphore.acquire() 请求一个信号量,这时候的信号量个数-1(一旦
没有可使用的信号量,也即信号量个数变为负数时,再次请求的时候就会阻塞,直到其他线程释放了信
号量)semaphore.release()释放一个信号量,此时信号量个数+1
如何创建线程池
《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过
ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽
的风险**
Executors 返回线程池对象的弊端如下:
FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为
Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。
CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为
Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。
方式一:通过构造方法实现
public class SemaphoreTest { private Semaphore mSemaphore = new Semaphore(5); public void run(){ for(int i=0; i< 100; i++){ new Thread(new Runnable() { @Override public void run() { test(); } }).start(); } }private void test(){ try {mSemaphore.acquire(); } catch (InterruptedException e) { e.printStackTrace(); }System.out.println(Thread.currentThread().getName() + " 进来了"); try {Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }System.out.println(Thread.currentThread().getName() + " 出去了"); mSemaphore.release(); } }
方式二:通过Executor 框架的工具类Executors来实现 我们可以创建三种类型的
ThreadPoolExecutor:
FixedThreadPool : 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。
当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在
一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
SingleThreadExecutor: 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程
池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。
CachedThreadPool: 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数
量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新
的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复
用。
对应Executors工具类中的方法如图所示:
55. 高并发、任务执行时间短的业务怎样使用线程池?并发不高、任
务执行时间长的业务怎样使用线程池?并发高、业务执行时间长的业
务怎样使用线程池?
这是我在并发编程网上看到的一个问题,希望每个人都能看到并且思考一下,因为这个问题非常好、非
常实际、非常专业。关于这个问题,个人看法是:
1、高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换
2、并发不高、任务执行时间长的业务要区分开看:
假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要
让所有的CPU闲下来,可以加大线程池中的线程数目,让CPU处理更多的业务
假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,和(1)一样
吧,线程池中的线程数设置得少一些,减少线程上下文的切换
3、并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些
业务里面某些数据是否能做缓存是第一步,增加服务器是第二步,至于线程池的设置,设置参考(2)。
最后,业务执行时间长的问题,也可能需要分析一下,看看能不能使用中间件对任务进行拆分和解耦。
第二种:将这些Runnable对象作为某一个类的内部类,共享的数据作为外部类的成员变量,对共享数据
的操作分配给外部类的方法来完成,以此实现对操作共享数据的互斥和通信,作为内部类的Runnable来
操作外部类的方法,实现对数据的操作
65. Java中活锁和死锁有什么区别?
活锁:一个线程通常会有会响应其他线程的活动。如果其他线程也会响应另一个线程的活动,那么就有
可能发生活锁。同死锁一样,发生活锁的线程无法继续执行。然而线程并没有阻塞——他们在忙于响应
对方无法恢复工作。这就相当于两个在走廊相遇的人:甲向他自己的左边靠想让乙过去,而乙向他的右
边靠想让甲过去。可见他们阻塞了对方。甲向他的右边靠,而乙向他的左边靠,他们还是阻塞了对方。
死锁:两个或更多线程阻塞着等待其它处于死锁状态的线程所持有的锁。死锁通常发生在多个线程同时
但以不同的顺序请求同一组锁的时候,死锁会让你的程序挂起无法完成任务。
class ShareData { private int x = 0; public synchronized void addx(){ x++; System.out.println("x++ : "+x); }public synchronized void subx(){ x–; System.out.println("x-- : "+x); }}public class ThreadsVisitData { public static ShareData share = new ShareData(); public static void main(String[] args) { //final ShareData share = new ShareData(); new Thread(new Runnable() { public void run() { for(int i = 0;i<100;i++){ share.addx(); } } }).start(); new Thread(new Runnable() { public void run() { for(int i = 0;i<100;i++){ share.subx(); } }}).start(); }}
1、当DeadLock 类的对象flag=1时(td1),先锁定o1,睡眠500毫秒
2、而td1在睡眠的时候另一个flag==0的对象(td2)线程启动,先锁定o2,睡眠500毫秒
3、td1睡眠结束后需要锁定o2才能继续执行,而此时o2已被td2锁定;
4、td2睡眠结束后需要锁定o1才能继续执行,而此时o1已被td1锁定;
5、td1、td2相互等待,都需要得到对方锁定的资源才能继续执行,从而死锁。
67. 如何避免死锁和检测
预防死锁
破坏互斥条件:使资源同时访问而非互斥使用,就没有进程会阻塞在资源上,从而不发生死锁
破坏请求和保持条件:采用静态分配的方式,静态分配的方式是指进程必须在执行之前就申请需要
的全部资源,且直至所要的资源全部得到满足后才开始执行,只要有一个资源得不到分配,也不给
这个进程分配其他的资源。
破坏不剥夺条件:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源,但是只
适用于内存和处理器资源。
破坏循环等待条件:给系统的所有资源编号,规定进程请求所需资源的顺序必须按照资源的编号依
次进行。
设置加锁顺序
如果两个线程(A和B),当A线程已经锁住了Z,而又去尝试锁住X,而X已经被线程B锁住,线程A和线程
B分别持有对应的锁,而又去争夺其他一个锁(尝试锁住另一个线程已经锁住的锁)的时候,就会发生
死锁,如下图:
两个线程试图以不同的顺序来获得相同的锁,如果按照相同的顺序来请求锁,那么就不会出现循环的加
锁依赖性,因此也就不会产生死锁,每个需要锁Z和锁X的线程都以相同的顺序来获取Z和X,那么就不会
发生死锁了,如下图所示:
public static void main(String[] args) { DeadLock td1 = new DeadLock(); DeadLock td2 = new DeadLock(); td1.flag = 1; td2.flag = 0; //td1,td2都处于可执行状态,但JVM线程调度先执行哪个线程是不确定的。 //td2的run()可能在td1的run()之前运行 new Thread(td1).start(); new Thread(td2).start(); } }
这样死锁就永远不会发生。 针对两个特定的锁,可以尝试按照锁对象的hashCode值大小的顺序,
分别获得两个锁,这样锁总是会以特定的顺序获得锁,我们通过设置锁的顺序,来防止死锁的发
生,在这里我们使用System.identityHashCode方法来定义锁的顺序,这个方法将返回由
Obejct.hashCode 返回的值,这样就可以消除死锁发生的可能性。
public class DeadLockExample3 { // 加时赛锁,在极少数情况下,如果两个hash值相等,使用这个锁进行加锁 private static final Object tieLock = new Object(); public void transferMoney(final Account fromAcct, final Account toAcct, final DollarAmount amount) throws InsufficientFundsException { class Helper { public void transfer() throws InsufficientFundsException { if (fromAcct.getBalance().compareTo(amount) < 0) throw new InsufficientFundsException(); else {fromAcct.debit(amount); toAcct.credit(amount); } } }// 得到两个锁的hash值 int fromHash = System.identityHashCode(fromAcct); int toHash = System.identityHashCode(toAcct); // 根据hash值判断锁顺序,决定锁的顺序 if (fromHash < toHash) { synchronized (fromAcct) { synchronized (toAcct) { new Helper().transfer(); } } } else if (fromHash > toHash) { synchronized (toAcct) { synchronized (fromAcct) { new Helper().transfer(); } } } else {// 如果两个对象的hash相等,通过tieLock来决定加锁的顺序,否则又会重新引入死 锁——加时赛锁 synchronized (tieLock) { synchronized (fromAcct) {
在极少数情况下,两个对象可能拥有两个相同的散列值,此时必须通过某种任意的方法来决定锁的
顺序,否则可能又会重新引入死锁。
为了避免这种情况,可以使用 “加时(Tie-Breaking))”锁 ,这获得这两个Account锁之前,从而
消除了死锁发生的可能性
68. 什么是可重入锁(ReentrantLock)?
Java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为Java 类,而不是
作为语言的特性来实现。这就为Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特
性或者锁定语义。
ReentrantLock 类实现了Lock ,它拥有与synchronized 相同的并发性和内存语义,但是添加了类似锁
投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换
句话说,当许多线程都想访问共享资源时,JVM可以花更少的时候来调度线程,把更多时间用在执行线
程上。)
Reentrant 锁意味着什么呢?
简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加
1,然后锁需要被释放两次才能获得真正释放。这模仿了synchronized 的语义;如果线程进入由线程已
经拥有的监控器保护的synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续)
synchronized块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个synchronized 块时,
才释放锁。
69. 讲一下 synchronized 关键字的底层原理
synchronized 关键字底层原理属于 JVM 层面。
① synchronized 同步语句块的情况
通过 JDK 自带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息:首先切换到类的对应目录
执行 javac SynchronizedDemo.java 命令生成编译后的 .class 文件,然后执行 javap -c -s -v -l SynchronizedDemo.class 。synchronized (toAcct) { new Helper().transfer(); } } } } } }public class SynchronizedDemo { public void method() { synchronized (this) { System.out.println(“synchronized 代码块”); } } }
从上面我们可以看出:
synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中
monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。 当
执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的
对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原
因) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行
monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等
待,直到锁被另外一个线程释放为止。
② synchronized 修饰方法的的情况
public class SynchronizedDemo2 { public synchronized void method() { System.out.println(“synchronized 方法”); } }
synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是
ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED
访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
70. synchronized和ReentrantLock的区别
synchronized是和if、else、for、while一样的关键字,ReentrantLock是类,这是二者的本质区别。既
然ReentrantLock是类,那么它就提供了比synchronized更多更灵活的特性,可以被继承、可以有方
法、可以有各种各样的类变量,ReentrantLock比synchronized的扩展性体现在几点上:
1、ReentrantLock可以对获取锁的等待时间进行设置,这样就避免了死锁
2、ReentrantLock可以获取各种锁的信息
3、ReentrantLock可以灵活地实现多路通知
另外,二者的锁机制其实也是不一样的。ReentrantLock底层调用的是Unsafe的park方法加锁,
synchronized操作的应该是对象头中mark word,这点我不能确定。
71. ConcurrentHashMap的并发度是什么
ConcurrentHashMap的并发度就是segment的大小,默认为16,这意味着最多同时可以有16条线程操
作ConcurrentHashMap,这也是ConcurrentHashMap对Hashtable的最大优势,任何情况下,
Hashtable能同时有两条线程获取Hashtable中的数据吗?
72. ReadWriteLock是什么
首先明确一下,不是说ReentrantLock不好,只是ReentrantLock某些时候有局限。如果使用
ReentrantLock,可能本身是为了防止线程A在写数据、线程B在读数据造成的数据不一致,但这样,如
果线程C在读数据、线程D也在读数据,读数据是不会改变数据的,没有必要加锁,但是还是加锁了,降
低了程序的性能。
因为这个,才诞生了读写锁ReadWriteLock。ReadWriteLock是一个读写锁接口,
ReentrantReadWriteLock是ReadWriteLock接口的一个具体实现,实现了读写的分离,读锁是共享的,
写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。
3、 CyclicBarrier 可重用, CountDownLatch 不可重用,计数值为0该 CountDownLatch 就不可再用
了
78. Hashtable的size()方法中明明只有一条语句"return count",
为什么还要做同步?
这是我之前的一个困惑,不知道大家有没有想过这个问题。某个方法中如果有多条语句,并且都在操作
同一个类变量,那么在多线程环境下不加锁,势必会引发线程安全问题,这很好理解,但是size()方法明
明只有一条语句,为什么还要加锁?
关于这个问题,在慢慢地工作、学习中,有了理解,主要原因有两点:
1、同一时间只能有一条线程执行固定类的同步方法,但是对于类的非同步方法,可以多条线程同时访
问。所以,这样就有问题了,可能线程A在执行Hashtable的put方法添加数据,线程B则可以正常调用
size()方法读取Hashtable中当前元素的个数,那读取到的值可能不是最新的,可能线程A添加了完了数
据,但是没有对size++,线程B就已经读取size了,那么对于线程B来说读取到的size一定是不准确的。
而给size()方法加了同步之后,意味着线程B调用size()方法只有在线程A调用put方法完毕之后才可以调
用,这样就保证了线程安全性
2、CPU执行代码,执行的不是Java代码,这点很关键,一定得记住。Java代码最终是被翻译成机器码执
行的,机器码才是真正可以和硬件电路交互的代码。即使你看到Java代码只有一行,甚至你看到Java代
码编译之后生成的字节码也只有一行,也不意味着对于底层来说这句语句的操作只有一个。一句"return
count"假设被翻译成了三句汇编语句执行,一句汇编语句和其机器码做对应,完全可能执行完第一句,
线程就切换了。
方法区和堆是所有线程共享的内存区域;而java栈、本地方法栈和程序员计数器是运行是线程私有的内
存区域。
1、Java堆(Heap),是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区
域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分
配内存。
2、方法区(Method Area),方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
3、程序计数器(Program Counter Register),程序计数器(Program Counter Register)是一块较小
的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。
4、JVM栈(JVM Stacks),与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程
私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时
候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信
息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
5、本地方法栈(Native Method Stacks),本地方法栈(Native Method Stacks)与虚拟机栈所发挥的
作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法
栈则是为虚拟机使用到的Native方法服务。
方法区和堆都是各个线程共享的内存区域,用于存储已经被JVM加载的类信息、常量、静态变量、JIT编
译器编译后的代码等数据;程序中的字面量(literal)如直接书写的100、”hello”和常量都是放在常量池
中,常量池是方法区的一部分。栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,栈
和堆的大小都可以通过JVM的启动参数来进行调整,栈空间用光了会引发StackOverflowError,而堆和
常量池空间不足则会引发OutOfMemoryError。
上面的语句中变量str放在栈上,用new创建出来的字符串对象放在堆上,而”hello”这个字面量是放在方
法区的。
补充1:
较新版本的Java(从Java 6的某个更新开始)中,由于JIT编译器的发展和”逃逸分析”技术的逐渐成熟,栈
上分配、标量替换等优化技术使得对象一定分配在堆上这件事情已经变得不那么绝对了。
补充2:
运行时常量池相当于Class文件常量池具有动态性,Java语言并不要求常量一定只有编译期间才能产生,
运行期间也可以将新的常量放入池中,String类的intern()方法就是这样的。 看看下面代码的执行结果是
什么并且比较一下Java 7以前和以后的运行结果是否一致。
7. JVM内存分哪几个区,每个区的作用是什么?
Java虚拟机主要分为以下一个区:
方法区:
=(n/2)logn-n/2 =O(nlogn)所以只用到比较的排序算法最低时间复杂度是O(nlogn)。
网络协议面试题
eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery >
range > index > ALL,一般来说,得保证查询至少达到 range 级别,最好能达到 ref。
只需要记住:system > const > eq_ref > ref > range > index > ALL 就行了,其他的不常
见。
system:表只有一行记录(等于系统表),这是 const 类型的特列,平时不会出现,这个也
可以忽略不计。
const:表示通过索引一次就找到了,const 用于比较 primary key 或者 unique 索引。因为
只匹配一行数据,所以很快。如将主键置于 where 列表中,MySQL 就能将该查询转换为一个
常量。
eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一
索引扫描。
ref:非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,它返回
所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以他应该属于查找和扫
描的混合体。
range:只检索给定范围的行,使用一个索引来选择行。key 列显示使用了哪个索引一般就是
在 where 语句中出现了 between、<、>、in 等的查询这种范围扫描索引扫描比全表扫描要
好,因为它只需要开始于索引的某一点,而结束语另一点,不用扫描全部索引。
index:出现 index 是 sql 使用了索引但是没用索引进行过滤,一般是使用了覆盖索引或者是
利用索引进行了排序分组。
all:将遍历全表以找到匹配的行。
其他 type 如下:
index_merge:在查询过程中需要多个索引组合使用,通常出现在有 or 关键字的 sql 中。
ref_or_null:对于某个字段既需要过滤条件,也需要 null 值的情况下。查询优化器会选择用
ref_or_null 连接查询。
index_subquery:利用索引来关联子查询,不再全表扫描。
unique_subquery:该联接类型类似于 index_subquery。子查询中的唯一索引。
table_name
order by rand() limit 1000;table_name
t1 join (select rand() * (select max(id) from table_name
) as nid) t2 ont1.id > t2.nid limit 1000; 8、区分in和exists, not in和not existstable_name
ADD FULLTEXT INDEX idx_user_name
(user_name
);mediumint 3字节 -8388608 ~ 8388607 int 4字节 bigint 8字节 int(M) M表示总位数 - 默认存在符号位,unsigned 属性修改 - 显示宽度,如果某个数不够定义字段时设置的位数,则前面以0补填,zerofill 属性修改 例:int(5) 插入一个数’123’,补填后为’00123’ - 在满足要求的情况下,越小越好。 - 1表示bool值真,0表示bool值假。MySQL没有布尔类型,通过整型0和1表示。常用tinyint(1)表 示布尔型。 – b. 浮点型 ---------- 类型 字节 范围 float(单精度) 4字节 double(双精度) 8字节 浮点型既支持符号位 unsigned 属性,也支持显示宽度 zerofill 属性。 不同于整型,前后均会补填0. 定义浮点型时,需指定总位数和小数位数。 float(M, D) double(M, D) M表示总位数,D表示小数位数。 M和D的大小会决定浮点数的范围。不同于整型的固定范围。 M既表示总位数(不包括小数点和正负号),也表示显示宽度(所有显示符号均包括)。 支持科学计数法表示。 浮点数表示近似值。 – c. 定点数 ---------- decimal – 可变长度 decimal(M, D) M也表示总位数,D表示小数位数。 保存一个精确的数值,不会发生数据的改变,不同于浮点数的四舍五入。 将浮点数转换为字符串来保存,每9位数字保存为4个字节。 2. 字符串类型 – a. char, varchar ---------- char 定长字符串,速度快,但浪费空间 varchar 变长字符串,速度慢,但节省空间 M表示能存储的最大长度,此长度是字符数,非字节数。 不同的编码,所占用的空间不同。 char,最多255个字符,与编码无关。 varchar,最多65535字符,与编码有关。 一条有效记录最大不能超过65535个字节。 utf8 最大为21844个字符,gbk 最大为32766个字符,latin1 最大为65532个字符 varchar 是变长的,需要利用存储空间保存 varchar 的长度,如果数据小于255个字节,则采用一个 字节来保存长度,反之需要两个字节来保存。 varchar 的最大有效长度由最大行大小和使用的字符集确定。 最大有效长度是65532字节,因为在varchar存字符串时,第一个字节是空的,不存在任何数据,然后还 需两个字节来存放字符串的长度,所以有效长度是65535-1-2=65532字节。 例:若一个表定义为 CREATE TABLE tb(c1 int, c2 char(30), c3 varchar(N)) charset=utf8; 问N的最大值是多少? 答:(65535-1-2-4-303)/3 – b. blob, text ---------- blob 二进制字符串(字节字符串) tinyblob, blob, mediumblob, longblob text 非二进制字符串(字符字符串) tinytext, text, mediumtext, longtext text 在定义时,不需要定义长度,也不会计算总长度。 text 类型在定义时,不可给default值 – c. binary, varbinary ---------- 类似于char和varchar,用于保存二进制字符串,也就是保存字节字符串而非字符字符串。 char, varchar, text 对应 binary, varbinary, blob. 3. 日期时间类型 一般用整型保存时间戳,因为PHP可以很方便的将时间戳进行格式化。 datetime 8字节 日期及时间 1000-01-01 00:00:00 到 9999-12-31 23:59:59
列属性(列约束)
date 3字节 日期 1000-01-01 到 9999-12-31 timestamp 4字节 时间戳 19700101000000 到 2038-01-19 03:14:07 time 3字节 时间 -838:59:59 到 838:59:59 year 1字节 年份 1901 - 2155 datetime YYYY-MM-DD hh:mm:ss timestamp YY-MM-DD hh:mm:ss YYYYMMDDhhmmss YYMMDDhhmmss YYYYMMDDhhmmss YYMMDDhhmmss date YYYY-MM-DD YY-MM-DD YYYYMMDD YYMMDD YYYYMMDD YYMMDD time hh:mm:ss hhmmss hhmmss year YYYY YYYYYY YY 4. 枚举和集合 – 枚举(enum) ---------- enum(val1, val2, val3…) 在已知的值中进行单选。最大数量为65535. 枚举值在保存时,以2个字节的整型(smallint)保存。每个枚举值,按保存的位置顺序,从1开始逐一递 增。 表现为字符串类型,存储却是整型。 NULL值的索引是NULL。 空字符串错误值的索引值是0。 – 集合(set) ---------- set(val1, val2, val3…) create table tab ( gender set(‘男’, ‘女’, ‘无’) ); insert into tab values (‘男, 女’); 最多可以有64个不同的成员。以bigint存储,共8个字节。采取位运算的形式。 当创建表时,SET成员值的尾部空格将自动被删除。 / 列属性(列约束) / ------------------ 1. PRIMARY 主键 - 能唯一标识记录的字段,可以作为主键。 - 一个表只能有一个主键。 - 主键具有唯一性。 - 声明字段时,用 primary key 标识。 也可以在字段列表之后声明 例:create table tab ( id int, stu varchar(10), primary key (id)); - 主键字段的值不能为null。 - 主键可以由多个字段共同组成。此时需要在字段列表后声明的方法。 例:create table tab ( id int, stu varchar(10), age int, primary key (stu, age)); 2. UNIQUE 唯一索引(唯一约束)
建表规范
使得某字段的值也不能重复。 3. NULL 约束 null不是数据类型,是列的一个属性。 表示当前列是否可以为null,表示什么都没有。 null, 允许为空。默认。 not null, 不允许为空。 insert into tab values (null, ‘val’); – 此时表示将第一个字段的值设为null, 取决于该字段是否允许为null 4. DEFAULT 默认值属性 当前字段的默认值。 insert into tab values (default, ‘val’); – 此时表示强制使用默认值。 create table tab ( add_time timestamp default current_timestamp ); – 表示将当前时间的时间戳设为默认值。 current_date, current_time 5. AUTO_INCREMENT 自动增长约束 自动增长必须为索引(主键或unique) 只能存在一个字段为自动增长。 默认为1开始自动增长。可以通过表属性 auto_increment = x进行设置,或 alter table tbl auto_increment = x; 6. COMMENT 注释 例:create table tab ( id int ) comment ‘注释内容’; 7. FOREIGN KEY 外键约束 用于限制主表与从表数据完整性。 alter table t1 add constraint t1_t2_fk
foreign key (t1_id) references t2(id); – 将表t1的t1_id外键关联到表t2的id字段。 – 每个外键都有一个名字,可以通过 constraint 指定 存在外键的表,称之为从表(子表),外键指向的表,称之为主表(父表)。 作用:保持数据一致性,完整性,主要目的是控制存储在外键表(从表)中的数据。 MySQL中,可以对InnoDB引擎使用外键约束: 语法: foreign key (外键字段) references 主表名 (关联字段) [主表记录删除时的动作] [主表记录 更新时的动作] 此时需要检测一个从表的外键需要约束为主表的已存在的值。外键在没有关联的情况下,可以设置为 null.前提是该外键列,没有not null。 可以不指定主表记录更改或更新时的动作,那么此时主表的操作被拒绝。 如果指定了 on update 或 on delete:在删除或更新时,有如下几个操作可以选择: 1. cascade,级联操作。主表数据被更新(主键值更新),从表也被更新(外键值更新)。主表记录被 删除,从表相关记录也被删除。 2. set null,设置为null。主表数据被更新(主键值更新),从表的外键被设置为null。主表记录 被删除,从表相关记录外键被设置成null。但注意,要求该外键列,没有not null属性约束。 3. restrict,拒绝父表删除和更新。 注意,外键只被InnoDB存储引擎所支持。其他引擎是不支持的。 / 建表规范 / ------------------ – Normal Format, NF - 每个表保存一个实体信息 - 每个具有一个ID字段作为主键 - ID主键 + 原子表 – 1NF, 第一范式 字段不能再分,就满足第一范式。 – 2NF, 第二范式
SELECT
满足第一范式的前提下,不能出现部分依赖。 消除复合主键就可以避免部分依赖。增加单列关键字。 – 3NF, 第三范式 满足第二范式的前提下,不能出现传递依赖。 某个字段依赖于主键,而有其他字段依赖于该字段。这就是传递依赖。 将一个实体信息的数据放在一个表内实现。 / SELECT / ------------------ SELECT [ALL|DISTINCT] select_expr FROM -> WHERE -> GROUP BY [合计函数] -> HAVING - > ORDER BY -> LIMIT a. select_expr – 可以用 * 表示所有字段。 select * from tb; – 可以使用表达式(计算公式、函数调用、字段也是个表达式) select stu, 29+25, now() from tb; – 可以为每个列使用别名。适用于简化列标识,避免多个列标识符重复。 - 使用 as 关键字,也可省略 as. select stu+10 as add10 from tb; b. FROM 子句 用于标识查询来源。 – 可以为表起别名。使用as关键字。 SELECT * FROM tb1 AS tt, tb2 AS bb; – from子句后,可以同时出现多个表。 – 多个表会横向叠加到一起,而数据会形成一个笛卡尔积。 SELECT * FROM tb1, tb2; – 向优化符提示如何选择索引 USE INDEX、IGNORE INDEX、FORCE INDEX SELECT * FROM table1 USE INDEX (key1,key2) WHERE key1=1 AND key2=2 AND key3=3; SELECT * FROM table1 IGNORE INDEX (key3) WHERE key1=1 AND key2=2 AND key3=3; c. WHERE 子句 – 从from获得的数据源中进行筛选。 – 整型1表示真,0表示假。 – 表达式由运算符和运算数组成。 – 运算数:变量(字段)、值、函数返回值 – 运算符: =, <=>, <>, !=, <=, <, >=, >, !, &&, ||, in (not) null, (not) like, (not) in, (not) between and, is (not), and, or, not, xor is/is not 加上ture/false/unknown,检验某个值的真假 <=>与<>功能相同,<=>可用于null比较 d. GROUP BY 子句, 分组子句 GROUP BY 字段/别名 [排序方式] 分组后会进行排序。升序:ASC,降序:DESC 以下[合计函数]需配合 GROUP BY 使用: count 返回不同的非NULL值数目 count()、count(字段) sum 求和 max 求最大值 min 求最小值 avg 求平均值 group_concat 返回带有来自一个组的连接的非NULL值的字符串结果。组内字符串连接。
UNION
子查询
e. HAVING 子句,条件子句 与 where 功能、用法相同,执行时机不同。 where 在开始时执行检测数据,对原数据进行过滤。 having 对筛选出的结果再次进行过滤。 having 字段必须是查询出来的,where 字段必须是数据表存在的。 where 不可以使用字段的别名,having 可以。因为执行WHERE代码时,可能尚未确定列值。 where 不可以使用合计函数。一般需用合计函数才会用 having SQL标准要求HAVING必须引用GROUP BY子句中的列或用于合计函数中的列。 f. ORDER BY 子句,排序子句 order by 排序字段/别名 排序方式 [,排序字段/别名 排序方式]… 升序:ASC,降序:DESC 支持多个字段的排序。 g. LIMIT 子句,限制结果数量子句 仅对处理好的结果进行数量限制。将处理好的结果的看作是一个集合,按照记录出现的顺序,索引从0开 始。 limit 起始位置, 获取条数 省略第一个参数,表示从索引0开始。limit 获取条数 h. DISTINCT, ALL 选项 distinct 去除重复记录 默认为 all, 全部记录 /* UNION / ------------------ 将多个select查询的结果组合成一个结果集合。 SELECT … UNION [ALL|DISTINCT] SELECT … 默认 DISTINCT 方式,即所有返回的行都是唯一的 建议,对每个SELECT查询加上小括号包裹。 ORDER BY 排序时,需加上 LIMIT 进行结合。 需要各select查询的字段数量一样。 每个select查询的字段列表(数量、类型)应一致,因为结果中的字段名以第一条select语句为准。 / 子查询 / ------------------ - 子查询需用括号包裹。 – from型 from后要求是一个表,必须给子查询结果取个别名。 - 简化每个查询内的条件。 - from型需将结果生成一个临时表格,可用以原表的锁定的释放。 - 子查询返回一个表,表型子查询。 select * from (select * from tb where id>0) as subfrom where id>1; – where型 - 子查询返回一个值,标量子查询。 - 不需要给子查询取别名。 - where子查询内的表,不能直接用以更新。 select * from tb where money = (select max(money) from tb); – 列子查询 如果子查询结果返回的是一列。 使用 in 或 not in 完成查询
连接查询(join)
TRUNCATE
exists 和 not exists 条件 如果子查询返回数据,则返回1或0。常用于判断条件。 select column1 from t1 where exists (select * from t2); – 行子查询 查询条件是一个行。 select * from t1 where (id, gender) in (select id, gender from t2); 行构造符:(col1, col2, …) 或 ROW(col1, col2, …) 行构造符通常用于与对能返回两个或两个以上列的子查询进行比较。 – 特殊运算符 != all() 相当于 not in = some() 相当于 in。any 是 some 的别名 != some() 不等同于 not in,不等于其中某一个。 all, some 可以配合其他运算符一起使用。 / 连接查询(join) / ------------------ 将多个表的字段进行连接,可以指定连接条件。 – 内连接(inner join) - 默认就是内连接,可省略inner。 - 只有数据存在时才能发送连接。即连接结果不能出现空行。 on 表示连接条件。其条件表达式与where类似。也可以省略条件(表示条件永远为真) 也可用where表示连接条件。 还有 using, 但需字段名相同。 using(字段名) – 交叉连接 cross join 即,没有条件的内连接。 select * from tb1 cross join tb2; – 外连接(outer join) - 如果数据不存在,也会出现在连接结果中。 – 左外连接 left join 如果数据不存在,左表记录会出现,而右表为null填充 – 右外连接 right join 如果数据不存在,右表记录会出现,而左表为null填充 – 自然连接(natural join) 自动判断连接条件完成连接。 相当于省略了using,会自动查找相同字段名。 natural join natural left join natural right join select info.id, info.name, info.stu_num, extra_info.hobby, extra_info.sex from info, extra_info where info.stu_num = extra_info.stu_id;
备份与还原
视图
/ TRUNCATE / ------------------ TRUNCATE [TABLE] tbl_name 清空数据 删除重建表 区别: 1,truncate 是删除表再创建,delete 是逐条删除 2,truncate 重置auto_increment的值。而delete不会 3,truncate 不知道删除了几条,而delete知道。 4,当被用于带分区的表时,truncate 会保留分区 / 备份与还原 */ ------------------ 备份,将数据的结构与表内数据保存起来。 利用 mysqldump 指令完成。 – 导出 mysqldump [options] db_name [tables] mysqldump [options] —database DB1 [DB2 DB3…] mysqldump [options] --all–database 1. 导出一张表 mysqldump -u用户名 -p密码 库名 表名 > 文件名(D:/a.sql) 2. 导出多张表 mysqldump -u用户名 -p密码 库名 表1 表2 表3 > 文件名(D:/a.sql) 3. 导出所有表 mysqldump -u用户名 -p密码 库名 > 文件名(D:/a.sql) 4. 导出一个库 mysqldump -u用户名 -p密码 --lock-all-tables --database 库名 > 文件名(D:/a.sql) 可以-w携带WHERE条件 – 导入 1. 在登录mysql的情况下: source 备份文件 2. 在不登录的情况下 mysql -u用户名 -p密码 库名 < 备份文件 什么是视图: 视图是一个虚拟表,其内容由查询定义。同真实的表一样,视图包含一系列带有名称的列和行数据。但 是,视图并不在数据库中以存储的数据值集形式存在。行和列数据来自由定义视图的查询所引用的表,并且在 引用视图时动态生成。 视图具有表结构文件,但不存在数据文件。 对其中所引用的基础表来说,视图的作用类似于筛选。定义视图的筛选可以来自当前或其它数据库的一个 或多个表,或者其它视图。通过视图进行查询没有任何限制,通过它们进行数据修改时的限制也很少。 视图是存储在数据库中的查询的sql语句,它主要出于两种原因:安全原因,视图可以隐藏一些数据, 如:社会保险基金表,可以用视图只显示姓名,地址,而不显示社会保险号和工资数等,另一原因是可使复杂 的查询易于理解和使用。 – 创建视图 CREATE [OR REPLACE] [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}] VIEW view_name [(column_list)] AS select_statement
事务
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。