java Socket(阻塞) api记录点
参考资料:
《Java TCP/IP Socket 编程》 2ed
记录点:
1 accept(),read()和receive()
对于这些方法,我们可以使用Socket类、ServerSocket类和DatagramSocket类的setSoTimeout()方法,设置其阻塞的最长时间(以毫秒为单位)。如果在指定时间内这些方法没有返回,则将抛出一个InterruptedIOException异常。对于Socket实例,在调用read()方法前,我们还可以使用该套接字的InputStream的available()方法来检测是否有可读的数据.
2 一般在建立Socket的时候,如下: 由于默认的到参数的构造器时阻塞的,并且系统的超时时间定义得比较长.
Socket socket = new Socket();
try{
socket.connect(SocketAddress endpoint,int timeout);
}catch(SocketTimeOutException e){
... do with timeOut...
}catch(OtherException){
... do with other Exception...
}
3 write方法
write()方法调用也会阻塞等待,直到最后一个字节成功写入到了TCP实现的本地缓存中。如果可用的缓存空间比要写入的数据小,在write()方法调用返回前,必须把一些数据成功传输到连接的另一端。因此,write()方法的阻塞总时间最终还是取决于接收端的应用程序。不幸的是Java现在还没有提供任何使write()超时或由其他线程将其打断的方法。所以如果一个可以在Socket实例上发送大量数据的协议可能会无限期地阻塞下去。
4 使用setSoTimeout()方法粗略的实现为每个Socket客户端提供有限的服务时间.之所以说是"粗略",是因为我们没有办法控制:write()方法. 服务器端的线程方法大体如下:
long endTime = System.currentTimeMillis() + timelimit; int timeBoundMillis = timelimit; clntSock.setSoTimeout(timeBoundMillis); wile((timeBoundMillis > 0) &&((recvMsgSize = in.read(echoBuffer)) != -1)) { out.write(echoBuffer, 0, recvMsgSize); totalBytesEchoed += recvMsgSize; timeBoundMillis = (int) (endTime - System.currentTimeMillis()) ; clntSock.setSoTimeout(timeBoundMillis); //每一次的读取或者输出操作后.重新设置timelimit } //注意处理超时的异常:
5 默认的Keep-Alive 机制:
如果一段时间内没有数据交换,通信的每个终端可能都会怀疑对方是否还处于活跃状态。TCP协议提供了一种keep-alive的机制,该机制在经过一段不活动时间后,将向另一个终端发送一个探测消息。如果另一个终端还出于活跃状态,它将回复一个确认消息。如果经过几次尝试后依然没有收到另一终端的确认消息,则终止发送探测信息,关闭套接字,并在下一次尝试I/O操作时抛出一个异常。注意,应用程序只要在探测信息失败时才能察觉到keep-alive机制的工作。
6 发送和接收缓存区的大小:
一旦创建了一个Socket或DatagramSocket实例,操作系统就必须为其分配缓存区以存放接收的和要发送的数据:
int getReceiveBufferSize()
void setReceiveBufferSize(int size)
int getSendBufferSize()
void setSendBufferSize(int size)
为ServerSocket调用这些方法的时候,相当于为其accept()方法接收的Socket分配缓冲大小;
7 地址重用:
对于TCP,当一个连接关闭后,通信的一端(或两端)必须在"Time-Wait"状态上等待一段时间,以对传输途中丢失的数据包进行清理(见第6.4.2节)。不幸的是,通信终端可能无法等到Time-Wait结束。对于这两种情况,都需要能够与正在使用的地址进行绑定的能力,这就要求实现地址重用。
boolean getReuseAddress()
void setReuseAddress(boolean on)
8 消除缓冲延迟:
TCP协议将数据缓存起来直到足够多时一次发送,以避免发送过小的数据包而浪费网络资源。虽然这个功能有利于网络,但应用程序可能对所造成的缓冲延迟不能容忍。好在可以人为禁用缓存功能:
boolean getTcpNoDelay()
void setTcpNoDelay(boolean on)
9 紧急数据:
void sendUrgentData(int data)
boolean getOOBInline()
void setOOBInline(boolean on)
要发送紧急数据需要调用sendUrgentData() 方法,它将发送其int参数的最低位字节。要接收这个字节,必须为setOOBInline()方法传递true参数启用接收者对频道外数据的接收。该字节在接收者的输入流中被接收。发送于紧急字节之前的数据将处于接收者的输入流中的紧急字节前面。如果没有启用接收者接收频道外数据的功能,紧急字节将被无声地丢弃。
注意Java中的紧急数据几乎没什么用,因为紧急字节与常规字节按照传输的顺序混在了一起。实际上,Java接收者并不能区分其是否在接收紧急数据
10 关闭后停留:
当调用套接字的close()方法后,即使套接字的缓冲区中还有没有发送的数据,它也将立即返回。这样不发送完所有数据可能导致的问题是主机将在后面的某个时刻发生故障。其实可以选择让close()方法"停留"或阻塞一段时间,直到所有数据都已经发送并确认,或发生了超时.
int getSoLinger()
void setSoLinger(boolean on, int linger)
如果发生了超时,TCP连接将强行关闭
11 基于性能的协议选择:
void setPerformancePreferences(int connectionTime, int latency, int bandwidth)
12 关闭连接:
调用Socket的close()方法将同时终止两个方向(输入和输出)的数据流.
Socket类的shutdownInput()和shutdownOutput()方法能够将输入输出流相互独立地关闭。调用shutdownInput()后,套接字的输入流将无法使用。任何没有发送的数据都将毫无提示地被丢弃,任何想从套接字的输入流读取数据的操作都将返回-1。当Socket调用shutdownOutput() 方法后,套接字的输出流将无法再发送数据,任何尝试向输出流写数据的操作都将抛出一个IOException异常。在调用shutdownOutput()之前写出的数据可能能够被远程套接字读取,之后,在远程套接字输入流上的读操作将返回-1。应用程序调用shutdownOutput()后还能继续从套接字读取数据,类似的,在调用shutdownInput()后也能够继续写数据。
java 序列化初探
参考资料:
http://www.ibm.com/developerworks/cn/java/j-5things1/index.html?ca=drs-cn-0504 以及文中的参考资料
java 核心技术 高级卷第一章----序列化技术
http://www.java2s.com/Code/Java/File-Input-Output/Reconstructinganexternalizableobject.htm 以及其中的相关序列化知识
最后给出一个简单的序列化工具类:
http://www.java2s.com/Code/Java/File-Input-Output/SerializationUtilities.htm
java 序列化前提: Serilizable接口的实现.
1 序列化一个对象(记为obj1),再读取该对象(记为obj2), 两个对象obj1 不等于(引用) obj2 . 并且obj2的构造并不调用构造函数. 在java 核心技术2中
作者提到,可以将obj2看做是obj1的一个深拷贝.
2 如果有一段代码是这样的:
Employee lily = new Employee("Lily")
Manager tom = new Manager("Tom");
Manager jack = new Manager("Jack");
//两个经理吃乐谱同一个秘书的引用
tom.secretary = lily;
jack.secretary = lily;
序列化以后再一次读出tom 和 jack
经过测试, 两者持有的秘书依然是同一个引用,但是和序列化前的lily并不是同一个引用.
3 序列化并不会序列transient字段和static字段.
4 超类的默认序列化: 序列化一个类的时候,默认会序列化其超类的属性(除transient和static意外的属性), 反序列化的时候,如果是实现了Externalizable.类的构造是通过类自身的空的构造函数来实现的(超类的构造也是通过超类自身的构造函数).否则是不会调用子类或者超类的构造函数的.
5 自定义的序列化(方法1): readObject方法和writeObject方法的实现
String name;
Employee(String name){
this.name = name;
}
private void writeObject(ObjectOutputStream obj){
//空的实现
}
private void readObject(ObjectInputStream obj){
//空的实现
}
当去序列化上面的类时候, 会保存一些相关的类信息,但是不会去序列化name.
实现readObject方法如下:
private void readObject(ObjectInputStream obj){
name = "readObject";
}
反序列化这个类后, name属性将会被赋值为"readObject';
一般来说,可以使用下面的代码来参考自己的序列化:
private void writeObject(ObjectOutputStream obj)throws IOException{
obj.defaultWriteObject();
//然后做自己的扩展序列化
}
private void readObject(ObjectInputStream obj) throws IOException,ClassNotFoundException{
obj.defaultReadObject();
//然后做自己的拓展反序列化
}
其实关于readObject和writeObject的方法实现可以参考JDK ArrayList,因为它的内置数组元素是transient的, 可以看看它是如何实现ArrayList的序列化.
注意点: 在实现了Serilizable接口,然后实现了private void readObject和private void writeObject两个方法的前提下, readObject和writeObject只需要关心
本类的属性序列/反序列化. 而不用去考虑超类的序列化.
5 序列化方法(2) : 实现Externalizable接口并实现public readExternal()方法和public writeExternal()方法
@Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
// TODO Auto-generated method stub
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
// TODO Auto-generated method stub
}
注意,这种方法在反序列化的时候是会去调用类的默认构造函数来构造类的,如果此时将类的默认构造函数的可见性设置为包默认或者其他序列化机制不能
调用的访问方式,会抛出:java.io.InvalidClassException:************* no valid constructor 异常;
在上面的空实现中, 序列化机制既不会去序列化任何本类域或者超类的域, 只是去记录一些属性的说明类型信息. 这和实现Serializable接口重写readObject
和writeObject的自定义序列化机制是不一样的. 后者在调用readObject和writeObject之前就对超类进行了必要的处理.
6 注意特殊形式的序列化:
//注意下述的形式,大部分的情况是这个类不会提供public的构造函数, 并且这个类属于"枚举类型". 这种方式下,默认的序列化机制是不合适的,或者
说这种方式可能并不合适去序列化.
public class TestSpecialClass implements Serializable{
static TestSpecialClass specialOne = new TestSpecialClass("one");
static TestSpecialClass specialTwo = new TestSpecialClass("two");
要实现上述情况的序列化, 可以实现下面的方法:
prtected Object readResolve() throws ObjectStreamException;
java IO
参考资料:
http://download.oracle.com/javase/tutorial/essential/io/ 官方io说明库以及常用的一些IO操作.
http://blog.csdn.net/redv/archive/2005/03/31/334697.aspx 关于BIO的相关性能分析;
http://java.sun.com/developer/technicalArticles/Programming/PerfTuning/ io性能调优;
http://tutorials.jenkov.com/java-io/index.html 详细的io类说明
此文讲述的是java io, 并非java.nio. 只是针对io的一些操作的记录. .
java IO :
1 直接读取的方式:
FileInputStream fis = new FileInputStream(file);
while(fis.read() != -1){...}
2 使用bufferedInputStream:
FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis =new BufferedInputStream(fis);
int b;
while ((b = bis.read()) != -1) {
...
}
3 使用直接的byte[]数组作为缓冲区的方式:
FileInputStream fis = new FileInputStream(file);
byte buf[] = new byte[2048];
int n;
while ((n = fis.read(buf)) != -1) {
for (int i = 0; i < n; i++) {
...
}
以上的三种情况使用,第三种比较有效率. 但是第二种和第三种差别并不是很大.一般使用第二种比较多. 一般可以在使用第三种方式的时候使用: file.length来确定byte[]的长度.
4 将每一行分别缓冲到ArrayList中,方便读取:
private ArrayList list = new ArrayList();
public LineCache(String fn) throws IOException {
FileReader fr = new FileReader(fn);
BufferedReader br = new BufferedReader(fr);
String ln;
while ((ln = br.readLine()) != null)
list.add(ln);
br.close();
}
5 使用Tokenization:
public class token1 {
public static void main(String args[]) {
if (args.length != 1) {
System.err.println("missing filename");
System.exit(1);
}
try {
FileReader fr = new FileReader(args[0]);
BufferedReader br = new BufferedReader(fr);
StreamTokenizer st = new StreamTokenizer(br);
st.resetSyntax();
st.wordChars('a', 'z');
int tok;
while ((tok = st.nextToken()) !=
StreamTokenizer.TT_EOF) {
if (tok == StreamTokenizer.TT_WORD)
;// st.sval has token
}
br.close();
}
catch (IOException e) {
System.err.println(e);
}
}
}
6 使用ObjectOutputStream 和ObjectInputStream 做对象序列化;
public class serial1 {
public static void main(String args[]) {
ArrayList al = new ArrayList();
Random rn = new Random();
final int N = 100000;
for (int i = 1; i <= N; i++)
al.add(new Integer(rn.nextInt()));
try {
FileOutputStream fos = new FileOutputStream("test.ser");
BufferedOutputStream bos = new BufferedOutputStream(fos);
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(al);
oos.close();
}
catch (Throwable e) {
System.err.println(e);
}
}
}
解序列化:
import java.io.*;
import java.util.*;
public class serial2 {
public static void main(String args[]) {
ArrayList al = null;
try {
FileInputStream fis = new FileInputStream("test.ser");
BufferedInputStream bis = new BufferedInputStream(fis);
ObjectInputStream ois = new ObjectInputStream(bis);
al = (ArrayList)ois.readObject();
ois.close();
}
catch (Throwable e) {
System.err.println(e);
}
}
}
7 如果是存储和读取一些基本对象,可以使用DataOutputStream和DataInputStream来完成:
FileOutputStream fos =new FileOutputStream("outdata");
BufferedOutputStream bos =new BufferedOutputStream(fos);
DataOutputStream dos =new DataOutputStream(bos);
Random rn = new Random();
final int N = 10;
dos.writeInt(N);
for (int i = 1; i <= N; i++) {
int r = rn.nextInt();
System.out.println(r);
dos.writeInt(r);
}
dos.close();
InnerClass 备忘录
参考资料:
java核心技术
http://www.javabeat.net/tips/124-inner-classes-in-java.html innerclass 示例
http://download.oracle.com/javase/tutorial/java/javaOO/innerclasses.html 内部迭代器实现
http://java.sun.com/developer/Books/certification/certbook.pdf 官方InnerClass PDF
InnerClass:
普通内部类:
1 编译器将编译为: OuterClass$InnerClass.class文件,并在这个编译后的Class中加入一个构造方法并添加一个final的OuterClass引用;
InnerClass(OuterClass){
...
}
final OuterClass out;
2 编译器在OuterClass中添加关于私有域的access方法: static <private_prperty_type> access$0(OuterClass),使得内部类能够通过此方法访问到OuterClass的私有域.
3 确实存在安全的一些问题在access方法上;
4 若权限足够,使用下面的方式创建内部类;
Outer outer=new Outer();
out.new Inner();
5 内部类可继承其他类和实现相关接口, 这部分可以参考Java JDK 集合中的ListIter这些内部迭代器的实现;
6 内部类可使用static final ,不能缺少final;
下面给出一个实例代码类说明外部资源[并非指外部类]如何较好的使用一个内部类:
class ToplevelClass {
private String msg = "Shine the inner light.";
//负责创建内部类
public NonStaticInnerClass makeInstance() {
return new NonStaticInnerClass();
}
//内部类
public class NonStaticInnerClass {
// private static int staticVar; // (6) Not OK.
private String string;
public NonStaticInnerClass() { string = msg; }
public void printMsg() { System.out.println(string); }
}
}
//外部资源访问
public class Client {
public static void main(String args[]) {
//具体内部类的创建
ToplevelClass topRef = new ToplevelClass();
ToplevelClass.NonStaticInnerClass innerRef1 = topRef.makeInstance();
innerRef1.printMsg();
// ToplevelClass.NonStaticInnerClass innerRef2 =
// new ToplevelClass.NonStaticInnerClass(); // (15) Not OK.
ToplevelClass.NonStaticInnerClass innerRef3 = topRef.new NonStaticInnerClass();
}
}
如果不用上面的实例来创建内部类,也可以使用下面的方式:
TLClassA a = new TLClassA("1");
TLClassA.InnerB b = a.new InnerB("1");
TLClassA.InnerB.InnerC c = b.new InnerC("1");
代码说明: TLClassA 中有一个内部类InnerB,而InnerB中又存在一个内部类InnerC.
上述的两个示例表明在获取内部类的实例时候,要通过外部类的实例创建.
下面是关于内部类继承的例子:
class B {
protected double x = 2.17;
}
class A { // Top-level Class
private double x = 3.14;
class C extends B { // Non-static inner Class
// private double w = x; // Compile time error
private double y = this.x; // x from superclass
private double z = A.this.x; // x from enclosing class
public void printX() {
System.out.println("this.x: " + y);
System.out.println("A.this.x: " + z);
}
}
}
//客户端调用;
public class Client4 { // (7)
public static void main(String args[]) {
A.C ref = new A().new C();
ref.printX();
}
}
局部内部类:
1 在使用了普通内部类的情况下, 如果这个普通内部类的使用仅仅是在外部类的一个方法中使用一次, 并且代码比较简洁,可以考虑使局部内部类;
2 局部内部类不能使用public private 作为修饰;
3 局部内部类的隐蔽性要好于统统内部类;
4 局部内部类不仅仅能够访问外部类,并且能够访问方法的局部变量, 但是这些局部变量必须是final的.原因是局部内部类的周期要大于这个局部变量;在这种时候,编译器将在内部类的构造器中传入局部变量, 并在内部类添加一个fiannal <function_var_type> propertyName; 有些时候, 当局部的变量需要更新的时候,不能使用final字段来限制基本类型,可以开去使用一个fnnal的array : final int[1] inMutableValue = new int[1]; 这样在内部类就能使用inMutableValue[1]了, 同时,改变其值就使用inMutableVale[1]=...,final只是限定了应用不可改变,但是并不限定了引用的内容不能改变,这个就相当于在并发中使用final StringBuffer为什么也不是很安全的原因.
匿名内部类:
1 匿名内部类是在局部内部类的基础上发现了只需要这个类的一个对象,就不用命名. 类似的有:
Action listenner = new ActionListener(){
public void actionPerformed(ActionEvent event){
....
}
}
静态内部类:
1 有时候只是希望把内部类隐藏,并不是想要内部类能够访问外部类对象,则可以使用static 内部类. 一般的, 这种内部类的结构比较简单,方法的代码都比较简洁. 这方面的例子,可以参考JDK LinkedList 中的私有静态内部类Entry<E>的使用.
2 一般的,使用staitc 内部类的时候更多是用private 和 default的访问修饰.
3 可以使用OuterClass.InnerClass inner = OuterClass.factoryMethod();方式来创建一个能够访问到的静态内部类;
下是JDK LinkedList中的使用静态类的源代码
【友情提示 : 注意属性的控制权限和内部类本身的控制权限】
private static class Entry<E> { //私有的内部静态类 E element; Entry<E> next; Entry<E> previous; // 包默认 Entry(E element, Entry<E> next, Entry<E> previous) { this.element = element; this.next = next; this.previous = previous; } } 使用如下: private Entry<E> addBefore(E e, Entry<E> entry) { Entry<E> newEntry = new Entry<E>(e, entry, entry.previous); newEntry.previous.next = newEntry; newEntry.next.previous = newEntry; size++; modCount++; return newEntry; } 上述的代码能够达到简化代码量以及提高可读性的目的.