博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java
阅读量:6983 次
发布时间:2019-06-27

本文共 21510 字,大约阅读时间需要 71 分钟。

第一章 Java基础

(1)Java语言的发展简史:
1995年Sun公司发布了Java语言。
1996年Sun公司发布了JDK1.0,这个版本包括两部分:运行环境和开发环境。
1997年Sun公司发布了JDK1.1,JDK1.1增加了JIT(即时编译)编译器。
1998年Sun公司发布了JDK1.2(最重要的版本),伴随着JDK1.2一同发布的还有JSP/Servlet、EJB等规范,并将Java分成了J2EE、J2SE、J2ME。
2000年Sun公司发布了JDK1.3。
2002年Sun公司发布了JDK1.4(最成熟的版本)。
2004年Sun公司发布了JDK1.5,JDK1.5增加了泛型、foreach循环、可变数量的形参、注释、自动装箱/拆箱。
2006年Sun公司发布了JDK1.6。
2009年Oracle以74亿美元收购Sun。
2011年Oracle发布了JDK1.7。

JDK的全称是Java SE Development Kit,即Java标准开发包。包括了Java编译器、JRE、Java类库等。

JRE的全称是Java Runtime Environment,JRE包括JVM。JVM是运行Java程序的核心虚拟机,而运行Java程序不仅需要核心虚拟机,还需要其它的类加载器、字节码校验器以及大量的基础类库。JRE除了包含JVM之外,还包含了运行Java程序的其它环境支持。

高级语言的运行机制:

计算机高级语言按程序的执行方式可以分为编译型和解释型两种。

编译型语言是指使用专门的编译器,针对特定平台(操作系统)将某种高级语言源代码一次性"翻译"成可被该平台硬件执行的机器码,并包装成该平台所能识别的可执行性程序的格式,这个转换过程称为编译(Compile)。编译生成的可执行性程序可以脱离开发环境,在特定的平台上独立运行。C、C++都属于编译型语言。

解释型语言是指使用专门的解释器对源程序逐行解释成特定平台的机器码并立即执行的语言。每次执行解释型语言的程序都需要进行一次编译,因此解释型语言的程序运行效率通常较低,而且不能脱离解释器独立运行;但是可以跨平台,只需要提供特定平台的解释器即可。Ruby、Rython都属于解释语言。

Java语言比较特殊,由Java语言编写的程序需要经过编译步骤,但这个编译步骤并不会生成特定平台的机器码,而是生成一种与平台无关的字节码(也就是class文件)。当然,这种字节码不是可执行性的,必须使用JVM来解释执行。当使用Java编译器编译Java程序时,生成的是与平台无关的字节码,这些字节码不面向任何平台,只面向JVM。不同平台上的JVM都是不同的,但它们都提供了相同的接口。JVM负责执行指令、管理数据、内存和寄存器。

Java文件 -- (使用Javac编译) -- 字节码文件 -- (JVM解释执行) -- 特定平台的机器码。

Java环境变量:

JAVA_HOME:D:\Java\jdk1.6.0_37
PATH:%JAVA_HOME%\bin
CLASSPATH:.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar

垃圾回收机制:

Java程序的内存分配和回收都是由JRE在后台自动进行的。JRE会负责回收那些不再使用的内存,这种机制被称为垃圾回收(GC)。JRE会提供一个后台线程来进行检测和控制,一般都是在CPU空闲或内存不足时自动进行垃圾回收,程序员无法精确控制垃圾回收的时间和顺序。Java的堆内存是一个运行时数据区,存储着正在运行的应用程序所建立的所有对象,GC只回收堆内存中的资源,对其它物理资源,如数据库连接、磁盘IO等资源则无能为力。

final、finally、finalize:

final用于声明属性,方法和类,分别表示属性不可变,方法不可重写,类不可继承。
finally是异常处理语句结构的一部分,表示总是执行。
finalize是Object中的方法,用于垃圾回收时清理资源。

标识符:

标识符是用于给程序中变量、类、方法命名的符号。必须以字母、下划线(_)和美元符号($)开头,后面可跟任意数目的字母、下划线、美元符号和数字。

Java数据类型分为两类:基本类型和引用类型

基本类型:byte、short、int、long、char、float、double、boolean
引用类型:实例、数组。实际上,引用类型变量就是指针,只是Java里没有使用指针这个说法。

当程序第一次使用某个字符串直接量时,Java会使用常量池来缓存该字符串直接量,如果程序后面的部分需要用到该字符串直接量,Java会直接使用常量池中的字符串直接量。

常量池指的是在编译期被确定,并被保存在已编译的class文件中的一些数据,它包括类、方法、接口中的常量,也包括字符串直接量。

int i = 1;

switch(i)
{
case 1:
...
break;
default:
}
1.switch可以用byte、short、int、char和枚举;JDK1.7新增String。
2.case后记得加break。

循环中可用continue结束本次循环。

int[] arr = new int[]{1,9,1,4,9};

int[] arr = new int[5];

堆内存&栈内存:

当我们在程序中创建一个对象时,这个对象将被保存在堆内存中。堆内存不能直接访问,只能通过引用变量访问。堆内存不会随方法的结束而销毁。
当执行每一个方法时,每一个方法都会建立自己的栈内存,在这个方法内定义的变量会逐个放入栈内存中,随着方法的执行结束,这个方法的栈内存随着销毁。

Java内存共划分5片内存区:

