Java学习 Part.3
40 流
//import java.io.*;
(1) 流的定义
流就是程序和设备之间嫁接起来的一根用于数据传输的管道,这个管道上有很多按钮,不同的按钮可以实现不同的功能,这根管道就是流。
(2) 流的分类标准
java:io包中定义了多个流类型(类或抽象类)来实现输入/输出功能;可以从不同的角度对其进行分类:
按数据流的方向不同可以分为输入流和输出流。
按处理数据单位不同可以分为字节流和字符流。
按照功能不同可以分为节点流和处理流。
J2SDK所提供的所有流类型位于包java.io内都分别继承自以下四种抽象流类型:Reader,Writer,Inputstream,Outputstream
(3) 节点流与处理流
节点流为可以从一个特定的数据源(节点)读写数据(如:文件,内存)。也叫原始流。
处理流是“连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能。也叫包裹流。
(4) 四大基本抽象流
//输入输出,读写都是针对程序本身来说而非设备。
//几乎所有的流都继承自lnputStream,OutputStream,Reader,Writer中的一个,但这几大抽象类不可直接使用。
lnputStream和OutputStream读写数据的单位是一个字节,Reader和Writer读写数据的单位是一个字符。在Java中一个字符占两个字节。
lnputStream,OutputStream,Reader,Writer这四个类都是抽象类。凡是以stream结尾的都是字节流。
//Read()方法中没有形参的返回值是数据本身的二进制的int表示,而形参是数组的则是读取的数据个数。
***(5) InputStream流中常用的方法//*****返回值皆为实际读取的字节数*****
读取一个字节并*以整数形式返回*,如果读取到输入流的末尾则返回-1。
public int read() throws IOException
从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中。以整数形式返回*实际读取的字节数*。
如果b的长度为0,则不读取任何字节并返回0;如果因为流位于文件末尾而没有可用的字节,则返回值-1。
public int read(byte[] b) throws IOException
例子:
Filelnputstream fis = new Filelnputstream("d:\\share\\errorlog.txt");
****len=fis.read(buf); //如果读取到文件的结尾,则返回-1。
从fis流所关联的d:\\share\\errorlog.txt文件中读取数据,并将读取出来的数据写入buf数组中,返回值是实际写入buf数组的字节个数,***如果读取到文件的结尾,则返回-1***。
从输入流中最多读取len个字节的数据并存入byte数组中,b表示读取的数据要存入的数组的名字,off表示第一个读出的数据要存入的位置,是下标,len表示最多能读取的字节数,
将从输入流所关联到的设备中读取的第一个字节存储在元素b[off]中,下个字节存储在b[off+1]中,依次类推。读取的字节数最多等于len,尝试读取len个字节,但读取的字节也可能小于该值。以整数形式返回实际读取的字节数。如果读到了文件的末尾,则返回-1。
public int read(byte b[], int off, int len) throws IOException
关闭此输入流并释放与该流关联的所有系统资源
void close() throws IOException
跳过和丢弃此输入流中数据的n个字节(这个用的很少)
long skip(long n) throws IOException
***(6) OutputStream流中常用的方法
向输出流中写入一个学节数据,该字节数据为参数b的低8位
void write(int b) throws IoException
将一个字节类型的数组中的数据写入输出流
void write(byte[] b) throws IoException
将一个字节类型的数组中的从指定位置 off 开始的 len 个字节写入到输出流
void write(byte[] b, int off, int len) throws IoException
关闭流释放内存资源
void close() throws IoException
将输出流中缓冲的数据全部写出到目的地
void flush() throws IoException
***(7) Reader流的常用方法
*读取一个字符并以整数的形式*返回(0~255),如果返回-1已到输入流的末尾
int read() throws IoException
读取一系列字符并存储到一个数组buffer,返回*实际读取的字符数*,如果读取前已到输入流的末尾返回-1
int read(char[] cbuf) throws IoException
最多读取length个字符,并存储到一个数组buffer,从length位置开始,返回实际读取的字符数,如果读取前以到输入流的末尾返回-1
int read(char[] cbuf, int offset, int length) throws IoException
关闭流释放内存资源
void close() throws IoException
跳过n个字符不读,返回实际跳过的字节数
long skip(long n) throws IoException
***(8) Writer流中常用方法
向输出流中写入一个字符数据,该字节数据为参数b的低16位
void write(int g) throws IoException
将一个字符类型的数组中的数据写入输出流
void write(char[] cbuf) throws IoException
将一个字符类型的数组中的从指定位置(offset)开始的length个字符写入到输出流
void write(char[] cbuf, int offset,int length) throws IoException
将一个字符串中的字符写入到输出流
void write(string string) throws IoException
将一个字符串从offset开始的length个字符写入到输出流
void write(string string, int offset, int length) throws IoException
关闭流释放内存资源
void close() throws IoException
将输出流中缓冲的数据全部写出到目的地
void flush() throws IoException
//int i=dis.read(byte[] b)表示,管道(dis)使用读功能,而管道连接的设备一端(dis关联的文件)“被”读出。
//管道另一端连接程序,程序再指定将内容存放至何处。
//dos.write(byte[] b)表示,管道(dos)使用写功能,而管道连接的设备一端(dos关联的文件)“被”写入。
//管道另一端连接程序,程序再指定写入的内容。
41 文件流
###(原始流:FilelnputStream、FileOutputStream、FileReader、FileWriter)
//作用:将一个文件的内容按字节为单位读取出来。
//如果通过一个字节流把文本文件的内容输出到显示器上,当输出***汉字***时就会出现*乱码*。
(1) 文件流包括:字节流(FilelnputStream FileOutputStream)、字符流(FileReader FileWriter)。
(2) FilelnputStream的使用
InputStream是用来读取字节的,是个抽象类,我们通常使用的是该类的子类。
FilelnputStream是lnputStream的子类,利用FilelnputStream可以将一个文件的内容按字节为单位读取出来。
***Filelnputstream有一个很常用的构造函数
public FilelnputStream(String fileName) throws FileNotFoundException
利用该构造函数可以实现将输入流连接到某个文件的功能。
必须对本构造函数**抛出的异常**进行捕捉。
***如果用字符串来表示操作系统的文件路径时,我们可以使用\\和/两种方式来作为文件夹的路径分隔符。
FileOutputstream同理,不再赘述。
(3) FileReader的使用
Reader是用来读取字符的,是个抽象类,我们通常使用的是该类的子类。
FileReader是Reader的子类,利用FileReader可以将一个文件的内容以字符为单位读取出来。
***FileReader有一个很常用的构造函数
public FileReader(String fileName) throws FileNotFoundException
利用该构造函数可以实现将输入流连接到某个文件的功能。
必须对本构造函数抛出的异常进行捕捉。
如果用字符串来表示操作系统的文件路径时,我们可以使用\\和/两种方式来作为文件夹的路径分隔符。
FileWriter同理,不再赘述。
***(4) 字节流与字符流区别
Filelnputstream和FileOutputStream可以完成所有格式文件的赋值。
FileReader和FileWriter只可以完成文本文件的复制,却无法完成视频格式文件的复制,因为字节是不需要解码和编码的,将字节转化为字符才存在解码和解码的问题。
字节流可以从所有格式的设备中读写数据,但字符流只能从文本格式的设备中读写数据。
42 缓冲流
###(包裹流:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter)
//作用:对读写的数据提供了缓冲的功能,使流速度更快。
(1) 缓冲流就是带有缓冲区的输入输出流,缓冲流可以显著的减少我们对IO访问的次数,保护我们的硬盘!
缓冲流本身就是处理流(处理流也叫包裹流),缓冲流必须得依附于节点流(节点流也叫原始流),是包裹在原始节点流上的流,相当于包括在管道上的管道!
(2) 缓冲流要“套接”在相应的节点流之上,对读写的数据提供了缓冲的功能,提高了读写的效率,同时增加了一些新的方法。
J2SDK提供了四种缓存流,其常用的构造方法为:
BufferedReader(Reader in)
BufferedReader(Reader in,int sz)//sz为自定义缓存区的大小,一般不用,缓冲区会自动增加大小。
BufferedWriter(Writer out)
BufferedWriter(Writer out,int sz)
BufferedInputStream(Inputstream in)
BufferedInputStream(Inputstream in,int size)
BufferedOutputStream(OutputStream out)
BufferedOutputStream(OutputStream out,int size)
缓冲输入流支持其父类的mark和reset方法。
BufferedReader提供了readLine()方法用于读取一行字符串(以\r或\n分隔),BufferedWriter提供了newLine()用于写入一个行分隔符。
对于输出的缓冲流,写出的数据会先在内存中缓存,使用flush()方法将会使内存中的数据立刻写出。
(3) BufferedOutputStream和BufferedlnputStream
BufferedOutputStream:带缓冲的输出流,允许一次向硬盘写入多个字节的数据。
BufferedlnputStream:带缓冲的输入流,允许一次向程序中读入多个字节的数据。
**BufferedOutputStream和 BufferedlnputStream都是包裹流:必须的依附于OutputStream和lnputStream。
利用BufferedOutputStream和BufferedlnputStream完成大容量文件的复制,这远比单纯利用FilelnputStream和FileOutputStream要**快得多**。
BufferedlnputStream流中有
public int read(byte[] b)
方法用来把从当前流关联到的设备中读取出来的数据存入一个byte数组中。
BufferedOutputStream流中有
public int write(byte[] b)
方法用来把byte数组中的数据输出到当前流所关联到的设备中。
如果我们希望用BufferedInputStream和BufferedOutputStream完成“将一个设备中的数据导入另个设备中”。我们就应该定义一个临时的byte类型的数组,用这个临时数组作为输入流与输出流进行交互的中转枢纽。
(4) BufferedReader和BufferedWriter
我们只有
//只有四大基本抽象流的缓冲流
BufferedlnputStream
BufferedOutputStream
BufferedWriter
BufferedReader
没有
Bufferedstream
BufferedFilelnputStream(但有FileOutputStream)
BufferredFileOutputStream(但有FilelnputStream)
BufferedFileReader(但有FileReader)
BufferedFileWriter(但有FileWriter)
所以前四个流都是包裹流
Reader FileReader InputStream FilelnputStream BufferedlnputStream流中都没有readLine方法。
Datalnputstream流中有readLine方法,但已经被标记为过时。
**BufferedReader流中有**readLine**方法,并且该方法是可以正确被使用的。(只有是输入流的缓冲流中才有readline方法,且该流必须为字符流)
读取一个文本行。返回包含该行内容的字符串。
public String readLine() throws IOException
BufferedReader流中有readLine是个非常有用的方法。
BufferedReader是个要经常使用的流。
利用BufferedReader和BufferedWriter可以提高读写文本文件内容的**速度**。
43 数据流
###(包裹流:DatalnputStream、DataOutputStream)
//作用:可以“直接”将基本类型数据二进制写入或读出。
//写入的内容为基本数据类型的二进制,但读出为内容本身。
(1) DatalnputStream
DatalnputStream能够以一种与机器无关的方式,直接从底层字节输入流读取Java基本类型和String类型的数据。常用方法包括:
public DatalnputStream(InputStream in)//构造方法
public final boolean readBoolean()
public final byte readByte()
public final char readChar()
public final double readDouble()
public final float readFloat()
public final int readlnt()
public final long readLong()
public final short readShort()
public final String readUTF()***//*****返回值都是数据本身,而非实际读取的字节数。*****
DatalnputStream是包裹流,必须依附于InputStream。
(2) DataOutputStream
DataOutputStream能够以一种与机器无关的方式,直接将Java基本类型和String类型数据写出到其他的字节输出流。常见方法包括:
public DataOutputStream(Outputstream in)//构造方法
public final booleanwriteBoolean()
public final byte writeByte()
public final char writeChar()
public final double writeDouble()
public final float writeFloat()
public final int writelnt()
public final long writeLong()
public final short writeShort()
public final String writeUTF()
DataOutputstream是包裹流,必须依附干OutputStream。
(3) 数据流实例
TestByteArrayOutputStream1.java
TestByteArrayOutputStream2.java
例子:编程实现将long类型数据写入byte数组,然后再从byte数组中把该数据读出来。
附注:这是Socket编程中经常要完成的功能,因为网络编程中经常要把数值型数据存入byte数组中,然后把byte数组打包成数据包(即DatagramPacket),
再把数据包经过网络传输到目的机,目的机再从byte数组中把原数值型数据还原回来。
本程序要使用到:
***ByteArraylnputStream(数组流:原始流)
***ByteArrayOutputstream(数组流:原始流)
DatalnputStream
DataOutputstream
44 字节数组流
###(原始流:ByteArraylnputStream、ByteArrayOutputstream)
//作用:创建关联一个数组的字节流。
(1) ByteArraylnputStream的构造方法及常用方法:
创建一个 ByteArrayInputStream,使用 buf 作为其缓冲区数组。
public ByteArrayInputStream(byte[] buf)
创建 ByteArrayInputStream,使用 buf 作为其缓冲区数组。
public ByteArrayInputStream(byte[] buf,int offset,int length)
从此输入流中读取下一个数据字节。
public int read()
(2) ByteArrayOutputstream的构造方法及常用方法:
创建一个新的 byte 数组输出流。
public ByteArrayOutputStream()
创建一个新的 byte 数组输出流,它具有指定大小的缓冲区容量(以字节为单位)。
public ByteArrayOutputStream(int size)
创建一个新分配的 byte 数组。其大小是此输出流的当前大小,并且缓冲区的有效内容已复制到该数组中。
public byte[] toByteArray()
45 转换流
###(包裹流:InputStreamReader、OutputStreamWriter)
//作用:转化字节流和字符流。
(1) OutputStreamWriter流是把OutputStream流转化成Writer流的流,
InputStreamReader是把InputStream转化成Reader。
OutputStreamWriter和InputStreamReader都是包裹流。
(2) readLine()与回车符的问题
String str;
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
str = br.readLine();
如果直接输入回车的话.则br.readLine()会丢弃回车符,而不是返回回车符,即br.readLine()遇到回车符时终止读取,并且会把读取到的回车符自动丢弃掉。br.readLine()返回的是“”而不是null,“”表示空字符串,null表示空指针空指针就是空地址,空地址就是不指向任何存储单元的意思。
46 print流
###(既可为原始流,也可为数据流:PrintWriter、PrintStream)
//作用:写入所有基本类型数据的字符串形式表示。
**(1) Print流只输出,不输入。
分类:PrintWriter输出字符、PrintStream输出字节。
(2) Print流的由来
Writer的write方法可以写入:
一个字符
void write(int c)
一个字符数组
void write(char[] cbuf)
一个字符数组的一部分
void write(String str, int off, int len)
一个字符串
void write(String str)
一个字符串的一部分
void write(String str, int off, int len)
Outputstream的write方法可以写入:
一个字节
abstract void write(int b)
一个字节数组
void write(byte[] b)
一个字节数组的一部分
void write(byte[] b, int off, int len)
DataOutputstream流可以写入:
Outputstream中的所有write方法。
所有的基本类型数据的二进制代码。
如:writeDouble(8.8);写入的是8.8的二进制代码,共占8个字节
PrintStream流的可以写入:
*****print()方法以写入所有基本类型数据的字符串形式表示*****:
如:print(8.8);写入的是"8"".""8"这三个字符,共占3个字节
(3) PrintStream
构造方法及常用方法:
创建具有指定文件且不带自动行刷新的新打印流。
public PrintStream(File file) throws FileNotFoundException
创建新的打印流。此流将不会自动刷新。
public PrintStream(OutputStream out)
打印各种基本数据类型
public void print(data type)
打印各种数据类型,然后终止该行
public void println(data type)
PrintStream在OutputStream基础之上提供了增强的功能,即可以方便地输出各种类型数据(而不仅限于byte型)的格式化表示形式。
PrintStream重载了print和println方法,用于各种不同类型数据的格式化输出,格式化输出是指将一个数据用其字符串格式输出。
DataOutputStream中的writeXXX(data)方法是把data在内存中的二进制数据写入文件。
PrintStream中的println(data)写出的是该数据的格式化后的字符串。//println()和print()将字符串写入outputstream中所关联的文件中。
(4) PrintWriter
构造方法及常用方法:
创建具有指定文件且不带自动行刷新的新打印流。
public PrintStream(File file) throws FileNotFoundException
创建新的打印流。此流将不会自动刷新。
public PrintStream(OutputStream out)
打印各种基本数据类型
public void print(data type)
打印各种数据类型,然后终止该行
public void println(data type)
PrintWriter提供了PrintStream的所有打印方法,其方法也从不抛出IoException。
与PrintStream的区别:
作为处理流使用时,PrintStream只能封装OutputStream类型的字节流,而PrintWriter既可以封装OutputStream,还能够封装Writer。
PrintWriter中的println()方法具有更好的跨平台性。
***(5) 标准输入输出的重定向
例子:
编程实现将键盘输入的数据输入A文件中,如果输入有误,则把出错信息输出到B文件中:
TestSetOutErr.java
System.setOut(PrintStream p);
//修改Out默认关联的设备或文件。Out默认关联显示器。
System.setErr(PrintStream p);
//修改Err默认关联的设备或文件。e.printStackTrace()默认是输出到System.err所关联的设备中。err默认关联显示器。
47 Object流
###(包裹流:ObjectInputStream、ObjectOutputStream)
//作用:把一个Object对象直接转化为字节流,然后把这个字节流直接写入本地硬盘或网络中。
(1) 对象的序列化
所谓序列化是指:*****把一个Object对象直接转化为字节流,然后把这个字节流直接写入本地硬盘或网络中*****。
如果要想把某个对象序列化,则必须的实现Serializable接口。
(2) Serializable接口中并没有任何方法,该接口被称为标记接口,
如果一个类实现了Serializable接口,潜在含义就是告诉编译器这个类是允许被序列化的,
如果程序中存在序列该对象的代码,编译器就会自动进行相应的处理已完成该对象的序列化,
如果该对象没有实现Serializable接口,程序中却存在该对象被序列化的代码,编译器编译时就会报错!
(3) 在Java中transient修饰的成员变量在对象序列化时不被序列化。
(4) ObjectInputStream的构造方法及常用方法:
创建从指定 InputStream 读取的 ObjectInputStream。
public ObjectInputStream(InputStream in) throws IOException
从 ObjectInputStream 读取对象。
public final Object readObject() throws IOException,ClassNotFoundException
(5) ObjectOutputStream的构造方法及常用方法:
创建写入指定 OutputStream 的 ObjectOutputStream。
public ObjectOutputStream(OutputStream out)
将指定的对象写入 ObjectOutputStream。
public final void writeObject(Object obj)
48 容器
//容器中所有的元素都是类对象,即使为基本数据类型,也会“自动封装”成类对象。
(1) 数学上,集合类型可以归纳为三种:
集(Set):Set集合中不区分元素的顺序,不允许出现重复元素。
列表(List):List集合区分元素的顺序,且允许包含重复元素。
映射(Map):映射中保存成对的“键-值”(Key-Value)信息,映射中不能包含重复的键,每个键最多只能映射一个值。
Java设计了三个接口来对应数学上的三种集合类型,这三个接口名字分别是Set List Map。
(2) 容器API
Collection接口定义了存取一组对象的方法,其子接口Set和List分别定义了存储方式。
Set中的数据对象没有顺序且不可以重复。
List中的数据对象有顺序且可以重复。
Map接口定义了存储"键(key)一值(value)映射对"的方法。
(3) Collection接口中的方法介绍
返回此collection中的元素数
int size()
判断该容器是否为空。
boolean isEmpty()
如果容器包含指定的元素,则返回 true。
boolean contains(Object o)
判断形参c所指向的集合中所有的元素是不是已经全部包含在了当前集合中,是,返回true,否则返回false。
boolean containesAll(Collection c);
把e添加到当前集合中
boolean add(Object e)
把c中所有的元素添加到当前集合中
boolean addAll(Collection c)
删除该容器中对象元素o
boolean remove(Object o)
删除该容器中所有c容器元素
boolean removeAll(Collection c)
把当前容器中的所有元素清除
void clear()
返回能够遍历当前集合所有元素的迭代器
Iterator iterator()
容器不是数组,不能通过下标的方式访问容器中的元素,只有数组才可以通过下标来访问。
返回一个包含此collection中所有元素的数组。
Object toArray()
以下两个暂时不讲
boolean equals(Obiect o)
int hashCode()
(4) Collection接口的子接口--List接口
//可以增 删 改 查
List接口是Collection的子接口,实现List接口的容器类中的元素是有顺序的,而且可以重复。
List容器中的元素都对应一个整数型的序号,记载其在容器中的位置,可以根据序号存取容器中的元素。
J2SDK 所提供的 List容器类有 ArrayList,LinkedList等。
boolean add(E e)
void add(int index, object element)
object get(int index)
//index从0开始
object set(int index, object element)
object remove(int index)
int indexof(object o)
int lastIndexof(object o)//最后出现
(5) Collection接口的子接口--Set接口
//只能 增 删
因为Set和List都是继承自Collection接口,所以Set和List中有很多方法是一样的。
List接口中有add,set,indexOf方法,但是Set接口中却只有add方法,没有set,indexOf方法。
因为Set是无序不能重复的,不存在某元素具体位置这个概念。
set方法与collection中方法相同。
***(6) 输出容器中元素
Collection c = new ArrayList();
System.out.printin(c);
本语句等价于:
***调用第一个元素的toString方法,调用第二个元素的toString方法,调用第三个元素的toString方法,以此类推。
因此强烈建议:所有*****添加到Collection容器中的类对象都应该重写父类Object的tostring方法*****。
(7) ArrayList与LinkedList的比较
ArrayList和LinkedList都实现了List接口中的方法,但两者内部实现不同。
ArrayList底层采用数组完成,而LinkedList则是以一般的双向链表(double-linkedlist)完成。
其内每个对象除了数据本身外,还有两个引用,分别指向前一个元素和后一个元素。
如果我们经常在List的开始处增加元素,或者在List中进行插入和删除操作,我们应该使用LinkedList,否则的话,使用ArrayList将更加快速。
ArrayList存取速度快,插入删除慢;LinkedList存取速度慢,插入删除速度快。
(8) Colletions类
Collection接口的实现类,如ArrayList、LinkedList 本身并没有提供排序,倒置,查找等方法。
这些方法是由Collections类来实现的,****该类有很多public static方法,可以直接对collection接口的实现类进行操作。
类java.util.Collections提供了一些静态方法实现了基于***List容器的一些常用算法。
对List容器内的元素排序
public static void sort(List)//默认升序排序,若想降序排序,先调用Collections.sort()再调用Collections.reverse()
对List容器内的对象进行随机排列
public static void shuffle(List)
对List容器内的对象进行逆续排列
public static void reverse(List)
用一个特定的对象重写整个List容器
public static void fill(List,object)
将srcList容器内容拷贝到destList容器
public static void copy(List dest,List src)
对于顺序的List容器,采用折半查找的方法查找特定对象
public static int binarySearch(List, object)//默认容器元素已为升序排序,使用binarySearch()方法的前提是该客器已升序排序。
使用:Collections.方法名();
49 Comparable接口
(1) 为何要使用Comparable接口
基本类型数据和String类型数据,它们彼此的比较标准Java语言本身已经提供好了。
用户***自定义类对象***之间比较的标准Java语言本身是***没有提供的***。
所以如果一个容器中含有用户自定义类型的数据,并且我们需要对容器中元素***进行排序,或查找某一元素时***,
我们就必须得制定容器中元素与元素之间比较的标准。
先使类继承Comparable接口
class A implements Comparable{}
再重写其中的compareTo()方法
public int compareTo(object obj){
......
if(this==obj)
return 0;
if(this>obj)
return 正数;
if(this<obj)
return 负数;
或
......
return this.i-obj.i;//i为分类标准。
}
凡是需要进行对象比较排序的场合均可考虑实现。
(2) 所有可以"排序"的类都实现了java.lang.Comparable接口,Comparable接口中只有一个方法:
public int compareTo(object obj);
该方法:
返回0表示this==obj
返回正数(比如1)表示this>obj
返回负数(比如-1)表示this<obj
*****实现了Comparable接口的类通过实现compareTo方法从而确定该类对象的排序方式。
50 Set接口
(1) Set接口是Collection的子接口,Set接口没有提供额外的方法,但实现Set接口的容器类中的元素是没有有顺序的,而且不可以重复。
Set容器可以与数学中“集合”的概念相对应。
J2SDKAPI中所提供的Set容器类有HashSet,TreeSet等。
(2) HashSet类
HashSet类实现了Set接口
HashSet容器中的元素是不能重复,无顺序的。
***存放入HashSet容器中的类必须要重写equals()(参见25)和hashCode()方法。
若不重写equals()和hashCode()方法,则set不能实现“不重复”的功能。
(3) 什么类必须得重写equals()和hashCode()方法
Hashtable HashSet HashMap都必须要同时实现equals()方法和hashCode()方法,TreeSet和TreeMap则不需要实现equals()方法和hashCode()方法。
//因为前三者是由哈希表实现,后两者是由树实现。
(4) 为什么要重写equals()和hashCode()方法
散列码:
Object中的hashCode()方法会返回该对象的内存真实地址的整数化表示,这个形象的不是真正地址的整数值就是哈希码。
向HashSet中添加对象时,HashSet先通过该对象的hashCode()计算出相应的桶(即地址空间)。然后再根据equals()方法查找地址空间中是否有相同的对象。
如果容器中已存在该对象则不再添加,如果不存在,则添加进去!
//hashCode()方法保证不同内容的类对象,放入不同的地址空间。
//equals()方法保证相同内容的类对象不会同时出现在同一地址空间。
//两方法共同保证元素不重复。
(5) 样重写equals()和hashCode()方法
如何重写equals()
参上。
如何重写hashCode方法
public int hashCode(){
return 当前类中基本类型数据对象的hashcode()方法;
//除String以外基本数据类型都要先封装成为对象,才能调用hashCode()方法。
//再用其他数字类型的属性与该哈希码相乘。
}
//哈希码为类对象地址的十六进制近似表示,所以即使类对象内容相同,hashCode()返回的哈希码仍不相同。
//自定义类若要使相同内容的类对象hashCode()返回相同的哈希码,必须要重写hashCode()方法。
//*****基本数据类型类中的hashCode()方法已被重写,相同内容的对象可以输出相同的哈希码。*****
(6) HashSet类中并没有public Object get(int index)方法,因此要想输出HashSet容器所有的元素,可以借助iterator()方法来实现。
(7) TreeSet类
TreeSet类实现了Set接口。
TreeSet是一个有序集合,TreeSet中元素将按照升序排列缺省是按照自然顺序进行排列,因此TreeSet中元素要实现comparable接口,重写compareTo()方法。
记住:所有可以进行排序的类都应该实现Comparable接口。
(8) HashSet和TreeSet的比较
HashSet是基于Hash算法实现的,其性能通常都优于TreeSet。我们通常都应该使用HashSet。
在我们需要排序的功能时,我们才使用Treeset。
51 Iterator接口
(1) lterator接口用来以统一的方式对集合中的各个元素进行遍历。
Iterator接口的对象又称为迭代器,利用该对象可以方便的遍历容器中的元素。
所有实现了Collection接口的容器类都有一个iterator方法,方法返回一个实现了lterator接口的对象。//c.iterator()相当于返回一个容器头指针。
(2) Iterator方法介绍
是用来判断当前游标的后面还是否存在元素,如果存在返回真,否则返回假。
boolean hasNext()
先返回当前游标后边的元素,然后游标后移一个位置。
Object next()
删除最近返回的元素。
void remove()
//在调用remove之前,我们至少保证先调用一次next方法,而且调用next之后只能调用一次remove方法。
//remove()方法不推荐使用
***(3) 可以Iterator可实现对所有容器的遍历。
Iterator i = c.iterator();//相当于返回c容器头指针。
while (i.hasNext()){
System.out.println(i.next());
}
52 Map接口
(1) 哈希表的定义:
哈希表不是只存储需要保存的数据,而是既保存数据,也保存该数据的主键,实际是:
先保存主键,然后哈希表会根据某种算法自动计算出以当前主键为主键的数据的存储位置,然后再把该数据保存进去。
哈希表:
假设待保存的数据是val,val的主键是key,则哈希表是先存储key,然后哈希表会自动根据key计算出val的存储位置,并最终把val存储进去。
哈希表注意事项:
Hash即哈希表文称散列表。
哈希表主要是为了提高数据的存储速度和查找速度而设计的。
哈希表是人类的一种追求,人很难设计出完美的哈希表。
几乎所有的哈希表都会产生哈希冲突。
Java中是利用桶来解决哈希冲突的。
什么容器必须的重写equals方法和hashCode方法:
Hashtable HashSet HashMap都必须得同时实现equals()方法和hashCode()方法,TreeSet和TreeMap则不需要实现equals()方法和hashCode()方法。
//以哈希表实现的容器,都必须重写equals()方法和hashCode()方法
(2) Map接口
//使用键值key来增 删 查
//HashMap中key不可以重复,value可以重复。
Map(映射)是一种把键对象和值对象进行映射的集合,它的每一个元素都包含一对键对象和值对象。
//Map中键和值的类型是任意的,这也是Map强大的重要表现。
向Map集合中加入元素时,必须提供一对键对象和值对象,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。
接口Map提供了根据关键字找对应的值的方法get(),定义如下:
Object get(Object key)
接口Map还提供了设置关键字和对应值的方法put(),定义如下:
Object put(Object key,Object value)
//根据key计算出存储位置,然后把value存入此位置,如果该位置原来已有元素,则覆盖它。
//该方法返回覆盖之前的元素对象的值,如果以前此位置为空,则****返回null。
把m容器中所有的元素存入当前容器中
*void putAll(Map m)
Object remove(Object key)
void clear()
boolean containsKey(Object key)
boolean containsValue(Object value)
int size()
boolean isEmpty()
(3) 遍历hashmap中所有的元素
System.out.println("hm容器中所有的元素是:");
Set s = hm.keySet(); //获取到当前容器键的集合,实际就是Integer对象的集合。放入容器中。
Iterator it = s.iterator();//返回s容器中的头指针(指针指向第一个元素之前)。
while (it.hasNext()){
//int Key = (Integer)it.next(); // (Integer) 不能省, 利用了自动拆分技术。
Integer kk = (Integer)it.next();//Object next()返回的是Object类型,要强制转换为Integer。
//若使用了泛型则不必再强制转换。
System.out.println(hm.get(kk));
}
//若要输出容器中某自定义类元素,必须重写自定义类toString()方法。
//若要对容器中某自定义类元素使用排序,查找方法时,必须使自定义类实现Comparable接口,重写其中compareTo()方法。
//若要使用以哈希表实现的容器,必须对放入其中的自定义类元素重写equals()和hashcode()方法。
//遍历容器:
//<1> system.out.println(容器名):只能输出元素,不能对元素进行操作。
//<2> iterator + hasNext() + next():可以对元素进行操作。
53 泛型
(1) 泛型就是限制传入容器、接口中的数据类型。
(2) 若不使用泛型,默认容器中的元素类型为object,若使用泛型,则容器中元素类型为泛型指定类型。
(3) JDK API中类名有<E>都可使用泛型。
TestGenerics_1.java
TestGenerics_2.java
54 网络编程
(1) 网络程序:能够接受另一台计算机发送过来的数据或者能够向另一台计算机发送数据的程序叫做网络程序。
(2) IP:能够在网络中唯一标示一台主机的编号就是IP,网络中每台主机都必须有一个惟一的IP地址;
IP地址是一个逻辑地址;
因特网上的IP地址具有全球唯一性;
(3) 端口号:
一台计算机上可以同时运行多个网络程序。
一台计算机从网卡接受过来的数据到底应该交给本地的哪个网络程序来处理的,这是由端口号来决定的。
端口是用一个16位的数字来表示,它的范围是0~65535,1024以下的端口号保留给预定义的服务。
例如:80端口访问网页,25端口用来邮件发送。
(4) 协议:为进行网络中的数据交换(通信)而建立的规则、标准或约定。
常见协议:
TCP:面向连接的可靠的传输协议。类似于打电话。
UDP:是无连接的,不可靠的传输协议。类似于写信。
(5) 套接字(socket)的引入
为了能够方便的开发网络应用软件,由美国伯克利大学在Unix上推出了一种应用程序访问通信协议的操作系统调用socket(套接字)。
socket的出现,使程序员可以很方便地访问TCP/IP,从而开发各种网络应用的程序。
随着Unix的应用推广,套接字在编写网络软件中得到了极大的普及。后来,套接字又被引进了Windows等操作系统中。
Java语言也引入了套接字编程模型。
55 基于UDP的socket编程
(1) 基于UDP的socket编程步骤
<1> 定义码头
定义一个DatagramSocket对象ds。
<2> 定义可以用来接受或发送数据的集装箱
定义DatagramPacket对象dp。
<3> 在码头上用集装箱接受对方发送过来的数据
ds.receive(dp)
<4> 或者在码头上把集装箱中的数据发送给对方
ds.send(dp)
<5> 关闭码头
ds.close()
(2) 基于UDP的socket编程
+----------------------------------------+
|接收端(Server) |
+----------------------------------------+
|DatagramSocket(int port) |
+----------------------------------------+
|DatagramPacket(byte[] buf, int length) |
|DatagramSocket.receive(DatagramPacket p)|
+----------------------------------------+
|DatagramSocket.close() |
+----------------------------------------+
<1> 接收端程序编写:
①调用DatagramSocket(int port)创建一个数据报套接字,并绑定到指定端口上。
②调用DatagramPacket(byte[] buf,int length),建立一个字节数组以接收UDP包。
③调用DatagramSocket类的receive(),接收UDP包。
④最后关闭数据报套接字。
+--------------------------------------------------------------------+
|发送端(Client) |
+--------------------------------------------------------------------+
|DatagramSocket() |
+--------------------------------------------------------------------+
|DatagramPacket(byte[] buf,int length, InetAddress address, int port)|
|DatagramSocket.send(DatagramPacket p) |
+--------------------------------------------------------------------+
|DatagramSocket.close() |
+--------------------------------------------------------------------+
<2> 发送端程序编写:
①调用DatagramSocket()创建一个数据报套接字。
②调用DatagramPacket(byte[] buf,int offset,int length,InetAddress address,int port),建立要发送的UDP包。
③调用DatagramSocket类的send(),发送UDP包。
④最后关闭数据报套接字。
(3) UDP的socket编程注意的问题
“127.0.0.1”是String,不是IP地址,IP地址在java中是用java.net.lnetAddress类表示。
java.net.InetAddress并没有构造方法,因此要把"127.0.0.1"转化为IP地址。
必须写成:
InetAddress.getByName("127.0.0.1")
不能写成
new InetAddress("127.0.0.1");
DatagramPacket是个集装箱,既可以用来存放即将要发送到客户端的数据,也可以用来存放即将要接受的客户端数据。
如果UDP中两个网络程序要通信的话,这两个程序都必须得定义DatagramPacket包。
如果定义的DatagramPacket对象要用来接受客户端发送过来的数据,则不建议指定目的端口号和目的IP。
如果定义的DatagramPacket对象用来存储要发送到客户端的数据,则必须的指定目的端口号和目的IP。
DatagramPacket所有的构造函数的第一个形参都是byte[] buf。
即:所有的DatagramPacket对象的内核都是个byte数组
即:定义DatagramPacket对象时必须的借助byte数组来实现
56 基于TCP的socket编程
(1) 基于TCP的socket编程
+----------------------------------------+
|接收端(Server) |
+----------------------------------------+
|ServerSocket(int port) |
|Socket accept() |
+----------------------------------------+
|InputStream is=Socket.getInputStream() |
|或 |
|OutputStream os=Socket.getOutputStream()|
+----------------------------------------+
|Socket.close() |
+----------------------------------------+
<1> 服务器程序编写:
①调用SeryerSocket(int port)创建一个服务器端套接字,并绑定到指定端口上。
②调用accept(),监听连接请求,如果客户端请求连接,则接受连接,并返回和客户端匹配的套接字。
③调用Socket类的getOutputStream()和getInputStream()获取输出流和输入流,开始网络数据的发送和接收。
④最后关闭通信套接字。
+----------------------------------------+
|发送端(Client) |
+----------------------------------------+
|Socket(InetAddress address, int port) |
+----------------------------------------+
|OutputStream os=Socket.getOutputStream()|
|或 |
|InputStream is=Socket.getInputStream() |
+----------------------------------------+
|Socket.close() |
+----------------------------------------+
<2> 客户端程序编写:
①调用Socket()创建一个客户端套接字,该套接字会自动向服务器端发送连接请求。
②调用Socket类的getOutputStream()和getInputStream()获取输出流和输入流,开始网络数据的发送和接收。
③最后关闭通信套接字。
(2) 注意的问题
<1> TCP客户端
一旦new出了Socket对象,该对象就会自动向服务器端发送连接请求,如果连接不成功则程序立即终止TCP。
<2> 服务器端
new出的ServerSocket对象并不会自动监听客户端的请求,只有调用了ServerSocket对象的accept()方法时,才会监听客户端的请求。

浙公网安备 33010602011771号