类与对象
基础概念辨析
- 面向对象的好处: 模块化、信息隐藏、代码重用、易于调试。
- 方法重载overload和方法覆盖override: 重载是多态性的一种,方法名相同,参数不同;覆盖/重写是重写方法体。返回类型不同不属于重载。
- 对象实例化的过程: 对象实体是用类模版创建对象时,新建对象所获得的内存区域。使用new运算符和类的构造方法为新建对象分配内存,为其中的域赋初值,并将这段内存的引用返回给对象变量。
- Java虚拟机的垃圾回收机制: C++中要求程序员跟踪通过new创建的所有对象,不需要时显式地销毁,这样非常容易出错;Java虚拟机的垃圾回收器定期回收已经不再被引用的对象实体所占的内存。但有时垃圾回收会有一定的滞后性,可能导致性能下降。
- static类变量和成员变量: 类变量由static修饰,一个类所有的对象的该类变量都存储在同一内存空间,公用一个类变量;成员变量是对象自己的实例变量副本,存储在成员变量的空间中。类变量可以通过类名或对象实例访问,成员变量只能通过对象实例访问。
- final: 如果一个域被final修饰,就成为常量,该域的值不能被改动,常量不占内存。
- 类方法和成员方法: 一个类中的方法可以相互调用,在方法中可以访问这个类的成员变量。在实例方法中,可以访问实例变量实例方法+类变量类方法;在类方法中不能访问实例变量实例方法,否则可能存在越界访问错误,访问未分配的内存区域。
- 实参和形参: 按值传递基本类型数据参数,传入实参的级别不能高于形参级别(如能将float实参值传给double形参,但不能将float传给int,除非强制类型转换);按值传递引用类型数据参数,是将地址传给函数,在函数中新建的对象赋地址中的值,因此数组、哈希表等引用类型数据在传入函数后是会改变的(但不是地址改变,是地址中保存的值改变),但是String类型比较特殊,传入后是不会改变的,因为String是被final修饰的,要改变只能新开辟一块内存空间,而引用类型数据传参不改变地址值,所以引用的还是原来的String。
- this关键字: 引用当前实例对象,不能在类方法中出现。可用于调用同一个类的其他构造方法,例如Rectangle(int x, int y, int width, int height){...}, Rectangle(int width, int height){this.Rectangle(0, 0, width, height)}
访问权限
| 访问权限修饰符 | 含义 |
|---|---|
| public | 公有变量/公有方法:无论在同一个类的内部、同一个包的不同类、不同包中都可以访问 |
| private | 私有变量/私有方法:只有本类中创建的类对象可以访问。用于隐藏内部属性,防止非法访问,是封装性的体现。 |
| protected | 受保护的变量/方法:不牵涉继承时,protected和无修饰符作用一样;有继承时,子类能访问(同包/不同包)父类的变量/方法 |
| 无/friendly | 友好变量/友好方法:没有权限修饰符时,默认为包私有访问权限,只有和这个类在同一个包中的类才能访问。 |
嵌套类和内部类
| 类型 | 含义 |
|---|---|
| 外部类 | 包含嵌套类的类 |
| 嵌套类 | 在一个类中定义另一个类,是外部类的成员,可以访问外部类的其他成员(包括public/protected/private) |
| 静态嵌套类 | static,只和外部类相关,和外部类生成的实例对象无关,不能直接访问外部类定义的实例变量和方法 |
| 非静态嵌套类(内部类) | 内部类与实例相关联,不能定义静态成员;实例化内部类之前必须先实例化外部类 |
/**实例化内部类之前必须先实例化外部类**/
OuterClass outer = new OuterClass();
OuterClass.InnerClass inner = outer.new InnerClass();
包
注意点:
- 包用于避免命名冲突,实现访问控制。
- 使用小写字母命名包,避免和类名或接口名发生冲突。
- 可以以机构的域名作为包名:cn.edu.szu.csse
- Java语言本身的包使用java.或javax.开头
- 使用另一个包中的类,需要import这个包,再调用包中的类和接口。
- Java运行时环境按需加载所需类的字节码,不加载无关的类。
继承与接口
子类和父类
注意点:
- Java只支持单继承。
- 子类会继承父类所有public和protected的成员变量和成员方法。如果子类和父类在一个包中,子类还会继承父类的友好变量/方法。
- 子类不能继承父类的构造方法。
class Son extends Father{}
当子类创建实例对象时,他的所有成员变量都被分配了内存空间,他的志杰父类和所有祖先类成员变量也都被分配了内存空间。子类可以通过从父类继承的方法,访问父类的私有成员。
成员变量隐藏和方法覆盖
子类中如果有与父类成员名称相同的变量,同名的父类成员会被隐藏。子类不能直接访问被隐藏的父类成员,但可以通过从父类继承的方法访问隐藏变量。
class Father{
int house_area = 100;
public void print_father_house_area(){
System.out.println(house_area);
}
}
class Son extends Father{
double house_area = 120.2;
public void print_son_house_area(){
System.out.println(house_area);
}
}
主函数里:
可以son.print_son_house_area得到被覆盖的house_area,
也可以son.print_father_house_area访问父类被隐藏的同名变量house_area。
- 如果子类中的方法与父类中的方法名字、返回类型、参数个数和类型都相同,父类方法被覆盖。覆盖方法的级别不能降低,即父类中是protected的方法,子类中可以定义为public,但不能改为private。
- 如果父类中的方法不想被覆盖,要使用final限定。
- 被声明为final的类不能被继承,例如String类。
super关键字
super的用法:
- “如果子类中的方法与父类中的方法名字、返回类型、参数个数和类型都相同,父类方法被覆盖。”-->但可以使用super关键字访问父类中被隐藏的成员。
- “子类不能继承父类的构造方法。”-->但可以使用super来调用父类的构造方法。(但是这种方法在对象有较多祖先时,会导致一系列有相互继承关系的类被初始化。)
class Son extends Father{
public Son(int age, int sex){
super(age, sex);
}
}
上转型对象:多态性体现
上转型:子类->父类 强制类型转换:父类->子类
Person p = new Student();
对象p是子类Student的对象实例的上转型对象。 上转型对象会失去子类中的部分属性和功能,只保留与父类相同名称的方法和变量。上转型对象从父类模版中寻找成员变量名和成员方法,因此
- 不能通过上转型对象访问子类对象实例中的成员。
- 子类重写过的同名方法,执行的代码是子类中重写的方法体。
- 可以访问父类中被隐藏的成员变量。
抽象类
- 由关键字abstract修饰
- 不能直接new实例化
- 可能包含也可能不包含抽象方法:抽象方法只有声明,没有实现。
- 有抽象方法的类一定是抽象类。
- 抽象类的子类必须具体实现抽象方法,否则该类也要声明为抽象类。
接口
- Java中可以实现多重接口。
- 接口中只能包含常量、方法声明,不能包含方法体。
- 接口只能用于声明变量,不能实例化。
- 接口中的方法默认是public、abstract的->实现类必须具体实现接口的所有方法。
- 接口也可以继承接口,并可以继承多个接口。
接口回调也是多态性的体现:可以把某一个接口类所创建的对象引用赋值给使用过该接口声明的接口变量。
interface Workable{
void working();
}
class Worker implements Workable{
public void working(){
//....
}
}
在主函数中设定:
Workable wkb;
Worker worker = new Worker();
wkb = worker;
wkb.working();//此时调用的就是worker中已实现的方法体。
String / StringBuilder/ StringBuffer
String
StringBuilder / StringBuffer
String对象一旦创建,字符串的长度和内容将不再发生变化。
- 如果对字符串的修改比较频繁,应使用StringBuilder类。
- 如果需要线程安全,应使用StringBuffer类。
| 作用 | 方法 |
|---|---|
| 长度 | sbd.length() |
| 容量 | sbd.capacity() |
| 构造方法1:无参构造 | 无参构造StringBuilder(),创建容量为16的空字符串 |
| 构造方法2:放入字符序列 | StringBuilder(CharSequence cs),内容与cs相同,尾部添加16个空元素 |
| 构造方法3:指定初始容量 | StringBuilder(int initCapacity) |
| 构造方法4:放入字符串 | StringBuilder(String s),尾部添加16个空元素 |
| 设置字符序列长度 | sbd.setLength(int newLength),如果小于当前字符序列,后面的会被截掉 |
| 保障最低容量 | sbd.ensureCapacity(int minCap) |
| 添加内容 | sbd.append(各种数据类型都可以),容量不够时自动增加 |
| 删除一部分字符 | sbd.delete(int start, int end) |
| 删除一个字符 | sbd.deleteCharAt(int index) |
| 插入 | sbd.insert(int offset, 各种数据类型),插入是第二个参数会先转化为string |
| 替换 | sbd.replace(int start, int end, String s) 或 sbd.setCharAt(int index, char c) |
| 字符串序列翻转 | sbd.reverse() |
| 转为字符串 | sbd.toString() |
泛型和集合
graph LR
Collections-->Collection
Collections-->Map
Collection-->List
Collection-->Set
List-->ArrayList
List-->Vector
List-->LinkedList
Set-->HashSet
Set-->LinkedHashSet
Set-->TreeSet
Map-->TreeMap
Map-->HashMap
Map-->Hashtable
Collection和Map是并列关系,Collection容纳一组集合元素,Map提供从键到值的转换。 Collection中的List表达一个有序集合,类似于数组;Set的特点是不能包含重复的元素,最多有一个null元素。
| 集合类型 | 注意点 |
|---|---|
| ArrayList | 读取操作多时一般用ArrayList。线程不安全。 |
| Vector | 线程安全 |
| LinkedList | 需要频繁地增删元素时用LinkedList |
| HashSet | 使用较多,读取元素是线性时间,设定容量时需要在扩容和检索开销中取舍 |
ArrayList方法
| 功能 | 方法 |
|---|---|
| 添加到列表尾部 | add(E e) |
| 添加到指定位置 | add(int index, E e) |
| 移除所有元素 | clear() |
| 克隆列表 | clone() |
| 是否包含某元素 | contains() |
| 确保最小容量 | ensureCapacity() |
| 获取指定位置元素 | get(int index) |
| 指定元素的索引 | indexOf() |
| 是否为空 | isEmpty() |
| 最后出现的元素位置 | lastIndexOf() |
| 移除指定位置元素 | remove(int index) |
| 移除首次出现的某元素 | remove(Object o) |
| 移除某范围的元素 | removeRange(int idx1, int idx2) |
| 用指定元素替代某元素 | set(int index, E element) |
| 列表元素数 | size() |
| 返回该列表包含的所有元素的数组 | toArray() |
LinkedList在以上基础之上,多了addFirst(), getFirst(), removeFirst(),addLast(), getLast(), removeLast()(增删查)
TreeSet
TreeSet是一个有序集合,使用元素的自然顺序对元素进行排序,或根据创建set时提供的Comparator进行排序(实现Comparator接口)。
- String按字段顺序排列,标点在前,大写字母在后,小写字母在最后
- compareTo()中比较int类型用return this.age - age;(从大到小排列),比较String类型用this.name.compareTo(name)
HashMap
- HashMap<K,V>允许使用null键和null值。
- 不保证映射顺序,不保证顺序不变。
- 加载因子和初始容量会影响HashMap的性能:初始容量时哈希表在创建时的容量;加载因子是HashMap在容量自动增加前最多可以到达多少比例的尺度,当哈希表数量超过加载因子*当前容量时,哈希表进行重建哈希操作。
- 如果迭代性能很重要,就不应该将初始容量设置地过高或将加载因子设置地过低。
File类与输入输出流
Java整个io包中,File类是唯一表示与文件本身有关的类。
File类常见构造方法:
File(String parent, String child)
File(File parent, String child)
File(URI uri)
File(String pathname)
File 类的主要方法
文件名处理
| 作用 | 方法名 | | --- | --id3--IO请求-->id7[中断(阻塞)] id3--sleep-->id8[中断(休眠)] id6--notify-->id2 id7--IO完成-->id2 id8--休眠结束-->id2- | | 得到文件的名称(不包括路径) | getName() | |得到文件的上一级目录名|getParent()| |得到文件的路径名|getPath()| |得到文件的绝对路径名|getAbsolutePath()| |将当前文件名更名为给定文件的完整路径|renameTo(File newName) |
文件属性测试
| 作用 | 方法名 |
|---|---|
| 测试此抽象路径名是否为绝对路径名 | isAbsolute() |
| 测试当前文件是否可读/可写 | canRead()/canWrite() |
| 测试当前File对象指示的文件是否存在 | exists() |
| 测试当前文件是否是文件(不是目录) | isFile() |
| 测试当前文件是否是目录 | isDirectory() |
| 测试当前文件是否是一个隐藏文件 | isHidden() |
普通文件信息和工具
| 作用 | 方法名 |
|---|---|
| 得到文件最近一次修改时间 | lastModified() |
| 得到文件长度 | length() |
| 删除当前文件 | delete() |
目录操作
| 作用 | 方法名 |
|---|---|
| 根据当前对象生成一个由该对象指定的路径 | mkdir() |
| 列出当前目录中的文件和目录 | list() |
IO:输入输出流
概述
- 按数据流动的方向分 输入流:程序需要读取数据时,开启一个通向数据源的流。 输出流:程序需要写入数据时,开启一个通向目的地的流。
- 按流所能处理的数据类型分 字节流:用于处理字节数据 字符流:用于处理Unicode字符数据
- 按流所处理的源分 节点流/低级流:从/向一个特定的IO设备读/写数据的流 处理流/高级流:对已存在的流进行连接和封装的流
字节流
InputStream+OutputStream -> Byte
graph LR
Stream-->InputStream
Stream-->OutputStream
InputStream-->FileInputStream
InputStream-->PipedInputStream
InputStream-->FilterInputStream
InputStream-->ByteArrayInputStream
InputStream-->SequenceInputStream
InputStream-->StringBufferInputStream
InputStream-->ObjectInputStream
FilterInputStream-->LineNumberInputStream
FilterInputStream-->DataInputStream
FilterInputStream-->BufferedInputStream
FilterInputStream-->PushbackInputStream
OutputStream-->FileOutputStream
OutputStream-->PipedOutputStream
OutputStream-->FilterOutputStream
OutputStream-->ByteArrayOutputStream
OutputStream-->ObjectOutputStream
FilterOutputStream-->DataOutputStream
FilterOutputStream-->BufferedOutputStream
FilterOutputStream-->PrintOutputStream
- InputStream常用方法
| 作用 | 方法名 |
|---|---|
| 读取一个字节;如果达到流的末尾返回-1 | read() |
| 从输入流中读取一定数量的字节,存储在缓冲区数组b中 | read(byte[] b) |
| 将输入流中最多len个数据字节读入byte数组 | read(byte[] b, int off, int len) |
| 跳过和丢弃此输入流中数据的n个字节 | skip(long n) |
| 可以不受限制地从此输入流中读取或跳过估计的字节数;如果到达末尾返回0 | available() |
| 在输入流中标记当前位置,readlimit参数告知此输入流在标记位置失效之前允许读取的字节数 | mark(int readlimit) |
| 将此流重新定位到最后一次对此流使用mark方法时的位置 | reset() |
| 此输入流实例是否支持mark和reset方法 | markSupported() |
| 关闭输入流并释放与流关联的所有系统资源 | close() |
- OutputStream常用方法
| 作用 | 方法名 |
|---|---|
| 将制定的字节写入此输出流,要写入的字节是参数的八个低位 | write(int b) |
| 将b.length个字节从指定的byte数组写入此输出流 | write(byte[] b) |
| 将制定的byte数组从off开始的len个字节写入此输出流 | write(byte[] b, int off, int len) |
| 刷新此输出流并强制写出所有缓冲的输出字节 | flush() |
| 关闭输出流并释放与流关联的所有系统资源 | close() |
文件字节流
FileInputStream类
构造方法:
FileInputStream(String name) throws FileNotFoundException //文件找不到则抛出FileNotFoundException异常
FileInputStream(File file) throws FileNotFoundException
读取代码:
public static void main(String args[]){
try{
//创建文件输入流对象
FileInputStream in = new FileInputStream("test.java");
int n = 2;
byte[] buffer = new byte[n];
//读取输入流
while(in.read(buffer, 0, n) != -1){
//...代码逻辑
}
}
catch(IOException e){
e.printStackTrace();
}
finally{
//关闭输入流
in.close();
}
}
FileOutputStream类
在生成FileOutputStream类对象时,如果指定文件不存在,则创建一个新文件;若指定文件存在但是是目录,抛出FileNotFoundException。 文件读写时可能存在IO异常,需要抛出IOException。
构造方法:
FileOutputStream(String name) throws FileNotFoundException //文件找不到则抛出FileNotFoundException异常
FileOutputStream(File file) throws FileNotFoundException
FileInputStream(String name, Boolean append) throws FileNotFoundException
FileInputStream(File file, Boolean append) throws FileNotFoundException
//当append为true时,输出流不会刷新所指向的文件,顺序地向文件写入数据
写入代码:
public static void main(String args[]){
try{
int count, n = 512;
byte[] buffer = new byte[n];
//读取标准输入流
count = System.in.read(buffer);
//创建文件输出流对象
FileOutputStream fos = new FileOutputStream("write.txt");
//写入输出流
fos.write(buffer, 0, count)
}
catch(IOException e){
e.printStackTrace();
}
finally{
//关闭输出流
fos.close();
}
}
实例
/*
* 以字节为单位读取文件,常用于读二进制文件,如图片、声音、影像等文件。
*/
public static void readFileByBytes(String inFile, String outFile) {
File file = new File(fileName);
InputStream in = null;
OutputStream out = null;
try {
byte[] tempbytes = new byte[100];
int byteread = 0;
in = new FileInputStream(inFile);
out = new FileOutputStream(outFile);
while ((byteread = in.read(tempbytes)) != -1) {
out.write(tempbytes, 0, byteread);
}
} catch (Exception e1) {
e1.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e1) {
}
try {
out.close();
} catch (IOException e1) {
}
}
}
}
管道流
-
管道用于将一个程序、线程、代码块的输出连接到另一个的输入。
-
管道流必须是输入输出并用,需要建立连接。
(1) 在构造方法中建立连接 PipedInputStream(PipedOutputStream pos); PipedOutputStream(PipedInputStream pis); (2) 通过各自的connect()方法连接 connect(PipedOutputStream pos); connect(PipedInputStream pis);
数据流
允许应用程序以与机器无关方式从底层输入流中读写基本 Java 数据类型
/**
* DataOutputStream的API测试函数
*/
private static void testDataOutputStream() {
DataOutputStream out = null;
try {
File file = new File("file.txt");
out = new DataOutputStream(new FileOutputStream(file));
out.writeBoolean(true);
out.writeByte((byte)0x41);
out.writeChar((char)0x4243);
out.writeShort((short)0x4445);
out.writeInt(0x12345678);
out.writeLong(0x0FEDCBA987654321L);
out.writeUTF("abcdefg");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch(IOException e) {
}
}
}
/**
* DataInputStream的API测试函数
*/
private static void testDataInputStream() {
DataInputStream in = null;
try {
File file = new File("file.txt");
in = new DataInputStream(new FileInputStream(file));
System.out.printf("byteToHexString(0x8F):0x%s\n", byteToHexString((byte)0x8F));
System.out.printf("charToHexString(0x8FCF):0x%s\n", charToHexString((char)0x8FCF));
System.out.printf("readBoolean():%s\n", in.readBoolean());
System.out.printf("readByte():0x%s\n", byteToHexString(in.readByte()));
System.out.printf("readChar():0x%s\n", charToHexString(in.readChar()));
System.out.printf("readShort():0x%s\n", shortToHexString(in.readShort()));
System.out.printf("readInt():0x%s\n", Integer.toHexString(in.readInt()));
System.out.printf("readLong():0x%s\n", Long.toHexString(in.readLong()));
System.out.printf("readUTF():%s\n", in.readUTF());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
in.close();
} catch(IOException e) {
}
}
}
缓冲流
BufferedInputStream是带缓冲区的输入流,它继承于FilterInputStream。默认缓冲区大小是8M,能够减少访问磁盘的次数,提高文件读取性能。
BufferedOutputStream是带缓冲区的输出流,它继承于FilterOutputStream,能够提高文件的写入效率。
它们提供的“缓冲功能”本质上是通过一个内部缓冲区数组实现的。例如,在新建某输入流对应的BufferedInputStream后,当我们通过read()读取输入流的数据时,BufferedInputStream会将该输入流的数据分批的填入到缓冲区中。每当缓冲区中的数据被读完之后,输入流会再次填充数据缓冲区;如此反复,直到我们读完输入流数据。
public static void readAndWrite(String[] args) {
try {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("f:/a.mp3"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("f:/b.mp3"));
byte[] b=new byte[1024];
int len=0;
while(-1!= (len = bis.read(b, 0, b.length))) {
bos.write(b, 0, len);
}
} catch (FileNotFoundException e) {
System.out.println("文件找不到");
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
if (null! = bos){
bos.close();//自动关闭内部的fos
}
if (null! = bis){
bis.close();//自动关闭内部的fis
}
}
}
字符流
- 字符流不能操作Unicode字符,由于Java使用16位Unicode字符,所以需要使用字符流。
- Unicode字符中,一个汉字被看作两个字节,如果读取不当会出现乱码。
- Reader & Writer的基本操作和IOStream大致相同。
graph LR
Character-->Reader
Character-->Writer
Reader-->BufferedReader-->LineNumberReader
Reader-->CharArrayReader
Reader-->InputStreamReader-->FileReader
Reader-->FilterReader-->PushbackReader
Reader-->PipedReader
Reader-->StringReader
Writer-->BufferedWriter
Writer-->CharArrayWriter
Writer-->OutputStreamWriter-->FileWriter
Writer-->FilterWriter
Writer-->PipedWriter
Writer-->StringWriter
- 当需要按行读写时,常使用BufferedReader/BufferedWirter BufferedReader/BufferedWirter除了基本的read()外,还提供了readLine()/newLine()方法,用于按行读取/新建行。
线程
概念
-
如何运行多个程序? 有足够多的CPU:一个CPU运行一个程序。 如果没有: - 时间片式:时间一到,换另一个程序用。 - 抢占式:抢到就一直霸占CPU直到程序结束(可能出现阻塞)。
-
Java中使用多线程机制封装并发逻辑。
-
程序/进程/线程 程序:没进入CPU之前的是程序,进入之后是进程。 进程:从加载到内存、运行、运行完毕退出内存,是一个完整的产生、发展、小王的生命周期。每个进程享有CPU分配的专用内存数据区。CPU切换时,需要保存当前进程数据区,再恢复另一进程数据区。 线程:比进程小的执行单位,由进程创建,是进程里的一条执行路径。多线程是指进程中有多条线程。同属于一个进程的线程共享进程的资源,包括内存数据区和打开的文件;由于线程本身不拥有资源,所以线程的切换开销小于进程切换开销。
-
JVM加载代码时,会寻找主类的main方法,启动一个线程运行他。如果main中还创建了其他线程,JVM就要在主线程和其他线程之间切换。JVM会等主线程中所有线程结束后才停止。
线程的状态
graph TB
id1[新建]==start==>id2[就绪]
id2==分派CPU==>id3[运行]
id3-.wait.->id6[中断-等待]
id3-.IO请求.->id7[中断-阻塞]
id3-.sleep.->id8[中断-休眠]
id3==run方法完成或stop==>id4[死亡]
id3--时间片结束-->id2
subgraph 中断状态
id6-.notify.->id2
id7-.IO完成.->id2
id8-.休眠结束.->id2
end
- 新建状态:在使用线程类或其子类建立了一个线程对象后,这个对象就一直处于新建状态,在调用start方法之前,该线程一直处于新建状态,但它已拥有相应的内存空间及资源。
- 就绪状态:当新建的线程对象调用start方法后,线程就被启动了,从而进入了就绪状态,进入线程队列排队,等待CPU资源。
- 运行状态:当JVM把CPU的使用权切换给线程,线程就获得了CPU等资源,这时它便自动调用本类中已经定义的run方法开始执行。
- 中断状态:运行状态中的线程,其run方法还没有执行完毕,却放弃CPU的使用,就进入到中断状态。
- 本线程使用CPU时间到,CPU切换到下一个等待的线程。
- 本线程在run方法中调用了sleep,主动出让CPU
- 本线程在run方法中调用了wait()主动出让CPU,知道其他线程调用notify()唤醒,才重新进入就绪状态。
- 线程在使用CPU期间,执行某个操作进入了中断操作,例如等待用户输入。
- 死亡状态:run方法结束或因为其他原因被强行终止。释放内存,不再具有运行的能力。
创建线程
继承Thread类
class TestThread extends Thread{
public void run(){
//在此处写需要并发执行的代码逻辑
}
}
public class Test{
public static void main(String args[]){
TestThread t1 = new TestThread();
th1.start();//开始执行
}
}
实现Runnable接口
class TestThread implements Runnable{
public void run(){
//在此处写需要并发执行的代码逻辑
}
}
public class Test{
public static void main(String args[]){
TestThread testThread = new TestThread();
Thread t1 = new Thread(testThread);//将实现Runnable接口的类传递给线程。
th1.start();//开始执行
}
}
线程的主要方法
| 作用 | 方法名 |
|---|---|
| 设置/获得线程名称 | setName()/getName() |
| 返回当前线程 | currentThread() |
| 线程是否活动:start前返回false,run结束前返回true,run结束后返回false | isAlive() |
| 线程休眠:一般在高优先级的线程中用sleep方法,主动放出CPU,要放在try-catch中 | sleep(int millsecond) |
| 中断线程休眠,进入就绪状态 | interrupt() |
| 使线程进入等待状态 | wait() |
| 唤醒正在等待的线程 | notify()/notifyAll() |
| 目标线程死亡后,当前线程才能运行 | join() |
线程同步
使用synchronized关键字上锁
synchronized(A){S;} //将对象A设为临界资源,S是临界区域代码(需要同步的代码)。【优先使用】
synchronized method(){}//整个方法设为同步的
- synchronized同步代码块,实际上在并发操作中实现互斥机制。
- 只有操作与相同目标对象(锁) 上的同步方法才会互斥。
- 对目标对象的互斥访问只存在于同步代码之间,对于非同步代码是无效的。
synchronized(A){S;}
public class Bank implements Runnable{
public void getMoney(float amount){
sychronized(this){
//取钱的代码逻辑
}
}
}
synchronized method(){}
public class Bank implements Runnable{
public synchronized void getMoney(float amount){
//取钱的代码逻辑
}
}
线程协作
wait() + notify() 锁池和等待池 锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。 等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池中.
notify和notifyAll的区别 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。 当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只有一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争。 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
例子1:银行取钱
//此处省略try-catch
public class Bank implements Runnable{
static Object lock = new Object;
float balance;
public void getMoney(float amount){
sychronized(lock){
lock.wait();
//取钱的代码逻辑
...
lock.notify();
}
}
public void checkBalance(float amount){
sychronized(lock){
lock.wait();
//检查余额
...
lock.notify();
}
}
}
例子2:消费者生产者
public class ProducerConsumerInJava {
public static void main(String args[]) {
Thread producer = new Producer();
Thread consumer = new Consumer();
producer.start();
consumer.start();
}
}
//生产者线程
class Producer extends Thread {
@Override public void run() {
while (true) {
synchronized (queue) {
while (queue.size() == maxSize) {
try {
queue.wait();
} catch (Exception ex) {
ex.printStackTrace(); }
}
...
//TODO:生产一个产品
queue.notifyAll();
}
}
}
}
//消费者线程
class Consumer extends Thread {
@Override public void run() {
while (true) {
synchronized (queue) {
while (queue.isEmpty()) {
try {
queue.wait();
} catch (Exception ex) {
ex.printStackTrace();
}
}
//TODO:消费一个产品
queue.notifyAll();
}
}
}
}
线程挂起
需要先线程A中让B先运行,在A代码中加入B.join()语句。
public static void main(String[] args){
Thread B = new Thread(new testThread());//testThread类实现了Runnable
//main的代码
...
B.join();
//main的其他代码
...
}
Java网络编程
网络地址InetAddress(IP地址)
IPv4使用4个字节(32个比特)来表示一个IP地址。
常用方法
| 作用 | 方法名 |
|---|---|
| 获取本机地址 | InetAddress的静态方法getLocalHost() |
| 获取互联网主机地址 | InetAddress的静态方法getByName("www.baidu.com") |
UDP数据报
- UDP是面向无连接的、不可靠的传输协议。开销小,传输延迟短,对传输环境要求高。
- 发送时需要制定IP地址、发送端口、接收端口;接收时需要监听相应的目的端口。
- 协议、IP地址、端口是上层应用程序间通信的窗口,即套接字socket。UDP协议的socket为DatagramSocket。
- 由于UDP是面向无连接的,不关注发送端口,发送端的DatagramSocket可以无参构造,而接收端监听port:DatagramSocket(int port)。
- DatagramSocket收发的是数据报包裹DatagramPacket 发送包裹时,需要填写接收地址: DatagramPacket(byte[] data, int length, InetAddress ip, int port) 接收包裹时,只关注包裹的内容: Datagrampacket(byte[] data, int length)
发送
public class Sender{
public static void main(String args[]) throws Exception{
//注意实际使用时需要用try-catch-finally包一下socket
DatagramSocket ds = null;
DatagramPacket pkg = null;
//实例化socket,端口号为2345
ds = new DatagramSocket(2345);
//需要发送的数据
String str = "Cherry Chen";
//数据包
pkg = new DatagramPacket(str.getBytes(), str.length(),
InetAddress.getByName('127.0.0.1'), 8888);
//发送数据
ds.send(pkg);
//关闭(要放在finally里面)
ds.close;
}
}
接收
public class Receiver{
public static void main(String args[]){
//注意实际使用时需要用try-catch-finally包一下socket
DatagramSocket ds = null;
DatagramPacket pkg = null;
//定义接收空间大小
byte[] buffer = new byte[1024];
//实例化socket,绑定8888端口
ds = new DatagramSocket(8888);
//实例化套接字数据存放空间
pkg = new DatagramPacket(buffer, buffer.length);
//将收到的信息存放在pkg中
ds.receive(pkg);
//关闭(要放在finally里面)
ds.close;
}
}
TCP
- TCP是面向连接的传输控制协议,提供可靠的数据流传输服务。
- 客户端创建Socket对象连接到服务端,服务端使用ServerSocket类对象监听客户端的socket。
客户端
public class Client {
public static void main(String[] args) throws IOException {
InetAddress address = InetAddress.getByName("127.0.0.1");
int port = 9999;
//建立socket
Socket socket = new Socket(address, port);
OutputStream outputStream = null;
String text = "";
try {
outputStream = socket.getOutputStream();
Scanner scanner = new Scanner(System.in);
text = scanner.next();
outputStream.write(text.getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
服务器
public class Server {
public static void main(String[] args) throws Exception {
final int SERVICE_PORT = 9999;
Socket nextClient = null;
InputStream inputStream = null;
InputStreamReader inputStreamReader = null;
BufferedReader bufferedReader = null;
ServerSocket serverSocket = null;
try {
// 建立服务器socket
serverSocket = new ServerSocket(SERVICE_PORT);
while (true) {
//获取下一个TCP客户端
nextClient = serverSocket.accept();
//显示连接细节
System.out.println("Received request from " +
nextClient.getInetAddress() + ":" + nextClient.getPort());
inputStream = nextClient.getInputStream();
inputStreamReader = new InputStreamReader(inputStream);
bufferedReader = new BufferedReader(inputStreamReader);
String tmp = "";
while((tmp = (bufferedReader.readLine()))!=null){
System.out.println(tmp);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭流
inputStream.close();
inputStreamReader.close();
bufferedReader.close();
//关闭连接
nextClient.close();
serverSocket.close();
}
}
}
socket还可以半关闭(接收端继续接收但关闭其他操作):socket.shutdownOutput();
URL链接
- 统一资源定位符URL类似于文件名在网络上的扩展,可以理解为与互联网相连的机器上的任何可访问对象的指针。
- 一般形式为:<URL的访问协议>://<主机名或IP地址>:<端口号(可选)>/<路径>
- 常见的访问协议为http和ftp。
| 作用 | 方法名 |
|---|---|
| 返回URL对象的查询部分 | getQuery() |
| 返回URL对象的路径部分 | getPath() |
| 返回URL对象的用户信息部分 | getUserInfo() |
| 返回URL对象的协议部分 | getProtocol |
| 返回URL对象的引用部分 | getRef() |
| 返回URL对象的主机名部分 | getHost() |
参考资料
张席 《Java语言程序设计教程》
djzhao blog.csdn.net/djzhao/arti…