1.堆内存 :存储对象。
2.栈内存 :局部变量数据。所属区域结束释放。
3.方法区 :代码表,静态区,常量池等,可以成为数据共享区。因为生命周期长。
4.本地方法区 :和系统相关,JVM对系统底层进行调用,比如调用C的代码。
5.寄存器 : 和处理器相关。

方法区:方法区中存放了每个Class的结构信息,包括常量池、字段描述、方法描述等。

(2)面向对象:
Java是面向对象的程序设计语言,面向对象中有两个重要概念:类和对象。类用于描述客观世界里某一类对象的共同特征,类是某一批对象的抽象,可以把类理解成某种概念;对象才是一个具体存在的实体。

面向对象的四大特征:封装、继承、多态、抽象。

封装是面向对象编程语言对客观世界的模拟,客观世界里的属性都是被隐藏在对象内部的,外界无法直接操作和修改。

private: 同类

default: 同类、同包
protected:同类、同包、子类中
public: 同类、同包、子类中、全局范围

Java的常用包:

Java.lang:包含了Java语言的核心包,如String、Math、System和Thread类等,使用这个包下的类无须使用import语句导入,系统会自动导入这个包下的所有类。
Java.util:包含了Java大量工具类/接口和集合框架类/接口,例如Arrays和List、Set等。
Java.net:包含了一些Java网络编程相关的类/接口。
Java.io:包含了一些Java输入/输出编程相关类/接口。
Java.text:包含了一些Java格式化相关的类。
Java.sql:包含了Java进行JDBC数据库编程的相关类/接口。
Java.awt:包含了抽象窗口工具集的相关类/接口,构建图形用户界面(GUI)程序。
Java.swing:包含了Swing图形用户界面编程的相关类/接口,构建平台无关的GUI程序。

成员变量是随类初始化或对象初始化的。当类初始化时,系统会为该类的类属性分配内存,并分配默认值;当创建对象时,系统会为该对象的实例属性分配内存,并分配默认值。

继承:

方法重载(Overload):
在同一个类中,方法名相同,形参列表不同,与返回类型无关的方法。
方法重写(Override):
在继承的基础上,方法名相同,形参列表相同,返回值类型相同的方法。

private修饰的方法只能在本类中使用,因此无法重写private修饰的方法,但子类的方法可与父类中private方法重名。

static修饰的方法属于类,不存在多态性,因此static修饰的方法也不能被重写。

判断方法是否是重写父类的,可在方法前加@Override注解。

方法重写要遵循“两同两小一大”规则:方法名相同,形参列表相同;子类方法的返回类型小于或等于父类,子类方法声明抛出的异常小于或等于父类;子类方法的访问权限应大于或等于父类。

super用于限定该对象调用它从父类继承得到的属性和方法。正如this不能出现在static修饰的方法中一样,super也不能出现在static修饰的方法中。也可在子类构造器中使用super调用父类构造器,使用super调用父类构造器也必须出现在子类构造器执行体的第一行,所以this调用和super调用不会同时出现。

构造器:

构造器是一个特殊的方法,用于创建实例时执行初始化。构造器是创建对象的重要途径(即使使用工厂模式、反射等方式创建对象,依然需要依赖于构造器),因此,Java类至少需要一个构造器。

多态:

Java引用变量有两种类型:编译时类型和运行时类型。编译时类型是由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就可能出现多态(Polymorphism)。相同的类型变量调用同一个方法呈现出多种不同的行为特征,这就是多态。

当把一个子类对象直接赋给父类引用变量调用方法时,其方法行为总是表现出子类方法的行为特征,而不是父类方法的行为特征。但是这个父类引用变量只能调用父类中拥有的方法。与方法不同的是,对象的属性不具备多态性。

引用变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法。

继承和组合:

继承和组合都是实现类重用的重要手段,但继承带来了一个最大的坏处:破坏封装。

为了保证父类有良好的封装性,不会被子类随意改变,设计父类通常应该遵循如下规则:

1.尽量用private修饰父类中所有属性,不要让子类直接访问父类的属性。
2.不要让子类随意访问、修改父类的方法。父类中那些仅为辅助的工具方法,应该使用private修饰,让子类无法访问;如果父类中的方法需要被外部调用,又不希望被子类重写,则可以使用public final修饰;如果希望父类的某个方法被子类重写,又不希望被其它类访问,则可使用protected修饰。
3.尽量不要在父类构造器中调用将要被子类重写的方法。

组合:

把类A当成另类B的组成部分,在类B中可直接调用类A的public方法。

字符串转换成基本类型:

1. int i = Integer.parseInt("123");
2. int i = new Integer("123");
基本类型转换成字符串:
String str = new String.valueOf(1.23f);

Integer a = 1; Integer b = 1; a == b;

Integer a = 128; Integer b = 128; a != b;
Integer会把-128到127之间的整数放入一个名为cache的数组中缓存起来。

public String toString()

{
return "User[id=" + id + ",name=" + name +"]";
}

new String("hello"):

当直接使用形如"hello"的字符串直接量时,JVM将会使用常量池来管理字符串直接量。
当使用new String("hello")时,JVM会先使用常量池来管理"hello"直接量,再调用String类的构造器来创建一个新的String对象,新创建的String对象被保存在堆内存中。换句话说,new String("hello")一共产生了两个对象。

单例(Singleton):

public class Singleton
{
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance()
{
if (null == instacne)
{
instance = new Singleton();
}
return instance;
}
}

