Java
-
什么是Java
Java是一门面向对象的高级程序设计语言,由sun公司开发,后被Oracle公司79亿美元收购! -
java语言的特点
a)简单易学.
b)面向对象.
c)跨平台(即一次编译到处运行,说的是java的可移植性,平台指的是操作系统,如windows linux). -
java是如何实现跨平台的:
通过jvm,我们编写的java文件编译后会生成.class文件称为字节码文件,它是运行在jvm上的(java虚拟机就是 负责将字节码文件翻译成特定平台下的机器码然后运行)而jvm在我们安装jdk的时候的部署好的,所以只要我们在不同的平台上安装对应的jdk就可以运行字节码文件,运行我们编写的java程序,与操作系统无关
Java语言是高性能的(就是功能强大性能很好,这讲的是java的安全性) -
Java编程可分为三个方向
a)J2SE商业版本,标准版本 (Java2 Standard EDItion) 定位在客户端,主要用于桌面应用软件的编程
b)J2EE:java2 Enterprise EDItion(企业级的web应用开发)
什么是j2ee:j2EE是使用Java技术开发企业级应用的一种事实上的工业标准(javaEE是企业级的web应用开发)
(J2EE是一套全然不同于传统应用开发的技术架构,包含许多组件,主要可简化且规范应用系统的开发与部署,进而提高可移植性,安全与再用价值)
c)J2ME:Java2 Micro EDItion(Java2 微缩版)手机开发 -
java编译和运行的关系(java运行原理)
-
JDK
全称是Java Development Kit 中文名叫java的开发工具包 -
JDK包含的基本组件包括
a)javac – 编译器,将源程序转成字节码
b)jar – 打包工具,将相关的类文件打包成一个文件
c)javadoc – 文档生成器,从源码注释中提取文档
d)jdb – debugger,查错工具
e)java – 运行编译后的java程序(.class后缀的)
f)appletviewer:小程序浏览器,一种执行HTML文件上的Java小程序的Java浏览器。
g)Javah:产生可以调用Java过程的C过程,或建立能被Java程序调用的C过程的头文件。
h)Javap:Java反汇编器,显示编译类文件中的可访问功能和数据,同时显示字节代码含义。
i)Jconsole: Java进行系统调试和监控的工具 -
JDK和jre的区别
a)jre:java运行时环境jre是将要执行java程序的java虚拟机
b)jdk:java的开发工具包,包含了jre,编译器,解释执行器
c)jdk = jre + 开发工具(编译器,解释执行器)
d)jre = jvm + java类库 -
八大基本数据类型和对应的封装类
byte(字节型8位) Byte 一个字节 char(字符型16位) Character 两个字节 short(短整型16位) Short 两个字节 int(整型 32位) Integer 四个字节 long(长整型 64位) Long 八个字节 float(单精度浮点 32位) Float 四个字节 double(双精度浮点 64位) Double 八个字节 boolean(true/false) Boolean 八分之一个字节
-
数据类型之间的转换
调用基本数据类型相对应的包装类的方法:parsexxx(String)或valueOf(String)即可返回基本数据类型 -
怎么将iso-8859-1编码转换成utf-8
String str = “1”;
String str1 = new String(str.getBytes(“iso-8859-1”), “utf-8”); -
Integer的总结:
a)无论如何Integer i = 1与Integer ii = new Integer(1)不会相等,因为new操作的引用指向堆,i指向常量池,他们的内存地址不一样,所以为false
b)两个非new出来的Integer,用==比较,如果两个相同的数值在-128到127之间比较则是true,否则为false
a}Java在编译Integer i = 128的时候被翻译为Integer i = Integer.valueOf(128);而valueOf()函数只会对-128到127之间的数进行缓存(下图为valueOf()方法的源码)
c)两个new出来的Integer用= =比较,都为false,因为= =比较的是地址,两个new出来的地址不可能一样
d)Int和Integer(无论new否)比,都为true,因为Integer会自动拆箱为int再去比 -
char型变量能否存一个中文汉字?为什么?
简单的说:char类型占两个字节,一个中文也占两个字节,所以当然可以存储一个中文汉字
更深入一点的回答:char类型的变量是用来存储Unicode编码的字符,unicode编码字符集中包含了汉字,所以,char类型的变量中当然可以存储汉字,不过如果某个特殊的汉字没有被包含在unicode编码字符集中,那么这个char类型的变量中就不能存储这个特殊汉字
unciode编码占用两个字节,所以char类型的变量也是占用两个字节。 -
| 和 || 的区别
逻辑运算符 or 多个表达式只要成立一个就会进入
| 对所有表达式都会判断
|| 如果第一个表达式成立,后面的表达式不判断 -
& 和 && 的区别
逻辑运算符 and 多个表达式要同时成立才会进入
&& 第一个表达式不成立,后面的都不判断
& 对所有表达式都判断 -
问short s1 = 1;s1 = s1 + 1有错吗?short s1 = 1;s1 += 1有错吗?
对于s1 = s1 + 1:由于1是int类型,所以s1 = s1 + 1的运算结果也是int类型,需要强制转换成short类型,才能给赋值给short型
对于s1 += 1:可以正确编译,因为s1 += 1相当于s1 =(short) (s1 + 1)其中有隐式的强制类型转换 -
Java中怎么打印数组
a)循环打印
b)使用Arrays.toString(arrr) -
i++和++i的区别
都会+1
i++:先运算,再自增 i++返回原来的值
++i:先自增,再运算 ++i返回加1后的值 -
Java中的++操作线程安全吗
不是线程安全的操作,它涉及到多个指令,如读取变量值,增加,然后存储回内存这个过程可能会出现多个线程交叉 -
什么是面向对象?
面向对象就是将某类具有共同状态和行为的事物封装成属性和方法,通过对象调用的过程 -
面向对象的三大特征
封装,继承,多态
(也有说法:面向对象的四大特征 是抽象,封装,继承,多态)
类是对象的模板,对象是类的实例. -
抽象
就是将一类对象的共同特征抽取出来构造成类 -
继承
继承是面向对象最显著的一个特性,继承是从已有的类中派生出新的类,新的类能吸收已有的类的数据属性和行为,并能扩展新能力.
继承是为了重用父类代码,同时为实现多态性作准备.主要体现了代码重用
Java只支持单一继承.一个类只能拥有一个父类.但是可以拥有多个子类 -
多态
一种功能多种实现方式. (父类可以new子类的对象,调用不同子类的方法,从而产生不同的行为,这就是多态)
(1)首先多态是建立在继承制上的,当方法被同一种类对象调用时,会根据对象所属类的不同而产生不同的行为,这种现象叫多态
比如有一个函数是叫某个人来吃饭,函数要求传递的参数是人的对象,可是来了一个美国人,你看到的可能是用刀和叉子在吃饭,而来了一个中国人你看到的可能是用筷子在吃饭,这就体现出了同样是一个方法,可以却产生了不同的形态,这就是多态
(2)多态的作用:消除类型之间的耦合关系
(3)多态存在的三个必要条件:1.要有继承 2.要有重写 3.父类引用指向子类对象
(4)多态的好处:
应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可,大大提高程序的可复用性.//继承
派生类的功能可以被基类的方法或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性. //多态的真正作用,
(5)表现形式:方法重载和方法重写都是多态的表现形式
(6)多态的机制:java中实现多态的机制是依靠父类或接口的引用指向子类或实现类,从而实现了一个对象多种形态的特性 -
方法重写(Override)
(子类和父类的方法必须一致,包括名字,参数类型和返回类型)
重写方法是java实现多态性"一个接口,多个方法"的另一种方式.
特点:
1.不是属于同一个类,来自于继承
2.方法的参数,名字,返回类型都必须和父类保持一致
3.面向对象多态的一种表现形式
方法重写的时候重写的方法的修饰符一定要比被重写的方法的修饰符的权限大
为什么重写方法
方法重写构成了java最强大的一个概念基础:动态方法调度(dynamic method DIspatch).它是一种机制,借助于这种基础,对一个已重写的方法的调用将在运行时而不是在编译时解析。这关系到java如何实现运行时多态性的问题。 -
方法重载(Overload)
(与返回类型无关,形参的类型,顺序,和个数不一样就行了)
重载的价值:它允许相关方法通过使用一个公用的名称来访问。
方法重载是java实现多态性的方式之一
特点:
1.所有的方法名都一样
2.方法重载与返回类型无关,与形参的类型,顺序,个数有关
3.必须属于同一个类 -
方法重载和方法重写的区别
1.方法重载必须在同一个类,而方法重写来自于继承
2.方法重载对权限修饰符没有要求,被子类重写的方法不能比父类的方法更严格
3.方法重载要求方法名相同,参数类型和个数可以不同;方法重写要求方法名称,参数类型,返回类型要相同 -
构造方法(constructor)
构造方法(constructor) 是一个很特殊的方法,在创建一个对象时调用。
特点:
1.方法名与类名一致。
2.且无返回值的方法叫构造方法。
在new一个对象时自动调用构造方法,构造方法隐含的返回类型是类的本身。
程序员如果显示提供构造方法,那么系统不再默认构造方法,否则系统会默认提供空的构造方法。除构造方法以外,其他任何方法都 不能调用构造方法。
问:构造器(constructor)是否可以被重写(override)?
答:构造器(constructor)不能被继承,所以就不能被重写(override)但是可以被重载(overload),你可以写多个构造方法
构造方法的作用:实例化对象,当实例化一个对象的时候会自动调用构造方法 -
垃圾回收(gc)
1.gc是什么?为什么要有gc?
gc是垃圾回收,java有了gc就不需要程序员去人工释放内存空间;当java虚拟机发觉内存资源紧张的时候就会
自动的去清理无用变量所占用的内存空间.
2.垃圾回收器可以马上回收吗?有什么办法主动通知虚拟机进行垃圾回收?
可以。程序员可以手动执行System.gc()或Runtime.getRuntime().gc()通知GC运行,什么时候执行取决于虚拟机也并不保证GC一定会执行
3.它的工作原理如下:
当不存在对一个对象的引用时,就假定不再需要该对象了,那么就可以回收该对象所占有的内存。在java中不需要明确的销毁对象,垃圾
收集仅在程序执行时偶尔出现,它不会因为一个或几个存在的对象不再被使用而发生。另外,不同的java运行时实现将会使用
不同的垃圾收集方法,但大多情况下编程时不必考虑它.
4.它的优点
(1)它使java程序员在编写程序时不再考虑内存管理的问题
(2)垃圾回收机制有效的防止了内存泄露
5.回收机制
分代收集(大部分由分代收集计算)
增量收集.
标记收集. -
Java内存溢出的原因及修复
http://outofmemory.cn/c/java-outOfMemoryError
-
有什么办法可以终止正在运行的JVM
1.System.exit(0)或者System.exit(1)
a)System.exit(0)是正常退出程序,而System.exit(1)或者说非0表示非正常退出程序
b)比如说:在一个if-else判断中,如果我们程序是按照我们预想的执行,到最后我们需要停止程序,那么我们使用System.exit(0),而System.exit(1)一般放在catch块中,当捕获到异常,需要停止程序,我们使用System.exit(1)。这个status=1是用来表示这个程序是非正常退出。
2.Runtime.getRuntime.exit(0) -
Java支持多继承吗?
java不支持多继承,每个类都只能继承一个类,但是可以实现多个接口,接口可以继承多个接口 -
Java值传递和引用传递
http://www.cnblogs.com/coderising/p/5697986.html
java中只有值传递没有引用传递。
所谓值传递,在java里面参数传递都是按值传递,这句话的意思是:按值传递是传递的是值的拷贝
所谓的引用传递,传递的就是引用地址的值,其实还是传的是一个值。只是传的是引用地址的标识(这是一个值) -
super关键字
如果超类的属性是私有的,这时子类就无法访问,java中提供了解决方案。只要子类需要引用最近的超类就可以使用super关键字。
Super如果要调用父类的构造方法一定要放在子类构造方法的第一行(否则编译不通过),如果不是调用父类的构造方法,那么请随意
用法:1,调用父类的构造方法2,调用父类成员名被子类同样成员名所隐藏的情况 -
this关键字
1.调用本类中的成员变量和方法(包括在构造方法中调用本类中的其他构造方法)
2.this关键字不能在静态方法中使用
public class Test {
Integer a = 10;
public Test() {
this("aaa");// 在构造方法中调用其他构造方法
System.out.println(this.a);// 调用本类的属性
this.test();// 调用本类中的普通方法
}
public Test(String name) {
System.out.println("Test()");
}
public void test() {
System.out.println("test()");
}
}
-
final关键字(最终的,不可修改的)
1.被final修饰的变量叫常量,常量的值不能被修改
2.被final修饰的方法不能被重写
3.被final修饰的类不能被继承
补充:如果是基本数据类型的变量,其数值在初始化之后便不能更改,如果是引用类型的变量,则在对其初始化之后就不能让其再指向另一个对象,内容还是可以改变的
如下图所示:
-
final finally finalize的区别
1.final用于声明属性,方法和类,分别表示属性不可改变,方法不可重写,类不可继承.
2.finally是异常处理语句结构的一部分,表示无论是否有异常都会执行.
3.finalize是Object类的一个方法,在垃圾收集器执行的时候会调用对象的finalize()方法,可以保证对象被彻底的销毁 -
抽象类(abstract)
用abstract修饰的类就是抽象类,用abstract修饰的方法句是抽象方法,特点如下:
1.抽象类不能实例化,但可以创建一个抽象类的引用(声明一个没有new的类变量).
2.普通类继承抽象类一定要重写父类的抽象方法.
3.抽象方法不能有主体.
4.抽象方法不能存在于普通类.
5.abstract不能和final static private同用.
final 修饰的类/方法/属性均不可再次更改,故而不可以被重写.
private修饰的类只可以定义在内部类;private 修饰的方法/属性只可以在类中调用,重写时是访问不到这些方法和属性的.
static 静态的,其修饰的方法/属性,是随着类的创建而创建,类的消亡而消亡,在编译时就已经确定的东西;但是abstract是dynamic(动态)的,其必须通过类继承或接口实现来动态重写abstract方法. -
接口(interface)
接口的作用:实现多重继承.
接口类类似于类的一种结构,比抽象类更抽象,可以理解为纯抽象类,不能有非抽象的方法.
接口中的抽象方法,可以不加权限修饰符,可以直接这样写:void fun();
在实现接口的类中可以不重写抽象方法,做法:让接口的实现类继承一个超类,在超类中重写接口中的方法,那么此接口的实现类就可以不重写接口中的抽象方法。
1.接口不是类,不能实例化
2.接口可以继承多个接口.普通类可以实现多个接口
3.抽象类实现接口可以不重写接口里的方法.
4.接口中的方法不能以private,protected修饰,但一定是abstract修饰的
5.当接口里的方法与超类里的方法相同时当前类不必实现接口里的方法.
6.接口定义的成员默认都是静态的常量,不可修改. -
为什么要使用接口?
1.首先,接口弥补了java不能多重继承的不足
2.其次,使用接口能隐藏我们程序内部的具体实现,我们只需要对外部提供接口的调用,而将具体的实现放在实现接口的类里面
3.它构成了一种新的思想,面向接口编程 -
什么是全局变量,什么是局部变量?
静态变量也叫类变量,也就是所谓的全局变量
所谓全局变量,就是在类里面方法外部定义的变量,随着类的初始化而初始化,类的消亡而消亡
所谓局部变量,就是在方法内定义的变量,在方法内声明和初始化,当方法执行结束后,局部变量会被销毁 -
深拷贝和浅拷贝
所谓深拷贝,不仅拷贝对象本身,还拷贝对象所引用的地址的内容,当改变一个对象的属性,并不影响另一个对象所引用的内容
所谓浅拷贝,只拷贝对象的本身,当一个对象的属性改变之后,另一个对象的相应属性也会做同样的改变 -
接口和抽象类的区别
1.抽象类可以有普通方法和抽象方法,接口所有方法都是抽象方法
2.接口当中所有变量默认为final修饰
3.抽象类用extends继承,接口用implement实现
4.接口只能继承接口,不能实现接口
5.抽象类只能单一继承,接口可以继承多个接口,普通类可以实现多个接口
补充:
抽象类中可以有构造方法,接口中不能有构造方法
抽象类中可以包含静态方法,接口中不能包含静态方法(1.8后可以) -
equals和 ==
1.对象用equals()方法比较,比较的是值是否相等
2.对象用比较,比较的是地址是否相等
3.基本类型没有equals()方法,所以不能用equals比较,只能用比较值是否相等,
4.常用equals()比较字符,用==比较基本数据类型的数值
String aa = new String(“1”);
String bb = new String(“1”);
String cc = “1”;
String dd = “1”;
System.out.println(aa == bb);// false 这的aa和bb分别新new了一块地址虽然值一样,但是地址却不一样
System.out.println(cc == dd);// true 这里cc和dd同时指向了"1"这块地址所以是相等的
System.out.println(aa.equals(bb));// true
System.out.println(cc.equals(dd));// true
问:String aa = new String(“1”);此句创建了几个对象 答案是两个 new String() 和 “1”
问:String aa = “bb” + “cc”;此句创建了几个对象 答案是三个 bb, cc, bbcc
参考网址:http://www.cnblogs.com/ydpvictor/archive/2012/09/09/2677260.html -
equals()和hashCode()的区别
1.首先equals()和hashCode()都是从Object类中继承过来的
2.equals()方法用于比较对象的内容是否相等,hashCode()方法只有在集合中用到
3.如果对象相同(指用equals比较相同),那么他们的hashCode值一定相同;如果两个对象的hashCode相同,他们并不一定相同 -
Object类常见方法
1.equals()
2.getClass()
3.hashCode()
4.wait()
5.notify(),notifyAll()
6.toString()
7.clone()
8.finalize() -
一个java源文件(.java)中,是否可以包含多个类(不是内部类),有什么限制
可以有多个类,但只能有一个public类,并且public的类名和源文件名一致
package com.yr.test;
public class Test1 {
public static void main(String[] args) {
new Test1();
new Test2();
new Test3();
}
public Test1() {
System.out.println("Test1()");
}
}
class Test2 {
public Test2() {
System.out.println("Test2()");
}
}
class Test3 {
public Test3() {
System.out.println("Test3()");
}
}
- If和swich的区别:
1.swich不会主动跳出,满足条件之后,如果不使用break,后面的语句都会执行,如果不满足条件就会执行default里面的语句
2.If else会主动跳出
3.用swich能做的,用if都能做,反过来则不行
4.使用场景:
a)switch建议判断固定值的时候使用
b)If建议判断区间或范围的时候使用
5.If else和swich对比使用实例
package com.yr.test;
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
@SuppressWarnings("resource")
Scanner sc = new Scanner(System.in); // 键盘录入
while (true) { // 死循环方便测试
System.out.println("请输入要转换的星期数"); // 键盘录入提示
int week = sc.nextInt();
switch (week) {
case 1:
System.out.println("星期1");
break;
case 2:
System.out.println("星期2");
break;
case 3:
System.out.println("星期3");
break;
case 4:
System.out.println("星期4");
break;
case 5:
System.out.println("星期5");
break;
case 6:
System.out.println("星期6");
break;
case 7:
System.out.println("星期天");
break;
default:
System.out.println("您输入的数字有误,请重新输入");
}
}
}
}
package com.yr.test;
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
@SuppressWarnings("resource")
Scanner sc = new Scanner(System.in); // 键盘录入
while (true) { // 死循环方便测试
System.out.println("请输入要转换的星期数"); // 键盘录入提示
int week = sc.nextInt();
if (1 == week) {
System.out.println("星期1");
} else if (2 == week) {
System.out.println("星期2");
} else if (3 == week) {
System.out.println("星期3");
} else if (4 == week) {
System.out.println("星期4");
} else if (5 == week) {
System.out.println("星期5");
} else if (6 == week) {
System.out.println("星期6");
} else if (7 == week) {
System.out.println("星期天");
} else {
System.out.println("您输入的数字有误,请重新输入");
}
}
}
}
-
static变量和static方法
用static修饰的变量属于类变量,并不是某个对象,当类被装载器装载的时候所有的静态变量会在实例化变量之前得到初始化。(注:static的方法和static变量都是当类加载的时候被初始化,而不是类编译时被初始化)
用static修饰方法时该方法仅可以调其他的静态方法和变量,只能访问静态数据
1.静态方法和非静态方法可以直接调用静态方法(因为static方法在类加载的时候就初始化好了,可以直接调用)
2.静态方法调用非静态方法需要new对象(因为非static方法是要与对象关联在一起的必须创建一个对象后,才可以调用)
3.静态的变量是共享的(私有的静态变量只对本类共享) -
静态变量和实例变量的区别
1.静态变量(类变量)在类加载的时候就会被分配空间,实例变量要new操作之后才会被分配空间
2.实例变量必须创建对象后才可以通过这个对象使用,而静态变量则直接通过类名点
3.静态变量属于所有对象公有,其中一个对象将它的值改变,其他对象得到的就是改变后的结果,而实例变量属于对象私有,某一个对象将其值改变,不影响其他对象 -
静态方法和静态块的区别
1.两者都是在类装载的时候被初始化
2.静态方法是被调用的时候才会执行,而静态块是自动执行且只执行一次
3.静态方法可以抛出异常或捕获异常,静态块只能捕获异常 -
静态内部类和非静态内部类的区别
1.静态内部类只能访问外部类的静态成员(包括静态变量和静态方法),非静态成员则不可访问
2.静态内部类可以定义静态或非静态的成员,非静态内部类则不能定义静态成员
3.静态内部类不需要通过外部实例就可以创建对象,常规内部类则需要通过外部类的实例才可以创建对象 -
Java中怎么跳出多重循环
1.return
for (int i = 1; i < 10; i++) {
for (int j = 0; j < 10; j++) {
if (j == 5) {
return;
}
System.out.println(“j:” + j);
}
System.out.println(“i:” + i);
} -
If和else if的区别
多个if表示所有的if都会进行判断然后执行,if else if只要是满足条件的,就不再对之后的else if进行判断,直接跳出
效率比多个if高 -
List item
1.do while:先循环再判断,至少要执行一次循环
2.for/while:必须先判断条件是否成立,然后才决定是否循环
package com.yr.test;
public class Test {
public static void main(String[] args) {
int sum = 0;
int i = 1;
do { // do 就是做,先循环一次
sum += i;
i++;
} while (i <= 100); // 然后再判断条件语句
System.out.println("sum = " + sum);
}
}
-
swich能否作用在byte上,能否作用在long上,能否作用在String上?
1.swich可以作用在byte,short,char,int,String,Enum
问:为什么swich可以作用在byte上,而不可以作用在long上?
答:byte,short,char都可以隐含转换为int, 所以,这些类型以及这些类型的包装类型也是可以的。显然,long和String类型都不符合 switch的语法规定,并且不能被隐式转换成int类型,所以,它们不能作用于swtich语句中。
补充:jdk1.7可以作用到String上 -
说两个Jdk1.5的新特性
jdk1.5的发行是java生命里程碑上的一个重要事件,从根本上拓展了java语言的作用域
1.支持泛型和枚举
2.自动装箱和拆箱
3.增强了for-each风格和for循环
4.变长参数 -
说两个Jdk1.7的新特性
1.String类型可以作用于swich,在jdk1.7之前String不可以作用于swich
package com.yr.test;
public class Test {
public static void main(String[] args) {
String str = "1";
switch (str) {
case "1":
System.out.println("星期一");
break;
default:
break;
}
}
}
2.泛型实例化类型自动推断,所有集合后面的尖括号当中可以不用写数据类型,jdk1.7之前需要写数据类型
package com.yr.test;
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
}
}
-
java排序算法
1.冒泡排序(效率低)
a) 原理:双重循环,逐个比较
2.快速排序
3.选择排序
4.插入排序
5.希尔排序
6.归并排序
7.堆排序 -
包
public protected default private
同类 √ √ √ √
同包子类 √ √ √ ×
同包非子类 √ √ √ ×
非同包子类 √ √ × ×
非同包非子类 √ × × ×
-
自动装箱拆箱
什么是自动装箱与拆箱:
自动装箱是将一个基本类型自动转换成它的包装类进行运算的过程;
自动拆箱则是将一个封装类自动转换成相应的基本类型进行运算的过程。
int a = 10; 基本数据类型
Integer intv = 10;int类型的对象 自动装箱(直接用Integer定义的,而不是用int)
int b = intv + 12;自动拆箱 这里的intv是一个对象而不是基本数据类型,但是12是基本类型,所以intv会自动拆箱再进行运算
Integer aaa = new Integer(a);把a包装成为对象,也叫装箱,也叫手动装箱(因为有new) -
基本类型的默认值:
注: char 是一个空格 -
三元运算符
在java中用?和:表示,条件成立执行问号后面的表达式,条件不成立,执行冒号后面的表达式
语法: ${表达式?真:假} -
基本类型和封装类型的区别:
1.在java中一切皆为对象,但八大基本数据类型却不是对象
2.声明方式的不同,基本数据类型无需通过new关键字来创建,而封装类型可以通过new关键字来创建
3.初始化值不同,封装类型的初始化值为null,基本数据类型初始化值如上图所示
4.使用的方式不同,比如集合使用时只能使用包装类型 -
数据结构:
数组,一维数组,二维数据,链表,单向链表,双向链表,树,二叉树(大的放右边,小的放左边) -
内存分配
http://www.cnblogs.com/SaraMoring/p/5687466.html
-
Quartz
是一个开源定时调度框架
https://blog.youkuaiyun.com/senior_lee/article/details/50646774
作用: 在某一个有规律的时间点干某件事。并且时间的触发的条件可以非常复杂
实现方式:
Main方法
1.实现Job接口, 重写execute方法
2.在重写方法中自定义具体调度工作内容(就是到时间执行什么事情)
3.创建Main类, 在普通方法内定义调度器, 并指定需调度的类,同时配置调度时间Web实现
1.实现Job接口, 重写execute方法
2.在重写方法中自定义具体调度工作内容(就是到时间执行什么事情)
3.在web.xml中配置quartz的过滤器
4.在web.xml初始化时加载quartz文件
5.在quartz文件中配置需调度的类与执行时间与spring整合
1.在一个类的方法中自定义具体调度工作内容
2.定义quartz文件, 并在文件中指定需调度的类与时间
3.在spring.xml文件中将quartz文件引入
4.在web.xml中配置spring建立一个简单的调度需要有如下几个步骤:
1.定义Job;
2.定义Trigger;
3.获得Scheduler;
4.建立JobDetail对象,通过JodDetail对象实例化Job;
5.Job和Trigger建立关联;
6.开始调度 scheduler.start方法。
需要注意的是第四步,在前面很大的篇幅都是在说Job,第四步多了一个JobDetail,虽然Job是Quartz的核心组件,但是Scheduler 并不直接和Job打交道,而是通过JobDetail来建立关联,这样的好处是:
1.可以将Job分组;
2.可以通过访问JobDetail的JobDataMap向Job传递一些运行时参数和属性分布式实现定时任务
https://www.jianshu.com/p/e0e7e8494d96
分布式定时任务: 把分散的,可靠性差的计划任务纳入统一的平台,并实现集群管理调度和分布式部署的一种定时任务的管理方式
实现:将定时任务放在调用机上, 在调用某任务时触发
为什么使用分布式定时任务
提高了容错
方便灵活高效
减少了开发和维护的成本
保证系统的可用性,与负载均衡
可持久化到数据库,避免数据丢失
-
Java定时器
是java中自带的定时器
实现方式:
1)使用线程(创建一个thread,然后让它在while循环里一直运行着,通过sleep方法来达到定时任务的效果)
2)JDK自带的Timer和TimerTask
3)使用ScheduledExecutorService
是java.util.concurrent里,做为并发工具类被引进的,是最理想的定时任务实现方式
Timer,TimerTask实现比线程实现的好处
1)取消和启动任务时可以控制
2)第一次执行任务,可以指定自己想要等待的(delay)时间
ScheduledExecutorService比较于其他两种方式的好处
1)灵活的设定第一次执行任务的delay时间
2)提供良好的约定, 以便设定执行的时间间隔
3)相比于Timer的单线程, 它是通过线程池的方式来执行任务的
4)Timer是基于绝对时间的(对时间敏感), ScheduledThreadPoolExecutor则是基于相对时间
Timer缺点
1)不会捕获异常
2)对调度的支持是基于绝对时间,对系统时间的改变非常敏感(运行得到的时间结果与预想的不一致)
3)如果TimerTask抛出的了未检查异常则会导致Timer线程终止,不会重新恢复线程的执行, 错误的认为整个Timer线程取消, 未执行的TimerTask不会再执行, 新任务也不能被调度
如果设定的时间等于或超过当前时间,会发生什么?
会立即执行任务
如果执行task发生异常,是否会影响其他task的定时调度
如果TimeTask抛出RuntimeException, 那么Timer会停止所有任务的运行! -
验证码
是一种区分用户是计算机还是人的公共全自动程序
可以防止: 恶意破解密码, 刷票, 论坛灌水
固定大小, 固定位置, 固定字体,固定颜色的范围, 实现起来相对简单 -
同步与锁
lock可以提高多个线程进行读操作的效率
synchronized是内置语言实现,锁是通过代码实现
lock可以知道有没有成功获取锁, 而synchronized却无法办到
lock可以让等待锁的线程响应中断, 线程可以中断去干别的事务, 而synchronized却不行, 使用synchronized时, 等待的线程会一直等待下去, 不能够响应中断
synchronized发生异常时, 自动释放线程占有的锁, 因此不会导致死锁现象发生; 而Lock发生异常时, 如果没有主动通过unLock()去释放锁, 则很可能造成死锁现象, 因此使用lock时需要在finally块中释放锁 -
加密技术
https://blog.youkuaiyun.com/JHON_03/article/details/78268218
单向: MD5 base64 SHA(种类较多(1512)) 只能加密,不能解密 (严格地说, base64属于编码格式, 而非加密算法)对称: DES(8位) AES(16位) 3DES 通过一串密钥,进行加密.解密也需要这个密钥(密钥(key)会随时改变)
对称加密也可称双向加密 (能解的都是双向);加解密使用同一个KEY
优点:“DES”:运算速度快,资源消耗较少;“AES”:运算速度快,安全性高,资源消耗少
缺点:“DES”:安全性低
3DES是DES向AES过渡的加密算法,是DES的一个更安全的变形非对称加密: RSA DSA 私钥解密,公钥加密
http://blog.sina.com.cn/s/blog_60cf05130101ew6r.html 区别
https://cloud.tencent.com/developer/news/254061 算法区别java mail(发送邮件,短信)
https://www.cnblogs.com/xdp-gacl/p/4216311.html
基于MIME协议
以使用Java Mail创建出包含图片,包含附件的复杂邮件
Java spring
-
Java String
是否可以继承String类
不可以.String类是final类,被final修饰不能被继承.
String StringBuffer StringBuilder的区别
尽可能使用StringBuilder,因为它运行时比StringBuffer快。但如果需要强调线程安全,那就应该使用StringBuffer。1.执行速度方面的比较:StringBuilder > StringBuffer > String
2.原因:String是字符串常量不可修改,StringBuilder和StringBuffer是字符串变量可修改
3.String实现了equals()方法,而StringBuilder和StringBuffer则没有实现equals()方法
a)System.out.println(new String(“a”).equals(new String(“a”))); true
b)System.out.println(new StringBuffer(“a”).equals(new StringBuffer(“a”))); false
4.StirngBuffer和StringBuilder区别:
StringBuffer:线程安全的
StringBuilder:线程不安全的
5.什么情况使用:
String:如果操作数量少用String
StringBuilder:单线程操作字符串缓冲区下操作大量数据用StringBuilder
StringBuffer:多线程操作字符串缓冲区下操作大量数据用StringBuffer
6.StringBuilder由于使用于单线程下,但是它不执行同步,所以速度比StringBuffer更快;如果需要同步则使用StringBuffer,数据更安全
7.实例:将1到100所有数字拼起来组成一个串
package com.yr.test;public class Test {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
Test test = new Test();
test.test1();
long endTime = System.currentTimeMillis();
System.out.println(“用时:” + (endTime - startTime) / 1000);
}public void test1() {// 26s 效率低,创建了1000001个对象 String str = new String(); for (int i = 0; i < 100000; i++) { str = str + i; } } public void test2() {// 0s 效率高,只创建了一个对象 StringBuffer buffer = new StringBuffer(); for (int i = 0; i < 100000; i++) { buffer.append(i); } }
}
-
为什么针对安全保密高的信息,char[]比String更好?
因为String是不可变的,就是说它一旦创建就不能更改了,而字符数组中的元素是可以更改的,这就意味着你可以在使用完之后将其修改,而不会保留原始的数据,所以使用字符数组的话,安全保密性高的信息将不会存在于系统中被他人看见。 -
统计每个字符串的个数
// 统计每个字符串的个数
String str = “123 233 23”;
int x = 0;
for (int i = 0; i <= str.length() - 1; i++) {
String getstr = str.substring(i, i + 1);
if (getstr.equals(" ")) {
x++;
}
}
System.out.print(x); -
Collection 和 Collections的区别
Collection是java.util下的接口,它包含各种有关集合结构的父接口
Collections是java.util下的类,它包含各种有关集合操作的静态方法
集合
-
集合数组互相转换
List list = new ArrayList<>();
list.add(“a”);
list.add(“b”);
list.add(“c”);
for (String string : list) {
System.out.println(string.toString());
}
// 将集合转换成数组
Object object[] = list.toArray();
for (Object object2 : object) {
System.out.println(object2);
}//将数组转换成集合 List<Object> list1 = new ArrayList<>(); list1 = Arrays.asList(object); for (Object object2 : list1) { System.out.println(object2); }
-
将一个集合的元素复制到另一个集合
List list1 = new ArrayList<>();
list1.add(“1”);
list1.add(“2”);
list1.add(“3”);List<String> list2 = new ArrayList<>(Arrays.asList(new String[list1.size()])); // 将一个集合的元素复制到另一个集合 Collections.copy(list2, list1); for (Object object2 : list2) { System.out.println("object2:" + object2); }
-
如何实现集合排序
通过Collections类的sort()方法,如下所示
List in1 = new ArrayList<>();
in1.add(2);
in1.add(3);
in1.add(1);
in1.add(6);
in1.add(5);
Collections.sort(in1);// 没排序前打出23165,排序之后打出123456
for (Integer integer : in1) {
System.out.println(integer);
} -
如何实现数组排序
通过Arrays类的sort()方法,如下所示
int in[] = new int[3];
in[0] = 5;
in[1] = 3;
in[2] = 1;
Arrays.sort(in);//没排序之前打出531,排序之后打出135
for (int i = 0; i < in.length; i++) {
System.out.println(in[i]);
}
List
-
List
list是一个接口,继承Collection接口代表有序的队列
-
ArrayList(父类:AbstractList)
实现了list接口.ArrayList是一个数组队列,相当于动态数组。它由数组实现,随机访问效率高,随机插入,删除效率低
数组结构操作起来查询效率高,因为可以下标直接找到元素,但插入和删除比较低,因为要做移位操作
特点:
1.由数组实现
2.非线程安全
3.有序的
4.非同步
5.允许null元素和相同的元素
6.默认大小是10个元素,长度不够时自动增加长度的50% -
LinkedList(父类:AbstractSequentialList)
实现了list接口.LinkedList是一个双向链表,随机访问效率低,但随机插入,删除效率高
特点:
1.双向链表
2.非线程安全
3.允许null元素
4.非同步
5.先进先出 -
Vector(父类:AbstractList)
实现了list接口.也是一个动态数组,由数组实现,初始容量为10,当元素个数超过容量长度时增加一倍,一次扩容后容量为20
特点:
1.动态数组
2.线程安全的
3.有序的
4.同步
5.允许null元素和相同的元素 -
Stack(父类:Vector)
实现了list接口.Stack是栈,它的特性是:先进后出
特点:
1.数组实现
2.先进后出
3.下标从1开始 -
arrayList,Vector,hashMap,HashSet的初始容量,加载因子,扩容增量
学习网址:http://www.cnblogs.com/xiezie/p/5511840.html
-
队列和栈的区别
1.队列先进先出,栈先进后出
2.遍历数据速度不同
a)栈只能从头部取数据,也就是说最先放进去的要最后才能取出来,遍历的时候还要为数据开辟临时空间
b)队列则不同,它基于地址指针进行遍历,可以从头部开始,也可以从尾部开始,但不能同时遍历,无需开辟临时空间
3.集合当中最具有代表性的队列是:LinkedList,栈是:Stack. LinkedList使用add()方法将元素添加到集合当中去,Stack使用push()方法将元素添加到集合当中去
LinkedList使用remove()方法移除元素,Stack使用pop()方法移除最后添加的元素,Stack也可以使用通过继承父类Vector而来的remove()方法,
4.什么时候用队列:处理消息,处理先后顺序的数据 -
堆和栈的区别
Set
- Set
Set集合中的元素是无序且唯一的,允许null元素
部分集合的比较
- HashSet(父类:AbstractSet)
它实现了Set接口,HashSet使用哈希方法存储元素,具有最好的性能,但元素没有顺序(无序),
特点:
1.非线程安全
2.无序的
一些集合的比较
-
List和set的区别
1.list是一个数组,set是一个容器
2.list可以放多个null,set只能放一个null
3.list是有序的,set是无序的 -
HashMap和HashSet区别
1.HashMap实现了Map接口;HashSet实现了Set接口
2.HashMap存储键值对;HashSet存储对象
3.HashMap使用put()方法将元素放入Map中;HashSet使用add()方法将元素放入Set中
4.HashMap中使用键对象来计算HashCode值;HashSet使用成员对象来计算HashCode值
5.HashMap比较快,因为是使用唯一的键获取对象,HashSet和HashMap存取速度基本一致,HashSet把HashMap进行了封装 -
ArrayList LinkedList Vector Stack区别
HashMap与Hashtable的区别:
1.HashMap实现Map接口,继承AbstractMap;Hashtable实现了Map接口继承Dictionary
2.HashMap非线程安全;Hashtable线程安全
3.HashMap允许null键和null值;Hashtable的key和value都不允许为null
4.HashMap的数组的默认大小是16,HashTable数组的默认大小是11 -
HashSet LinkedHashSet TreeSet三者的区别
Map
-
Map
请注意:Map没有实现Collection接口,Map提供Key到Value的映射 -
Map的四种遍历方式:
package com.yr.test;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
public class Test {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("1", "1");
map.put("2", "2");
map.put("3", "3");
// 第一种:通过Map.keySet遍历key和value(普遍使用)
for (String key : map.keySet()) {
System.out.println("key= " + key + " and value= " + map.get(key));
}
// 第二种:通过Map.entrySet使用iterator遍历key和value
Iterator<Entry<String, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
Entry<String, String> entry = it.next();
System.out.println("key: " + entry.getKey() + "|value: " + entry.getValue() + "\n");
}
// 第三种:通过Map.entrySet遍历key和value(推荐,尤其是容量大时)
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
}
// 通过Map.values()遍历所有的value,但不能遍历key
for (Object v : map.values()) {
System.out.println("value= " + v);
}
}
}
-
HashMap(父类:AbstractMap)
实现Map接口;使用hash算法,里面的数据是无序的;并且存储的是键值对key-value,具有映射关系的数据
特点:
1.无序的
2.非线程安全
3.key不允许重复,value可以重复
4.允许null键和null值
5.键值对形式
6.hashMap的默认长度是16个元素 -
我们能否使用任何类作为Map的key,需要注意什么?
可以。但是需要注意以下几点
1.如果类重写了equals()方法,它也应该重写hashCode()方法
2.如果一个类没有使用equals(),你不应该在hashCode()中使用它
3.类的所有实例需要遵循与equals()和hashCode()的相关规则
4.用户自定义的key类最好是不可变的,这样hashCode()的值可以被缓存起来,拥有更好的性能,不可变的类也可以确保hashCode()和equals()在未来不会改变. -
如何决定选用HashMap还是TreeMap
答:对于在Map中插入,删除,定位这类操作,HashMap是最好的选择。如果数据需要按照一定的顺序进行存储可以选择TreeMap -
HashMap怎么同步
使用synchronized关键字
用lock
读写锁
用java.util.concurrent.ConcurrentHashMap类
concurrentHashMap最快,synchronized关键字最慢 -
如何线程安全的使用hashMap,ArrayList,HashSet?
方法:
Map
1. 直接使用Hashtable
2. 使用Collections的方法:Map<String, Object> map = Collections.synchronizedMap(new HashMap<String, Object>());
3. 使用ConcurrentHashMap:Map<String, Object> concurrentHashMap = new ConcurrentHashMap<>();//看ConcurrentHashMap 效率最高速度最快
List
1.直接使用Vector
2.继承ArrayList重写或编写自己所需方法,用synchronize修饰,在其中调用ArrayList方法
3.List list = Collections.synchronizedList(new ArrayList());
Set
1.Set<String> s = Sets.newConcurrentHashSet();
2.Set<T> s1 = Collections.synchronizedSet(set)
3.Set<String> words = ConcurrentHashMap.<String> newKeySet();
4.ConcurrentHashMap<String, Long> map = new ConcurrentHashMap<String, Long>();
Set<String> set2 = map.keySet(1L);
性能对比:
这是要靠数据说话的时代,下面的测试通过HashTable,synchronizedMap,ConcurrentHashMap三种不同的方法创建Map对象使用ExecutorService来并非运行5个线程,每个线程添加获取500K个元素
package com.yr.test;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class Test {
public final static int THREAD_POOL_SIZE = 5;
public static Map<String, Integer> crunchifyHashTableObject = null;
public static Map<String, Integer> crunchifySynchronizedMapObject = null;
public static Map<String, Integer> crunchifyConcurrentHashMapObject = null;
public static void main(String[] args) throws InterruptedException {
// Test with Hashtable Object
crunchifyHashTableObject = new Hashtable<>();
crunchifyPerformTest(crunchifyHashTableObject);
// Test with synchronizedMap Object
crunchifySynchronizedMapObject = Collections.synchronizedMap(new HashMap<String, Integer>());
crunchifyPerformTest(crunchifySynchronizedMapObject);
// Test with ConcurrentHashMap Object
crunchifyConcurrentHashMapObject = new ConcurrentHashMap<>();
crunchifyPerformTest(crunchifyConcurrentHashMapObject);
}
public static void crunchifyPerformTest(final Map<String, Integer> crunchifyThreads) throws InterruptedException {
System.out.println("Test started for: " + crunchifyThreads.getClass());
long averageTime = 0;
for (int i = 0; i < 5; i++) {
long startTime = System.nanoTime();
ExecutorService crunchifyExServer = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
for (int j = 0; j < THREAD_POOL_SIZE; j++) {
crunchifyExServer.execute(new Runnable() {
@SuppressWarnings("unused")
@Override
public void run() {
for (int i = 0; i < 500000; i++) {
Integer crunchifyRandomNumber = (int) Math.ceil(Math.random() * 550000);
// Retrieve value. We are not using it anywhere
Integer crunchifyValue = crunchifyThreads.get(String.valueOf(crunchifyRandomNumber));
// Put value
crunchifyThreads.put(String.valueOf(crunchifyRandomNumber), crunchifyRandomNumber);
}
}
});
}
crunchifyExServer.shutdown();
crunchifyExServer.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
long entTime = System.nanoTime();
long totalTime = (entTime - startTime) / 1000000L;
averageTime += totalTime;
System.out.println("2500K entried added/retrieved in " + totalTime + " ms");
}
System.out.println(crunchifyThreads.getClass() + " 平均时间 " + averageTime / 5 + " ms\n");
}
}
效果对比:
总结
通过以上对比,我们发现ConcurrentHashMap既可以保证线程安全,速度更快,效率更高,而线程安全的HashMap和HashTable不相上下
所以如果需要保证线程安全的情况下首选ConcurrentHashMap
4.Set set = Collections.synchronizedSet(new HashSet<>());
5.List list = Collections.synchronizedList(new ArrayList<>());
-
为什么Map没有继承Collection接口
Map和List,Set不同Map放的是键值对,List和Set放的是一个个的对象,说到底是因为数据结构不同,操作不一样,所以接口是分开的 -
poll()方法和remove()方法有什么区别
poll()方法和remove()方法都是移除一个元素,poll()移除元素失败返回null,remove()方法移除失败抛出异常 -
Iterator和ListIterator有什么区别
1.Iterator可以用来遍历Set,Map,和List,而ListIterator只能用来遍历List
2.Iterator对集合只能是向前遍历,ListIterator既可以向前又可以向后
3.ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个元素和后一个元素的索引 -
数组(Array)和列表(ArrayList)有什么区别和联系
1.数组的长度是固定的,一旦创建无法扩容,而集合的长度可以动态增加
2.数组用来存放基本类型的数据,集合用来存放对象的引用
3.数组是线型结构(是java语言中内置的数据类型,是线型排列的)执行效率是最快的,ArrayList是基于数组创建的容器类
4.它们之间可以通过toArray和Arrays.asList互相转换 -
哪些集合类提供对元素的随机访问?
ArrayList,HashMap,TreeMap,HashTable提供对元素的随机访问 -
什么情况下使用数组比集合好?
当你需要使用到多维数组的时候,那么使用数组就比使用集合好 -
什么是序列化,反序列化?序列化有什么作用?
1.序列化:所谓序列化就是一种用来处理对象流的机制,将对象的内容进行流化,可以对流化后的对象进行读写操作
2.反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程
3.作用:
a)把一个对象序列化方便传输和存储
b)用于对象的持久化,将对象写到磁盘中,需要用的时候再反序列化取出来
4.什么时候使用到序列化
a)将对象进行网络传输的时候,比如说使用socket的时候
b)Rmi远程接口调用需要使用到对象的时候,就需要将对象序列化
c)redis传输对象的时候
d)将内存当中的对象保存到文件或数据库当中将对象序列化 -
异常
异常指在程序中遇到的各种状况,它是一个事件,发生在程序运行期间,干扰了正常的指令流程。Java通过API中Throwable类的众多子类描述各种不同的异常。因而,Java异常都是对象,是Throwable子类的实例,描述了出现在一段编码中的错误条件。当条件生成时,错误将引发异常。
异常又分为运行时异常和非运行时异常(编译时异常)
运行时异常:编译时不会报错,运行时可能会出现错误,这类异常一般是由程序逻辑错误引起的。
非运行时异常:如果不处理,编译不能通过,对于这种异常我们可以通过throws抛出或者通过try catch将异常捕获
错误:错误是毁灭性的,无法通过修改代码而解决
处理异常机制:throws抛出异常,try catch捕获异常
总结:
try块:用于捕获异常,其后可接零个或多个catch块,如果没有catch块则必须跟一个finally块
catch块:用于处理try捕获到的异常
finally:无论是否捕获或处理异常,finally块的语句都会被执行,当在try块或catch块中遇到return语句时finally语句块
将在方法返回之前被执行
注意:以下四种特殊情况finally块中的语句将不会被执行
1.finally块中发生了异常
2.在前面的代码中使用了System.exit()退出程序
3.程序所在的线程死亡
4.关闭cpu
自定义异常:
用法:提供一个类继承Exception,在提供一个参数为String类型的构造方法
-
throws和throw的区别
1.throws声明在方法后面,表示该方法可能要抛出异常,由该方法的调用者来处理;throw声明在方法体内,由方法体内的语句处理
2.throws出现在方法函数头;而throw出现在函数体
3.throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常
4.两种方式都是消极处理异常的方式,只是抛出或可能抛出异常,但不会由函数去处理异常,真正处理异常的是该方法的调用者 -
什么是异常链?
异常链是java中非常流行的异常处理概念,是指在进行一个异常处理是抛出了另一个异常,由此产生了一个异常链条
线程
-
线程
1.进程:进程是指内存中运行的应用程序,每个进程都有自己独立的一块内存,一个进程可以可以启动运行多个线程
2.线程:线程可以理解为在进程中独立运行的子任务,一个进程包含多个线程,多个线程组成一个进程
3.多线程:多线程是实现多种任务的方式,充分利用cpu资源,提高程序执行效率 -
创建线程的三种方式:
1.继承Thread类,重写run()方法
2.实现Runable接口,重写run()方法
3.实现Callable接口,重写call()方法:这种方式创建的线程可以有返回值
package com.yr;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutrueTask;
public class CallableThreadDemo implements Callable<Integer> {
public static void main(String[] args) {
CallableThreadDemo ctd = new CallableThreadDemo();
// 1.执行Callable方式,需要FutrueTask实现类的支持,用于接收运算结果
FutrueTask<Integer> result = new FutrueTask<Integer>(ctd);
new Thread(result).start();
// 2.接收线程运算后的结果
try {
Integer sum = result.get(); // FutrueTask 可用于闭锁
System.out.println(sum);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
System.out.println(sum);
}
return sum;
}
}
- Thread和Runnable的区别:
如果一个类继承Thread,则不适合资源共享,但是如果实现了Runable接口的话,则很容易实现资源共享
-
Thread和Runnable的区别:
1.新建(new Thread()):
当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)
2.就绪(runnable):
线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候得到CPU资源运行t1.start()
3.运行(running):
线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束
4.堵塞(blocked):
由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。
正在睡眠:用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。
正在等待:调用wait()方法。(调用notify()方法回到就绪状态)
5.死亡(dead):
当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行
自然终止:正常运行run()方法后终止
异常终止:调用stop()方法让一个线程终止运行 -
线程的优先级
线程的优先级默认是5,最大是10,最小是1,并不是优先级高的就先执行,只是获取cpu资源的概率比较大 -
线程常用的方法
1.sleep()使调用该方法的线程休眠一段时间
2.join()使调用该方法的线程运行完成之后才执行其他线程,例如在线程t1中调用t2.join()那么必须等t2线程执行完成之后,才能执行t1线程
3.wait()使当前线程暂停并释放对象锁,让其他线程能够进入synchronized 语句
4.notify()随机唤醒单个线程
5.notifyAll()唤醒所有线程
6.getId() 得到线程的唯一标识
7.currentThread () 返回调用该方法的线程对象
8.interrput() 中断线程
9.setDaemon(true) 把该用户线程设置成守护线程
10.setPriority(10) 设置线程的优先级
11.yield() 让步,暂停当前线程,让其他线程先执行 -
多线程
多线程:指同一时间运行时产生了多个线程
好处:更好的利用cpu资源,提高程序执行速度和效率
-
线程池
线程池顾名思义就是事先初始化多个线程放入一个池中(也可以说是容器),需要用的时候就从线程池里面取,而不用自行创建,用完之后再放回线程池里面,从而减少创建和销毁线程对象的开销 -
连接池:
像我们平时获得连接,都是直接对数据库进行操作,每次要用的时候,都会直接对数据库进行连接,不用的话就close掉,这样做对数据库负担很大,而且也很耗时,耗资源。连接池就像一个容器,将连接管理起来,连接池有初始化连接数,最大连接数,等待关闭时间等等参数,比如初始化有5个连接,这5个连接就会一直连接数据库。我们要获取连接就从连接池里取,不用的时候就close,不过这里的close只是断开我们和连接池的连接,连接池和数据库并没有断开,有了连接池我们就避免了直接对数据库进行连接,极大的减轻了数据的负担,这里的最大连接数据比如说是100,初始化5个,如果来了第六个人那么连接池就会自动创建一个连接,最多创建100个,而等待关闭时间就是,多长时间没对连接池进行操作就自动断开,时间自行设置1.连接池的好处:
a)资源重用:避免了频繁创建连接,释放连接引起大量的性能开销
b)更快的响应速度:连接池是事先就初始化好的,所以直接利用就行了,避免了数据库连接初始化和释放过程的时间开销,从而缩短了系统整体响应时间
c)统一的连接,方便管理,避免了数据库连接泄漏:可根据预先的连接占用超时设定,强制收回被占用的连接,避免了常规数据库连接操作中可能出现的资源泄漏 -
线程池的四种创建方法
package com.yr.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
1.ExecutorService executorService = Executors.newCachedThreadPool();
/**创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程,线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。*/
public void method1() {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(index * 1000);
} catch (Exception e) {
e.printStackTrace();
}
executorService.execute(new Runnable() {
public void run() {
System.out.println(index);
}
});
}
}
2.ExecutorService executorService = Executors.newFixedThreadPool(3);
/**创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。 因为线程池大小为3,每个任务输出index后sleep2秒,所以每两秒打印3个数字。定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()*/
public void method2() {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int index = i;
executorService.execute(new Runnable() {
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
3.ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
/**创建一个定长线程池,支持定时及周期性任务执行。延迟执行 表示延迟1秒后每3秒执行一次。*/
public void method3() {
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
service.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println("delay 1 seconds, and excute every 3 seconds");
}
}, 1, 3, TimeUnit.SECONDS);
}
4.ExecutorService service = Executors.newSingleThreadExecutor();
/**创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行*/
public void method4() {
ExecutorService service = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
service.execute(new Runnable() {
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
-
守护线程:(也叫精灵线程)
1.在java中有两类线程:User Thread(用户线程)和Daemon Threa(守护线程)
2.守护线程的作用就是用来服务用户线程的,比如垃圾回收线程就是一个很称职的守护者
3.用户在编写程序的时候也可自行设置守护线程:使用Thread类的setDaemon(true)方法可以将线程设置为守护线程
注意:需要在调用start()方法前调用这个方法,否则会抛出IllegalThreadStateException异常。如果线程都是守护线程那么就没有线 程可守护这样子的话什么都不会执行 -
用户线程和守护线程
1.用户线程是用户创建的一般线程,而守护线程则是为用户线程提供服务的
2.区别:当用户线程结束后,虚拟机会检查系统中是否还存在用户线程,如果存在正常调用,如果不存在程序就会终止 -
synchronized
1.用synchronized修饰的方法叫同步方法,用synchronized修饰的代码块叫同步块,synchronized是针对于同一个对象来说的
2.使用synchronized的目的:防止多个线程访问同一个资源时对资源的破坏
3.synchronized可以在任何对象和方法上加锁,而加锁的这段代码称为互斥区或临界区。加了synchronized的方法被称为同步方法也称为是线程安全的,当一个线程想要执行同步方法里的代码时,线程首先尝试去拿这把锁,如果能拿到这把锁那么就可以执行synchronized里面的代码,如果拿不到就会不断的尝试去拿这把锁,直到拿到为止
4.在run方法上用synchronized修饰,使run方法在被调用时是以排队的方式进行处理.当一个线程调用run前,先判断run方法有没有被上锁
5.如果有上锁说明其他线程正在调用run方法,必须等其他线程对run方法调用结束后才可以执行run方法
6.注意:同一个对象当中的同步块和同步方法只能同时进入一个,synchronized是对于同一个对象来说的,如果不是同一个对象就没有用 -
同步块和同步方法
同步方法是在方法上加synchronized实现加锁,同步块则是在方法内部加锁
很明显,同步方法锁的范围比较大,而同步块范围要小点,一般同步的范围越大,需要同步的代码就越多,所以性能就越差,范围越小,性能越好 -
停止线程的方法
1.run()方法运行完毕正常退出
2.stop()方法强制停止,此方法已过时不推荐使用
3.interrupt()方法中断线程,调用此方法只是在当前线程中打了一个停止的记号,并不会真正的停止线程
4.抛异常 -
线程出现异常怎么处理
使用try cache捕获异常,注意:线程出现异常只能使用try cache捕获异常,不能使用throws抛出异常,因为run方法不支持throws语句
问:为什么run方法不支持throws
答:因为run方法是通过重写父类或实现接口而来的,父类或接口里面的run方法没有做抛出声明,所以子类或实现类就没办法做出throws声明
相反,如果父类做出throws什么子类可以不做 -
哪些方法可以使线程进入阻塞状态
1.yield():让步,暂停当前线程,让其他线程先执行
2.sleep():使线程进入休眠状态
3.join():调用该方法的线程必须执行完毕才能执行其他线程
4.wait():使线程进入等待
5.同步块和同步方法 -
wait
wait等待,使线程进入暂时的阻塞状态,需要由notify唤醒。1.注意
a)必须在同步块或同步方法中使用
b)wait()方法和notify()必须由同一个锁对象调用
2.wait和sleep方法的区别
a)都会使线程进入暂时的阻塞状态
b)wait在同步线程中等待,其他线程可以进入,sleep在同步线程中等待,其他线程不可以进入
c)wait()释放资源,释放锁;sleep()不释放资源,不释放锁。
d)sleep()必须捕获或抛出异常,wait()则不需要捕获异常
e)sleep()可以在任何地方使用,wait()只能在同步方法或同步块中使用
3.常用方法
a)wait():在使用notify()方法唤醒之前,会一直等待
b)wait(long millis):有参数的就是等待多少时长,规定时间没有唤醒就不在等待
c)notify():唤醒单个等待的线程
d)notifyAll():唤醒所有被等待的线程 -
wait(),notify()为什么只能在同步方法或同步块当中使用
当一个线程需要调用wait()方法的时候,这个线程必须拥有该对象的锁,当线程调用notify()方法时,它会释放这个对象的锁。由于这些方法
都需要线程持有对象的锁,这样就只能通过同步来实现,所以它们只能在同步方法或同步块中使用 -
什么情况造成线程阻塞
1.线程执行了Thread.sleep(1000)方法,当前线程放弃cpu,睡眠一段时间,然后再恢复执行
2.线程执行一段同步代码,但尚且无法获得相关的同步锁,只能进入阻塞状态,等获取了同步锁,才能恢复执行
3.线程执行了一个对象的wait()方法,就会进入阻塞状态,直到被唤醒
4.线程执行某些io操作,因为等待相关资源而进入了阻塞状态,比如说system.in,但是没有收到键盘的输入,则进入阻塞状态 -
线程同步的实现方式
1.同步方法:在方法上加上synchronized实现线程同步
2.同步块:使用synchronized修饰的语句块会自动上锁实现同步
3.等待wait(),notify()唤醒
4.使用重入锁实现线程同步
ReentrantLock类是可重入,互斥,实现了Lock接口的锁
a)创建一个锁的实例:ReentrantLock lock = new ReentrantLock();
b)获取锁:lock.lock();
c)释放锁:lock.unlock(); -
同步和异步
1.同步:指发送一个请求,需要等待返回,然后才能发送另一个请求,有个等待过程(同时只能做一件事情)
2.异步:指发送一个请求,不需要等待返回,随时可以发送另一个请求,不需要等待(可以同时做多件事情)
3.区别:
a)同步需要等待,异步不需要等待,项目中都会优先选择异步交互方式,因为不需要等待,效率高
4.使用情况:
a)比如银行的转账系统,对数据库的保存操作等待必须使用同步交互,其他情况优先选择异步交互 -
什么是死锁
所谓死锁:是指多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象 -
当一个线程进入一个对象的一个synchronized方法后,其他线程是否可以进入?
不能,一个对象的synchronized方法只能有一个线程访问 -
简述synchronized和Lock的异同
1.相同点:都能实现线程同步,Lock能完成synchronized所实现的所有功能
2.不同点:Lock比synchronized性能更全面,比如:时间锁等候,中断锁等候,锁投票什么的。
3.synchronized会自动释放锁,而Lock一定要手动释放锁,而且必须在finally块中释放,否则一旦程序出现异常,锁就有可能永远得不到释放 -
什么是线程的局部变量
线程的局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程直接共享。java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式
锁
-
ReentrantLock
ReentrantLock:可重入互斥锁,它是一种递归无阻塞的同步机制。它由最近成功获取锁还没有释放的线程所拥有(假如有AB两个线程,如果A先获取了锁那么B就要在外面等待,直到A释放了锁)当锁没有被线程所拥有时,调用Lock的线程将成功获取锁并返回1.提供的方法
Lock lock = new ReentrantLock();新建一把锁 a)获取锁:lock.lock() b)释放锁:lock.unlock()
2.实例
package com.yr.thread; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 互斥锁实现线程同步 * * @author liucong * * @date 2017年8月7日 */ public class MyThread implements Runnable { private Lock lock; public MyThread(Lock lock) { this.lock = lock; } @Override public void run() { /** * 上锁之后直到其中一个线程释放锁,另一个线程才会获取,所以可以实现同步的效果 */ lock.lock();// 获取锁 try { System.out.println(Thread.currentThread().getName() + ":aaaaaa"); Thread.sleep(2000); } catch (Exception e) { e.printStackTrace(); } lock.unlock();// 释放锁 } public static void main(String[] args) { Lock lock = new ReentrantLock();// 创建一把锁 MyThread myThread = new MyThread(lock); // 线程1 Thread thread1 = new Thread(myThread); thread1.setName("thread1"); thread1.start(); // 线程2 Thread thread2 = new Thread(myThread); thread2.setName("thread2"); thread2.start(); } }
-
ReentrantReadWriteLock
ReentrantReadWriteLock:读写锁,又叫共享锁。
特点:读读共享 读写互斥 写写互斥 写读互斥
好处:提供了读写锁分离,让读锁共享(就是多个线程可以共享读锁)
1.提供的方法
Lock lock = new ReentrantReadWriteLock();获取一把读写锁
a)获取读锁:lock.readLock().lock();释放读锁:lock.readLock().unLock();
b)获取写锁:lock.writeLock();释放写锁:lock.writeLock().unLock();
2.实例1
package com.yr.thread;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 读写锁读读和写写操作
*
* @author liucong
*
* @date 2017年8月7日
*/
public class MyThread implements Runnable {
private ReentrantReadWriteLock readWriteLock;
public MyThread(ReentrantReadWriteLock readWriteLock) {
this.readWriteLock = readWriteLock;
}
@Override
public void run() {
// 获取读锁,因为读读共享,所以两个线程可以同时进来,同时打出aaaaaa
readWriteLock.readLock().lock();
// 获取写锁,因为写写互斥,所以两个线程只能同时进来一个,所以会先后打出aaaaaa
// readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + ":aaaaaa");
Thread.sleep(2000);// 为了看效果这里休眠2秒
} catch (Exception e) {
e.printStackTrace();
}
readWriteLock.readLock().unlock();// 释放读锁
// readWriteLock.writeLock().unlock();// 释放写锁
}
public static void main(String[] args) {
// 创建一把读写锁
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
MyThread myThread = new MyThread(readWriteLock);
// 线程1
Thread thread1 = new Thread(myThread);
thread1.setName("thread1");
thread1.start();
// 线程2
Thread thread2 = new Thread(myThread);
thread2.setName("thread2");
thread2.start();
}
}
3.实例2
package com.yr.thread;
/**
* 线程1
*
* @author liucong
*
* @date 2017年8月9日
*/
public class Thread1 extends Thread {
private ReadWrite readWrite;
public Thread1(ReadWrite readWrite) {
this.readWrite = readWrite;
}
@Override
public void run() {
readWrite.test1();
readWrite.test2();
}
}
package com.yr.thread;
/**
* 线程2
*
* @author liucong
*
* @date 2017年8月9日
*/
public class Thread2 extends Thread {
private ReadWrite readWrite;
public Thread2(ReadWrite readWrite) {
this.readWrite = readWrite;
}
@Override
public void run() {
readWrite.test1();
readWrite.test2();
}
}
package com.yr.thread;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 读写锁读写操作
*
* @author liucong
*
* @date 2017年8月9日
*/
public class ReadWrite {
private ReentrantReadWriteLock rw;
public ReadWrite(ReentrantReadWriteLock rw) {
this.rw = rw;
}
/**
* 只要两个方法有一个是写锁,那么就会读写,写写互斥,将会先后打出消息 如果两个方法都是读锁,那么读读共享,将会同时打出消息
*/
public void test1() {
rw.readLock().lock();
try {
System.out.println("test1");
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
rw.readLock().unlock();
}
public void test2() {
rw.writeLock().lock();
try {
System.out.println("test2");
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
rw.writeLock().unlock();
}
public static void main(String[] args) {
// 新建一把读写锁
ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
ReadWrite readWrite = new ReadWrite(rw);
Thread1 th1 = new Thread1(readWrite);
th1.start();// 启动线程1
Thread2 th2 = new Thread2(readWrite);
th2.start();// 启动线程2
}
}
30道java线程面试:
1.什么是线程安全?
如果你的代码在多线程下执行和在单线程执行永远能都能获得一样的结果,那么这就可以称为是线程安全的
2.什么是线程?
线程是进程当中独立运行的子任务
3.线程和进程有什么区别?
线程是进程的子集,一个进程可以有多个线程。多个线程组成一个进程
4.如何在java中实现线程?
a)继承Thread,重写run()方法
b)实现Runable,重写run()方法
c)实现Callable,重写call()方法(jdk1.5才开始支持Callable方式实现线程)
5.用Runable还是Thread?
使用Runable,原因:java只支持单一继承,如果继承了Thread就不能继承其他类,扩展性不好,实现Runable接口,可以多继承,所以更灵活
6.Thread类当中的start()方法和run()有什么区别?
a)start()方法:使线程进入就绪状态;run()方法:使线程进入运行状态
b)调用start()方法是启动一个线程,直接调用run()方法和普通方法没区别,启动不了线程
7.java当中的Runable和Callable有什么区别?
a)Runable:jdk1.0就开始支持,没有返回值,无法抛出异常
b)Callable:jdk1.5才开始支持,可以有返回值,可以抛异常
8.Java当中如何停止一个线程
a)run()方法执行完毕,正常退出
b)stop():此方法已经过时,不推荐使用
c)Interrupt():中断线程,调用此方法只是在当前线程中打了一个停止的记号,并不会真正的停止线程
d)抛异常停止线程
9.一个线程运行时发生异常会怎样?
如果异常没有被捕获,那么会造成该线程停止,另外重要的一点是:如果这个线程是某个对象的监视器,那么这个对象的监视器会被立即释放
10.Java中什么是竞态条件?
当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件,在临界区中使用适当的同步就可以避免竞态条件,临界区的实现方式有两种一种是用synchronized一种是用Lock显示锁实现
11.Java中的同步集合与并发集合有什么区别?
同步集合和并发集合都为多线程和并发提供了合适的线程安全的集合,不过并发集合的可扩展性更高。在jdk1.5之前程序员只有使用同步集合,且用在并发的时候会导致争用,阻碍了系统的扩展性,jdk1.5增加了像ConcurrentHashMap线程安全的集合提高了系统可扩展性
12.如何在两个线程直接共享数据?
通过在线程之间共享对象就可以了,然后通过wait/notify/notifyAll进行唤醒和等待
13.Java中的notify()和notifyAll()有什么区别?
a)notify():唤醒单个线程,但是notify()方法不能唤醒某个具体的线程,所以只有一个线程的在等待的时候它才有用武之地
b)notifyAll():唤醒正在等待的所有线程
14.生产者消费者模型的作用是什么?
a)通过平衡生产者的生产能力和消费者的消费能力来提升整个系统的运行效率,这是生产者消费者模型最重要的作用
b)解耦。这是生产者消费者模型附带的作用,解耦意味着生产者和消费者之间的联系少,联系越少越可以独自发展而不需要收到相互的制约
15.为什么wait(),notify(),notifyAll()这些方法不再Thread类里面
a)这些方法都需要获取对象的锁,如果线程需要某些锁那么调用对象的wait()方法就有意义了,如果wait()方法定义在Thread类中,那么线程正在等待哪个锁就不明显了
b)简单点说:由于wait,notify,notifyAll都是锁级别的操作,所以把他们定义在Object类中,因为所属于对象
16.java当中interrupt(),interrupted()和isinterrupted()方法有什么区别?
a)Interrupt():该方法用于中断线程,调用该方法的线程状态将被置为”中断”状态,会设置中断表示为true
b)Interrupted():调用该方法来检查中断状态是会清除中断状态
c)isInterrupted():此方法则不会清除中断状态
17.为什么wait(),notify()方法要在同步方法或同步块当中使用?
a)因为java API 强制要求这样做,如果不这么做代码会抛出异常,还有一个原因,当一个线程需要调用wait()方法的时候,这个线程必须拥有该对象的 锁,由于这些方法需要线程持有对象的锁,这样就只能通过同步来实现,所以它们只能在同步方法或同步块中使用
b)简单点说:这是JDK强制的,这些方法在调用前都必须获得对象的锁
18.什么是线程池?为什么要使用它?
线程池顾名思义就是事先初始化多个线程放入一个池中(也可以说是容器),需要用的时候就从线程池里面取,而不用自行创建,用完之后再放回线程池里面,从而减少创建和销毁线程对象的开销,并节省昂贵的资源和时间
19.java中的活锁和死锁的区别
a)活锁和死锁类似,不同之处在于处于活锁的线程或进程的状态是不断改变的,而死锁的状态就是等待,活锁有可能自行解开,而死锁必须借助外部的力量才能解开
b)举例:
i.死锁:一条马路,迎面开来一辆兰博基尼(A)和一辆比亚迪(B),谁都不想让路,于是造成阻塞,没法改变阻塞的状态,于是叫交警处理(外部力量)
ii.活锁:一条马路,迎面开来两辆汽车A和B,A比较礼貌示意B先过,B也示意A先过,于是两人谦让谁也过不去,(只要一个人同意先过去,这种状就可以改变,也就是说,活锁有可能自行解开)
20.怎么检测一个线程是否拥有锁
a)Java.lang.Thread中有一个方法叫holdsLock(),它返回true表示当前线程拥有某个对象的锁
b)实例:
package com.yr.test;
public class User extends Thread {
Object object = new Object();
@Override
public void run() {
synchronized (object) {
System.out.println(Thread.holdsLock(object));//返回true表示当前线程拥有该对象的锁
}
}
public static void main(String[] args) {
User user = new User();
user.start();
}
}
21.有三个线程T1,T2,T3你怎么保证他们按顺序执行?
启动三个线程然后再依次使用join()方法,如下图所示:
22.如何避免死锁?
死锁:所谓死锁,是指多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象
比如说:2个人一起吃饭但是只有一双筷子,2人轮流吃(同时拥有2只筷子才能吃)。某一个时候,一个拿了左筷子,一人拿了右筷子,2个人都同时占用一个资源,等待另一个资源,这个时候甲在等待乙吃完并释放它占有的筷子,同理,乙也在等待甲吃完并释放它占有的筷子,这样就陷入了一个死循环,谁也无法继续吃饭。。。
在多线程并发实践中,要说完全避免死锁是不可能的,除非不写线程。我们只能尽量少用wait(),notify()等释放资源,释放锁的方法,提高自己的编码功底,养成良好的编码习惯,尽量避免的死锁的发生。发生死锁必须满足以下条件:
a)互斥:一个资源每次只能被一个进程使用
b)占用且等待:一个进程因请求资源而阻塞时,对已获得的资源保持不放
c)不可抢占:进程已经获得的资源,在未使用完之前,不能强行剥夺
d)循环等待:若干进程执行形成一种头尾相接的循环等待资源关系
只要破坏其中任何一个条件就可以解决死锁的问题
解决办法:
1.加锁顺序:线程按照一定的顺序加锁
2.加锁时限:线程尝试获取锁的时候加锁一定的时限,如果超时,则放弃对该锁的请求,并释放已占有的锁
23.JVM中哪个参数是用来控制线程的堆栈大小的
这个问题很简单,-Xss参数用来控制线程堆栈大小
24.java中synchronized和ReentrantLock有什么区别?
a)相同点:都能实现线程同步,Lock能完成synchronized所实现的所有功能
b)不同点:
i.首先synchronized是关键字,而ReentrantLock是类,这是二者本质的区别,既然是类,那么它就比关键字提供了更多灵活的特性,可以有继承,可以有方法,可以有各种各样的变量,主要体现如下:
1.ReentrantLock可以获取锁的等待时间进行设置,这样就避免了死锁
2.ReentrantLock可以获取各种锁的信息
3.ReentrantLock可以灵活的实现多路通知
4.synchronized会自动释放锁,而Lock一定要手动释放锁,而且必须在finally块中释放,否则一旦程序出现异常,锁就有可能永远得不到释放
ii.总结:ReentrantLock比synchronized性能更全面
25.Thread类中的yield()方法有什么作用?
yidld()方法可以暂停正在执行的线程,让其他具有相同优先级或更高优先级的线程执行
26.Java中ConcurrentHashMap的并发度是什么?
ConcurrentHashMap把实际map划分成若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度获得的,它是ConcurrentHashMap类构造函数的一个可选参数,默认值为16,这意味着最多同时可以有16条线程操作ConcurrentHashMap.这也是ConcurrentHashMap对Hashtable的最大优势,这样在多线程情况下就能避免争用。
27.如果你提交任务时,线程池队列已满,会发生什么?
会抛出一个异常,而不是一直阻塞到线程池队列有空位
28.Java线程池中的submit()和execute()方法有什么区别?
两个方法都可以向线程池提交任务,但execute()方法无返回值,submit()可以返回持有计算结果的Future对象
29.什么是阻塞式方法?
阻塞式方法是指程序会一直等待该方法完成期间不做其他任何事情,比如socket当中的accept()方法就是一直等待客户端连接。
30.Java当中的ReadWriteLock是什么?
所谓ReadWriteLock就是读写锁,用来提升并发程序性能的读写锁分离
ReentrantReadWriteLock:读写锁,又叫共享锁。(它最多支持65535个写锁和65535个读锁)
特点:读读共享 读写互斥 写写互斥 写读互斥
好处:提供了读写锁分离,让读锁共享(就是多个线程可以共享读锁)
31.如果同步块内的线程抛出异常会发生什么?
会释放锁。无论的你同步块是正常退出还是异常退出的,里面的线程都会释放锁。
32.写出三条你遵循的多线程最佳实践
a)给线程起个和它要完成的任务相关的名字:这样可以方便找bug或追踪OrderProcess这种名字比Thread-1,Thread-2好多了,
b)避免锁定和缩小同步的范围,因为锁定的范围越小效率越高,所以相对于同步方法我更喜欢同步块
c)多用同步类,少用wait()和notify(),因为wait()很难实现对复杂控制流的控制
33.如何强制启动一个线程
在java中没有办法强制启用一个线程,就像垃圾回收机制一样,虽然可以手动唤醒,但一样没办法保证它一定会执行
34.多线程中的忙循环是什么?
忙循环就是用循环使一个线程等待,它不像传统的wait()sleep()它们都放弃了对cpu的控制,而忙循环不会放弃cpu,这样做的目的是为了保留cpu的缓存,避免重建缓存减少等待重建的时间。
35.java当中的多线程同步是什么?
在多线程程序下,同步能控制对共享资源的访问,如果没有同步,当一个线程在修改一个共享变量时,另外一个线程在使用或修改同一个变量,这样容易导致程序出现错误的结果。
-
泛型
Java 泛型是java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类,接口和方法的创建中,分别称为泛型类,泛型接口,泛型方法。 -
泛型的好处:
1.类型安全。泛型的主要目标是提高java程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设
2.消除强制类型转换(避免了程序在运行时出现ClassCastException)。泛型的一个附带的好处是:消除源代码中许多强制类型转换,使代码更加可读并且减少了出错的机会
3.增强代码可读性
4.增加代码的重用率 -
枚举
枚举简单的说是一种自定义的数据类型,只不过这种数据类型只包含自定义的特定数据,它是一组有共同特性的数据的集合,java中以对象的方式实现了枚举的功能也就是enum类.
枚举的特征:
1.枚举类的构造函数是私有的,这样做可以保证客户代码没有办法新建一个实例
2.所有枚举的值都是public static final修饰的
3.枚举默认实现了java.lang.Comparable接口;实现此接口的对象列表和数组可以通过Collections.sort和Arrays.sort进行自动排序,实现此接口的对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器
4.Enum覆载了toString方法,因此我们如果调用Color.Blue.toString()默认返回字符串"Blue"
5.Enum还提供了values方法,通过此方法能够方便的遍历所有的枚举值1.问:java中的枚举类型可以继承其他类吗?
答:不可以.因为枚举类型已经继承了java.lang.Enum, java是单一继承2.枚举类和普通类的区别:
枚举类与普通类的不同在于,枚举类的构造器是私有的,这也决定了它如果需要被继承时的特殊性,如果其他类要继承枚举类,需要调用父类的构造方法,由于枚举类的构造器是私有的导致枚举类不可以被其他的外部类继承(有资料说有办法可以继承:需要用到内部类,内部类能访问外部类的任何成员,当然能访问已经被私有的构造器了)3.为什么会有values()方法
values()方法是编译器自动为枚举类加上去的4.好处:整洁性,可读性,可维护性,安全(枚举一般用来定义常量,而且不能被修改)
5.常用方法
(1)values():该方法可以将枚举类型的成员以数组类型的形式返回
(2)valuesOf():该方法可以实现将普通字符串转换为枚举实例
(3)compareTo():此方法用于比较两个枚举对象定义是的顺序
(4)orDInal():此方法用于获取枚举对象的下标
package com.yr.enumTest;
/**
* 枚举常用来存放常量,方便管理,提高代码整洁性,安全
*
* @author liucong
*
* @date 2017年8月18日
*/
public enum EnumTest {
NAME("aa"), AGE, ADDR, SEX;
private String value;
/**
* 枚举类型通过get方法得到值
*
* @return
*/
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
/**
* 构造方法必须是私有的
*/
private EnumTest() {
System.out.println(1);
}
/***
* 有参数的构造方法
*
* @param value
*/
private EnumTest(String value) {
System.out.println(value);
}
public static void main(String[] args) {
System.out.println(EnumTest.ADDR.getValue());
}
}
- EnumSet是什么?
EnumSet是一个用来操作Enum的集合,是一个抽象类,在使用的时候需要定制枚举类型,它的特点也是速度非常快,因为每次add的时候,每个枚举值只占一个长整型的一位。
- 反射(reflection)
1.反射(reflection)机制是什么?
java反射机制是在运行状态中,对于任意一个类,都能知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
Java反射机制:程序运行时,允许改变程序结构或者变量类型,这种语言称为动态语言
2.反射机制能做什么?
a)在运行时判断任意一个对象所属的类
b)在运行时构造任意一个类的对象
c)在运行时判断任意一个类所具有的成员变量和方法
d)在运行是调用任意一个对象的方法
e)生成动态代理
3.你在哪里用到反射机制?
jdbc有一行代码:Class.forName("com.mysql.jdbc.Driver").newInstance();这就是通过反射生成驱动对象实例。
4.通过反射实例化:
Io
- IO
.
流的定义:流是指一连串流动的字符,是以先进先出方式发送信息的通道
在Java.io包中操作文件内容的主要有两大类:输入流和输出流在这两大类的基础上又分为字节流,字符流。在对音乐,和视频等文件进行IO操作时,都是使用字节流;对字符文件进行io操作是使用字符流
io流大致可分为以下2种:
1.字节流:InputStream(输入流)和OutputStream(输出流)
a)InputStream已知直接子类:FileInputStream(常用),ObjectInputStream,SequenceInputStream(等等)
b)OutputStream已知直接子类:FileOutputStream(常用),ObjectOutputStream(等等)
2.字符流:Reader(输入流)和Writer(输出流)
a)Reader已知直接子类:BufferedReader,InputStreamReader(等等)
b)Writer已知直接子类:BufferedWriter,OutputStreamWriter,PrintWriter(等等)
注意:下面所有的流都是基于上面字节流和字符流的
3.Buffered缓冲流:是带缓冲区的处理流,缓冲区的作用主要目的是:避免每次和硬盘打交道,提高数据访问的效率
a)字符缓冲流:BufferedReader,BufferedWriter
b)字节缓冲流:BufferedInputStream,BufferedOutputStream
4.转化流
a)InputStreamReader:是字节流通向字符流的桥梁(能够指定编码)
b)OutputStreamWriter:是字符流通向字节流的桥梁
5.数据流:一般用于网络传输,比如Socket
a)DataInputStream
b)DataOutputStream
因为平时若是我们输出一个8个字节的long类型的或4个字节的float类型,那怎么办呢?可以一个字节一个字节输出,也可以把字节转换成字符串输出
但是这样转换费时间,若是直接输出该多好,因此这个数据流就解决了我们输出数据类型的困难。数据流可以直接输出float类型或long类型,提高了数据读写的效率
6.打印流
a)printStream,printWriter:一般打印到控制台,可以进行控制打印的地方
7.对象流:
a)ObjectInputStream ObjectOutputStream,把封装的对象直接输出,而不是一个个转换成字符串再输出
8.序列化流
a)SequenceInputStream
对象序列化:把对象直接转换成二进制,写入介质中,使用对象流需要实现Serializable接口,否则会报错
而若用transient关键字修饰成员变量,不写入该成员变量,若是引用类型的成员变量为null,值类型的成员变量为0
对文件进行操作:FileInputStream(字节输入流),FileOutputStream(字节输出流),FileReader(字符输入流),FileWriter(字符输出流,可以追加文件)
对管道进行操作:PipeDInputStream(字节输入流),PipedOutStream(字节输出流),PipedReader(字符输入流),PipedWriter(字符输出流)
PipeDInputStream的一个实例要和PipedOutputStream的一个实例共同使用,共同完成管道的读取写入操作。主要用于线程操作。
字节/字符数组:ByteArrayInputStream,ByteArrayOutputStream,CharArrayReader,CharArrayWriter是在内存中开辟了一个字节或字符数组。
- io面试题
1.说下常用的io流
a)字节输入输出流:InputStream OutputStream --------子类--------FileInputStream FileOutputStream
b)字节缓冲流:BufferedInputStream,BufferedOutputStream
c)字符输入输出流:Reader Writer
d)字符缓冲流:BufferedRead BufferedWriter
2.读取一个序列化对象一般使用哪种流?
ObjectInputStream
3.按照传输的单位,分为哪两种流?他们的父类叫什么?
a)字节流,字符流
b)字节流的父类:InputStream OutputStream
c)字符流的父类:Reader Writer
4.按照传输的方向可分为哪两种?
a)输入流:InputStream
b)输出流:OutputStream
5.按照实现功能分为哪两种,举例说明.
a)节点流,处理流
b)节点流:InputStream,OutputStream
c)处理流:InputStreamReader(将字节流转换成字符流),OutputStreamWriter(将字符流转换成字节流)
6.什么是节点流,什么是处理流,它们各有什么用处,处理流的创建有什么特征
a)节点流:直接与数据源相连,用于输入或输出
b)处理流:在节点流的基础上对之进行加工,进行一些功能的扩展
c)特征:处理流的构造器必须要传入节点流的子类
7.BufferedReader属于哪种流?主要用来做什么?,它里面有哪些经典的方法?
BufferedReader属于处理流当中的字符缓冲流,用来将读取的内容存在内存里面,避免每次和硬盘打交道,减少对磁盘的伤害,提高数据访问的效率, 主要 有readLine()方法,用来每次读取一行数据
8.如果我要对字节流进行大量的从硬盘读取,要使用哪个流,为什么?
使用字节缓冲流:BufferedInputStream,因为它可以将读取的内容存在内存当中,减少对硬盘的伤害,提高数据的访问效率
9.如何将字节输入输出流转换成字符输入输出流?
使用转换流InputStreamReader和OutputStreamWriter,里面分别放字节输入流和输出流的对象
10.什么叫对象序列化,什么是反序列化?怎么实现序列化?
a)对象序列化:将对象以二进制的形式保存在硬盘上
b)反序列化:将二进制的文件转化为对象读取
c)实现serializable接口
11.在实现序列化接口的时候一般要生成一个serialVersionUID字段,它叫什么?一般有什么用?
serialVersionUID是版本号,为了保持版本号的一致,来进行序列化,为了防止序列化出错
12.流一般需不需要关闭,用什么方法,一般在哪个代码块里面关闭?
流一旦打开就必须关闭,使用close()方法,如果不关闭流,那么就会一直占用你的内存空间不会释放,消耗内存
一般在finally语句块关闭,因为finally语句块一定会执行,如果在其他地方关闭,如果代码出现异常,可能导致流不能关闭
13.什么是比特(Bit),什么是字节(Byte),什么是字符(char),它们的长度是多少,各有什么区别?
a)Bit是最小的二进制单位,是计算机的操作部分,取值0或1
b)Byte是计算机操作数据的最小单位由8位Bit组成,取值(-128-127)
c)char是用户可读写的最小单位,由16位Bit组成,取值(0-65535)
-
字节流
使用字节流和字节缓冲流读取文件比较效率
package com.yr.test;
import java.io.BuffereDInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
/**
* 字节流读写文件
* @author liucong
*
* @date 2017年8月5日
*/
public class IoTest {
public static void main(String[] args) throws Exception {
IoTest ioTest = new IoTest();
/**
* 测试:用四种方法分别测试读取一个3.83 GB的文件的所用时长
* method1用时:523200 method2用时:2858
* method3用时:11446 method4用时:2235 method4效率最高
*/
long startTime = System.currentTimeMillis();
// ioTest.method1();
// ioTest.method2();
// ioTest.mothed3();
// ioTest.mothed4();
// ioTest.mothed5();
ioTest.method6();
long endTime = System.currentTimeMillis();
System.out.println("用时:" + (endTime - startTime));/// 1000
}
/**
* 使用基本字节流一次读写一个字节
*/
public void method1() {
try {
InputStream in = new FileInputStream(new File("D:/a.txt"));
OutputStream out = new FileOutputStream(new File("D:/b.txt"));
int read = 0;
while ((read = in.read()) != -1) {
out.write(read);
}
System.out.println("写入成功");
out.close();
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 使用基本字节流一次读写一个字节数组
*/
public void method2() {
try {
// 字节输入流读取文件
FileInputStream in = new FileInputStream(new File("D:/a.exe"));
// 文件太大就不能用 in.available()
byte byt[] = new byte[in.available()];
// int read = in.read(byt);
int read = 0;
// 字节输出流写入文件
OutputStream out = new FileOutputStream(new File("D:/c.exe"));
while ((read = in.read(byt)) != -1) {
out.write(byt, 0, read);
// 注意,如果读取的文件不是英文字母或数字的话,则不能转换成字符串打印
// System.out.println(new String(byt, 0, read));
}
System.out.println("写入成功");
out.close();
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 使用buffered缓冲流一次读写一个字节
*/
public void mothed3() {
try {
// 字节缓冲流
BuffereDInputStream bis = new BuffereDInputStream(new FileInputStream(new File("D:/a.exe")));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File("D:/b.exe")));
int read = 0;
while ((read = bis.read()) != -1) {
bos.write(read);
}
System.out.println("写入成功");
bos.close();
bis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 使用buffered缓冲流一次读写一个字节数组,此方法效率最高
*/
public void mothed4() {
try {
// 字节缓冲输入流读取文件
BuffereDInputStream bis = new BuffereDInputStream(new FileInputStream(new File("D:/win10/win10.gho")));
// 如果文件很大则不能使用 bis.available(),否则会内存溢出
byte byt[] = new byte[1024 * 1024];// bis.available()
int read = 0;
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File("D:/b.gho")));
// 到达文件尾部时,read()方法将返回-1,所以只要不等于-1就一直读取,直到读取完成
while ((read = bis.read(byt)) != -1) {
bos.write(byt, 0, read);
// 将读取的字节转为字符串对象 注意,如果读取的文件不是英文字母或数字的话,则不能转换成字符串打印
// System.out.println(new String(byt, 0, read));
}
System.out.println("写入成功");
bos.close();
bis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 读取图片
*/
public void mothed5() {
try {
// 字节缓冲输入流读取文件
BuffereDInputStream bis = new BuffereDInputStream(new FileInputStream(new File("F:/壁纸/23.jpg")));
// 如果文件很大则不能使用 bis.available(),否则会内存溢出
byte byt[] = new byte[1024 * 1024];// bis.available()
int read = 0;
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File("D:/b.jpg")));
// 到达文件尾部时,read()方法将返回-1,所以只要不等于-1就一直读取,直到读取完成
while ((read = bis.read(byt)) != -1) {
bos.write(byt, 0, read);
}
System.out.println("写入成功");
bos.close();
bis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 创建一个文件
*
* @throws Exception
*/
public void method6() throws Exception {
// 根据指定的路径创建一个文件的实例
File file = new File("D:/io/a.txt");
System.out.println("创建文件之前文件是否存在:" + file.exists());
if (file.exists()) {// 如果存在就删除
file.delete();
}
// file.mkDIr();//创建一级目录 D:/io
// file.mkDIrs();//创建多级目录D:/io/a.txt
file.createNewFile();// 不存在就创建
System.out.println("创建文件完成");
System.out.println("创建文件之后文件是否存在:" + file.exists());
System.out.println("文件名:" + file.getName());
System.out.println("文件路径:" + file.getPath());
System.out.println("文件父路径:" + file.getParentFile());
System.out.println("文件是否是一个文件:" + file.isFile());
System.out.println("文件长度:" + file.length());
// 字节流读取文件
FileInputStream ins = new FileInputStream(file);
int tempbyte;
System.out.println("一次读一个字节: ");
while ((tempbyte = ins.read()) != -1) {
System.out.write(tempbyte);
}
byte b[] = new byte[ins.available()];
int ind = ins.read(b);
System.out.println(ind);
while (ind != -1) {
ins.read(b);
}
for (int i = 0; i < b.length; i++) {
System.out.println("文件内容: " + b[i]);
}
// 字节流写文件
File writeFile = new File("D:/io/b.txt");
writeFile.createNewFile();
FileOutputStream ops = new FileOutputStream(writeFile);
ops.write(b, 0, ind);
System.out.println("写入文件b.txt成功");
// 关闭文件流
ins.close();
ops.close();
}
}
- 字符流
package com.yr.test;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
/**
* 字符流读写文件
*
* @author liucong
*
* @date 2017年8月5日
*/
public class IoTest {
public static void main(String[] args) throws Exception {
IoTest ioTest = new IoTest();
long startTime = System.currentTimeMillis();
// ioTest.method1();
// ioTest.method2();
// ioTest.method3();
ioTest.Method4();
//ioTest.readFileName();
long endTime = System.currentTimeMillis();
System.out.println("用时:" + (endTime - startTime));/// 1000
}
/**
* 基本字符流一个一个字符读取 耗时:168424毫秒 有乱码
*/
public void method1() {
try {
Reader reader = new InputStreamReader(new FileInputStream(new File("D:/a.txt")));
Writer writer = new OutputStreamWriter(new FileOutputStream(new File("D:/b.txt")));
int read = 0;
while ((read = reader.read()) != -1) {
writer.write(read);
System.out.println(read);
}
System.out.println("写入成功");
writer.close();
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 基本字符流每次读一个char数组 耗时:666毫秒 有乱码
*/
public void method2() {
try {
Reader reader = new InputStreamReader(new FileInputStream(new File("D:/a.txt")));
Writer writer = new OutputStreamWriter(new FileOutputStream(new File("D:/c.txt")));
char cha[] = new char[1024 * 1024];
int read = 0;
while ((read = reader.read(cha)) != -1) {
writer.write(cha, 0, read);
System.out.println(read);
}
System.out.println("写入成功");
writer.close();
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 使用字符缓冲流每次读取一行 耗时:515毫秒 推荐使用
*/
public void method3() {
try {
// InputStream 字节流输入流
InputStream in = new FileInputStream(new File("D:/a.txt"));
// InputStreamReader 字节流通向字符流的桥梁(读取文件解决乱码)
InputStreamReader read = new InputStreamReader(in, "utf-8");
// BufferedReader 字符缓冲输入流
BufferedReader reader = new BufferedReader(read);
// OutputStream 字节输出流
OutputStream out = new FileOutputStream(new File("D:/d.txt"));
// OutputStreamWriter 字符流通向字节流的桥梁(写入文件时解决乱码)
OutputStreamWriter osw = new OutputStreamWriter(out, "utf-8");
// BufferedWriter 字符缓冲输出流
BufferedWriter writer = new BufferedWriter(osw);
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();// 换行
System.out.println(line);
}
System.out.println("写入成功");
/**
* 关闭流
*/
writer.close();
osw.close();
out.close();
reader.close();
read.close();
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 字符流追加文件内容
* 注意:如果文件保存的编码是utf-8那么使用追加写入的时候开头会有一个乱码?
* 将文件已ANSI码的方式保存则不会
*/
public void Method4() {
try {
// InputStream 字节流输入流
InputStream in = new FileInputStream(new File("D:/a.txt"));
// InputStreamReader 字节流通向字符流的桥梁(读取文件解决乱码)
InputStreamReader read = new InputStreamReader(in, "utf-8");//
// BufferedReader 字符缓冲输入流
BufferedReader reader = new BufferedReader(read);
// FileWriter true表示可以追加文件内容
Writer write = new FileWriter("D:/b.txt", true);
// BufferedWriter 字符缓冲输出流
BufferedWriter writer = new BufferedWriter(write);
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();// 换行
System.out.println(line);
}
System.out.println("写入成功");
/**
* 关闭流
*/
writer.close();
write.close();
reader.close();
read.close();
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 读取一个文件夹下所有文件夹
*
* @throws IOException
*/
public void readFileName() throws IOException {
File file = new File("F:/Apps");
// 读取F:/Apps下的所有文件夹
if (file.exists()) {
file.createNewFile();
}
File[] fileArr = file.listFiles();
System.out.println(file.toURI());
System.out.println(file.toPath());
for (File file2 : fileArr) {
System.out.println(file2.getName());
}
}
}
package com.yr.test;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.InputStreamReader;
/**
* 字符流读写文件(StringBuffer)
*
* @author liucong
*
* @date 2017年8月5日
*/
public class IoTest {
public static void main(String[] args) {
String content = IoTest.readFile("D:/a.txt");
IoTest.writerFile("D:/b.txt", content);
}
public static String readFile(String filePath) {
// 定义变量存储信息
StringBuffer filecontent = new StringBuffer();
File file = new File(filePath);
try {
if (file.exists() && file.isFile()) {
FileInputStream stream = new FileInputStream(file);// 字节流
InputStreamReader reader = new InputStreamReader(stream, "UTF-8");// 字符流
BufferedReader buffer = new BufferedReader(reader);
String line;
while ((line = buffer.readLine()) != null) {
filecontent.append(line).append("\n");
}
System.out.println(filecontent.toString());
buffer.close();
stream.close();
buffer.close();
}
} catch (Exception e) {
System.out.println("读取文件内容时出错");
e.printStackTrace();
}
return filecontent.toString();
}
// 写文件
public static void writerFile(String filePath, String filecontent) {
File file = new File(filePath);
try {
if (!file.exists()) {
file.createNewFile();
}
FileWriter writer = new FileWriter(filePath, true);// true表示是往文件中追加内容,不会覆盖之前的内容
BufferedWriter buff = new BufferedWriter(writer);// 构建一个字符输出流,可以写出为string
buff.write(filecontent);// 将信息存写到文件中去
buff.newLine();// 每一句换行
System.out.println("写入成功");
buff.close();
} catch (Exception e) {
System.out.println("写文件内容时出错");
e.printStackTrace();
}
}
}