final修饰符:

final修饰的类不能被继承。
final修饰的方法不能被重写。
final修饰的属性值不能被改变。

抽象类:

抽象类体现的是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展,但子类总体上会保留抽象类的行为方式。

接口:

接口定义的是多个类共同的行为规范,这些行为是与外部交流的通道,这就意味着接口里通常是定义一组公用方法。

public interface InterA extends interA, InterB //接口之间可以多继承

抽象类和接口:

1.抽象类和接口都不能被实例化。
2.抽象类和接口都可以包含抽象方法,它们子类必须实现其中的抽象方法。

抽象类体现的是一种模板式设计,可以被当成系统实现过程中的中间产品,这个中间产品已经实现了系统的部分功能,但这个产品依然不能当成最终产品,必须有更进一步的完善,这种完善可以有几种不同方式。

接口类似于整个系统的总纲,它制定了系统各模块应该遵循的标准,因此一个系统中的接口不应该经常改变。

1.抽象类可以包含普通方法和抽象方法;接口只能包含抽象方法。

2.抽象类里可以定义静态方法;接口里不能定义静态方法。
3.抽象类里可以定义普通属性和静态常量;接口里只能定义静态常量。
4.抽象类里可以包含构造器,用于子类调用这些构造器来完成抽象类的初始化操作;接口里不能包含构造器。
5.抽象类里可以包含初始化块;接口里不能包含初始化块。
6.一个类最多只能有一个直接父类,包括抽象类;但一个类可以直接实现多个接口,从而弥补Java单继承的不足。

内部类:

非静态内部类里不能有静态方法、静态属性、静态初始化块。

1.在外部类中使用内部类:

InnerClass name;
new InnerClass().name;
2.在外部类以外使用非静态内部类:
OuterInstance.InnerClass varName;
OuterInstance.new InnerClass();
3.在外部类以外使用静态内部类:
OuterClass.InnerClass varName;
new OuterClass.InnerClass();

匿名内部类:

instance.method(new User()
{
public String getID()
{
return "1001";
}
public String getName()
{
return "lq";
}
});

枚举:

枚举类是一种特殊的类,可以定义属性、方法和构造器,可以实现一个或多个接口。
1.使用enum定义的枚举类默认继承Java.lang.Enum类,而不是继承Object类。
2.使用enum定义、非抽象的枚举类默认会使用final修饰,因此枚举类不能派生子类。
3.枚举类的构造器只能使用private修饰。
4.枚举类的所有实例必须在枚举类的第一行显示列出,系统会自动添加public static final修饰。

public:类/接口,成员属性,方法,构造器。

protected:成员属性,方法,构造器。
default:类/接口,成员属性,方法,构造器。
private:成员属性,方法,构造器。
abstract:类/接口,方法。
final:类/接口,成员属性,方法。
static:成员属性,方法。
strictfp:类/接口,方法。
synchronized:方法
native:方法。

(3)集合
Java集合是一个保存多个对象的容器,由Collection和Map接口派生而出,大致可分为Set、List和Map三种体系。
Set代表无序、不可重复的集合。
List代表有序、重复的集合。
Map按键值对存储,无放入顺序。

Collection

Set -- HashSet(LinkedHashSet)、SortedSet(TreeSet)
List -- ArrayList、LinkedList、Vector
Queue -- ArrayDeque、LinkedList

Map

HashMap -- LinkedHashMap
Hashtable -- Properties
SortedMap -- TreeMap

其中Set集合类似于一个罐子,把一个对象添加到Set集合时,Set集合无法记住添加这个元素的顺序,所以Set里的元素不能重复;List集合非常像一个数组,它可以记住每次添加元素的顺序,只是List的长度可变;Map集合也像一个罐子,只是它里面的每项数据都是由两个值组成。

集合和数组不一样,数组元素既可以是基本类型的值,也可以是对象(实际上保存的是对象的引用变量)。而且数组的长度不可变化。

集合只能保存对象。

HashSet通过Hash算法来存储集合中的元素,因此具有很好的存取和查找性能。

当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法得到该对象的hashCode值,然后根据该HashCode值决定该对象在HashSet中的存储位置。如果有两个元素通过equals()方法比较返回true,但它们的hashCode()方法返回值不相等,HashSet将会把它们存储在不同的位置,依然可以添加成功。
不能保证元素的排列顺序,顺序有可能发生变化。
非同步,需要通过代码来保证同步。
集合元素值可以是null。

LinkedHashSet集合也是根据HashCode值来决定元素的存储位置,同时使用链表来维护元素的次序,因此在性能上略低于HashSet。

TreeSet是SortedSet接口的实现类,通过红黑树算法来维护集合元素的次序。TreeSet默认为自然排序,从小到大。TreeSet会调用集合元素的compareTo(Object obj)方法,因此保存在TreeSet集合中的元素必须实现Comparable接口。

各Set实现类的性能分析:

HashSet的性能比TreeSet好,特别是添加和查询操作,因为TreeSet需要额外的红黑树算法来维护集合元素的次序。HashSet还有一个子类LinkedHashSet,对于普通的插入和删除操作,LinkedHashSet略慢一些,这是因为LinkedHashSet需要维护链表。不过正是有了链表,遍历LinkedHashSet会很快。
HashSet、TreeSet都是线程不安全的,可用Set<String> set = Collections.synchronizedSet(new HashSet<String>());同步。

ArrayList、Vector、LinkedList

LinkedList内部以链表的形式来保存集合中的元素,随机访问集合元素时性能较差,但在插入、删除元素时性能非常出色,因为只需要改变指针所指的地址即可。
ArrayList和Vector都是基于数组实现的List类,所以都封装了一个动态的、允许再分配的Object[]数组,在随机访问集合元素时有较好的性能。
ArrayList是线程不安全的,所以在性能上ArrayList比Vector高。

Queue是一个队列,先进先出(FIFO)。

Set与Map之间的关系非常密切。虽然Map中放的元素是key-value对,Set集合中放的元素是单个对象,但如果我们把key-value对中的value当成key的附庸:key在哪里,value就在哪里。这样就可以像对待Set一样来对待Map了。事实上,Map提供了一个Entry内部类来封装key-value对,而计算Entry存储时则只考虑Entry封装的key。从Java源码来看,Java是先实现了Map,然后通过包装一个所有value都为null的Map就实现了Set集合。

HashMap和Hashtable

HashMap线程非安全,Hashtable线程安全,所以HashMap比Hashtable的性能高一点。
HashMap允许null作为key或value,Hashtable不允许null作为key或value。

LinkedHashMap使用双向链表来维护元素的次序,迭代顺序与插入顺序保持一致。

操作集合的工具类:Collections

Collections提供了大量方法对集合元素进行排序、查询和修改等操作,还提供了将集合对象设置为不可变,对集合对象实现同步控制等方法。

(4)异常
异常机制已经成为判断一门编程语言是否成熟的标准(C语言没有提供异常机制),异常机制可以使程序中的异常处理代码和正常业务代码分离,提供更好的可读性,并可以提供程序的健壮性。Java的异常机制主要依赖try、catch、finally、throw和throws五个关键字。

Java7进一步增强了异常处理机制的功能,包括带资源的try语句、捕获多异常的catch两个新功能。

如果执行try里的业务逻辑代码时出现异常,系统会自动生成一个异常对象,该异常对象被提交给Java运行时环境,这个过程被称为抛出(throw)异常。当Java运行时环境收到异常对象时,会寻找能处理该异常对象的catch,如果找到合适的catch,则把该异常对象交给catch处理,这个过程被称为捕获(catch)异常。如果Java运行时环境找不到捕获异常的catch,则运行时环境终止,Java程序也将退出。

Java把所有的非正常情况分为两种:异常(Exception)和错误(Error),它们都继承Throwable父类。

Error错误,一般是指与虚拟机相关的问题,如系统崩溃、虚拟机错误,这种错误不可能捕获也无法修复,将导致程序中断。

异常处理语法结构中只有try块是必须的,catch块和finally块都是可选的,但catch块和finally块至少出现其中之一。Java7提供了自动关闭资源的try语句,因此Java7可以既没有catch块,也没有finally块。

除非在try块、catch块中调用了退出虚拟机的方法,否则不管在try块、catch块中执行怎样的代码,出现怎样的情况,异常处理的finally块总是会被执行。

当Java程序执行try块、catch块时遇到了return或throw语句,这两个语句都会导致该方法立即结束,但是系统执行这两个语句并不会结束该方法,而是去寻找该异常处理流程中是否包含finally块,如果没有finally块,程序立即执行return或throw语句,方法终止。如果有finally块,系统立即执行finally块,finally块执行完成后,系统再次跳转到try块、catch块里的return或throw语句。如果finally块中也使用了return或throw等导致方法终止的语句,finally块已经终止了方法,系统将不会跳转回去执行try块、catch块里的任何代码。

Java的异常分为两类:Checked异常和Runtime异常。

只有Java语言提供了Checked异常,Checked异常体现了Java的严谨性,它要求程序员必须注意该异常。要么显示声明抛出,要么显示捕获并处理它,总之不允许对Checked异常不闻不问。这是一种非常严谨的设计哲学,可以增加程序的健壮性。问题是:大部分的方法总是不能明确地知道如何处理异常,因此只能声明抛出该异常,而这种情况又是如此普遍,所以Checked异常降低了程序开发的生产率和代码的执行效率。Runtime异常是运行时可能发生的异常。

Checked在继承的情况下抛异常会受到父类的限制,Runtime可以自行抛出异常,更加简洁。

Checked异常处理的两种方式:

1.当前方法明确知道如何处理该异常,程序应该使用try...catch块来捕获该异常,然后在对应的catch中修复该异常。
2.当前方法不知道如何处理该异常,应该在定义该方法时声明抛出该异常。

使用try...catch捕获异常

使用finally回收资源(如数据库连接、网络连接和磁盘文件)
使用throws声明抛出异常(throws只能在方法签名中使用,场景:当前方法不知道如何处理这种类型的异常,该异常由上一级调用者处理)
使用throw抛出异常(throw抛出的不是异常类,而是一个异常实例,每次只能抛出一个异常实例)

if(a<0)

{
throw new RuntimeException("a的值小于0,不符合要求");
}

catch和throw同时使用:

catch(Exception e)
{
e.printStackTrace();
throw new MyException("自定义异常");//可在方法签名中抛出MyException。
}

自定义异常:

自定义异常都应该继承Exception或者RuntimeException。定义异常类时通常需要提供两个构造器,一个无参数的构造器,另一个是带一个字符串参数的构造器,这个字符串将作为该异常对象的描述信息,也就是getMessage()方法的返回值。

getMessage():返回该异常的详细描述字符串。

printStackTrace():打印该异常的跟踪栈信息。
System.out.println(e.getMessage());
e.printStackTrace();

不要过度使用异常,Java的异常机制确实方便,但滥用异常机制也会带来一些负面影响,过度使用异常主要有两个方面:

1.把异常和普通错误混淆在一起,不再编写任何错误处理代码,而是以简单地抛出异常来代替所有的错误处理。
2.使用异常处理来代替流程控制。
熟悉了异常使用方法后,程序员可能不再愿意编写繁琐的错误处理代码,而是简单地抛出异常。实际上这样做是不对的,对已完全已知的错误,应该编写处理这种错误的代码,增加程序的健壮性。只有对外部的、不能确定和预知的运行时错误才使用异常。

(5)多线程
单线程的程序功能非常有限,多线程的程序则可以包括多个顺序执行流,多个顺序流之间互不干扰。

进程(Process):

进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位。当一个程序进入内存运行时,即变成一个进程。

一般而言,进程包含3个特征:

1.独立性:它可以拥有自己独立的资源,每一个进程都拥有自己私有的地址空间。在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其它进程的地址空间。
2.动态性:进程具有自己的生命周期和各种不同的状态,这些概念在程序中都是不具备的。
3.并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。

线程(Thread):

线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程可以拥有自己的堆栈、自己的程序计数器和自己的局部变量,但不拥有系统资源,它与父进程的其它线程共享该进程所拥有的全部资源。因为多个线程共享父进程里的全部资源,因此编程更加简单;但必须更加小心,我们必须确保线程不会妨碍同一进程里的其它线程。

并发性和并行性:

并发指在同一时刻只能有一条指令执行,但多个进程指令被快速轮转执行,使得在宏观上具有多个进程同时执行的效果。
并行指在同一时刻,有多条指令在多个处理器上同时执行。

从逻辑角度来看,多线程存在于一个应用程序中,让一个应用程序中可以有多个执行部分同时执行,但操作系统无法将多个线程看做多个独立的应用,对多线程实现调度和管理以及资源分配。线程的调度和管理由进程本身负责完成。

归纳起来可以这么说,操作系统可以同时执行多个任务,每个任务就是进程;进程可以同时执行多个任务,每个任务就是线程。

多线程的优势:

进程之间不能共享内存,但线程之间共享内存非常容易。
系统创建进程时需要为该进程重新分配系统资源,但创建线程则代价小得多,因此使用多线程来实现多任务并发比多进程的效率高。

线程的创建和启动:

1.继承Thread类,重写run()方法。
public class XXX extends Thread
new XXX().start();
2.实现Runnable接口,重写run()方法。
public class XXX implements Runnable
new Thread(new XXX(), "my thread").start();
3.实现Callable接口,重写call()方法。
public class XXX implements Callable<Integer>
new Thread(new XXX(), "有返回值的线程").start();

一般推荐使用实现Runnable创建线程,可以继承其它类。

线程的生命周期

新建、就绪、运行、阻塞、死亡
新建:当程序使用new关键字创建了一个线程后,该线程就处于新建状态。
就绪:当线程对象调用了start()方法后,该线程就处于就绪状态,表示该线程可以运行了。至于该线程何时执行,取决于JVM中线程调度器的调度。
运行:处于就绪状态的线程获得了CPU,开始执行run()方法,则该线程为运行状态。
阻塞:1.线程调用sleep() 2.线程试图获得一个同步监视器,但该同步监视器正被其它线程持有 3.线程对象调用了wait()方法(线程在等待某个notify。wait、notify、notifyAll是Object中的方法,都被声明为final类) 4.程序调用了线程的suspend(),(等待resume(),但这个方法容易导致死锁,应尽量避免使用)。
死亡:1.run()或call()方法执行完成,线程正常结束 2.线程抛出一个未捕获的Exception或Error 3.调用该线程的stop(),该方法容易导致死锁,不推荐使用。

join线程:

public class JoinThread extends Thread ..
JoinThread jt = new JoinThread("join线程").start();
jt.join();
当前运行着的线程将阻塞直到jt线程执行完再执行。

后台线程:

又称“守护线程”或“精灵线程”,主要为其它线程提供服务,JVM的垃圾回收线程就是一个后台线程。如果所有的前台线程都死亡,后台线程会自动死亡。
thread.setDaemon(true); //setDaemon(true)必须在start()方法之前调用,否则会引发异常。

线程睡眠(sleep):

如果需要当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以调用线程的静态方法sleep(long millis),参数为毫秒。

sleep与wait的区别:

1.sleep不会释放同步锁,wait会释放同步锁,使得其它线程可以使用同步块或者同步方法。
2.sleep可以在任何地方使用,wait只能在同步块或同步方法中使用。
3.sleep来自Thread类,wait来自Object类。

线程让步(yield):

静态方法,告诉虚拟机让其他线程占用自己的位置,从运行状态转到可运行状态。

线程优先级:

通过一个int priority来控制优先级,范围为1-10,其中10最高,默认值为5。优先级高的线程获得较多的执行机会,而优先级低的线程获得较少的执行机会,main线程默认具有普通优先级。

线程同步:

1.同步代码块
synchronized(obj)
{
... //此处的代码就是同步代码块
}
上面synchronized后括号里的obj就是同步监视器,任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定。
2.方法同步
public synchronized void draw(drawAmount)...
同步方法的同步监视器是this,也就是该对象本身。
synchronized可以修饰方法、代码块,但是不能修饰构造器和属性等。
3.同步锁
lock.lock();
...
lock.unlock();

死锁:

当两个线程相互等待对方释放同步监视器时就会发生死锁,jvm没有监测,也没有采取措施来处理死锁的情况。当程序出现死锁时,不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。

线程通信:

当线程在系统内运行时,线程的调度具有一定的透明性,程序通常无法准确控制线程的轮转执行,但我们可以通过一些机制来保证线程协调运行。
Object提供了wait(),notify(),notifyAll()三个方法,这三个方法必须由同步监视器对象来调用(synchronized来保证同步),当用synchronized修饰方法时,可在同步方法中直接调用这三个方法。

线程池:

系统启动一个新线程的成本是比较高的,因为它涉及与操作系统交互。在这种情况下,使用线程池可以很好地提高性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池。

包装线程不安全的集合:

当多个并发线程向非同步的集合存、取元素时,就可能会破坏这些集合数据的完整性。
HashMap hashMap = Collections.synchronizedMap(new HashMap());
List list = Collections.synchronizedList(new ArrayList());

(6)输入/输出
使用输入机制,允许程序读取外部数据(包括来自磁盘、光盘等存储设备的数据)、用户输入数据;使用输出机制,允许程序记录运行状态,将程序数据输出到磁盘、光盘等存储设备。
每种输入、输出流分为字节流和字符流两大类。

注意:windows的路径分隔符使用反斜线(\),而Java程序中的反斜线表示转义符。可以使用斜线(/)或者F:\\abc\\test.txt

Java的IO流是实现输入/输出的基础,它可以方便地实现数据的输入/输出操作,在Java中把不同的输入/输出源(键盘、文件、网络连接等)抽象表达为“流”(stream),通过流的方式允许Java程序使用相同的方式来访问不同的输入/输出源。stream是从起源(source)到接受(sink)的有序数据。

流的分类:

按照流的流向来分,可以分为输入流和输出流。
输入流:只能从中读取数据,而不能向其写入数据。
输出流:只能向其写入数据,而不能从中读取数据。
输入流和输出流都是从程序运行所在的内存的角度来划分的。

字节流和字符流:

字节流和字符流的用法几乎完全一样,区别在于字节流和字符流所操作的数据单元不同——字节流操作的数据单元是8位的字节,而字符流操作的数据单元是16位的字符。字节流主要由InputStream和OutputStream作为基类,而字符流则主要由Reader和Writer作为基类。字节流的功能比字符流强大,因为计算机里所有的数据都是二进制的,而字节流可以处理所有的二进制文件。

按照流的角色来分,可以分为节点流和处理流。

可以从/向一个特定的IO设备(如磁盘、网络)读/写数据的流,称为节点流,节点流也被称为低级流。
处理流则用于对一个已存在的流进行连接或封装,通过封装后的流来实现输入读/写功能。处理流也被称为高级流。

Java把所有设备里的有序数据抽象成流模型,简化了输入/输出处理,理解了流的概念模型也就理解了Java IO。Java的IO流涉及40多各类,都是由如下4个基类派生。

InputStream/Reader:所有输入流的基类,前者是字节输入流,后者是字符输入流。
OutputStream/Writer:所有输出流的基类,前者是字节输出流,后者是字符输出流。

处理流的功能主要体现在以下两个方面:

性能的提高:主要以增加缓冲的方式来提高输入/输出的效率。
操作的便捷:处理流提供了一系列便捷的方式来一次输入/输出大批量的内容。
处理流可以“嫁接”在任何已存在的流的基础之上,这就允许Java应用程序采用相同的代码、透明的方式来访问不同的输入/输出设备的数据流。通过使用处理流,Java程序无须理会输入/输出节点是磁盘、网络还是其它的输入/输出设备,程序只要将这些节点流包装成处理流,就可以使用相同的输入/输出流代码来读写不同输入/输出设备的数据。

字节流和字符流:

InputStream和Reader是所有输入流的抽象基类,本身并不能创建实例来执行输入,但它们将成为所有输入流的模板,所以它们的方法是所有输入流都可使用的方法。
InputStream和Reader包含方法:
int read():从输入流中读取单个字节/字符,返回所读取的字节/字符数据。
int read(byte[] b)/int read(char[] c):从输入流中最多读取b.length个字节/字符的数据,并将其存储在数组中,返回实际读取的字节数/字符数。
FileInputStream fis = new FileInputStream("FileInputStreamTest.Java");
byte[] bytes = new byte[1024];
int hasRead = 0;
while((hasRead = fis.read(bytes)) > 0)
{System.out.println(new String(bytes,0,hasRead));}
fis.close(); //在finally中关闭文件输入流

OutputStream包含方法:

void write(int c):将指定的字节/字符输出到输出流中。
void write(byte[]/char[] b,int off,int len):将数组中从off位置开始,长度为len的字节/字符输出到输出流。
Write可以用字符串来代替字符数组,Write还包含了如下方法:
void write(String str):将str字符串输出到指定输出流中。
FileInputStream fis = new FileInputStream("FileInputStreamTest.Java");
FileOutputStream fos = new FileOutputStream("newFile.txt");
byte[] byte = new byte[1024];
int hasRead = 0;
while((hasRead = fis.read(byte) > 0))
{fos.write(byte,0,hasRead);}
//将FileInputStreamTest.Java中内容保存在newFile.txt中

//如果希望直接输出字符串内容,使用Write会有更好的效果:

FileWrite fw = new FileWrite("newFile.txt");
fw.write("Java基础\r\n");
fw.write("集合,线程,IO,异常\r\n");

使用处理流:

处理流可以隐藏底层设备上节点流的差异,并对外提供更加方便地输入/输出方法。使用处理流来包装节点流,程序通过处理流来执行输入/输出功能,让节点流与底层的IO设备、文件交互。识别处理流非常简单,只要流的构造器参数不是一个物理节点,而是一个已经存在的流,那这个流一定是处理流。

处理流的优势:

1.进行输入/输出操作更简单。
2.处理流的执行效率更高。

FileOutputStream fos = new FileOutputStream("test.txt");

PrintStream ps = new PrintStream(fos);
ps.prinltn("PrintStream流");
ps.println(new PrintStreamTest());
PrintStream的输出功能非常强大,System.out的类型就是PrintStream。通常如果需要输出文本内容,都应该将输出流包装成PrintStream后进行输出。

分类/字节输入流/字节输出流/字符输入流/字符输出流

抽象基类:InputStream/OutputStream/Reader/Writer
访问文件:FileInputStream/FileOutputStream/FileReader/FileWriter
访问数组:ByteArrayInputStream/ByteArrayOutputStream/CharArrayReader/CharArrayWriter
访问管道:PipedInputStream/PipeOutputStream/PipedReader/PipedWriter
访问字符串:null/null/StringReader/StringWriter
缓冲流:BufferedInputStream/BufferedOutputStream/BufferedReader/BufferedWriter
转换流:null/null/InputStreamReader/OutputStreamWriter
对象流:ObjectInputStream/ObjectOutputStream/null/null
抽象基类:FilterInputStream/FilterOutputStream/FilterReader/FilterWriter
打印流:null/PrintStream/null/PrintWriter
推回输入流:PushbackInputStream/null/PushbackReader/null
特殊流:DataInputStream/DataOutputStream/null/null

如果进行输入/输出的内容是文本内容,则应该考虑使用字符流。如果进行输入/输出的内容是二进制内容,则应该考虑使用字节流。

BufferedReader具有一个readLine()方法,可以非常方便地一次读入一行内容(以换行符做为标志),所以经常把读取文本内容的输入流包装成BufferedReader。

重定向标准输入/输出

Java的标准输入/输出分别通过System.in和System.out来代表,在默认情况下它们分别代表键盘和显示器,当程序通过System.in来获取输入时,实际上是从键盘读取输入;当程序试图通过System.out执行输出时,程序总是输出到屏幕。
在System类里提供了如下3个重定向标准输入/输出的方法:
static void setErr(PrintStream err):重定向“标准”错误输出流。
static void setIn(InputStream in):重定向“标准”输入流。
static void setOut(PrintStream out):重定向“标准”输出流。
PrintStream ps = new PrintStream(new FileOutputStream("out.txt"));
System.setOut(ps);
System.out.println("abcdefg");
上面程序不再输出到屏幕,而是输出到out.txt文件。

对象序列化:

对象序列化的目标是将对象保存到磁盘中,或允许在网络中直接传输对象。对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点。其它程序一旦获得了这种二进制流(无论是从磁盘中获取,还是通过网络获取的),都可以将这种二进制流恢复成原来的Java对象。

序列化的含义和意义:

序列化机制允许将实现序列化的Java对象转换成字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以备以后重新恢复成原来的对象。序列化机制使得对象可以脱离程序的运行而独立存在。
对象的序列化(serialize)指将一个Java对象写入IO流中,与此对应的是,对象的反序列化(Deserialize)则指从IO流中恢复该Java对象。实现接口:Serializable或Externalizable。

反序列化读取的仅仅是Java对象的数据,而不是Java类,因此采用反序列化恢复Java对象时,必须提供该Java对象所属类的class文件,否则将会引发ClassNotFoundException异常。

通过在Field前面使用transient关键字修饰,可以指定Java序列化时无须理会该Field。

另一种自定义序列化机制:

实现Externalizable接口,可以由程序员决定存储和恢复对象的数据,该接口里定义了两个方法。

序列化注意点:

1.对象的类型、field会被序列化。方法、static field、transient field不会被序列化。
2.不想某个field序列化,在相应field前面加transient而不是加static。
3.序列化对象的field必须为序列化。
4.反序列化对象时必须有有序列化对象的class文件。
5.当通过文件、网络来读取序列化后的对象时,必须按实际写入的顺序读取。

NIO:

从JDK1.4开始,Java提供了New IO,简称NIO。
Channel(通道)和Buffer(缓冲)是NIO中的两个核心对象,Channel是对传统的输入/输出系统的模拟,在NIO系统中所有的数据都需要通过通道传输。Channel与传统的InputStream、OutputStream最大的区别在于它提供了一个map()方法,通过map方法可以直接将“一块数据”映射到内存中。如果说传统的输入/输出系统是面向流的处理,则NIO则是面向块的处理。

Buffer可以被理解成一个容器,它的本质是一个数组,发送到Channel中的所有对象都必须先放在Buffer中,而从Channel中读取的数据也必须先放在Buffer中。

Java7对原有的NIO进行了重大改进,称为NIO.2:

1.提供了全面的文件IO和文件系统访问支持。
2.基于异步Channel的IO。

字符集和Charset:

计算机里的文件、数据、图片文件只是一种表面现象,所有文件在底层都是二进制文件,即全部都是字节码。对于文本文件而言,之所以可以看到一个个的字符,这完全是因为系统底层的二进制序列转换成字符的缘故。在这个过程中涉及两个概念:编码(Encode)和解码(Decode),通常而言,把明文的字符序列转换成计算机理解的二进制序列称为编码,把二进制序列转换成普通人能看懂的明文字符创称为解码。

(7)类加载机制与反射:
系统可能在第一次使用某个类时加载该类,也可能采用预加载机制来加载某个类。

JVM和类:

当我们调用Java命令运行某个Java程序时,该命令将会启动一个Java虚拟机进程,不管该Java程序有多么复杂,该程序启动了多少个线程,它们都处于该Java虚拟机进程里。同一个JVM的所有线程、所有变量都处于同一个进程里,它们都使用该JVM进程的内存区。当系统出现以下几种情况时,JVM进程将被终止:
1.程序运行到最后正常结束。
2.程序运行到使用System.exit()或Runtime.getRuntime().exit()代码处结束程序。
3.程序执行过程中遇到未捕获的异常或错误而结束。
4.程序所在平台强制结束JVM进程。

类的加载:

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化3个步骤对该类进行初始化。如果没有意外,JVM将会连续完成这3个步骤,所以有时也把这3个步骤统称为类加载或类初始化。

类加载指的是将类的class文件读入内存,并为之创建一个Java.lang.Class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个Java.lang.Class对象。类的加载由类加载器完成,类加载器通常由JVM提供,开发者可以通过继承ClassLoader基类来创建自己的类加载器。

类的连接:

当类被加载之后,系统为之生成一个对象的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。类连接又可分为如下3个阶段:
1.验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其它类协调一致。
2.准备:类准备阶段则负责为类的静态属性分配内存,并设置默认初始值。
3.解析:将类的二进制数据中的符号引用替换成直接引用。

类的初始化:

在类的初始化阶段,虚拟机负责对类进行初始化,主要就是对静态属性进行初始化。

类初始化的时机:

当Java程序首次用通过下面6种方式来使用某个类或接口时,系统就会初始化该类或接口。
1.创建类的实例。为某个类创建实例的方式包括:使用new操作符来创建实例,通过反射来创建实例,通过反序列化的方式来创建实例。
2.调用某个类的静态方法。
3.访问某个类或接口的静态属性,或为该静态属性赋值。
4.使用反射方式来强制创建某个类或接口对应的Java.lang.Class对象。
5.初始化某个类的子类。当初始化某个类的子类时,该子类的所有父类都会被初始化。
6.直接使用Java.exe命令来运行某个主类。
除此之外,需要注意final修饰的宏变量:final修饰的静态属性,该属性的指在编译阶段就已确定。

反射:

在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性。

获得Class对象:

1.使用Class类的forName(String clazzName)静态方法。
2.使用某个类的class属性来获取这个类对应的Class对象。Person.class。
3.调用某个对象的getClass()方法,该方法是Java.lang.Object类中的一个方法。

从Class中获取信息:

Method getMethod(String name, Class<?>... parameterTypes):返回此Class对象对应类的指定public方法。
Method getMethods():返回此Class对象对应类的所有public方法。
Method getDeclaredMethod(String name, Class<?>... parameterTypes):返回此Class对象对应类的指定方法,与方法的访问权限无关。
Method getDeclaredmethods():返回此Class对象对应类的全部方法。
Class<ClassTest> clazz = ClassTest.class;
clazz.getMethod("info", String.class);

使用反射生成并操作对象

Class对象可以获得该类里的方法(由Method对象表示)、构造器(由Constructor对象表示)、属性(由Field对象表示),这3个类都位于Java.lang.reflect包下。

创建对象:

1.使用Class对象的newInstance()方法来创建该Class对象对应类的实例,这种方式要求该Class对象的对应类有默认构造器。
2.先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()来创建该Class对象对应类的实例。 

转载于:https://www.cnblogs.com/lc19149/p/9341079.html

你可能感兴趣的文章
AC日记——[HNOI2010]BOUNCE 弹飞绵羊 洛谷 P3203
查看>>
MongoDB中ObjectId的误区,以及引起的一系列问题
查看>>
参与2011年7月13日举行的Azure国际猜拳锦标赛,赢取5,000美元大奖
查看>>
设计模式学习2 工厂模式
查看>>
C++2年经验
查看>>
【阿圆实验】Consul HA 高可用方案
查看>>
Android如何防止apk程序被反编译
查看>>
免费的私人代码托管(bitbucket) 和 常用git指令
查看>>
手机端head部分
查看>>
对象Equals相等性比较的通用实现
查看>>
codeforces 876 D. Sorting the Coins
查看>>
maven学习(4)-Maven 构建Web 项目
查看>>
java高并发编程(二)
查看>>
【转】unity3d 在UGUI中制作自适应调整大小的滚动布局控件
查看>>
Java 对synchronized的补充Lock锁
查看>>
Collection集合List、Set
查看>>
Effective java 43返回零长度的数组或者集合而不是null
查看>>
Spring学习系列(二) 自动化装配Bean
查看>>
学习Mybatis与mysql数据库的示例笔记
查看>>
将一个普通的java项目转化为maven项目
查看>>