JAVA内存分配与管理是Java的核心技术之一,之前我们曾介绍过Java的内存管理与内存泄露以及Java垃圾回收方面的知识,今天我们再次深入Java核心,详细介绍一下Java在内存分配方面的知识。一般Java在内存分配时会涉及到以下区域:
- 寄存器:我们在程序中无法控制
- 栈:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中
- 堆:存放用new产生的数据
- 静态域:存放在对象中用static定义的静态成员
- 常量池:存放常量
- 非RAM存储:硬盘等永久存储空间
Java内存分配中的栈
在函数中定义的一些基本类型的变量数据和对象的引用变量都在函数的栈内存中分配。 当在一段代码块定义一个变量时,Java就在栈中 为这个变量分配内存空间,当该变量退出该作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。
Java内存分配中的堆
堆内存用来存放由new创建的对象和数组。 在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。
在堆中产生了一个数组或对象后,还可以 在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。 引用变量就相当于是 为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。引用变量就相当于是为数组或者对象起的一个名称。
引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其作用域之外后被释放。而数组和对象本身在堆中分配,即使程序 运行到使用 new 产生数组或者对象的语句所在的代码块之外,数组和对象本身占据的内存不会被释放,数组和对象在没有引用变量指向它的时候,才变为垃圾,不能在被使用,但仍 然占据内存空间不放,在随后的一个不确定的时间被垃圾回收器收走(释放掉)。这也是 Java 比较占内存的原因。
实际上,栈中的变量指向堆内存中的变量,这就是Java中的指针! 常量池 (constant pool)
常量池指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。除了包含代码中所定义的各种基本类型(如int、long等等)和对象型(如String及数组)的常量值(final)还包含一些以文本形式出现的符号引用,比如:
- 类和接口的全限定名;
- 字段的名称和描述符;
- 方法和名称和描述符。
虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用到常量的一个有序集和,包括直接常量(string,integer和 floating point常量)和对其他类型,字段和方法的符号引用。
对于String常量,它的值是在常量池中的。而JVM中的常量池在内存当中是以表的形式存在的, 对于String类型,有一张固定长度的CONSTANT_String_info表用来存储文字字符串值,注意:该表只存储文字字符串值,不存储符号引 用。说到这里,对常量池中的字符串值的存储位置应该有一个比较明了的理解了。在程序执行的时候,常量池 会储存在Method Area,而不是堆中。
堆与栈
Java的堆是一个运行时数据区,类的(对象从中分配空间。这些对象通过new、newarray、 anewarray和multianewarray等指令建立,它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存 大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态 分配内存,存取速度较慢。
栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是 确定的,缺乏灵活性。栈中主要存放一些基本类型的变量数据(int, short, long, byte, float, double, boolean, char)和对象句柄(引用)。
栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:
- int a = 3;
- int b = 3;
编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着处理int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就出现了a与b同时均指向3的情况。
这时,如果再令 a=4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响 到b的值。
要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况a的修改并不会影响到b, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。
String是一个特殊的包装类数据。可以用:
- String str = new String("abc");
- String str = "abc";
两种的形式来创建,第一种是用new()来新建对象的,它会在存放于堆中。每调用一次就会创建一个新的对象。而第二种是先在栈中创建一个对String类的对象引用变量str,然后通过符号引用去字符串常量池 里找有没有"abc",如果没有,则将"abc"存放进字符串常量池 ,并令str指向”abc”,如果已经有”abc” 则直接令str指向“abc”。
比较类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==,下面用例子说明上面的理论。
- String str1 = "abc";
- String str2 = "abc";
- System.out.println(str1==str2); //true
可以看出str1和str2是指向同一个对象的。
- String str1 =new String ("abc");
- String str2 =new String ("abc");
- System.out.println(str1==str2); // false
用new的方式是生成不同的对象。每一次生成一个。
因此用第二种方式创建多个”abc”字符串,在内存中 其实只存在一个对象而已. 这种写法有利与节省内存空间. 同时它可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。
另 一方面, 要注意: 我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,创建了String类的对象str。担心陷阱!对象可能并没有被创建!而可能只是指向一个先前已经创建的 对象。只有通过new()方法才能保证每次都创建一个新的对象。
由于String类的immutable性质,当String变量需要经常变换 其值时,应该考虑使用StringBuffer类,以提高程序效率。
1. 首先String不属于8种基本数据类型,String是一个对象。因为对象的默认值是null,所以String的默认值也是null;但它又是一种特殊的对象,有其它对象没有的一些特性。
2. new String()和new String(”")都是申明一个新的空字符串,是空串不是null;
3. String str=”kvill”;String str=new String (”kvill”)的区别
示例:
- String s0="kvill";
- String s1="kvill";
- String s2="kv" + "ill";
- System.out.println( s0==s1 );
- System.out.println( s0==s2 );
结果为:true true
首先,我们要知结果为道JAVA 会确保一个字符串常量只有一个拷贝。
因为例子中的 s0和s1中的”kvill”都是字符串常量,它们在编译期就被确定了,所以s0==s1为true;而”kv”和”ill”也都是字符串常量,当一个字 符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,所以s2也同样在编译期就被解析为一个字符串常量,所以s2也是常量池中” kvill”的一个引用。所以我们得出s0==s1==s2;用new String() 创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放入常量池中,它们有自己的地址空间。
示例:
- String s0="kvill";
- String s1=new String("kvill");
- String s2="kv" + new String("ill");
- System.out.println( s0==s1 );
- System.out.println( s0==s2 );
- System.out.println( s1==s2 );
结果为:false false false
例2中s0还是常量池 中"kvill”的应用,s1因为无法在编译期确定,所以是运行时创建的新对象”kvill”的引用,s2因为有后半部分 new String(”ill”)所以也无法在编译期确定,所以也是一个新创建对象”kvill”的应用;明白了这些也就知道为何得出此结果了。
4. String.intern():
再补充介绍一点:存在于.class文件中的常量池,在运行期被JVM装载,并且可以扩充。String的 intern()方法就是扩充常量池的 一个方法;当一个String实例str调用intern()方法时,Java 查找常量池中 是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常 量池中增加一个Unicode等于str的字符串并返回它的引用;看示例就清楚了
示例:
- String s0= "kvill";
- String s1=new String("kvill");
- String s2=new String("kvill");
- System.out.println( s0==s1 );
- System.out.println( "**********" );
- s1.intern();
- s2=s2.intern(); //把常量池中"kvill"的引用赋给s2
- System.out.println( s0==s1);
- System.out.println( s0==s1.intern() );
- System.out.println( s0==s2 );
结果为:false false //虽然执行了s1.intern(),但它的返回值没有赋给s1 true //说明s1.intern()返回的是常量池中"kvill"的引用 true
最后我再破除一个错误的理解:有人说,“使用 String.intern() 方法则可以将一个 String 类的保存到一个全局 String 表中 ,如果具有相同值的 Unicode 字符串已经在这个表中,那么该方法返回表中已有字符串的地址,如果在表中没有相同值的字符串,则将自己的地址注册到表中”如果我把他说的这个全局的 String 表理解为常量池的话,他的最后一句话,”如果在表中没有相同值的字符串,则将自己的地址注册到表中”是错的:
示例:
- String s1=new String("kvill");
- String s2=s1.intern();
- System.out.println( s1==s1.intern() );
- System.out.println( s1+" "+s2 );
- System.out.println( s2==s1.intern() );
结果:false kvill kvill true
在这个类中我们没有声名一个”kvill”常量,所以常量池中一开始是没有”kvill”的,当我们调用s1.intern()后就在常量池中新添加了一 个”kvill”常量,原来的不在常量池中的”kvill”仍然存在,也就不是“将自己的地址注册到常量池中”了。
s1==s1.intern() 为false说明原来的”kvill”仍然存在;s2现在为常量池中”kvill”的地址,所以有s2==s1.intern()为true。
5. 关于equals()和==:
这个对于String简单来说就是比较两字符串的Unicode序列是否相当,如果相等返回true;而==是 比较两字符串的地址是否相同,也就是是否是同一个字符串的引用。
6. 关于String是不可变的
这一说又要说很多,大家只 要知道String的实例一旦生成就不会再改变了,比如说:String str=”kv”+”ill”+” “+”ans”; 就是有4个字符串常量,首先”kv”和”ill”生成了”kvill”存在内存中,然后”kvill”又和” ” 生成 “kvill “存在内存中,最后又和生成了”kvill ans”;并把这个字符串的地址赋给了str,就是因为String的”不可变”产生了很多临时变量,这也就是为什么建议用StringBuffer的原 因了,因为StringBuffer是可改变的。
下面是一些String相关的常见问题:
String中的final用法和理解
- final StringBuffer a = new StringBuffer("111");
- final StringBuffer b = new StringBuffer("222");
- a=b;//此句编译不通过
- final StringBuffer a = new StringBuffer("111");
- a.append("222");// 编译通过
可见,final只对引用的"值"(即内存地址)有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译期错误。至于它所指向的对象 的变化,final是不负责的。
【编辑推荐】
多态性是通过:
1 接口和实现接口并覆盖接口中同一方法的几不同的类体现的
2 父类和继承父类并覆盖父类中同一方法的几个不同子类实现的.
一、基本概念
多态性:发送消息给某个对象,让该对象自行决定响应何种行为。通过将子类对象引用赋值给超类对象引用变量来实现动态方法调用。
java 的这种机制遵循一个原则:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。
1. 如果a是类A的一个引用,那么,a可以指向类A的一个实例,或者说指向类A的一个子类。
2. 如果a是接口A的一个引用,那么,a必须指向实现了接口A的一个类的实例。
二、Java多态性实现机制
SUN目前的JVM实现机制,类实例的引用就是指向一个句柄(handle)的指针,这个句柄是一对指针:
一个指针指向一张表格,实际上这个表格也有两个指针(一个指针指向一个包含了对象的方法表,另外一个指向类对象,表明该对象所属的类型);
另一个指针指向一块从java堆中为分配出来内存空间。
三、总结
1、通过将子类对象引用赋值给超类对象引用变量来实现动态方法调用。
- DerivedC c2=new DerivedC();
- BaseClass a1= c2; //BaseClass 基类,DerivedC是继承自BaseClass的子类
- a1.play(); //play()在BaseClass,DerivedC中均有定义,即子类覆写了该方法
分析:
1、为什么子类的类型的对象实例可以覆给超类引用?
自动实现向上转型。通过该语句,编译器自动将子类实例向上移动,成为通用类型BaseClass;
2、a.play()将执行子类还是父类定义的方法?
子类的。在运行时期,将根据a这个对象引用实际的类型来获取对应的方法。所以才有多态性。一个基类的对象引用,被赋予不同的子类对象引用,执行该方法时,将表现出不同的行为。
在a1=c2的时候,仍然是存在两个句柄,a1和c2,但是a1和c2拥有同一块数据内存块和不同的函数表。
2、不能把父类对象引用赋给子类对象引用变量
- BaseClass a2=new BaseClass();
- DerivedC c1=a2;//出错
在java里面,向上转型是自动进行的,但是向下转型却不是,需要我们自己定义强制进行。
- c1=(DerivedC)a2; 进行强制转化,也就是向下转型.
3、记住一个很简单又很复杂的规则,一个类型引用只能引用引用类型自身含有的方法和变量。
你可能说这个规则不对的,因为父类引用指向子类对象的时候,最后执行的是子类的方法的。
其实这并不矛盾,那是因为采用了后期绑定,动态运行的时候又根据型别去调用了子类的方法。而假若子类的这个方法在父类中并没有定义,则会出错。
例如,DerivedC类在继承BaseClass中定义的函数外,还增加了几个函数(例如 myFun())
分析:
当你使用父类引用指向子类的时候,其实jvm已经使用了编译器产生的类型信息调整转换了。
这里你可以这样理解,相当于把不是父类中含有的函数从虚拟函数表中设置为不可见的。注意有可能虚拟函数表中有些函数地址由于在子类中已经被改写了,所以对象虚拟函数表中虚拟函数项目地址已经被设置为子类中完成的方法体的地址了。
4、Java与C++多态性的比较
jvm关于多态性支持解决方法是和c++中几乎一样的,只是c++中编译器很多是把类型信息和虚拟函数信息都放在一个虚拟函数表中,但是利用某种技术来区别。
Java把类型信息和函数信息分开放。Java中在继承以后,子类会重新设置自己的虚拟函数表,这个虚拟函数表中的项目有由两部分组成。从父类继承的虚拟函数和子类自己的虚拟函数。
虚拟函数调用是经过虚拟函数表间接调用的,所以才得以实现多态的。Java的所有函数,除了被声明为final的,都是用后期绑定。
四. 1个行为,不同的对象,他们具体体现出来的方式不一样,
比如: 方法重载 overloading 以及 方法重写(覆盖)override
- class Human{
- void run(){输出 人在跑}
- }
- class Man extends Human{
- void run(){输出 男人在跑}
- }
- 这个时候,同是跑,不同的对象,不一样(这个是方法覆盖的例子)
- class Test{
- void out(String str){输出 str}
- void out(int i){输出 i}
- }
这个例子是方法重载,方法名相同,参数表不同
ok,明白了这些还不够,还用人在跑举例
- Human ahuman=new Man();
这样我等于实例化了一个Man的对象,并声明了一个Human的引用,让它去指向Man这个对象
意思是说,把 Man这个对象当 Human看了.
比如去动物园,你看见了一个动物,不知道它是什么, "这是什么动物? " "这是大熊猫! "
这2句话,就是最好的证明,因为不知道它是大熊猫,但知道它的父类是动物,所以,这个大熊猫对象,你把它当成其父类 动物看,这样子合情合理.这种方式下要注意 new Man();的确实例化了Man对象,所以 ahuman.run()这个方法 输出的 是 "男人在跑 "如果在子类 Man下你 写了一些它独有的方法 比如 eat(),而Human没有这个方法,在调用eat方法时,一定要注意 强制类型转换 ((Man)ahuman).eat(),这样才可以...
对接口来说,情况是类似的...
实例:
- package domatic;
- //定义超类superA
- class superA {
- int i = 100;
- void fun(int j) {
- j = i;
- System.out.println("This is superA");
- }
- }
- // 定义superA的子类subB
- class subB extends superA {
- int m = 1;
- void fun(int aa) {
- System.out.println("This is subB");
- }
- }
- // 定义superA的子类subC
- class subC extends superA {
- int n = 1;
- void fun(int cc) {
- System.out.println("This is subC");
- }
- }
- class Test {
- public static void main(String[] args) {
- superA a = new superA();
- subB b = new subB();
- subC c = new subC();
- a = b;
- a.fun(100);
- a = c;
- a.fun(200);
- }
- }
- /*
- * 上述代码中subB和subC是超类superA的子类,我们在类Test中声明了3个引用变量a, b,
- * c,通过将子类对象引用赋值给超类对象引用变量来实现动态方法调用。也许有人会问:
- * "为什么(1)和(2)不输出:This is superA"。
- * java的这种机制遵循一个原则:当超类对象引用变量引用子类对象时,
- * 被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,
- * 但是这个被调用的方法必须是在超类中定义过的,
- * 也就是说被子类覆盖的方法。
- * 所以,不要被上例中(1)和(2)所迷惑,虽然写成a.fun(),但是由于(1)中的a被b赋值,
- * 指向了子类subB的一个实例,因而(1)所调用的fun()实际上是子类subB的成员方法fun(),
- * 它覆盖了超类superA的成员方法fun();同样(2)调用的是子类subC的成员方法fun()。
- * 另外,如果子类继承的超类是一个抽象类,虽然抽象类不能通过new操作符实例化,
- * 但是可以创建抽象类的对象引用指向子类对象,以实现运行时多态性。具体的实现方法同上例。
- * 不过,抽象类的子类必须覆盖实现超类中的所有的抽象方法,
- * 否则子类必须被abstract修饰符修饰,当然也就不能被实例化了
- */
以上大多数是以子类覆盖父类的方法实现多态.下面是另一种实现多态的方法-----------重写父类方法
1.JAVA里没有多继承,一个类之能有一个父类。而继承的表现就是多态。一个父类可以有多个子类,而在子类里可以重写父类的方法(例如方法print()),这样每个子类里重写的代码不一样,自然表现形式就不一样。这样用父类的变量去引用不同的子类,在调用这个相同的方法print()的时候得到的结果和表现形式就不一样了,这就是多态,相同的消息(也就是调用相同的方法)会有不同的结果。举例说明:
- //父类
- public class Father{
- //父类有一个打孩子方法
- public void hitChild(){
- }
- }
- //子类1
- public class Son1 extends Father{
- //重写父类打孩子方法
- public void hitChild(){
- System.out.println("为什么打我?我做错什么了!");
- }
- }
- //子类2
- public class Son2 extends Father{
- //重写父类打孩子方法
- public void hitChild(){
- System.out.println("我知道错了,别打了!");
- }
- }
- //子类3
- public class Son3 extends Father{
- //重写父类打孩子方法
- public void hitChild(){
- System.out.println("我跑,你打不着!");
- }
- }
- //测试类
- public class Test{
- public static void main(String args[]){
- Father father;
- father = new Son1();
- father.hitChild();
- father = new Son2();
- father.hitChild();
- father = new Son3();
- father.hitChild();
- }
- }
都调用了相同的方法,出现了不同的结果!这就是多态的表现!
【编辑推荐】
今天和同事好好的讨论了java接口的原理和作用,发现原来自己的对接口的理解仅仅是局限在概念的高度抽象上,觉得好像理解了但是不会变化应用其实和没有理解差不多。以前看一个帖子说学习一个东西不管什么时候都要带着“这个东西是什么?”、“这个东西有什么作用?”和“这个东西怎样用?”三个问题,这三个问题回答上来了说明你对这个事物的理解达到了一定的高度。
今天还有一个比较深的经验是要学习到知识就要多和人交流。就像以前某个管理人员说得“要疯狂的交流”。
现在对于今天学到的接口部分做一个详细地总结:
接口的概念其实并不难理解,接口关键字Interface,在使用时可以只定义函数体而不需要具体的实现。再类的继承过程中可以实现多个接口而取代了类的多继承。使用接口其实就有点像实现虚函数的调用一样,用继承接口的子类实例化声名得借口就可以通过接口调用子类内部接口定义的函数。使用这种接口方式编程,如果业务逻辑发生变化需要新增类多方法,就可以再不改变原来已经写好的代码基础上新增一个类来实现接口中定义的函数来实现。具体方法请看下面两个例子:
1、JAVA多态接口动态加载实例
用来计算每一种交通工具运行1000公里所需的时间,已知每种交通工具的参数都是3个整数A、B、C的表达式。现有两种工具:
Car 和Plane,其中Car 的速度运算公式为:A*B/C
Plane 的速度运算公式为:A+B+C。
需要编写三类:ComputeTime.java,Plane.java,Car007.java和接口Common.java,要求在未来如果增加第3种交通工具的时候,不必修改以前的任何程序,只需要编写新的交通工具的程序。其运行过程如下,从命令行输入ComputeTime的四个参数,第一个是交通工具的类型,第二、三、四个参数分别时整数A、B、C,举例如下:
计算Plane的时间:"java ComputeTime Plane 20 30 40"
计算Car007的时间:"java ComputeTime Car007 23 34 45"
如果第3种交通工具为Ship,则只需要编写Ship.java,运行时输入:"java ComputeTime Ship 22 33 44"
提示:充分利用接口的概念,接口对象充当参数。
实例化一个对象的另外一种办法:Class.forName(str).newInstance();例如需要实例化一个Plane对象的话,则只要调用Class.forName("Plane").newInstance()便可。
Java代码:
- import CalTime.vehicle.all.Common;
- import java.lang.*;
- public interface Common ...{
- double runTimer(double a, double b, double c);
- }
- public class Plane implements Common ...{
- public double runTimer(double a, double b, double c) ...{
- return (a+ b + c);
- }
- }
- public class Car implements Common ...{
- public double runTimer(double a, double b, double c) ...{
- return ( a*b/c );
- }
- }
- public class ComputeTime ...{
- public static void main(String args[]) ...{
- System.out.println("交通工具: "+args[0]);
- System.out.println(" 参数A: "+args[1]);
- System.out.println(" 参数B: "+args[2]);
- System.out.println(" 参数C: "+args[3]);
- double A=Double.parseDouble(args[1]);
- double B=Double.parseDouble(args[2]);
- double C=Double.parseDouble(args[3]);
- double v,t;
- try ...{
- Common d=(Common) Class.forName("CalTime.vehicle."+args[0]).newInstance();
- v=d.runTimer(A,B,C);
- t=1000/v;
- System.out.println("平均速度: "+v+" km/h");
- System.out.println("运行时间:"+t+" 小时");
- } catch(Exception e) ...{
- System.out.println("class not found");
- }
- }
- }
以前看过一个求形状的题目就是有两个圆形求交集现在定义了两种情况问要是扩展大别的情况应当怎么设计,想了很久不得其解,现在忽然觉得接口通杀矣~
2、JAVA接口作为参数传递
可以将借口类型的参数作为方法参数,在实际是使用时可以将实现了接口的类传递给方法,后方法或按照重写的原则执行,实际调用的是实现类中的方法代码体,这样便根据传进屋的参数的不同而实现不同的功能。重要的是,当我以后徐要林外一个对象并且拥有接受说生命的方法的时候的时候,我们不必须原类,只需新的类实现借口即可。
Java代码:
- import java.lang.*;
- interface Extendbroadable ...{
- public void inPut();
- }
- class KeyBroad implements Extendbroadable ...{
- public void inPut() ...{
- System.out.println(" hi,keybroad has be input into then mainbroad! ");
- }
- }
- class NetCardBroad implements Extendbroadable ...{
- public void inPut() ...{
- System.out.println(" hi,netCardBroad has be input into then mainbroad! ");
- }
- }
- class CheckBroad ...{
- public void getMainMessage(Extendbroadable ext)...{
- ext.inPut();
- }
- }
- public class InterfaceTest01 ...{
- public static void main(String []args) ...{
- KeyBroad kb=new KeyBroad();
- NetCardBroad ncb=new NetCardBroad();
- CheckBroad cb=new CheckBroad();
- cb.getMainMessage(kb);
- cb.getMainMessage(ncb);
- }
- }
希望本文的介绍,能给你带来帮助。
【编辑推荐】
所谓Java ServerSocket通常也称作"套接字",有不少的时候需要我们详细的注意。接下来我们就看看什么是Java ServerSocket,希望大家有所收获。用于描述IP地址和端口,是一个通信链的句柄。应用程序通常通过"套接字"向网络发出请求或者应答网络请求。
Socket和ServerSocket类库位于java.net包中。ServerSocket用于服务器端,Socket是建立网络连接时使用的。在 连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。对于一个网络连接来说,套接字是平等的,并没有差别,不因为在服 务器端或在客户端而产生不同级别。不管是Socket还是ServerSocket它们的工作都是通过SocketImpl类及其子类完成的。
下面给出一个最简单的Socket通信的例子供初学者参考:
服务器端:
- ServerDemo.java
- package com.lanber.socket;
- import java.io.DataInputStream;
- import java.io.DataOutputStream;
- import java.io.IOException;
- import java.net.ServerSocket;
- import java.net.Socket;
- public class ServerDemo {
- /**
- * 注意:Socket的发送与接收是需要同步进行的,即客户端发送一条信息,服务器必需先接收这条信息,
- * 而后才可以向客户端发送信息,否则将会有运行时出错。
- * @param args
- */
- public static void main(String[] args) {
- ServerSocket ss = null;
- try {
- ss = new ServerSocket(8888);
- //服务器接收到客户端的数据后,创建与此客户端对话的Socket
- Socket socket = ss.accept();
- //用于向客户端发送数据的输出流
- DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
- //用于接收客户端发来的数据的输入流
- DataInputStream dis = new DataInputStream(socket.getInputStream());
- System.out.println("服务器接收到客户端的连接请求:" + dis.readUTF());
- //服务器向客户端发送连接成功确认信息
- dos.writeUTF("接受连接请求,连接成功!");
- //不需要继续使用此连接时,关闭连接
- socket.close();
- ss.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
以上就是对Java ServerSocket的相关介绍希望大家有所收获。
【编辑推荐】
Java 多客户端通信在很多人看来是一项很繁琐的工作,其实我们在编写代码的时候只要是注意留心Java 多客户端通信的源代码就能发现,我们认为枯燥的东西其实都很简单。
来看服务端的代码:
- import java.net.*;
- import java.io.*;
- import java.util.*;
- public class Jserver3 {
- private ServerSocket server;
- List sManager = new ArrayList();
- public Jserver3(){}
- void startServer() //运行服务器
- {
- try
- {
- server=new ServerSocket(719);
- System.out.println("服务器套接字已创建成功!");
- while(true)
- {
- Socket socket=server.accept();
- System.out.println("已经与客户端连接");
- new J_Thread(socket).start();
- sManager.add(socket);
- System.out.println("当前客户端连结数:"+sManager.size());
- }
- }catch(Exception e){}finally
- {
- try
- {
- server.close();
- }catch(Exception e){}
- }
- }
- public static void main(String[] args) {
- Jserver3 server=new Jserver3();
- server.startServer();
- }
- class J_Thread extends Thread//与客户端进行通信的线程类
- {
- Socket socket; //套接字引用变量
- private DataInputStream reader; //套接字输入流
- private DataOutputStream writer; //套接字输出流
- J_Thread(Socket socket) //构造函数
- {
- this.socket=socket;
- }
- public void run()
- {
- try
- {
- reader=new DataInputStream(socket.getInputStream());//获取套接字的输入流
- writer=new DataOutputStream(socket.getOutputStream());//获取套接字的输出流
- String msg;
- while((msg=reader.readUTF())!=null)//如果收到客户端发来的数据
- {
- //向客户端发送信息
- writer.writeUTF("您的情书已经收到");
- writer.flush();
- System.out.println("来自客户端:"+msg);
- }
- }catch(Exception e){}finally
- {
- try
- {
- sManager.remove(socket); //删除套接字
- //关闭输入输出流及套接字
- if(reader!=null)reader.close();
- if(writer!=null)writer.close();
- if(socket!=null)socket.close();
- reader=null;
- writer=null;
- socket=null;
- System.out.println("客户端离开");//向屏幕输出相关信息
- System.out.println("当前客户端的连接数:"+sManager.size());
- }catch(Exception e){}
- }
- }
- }
- }
- import java.net.*;
- import java.io.*;
- import java.util.*;
- public class Jserver3 {
- private ServerSocket server;
- List sManager = new ArrayList();
- public Jserver3(){}
- void startServer() //运行服务器
- {
- try
- {
- server=new ServerSocket(719);
- System.out.println("服务器套接字已创建成功!");
- while(true)
- {
- Socket socket=server.accept();
- System.out.println("已经与客户端连接");
- new J_Thread(socket).start();
- sManager.add(socket);
- System.out.println("当前客户端连结数:"+sManager.size());
- }
- }catch(Exception e){}finally
- {
- try
- {
- server.close();
- }catch(Exception e){}
- }
- }
- public static void main(String[] args) {
- Jserver3 server=new Jserver3();
- server.startServer();
- }
- class J_Thread extends Thread//与客户端进行通信的线程类
- {
- Socket socket; //套接字引用变量
- private DataInputStream reader; //套接字输入流
- private DataOutputStream writer; //套接字输出流
- J_Thread(Socket socket) //构造函数
- {
- this.socket=socket;
- }
- public void run()
- {
- try
- {
- reader=new DataInputStream(socket.getInputStream());//获取套接字的输入流
- writer=new DataOutputStream(socket.getOutputStream());//获取套接字的输出流
- String msg;
- while((msg=reader.readUTF())!=null)//如果收到客户端发来的数据
- {
- //向客户端发送信息
- writer.writeUTF("您的情书已经收到");
- writer.flush();
- System.out.println("来自客户端:"+msg);
- }
- }catch(Exception e){}finally
- {
- try
- {
- sManager.remove(socket); //删除套接字
- //关闭输入输出流及套接字
- if(reader!=null)reader.close();
- if(writer!=null)writer.close();
- if(socket!=null)socket.close();
- reader=null;
- writer=null;
- socket=null;
- System.out.println("客户端离开");//向屏幕输出相关信息
- System.out.println("当前客户端的连接数:"+sManager.size());
- }catch(Exception e){}
- }
- }
- }
- }
嘎嘎 在这段代码里,服务端MM为每一个连接的客户端GG分配一个单独的线程,而每一个线程里都持有对应的客户端GG的Java 多客户端通信对象。SO,通过这些多线程,服务端MM就练就了一心N用的功力,可以同时接受N个客户端GG发来的情书了(,真的太贱了。。。。)
客户端的代码和上面的客户端代码一模一样的,这里就不多说啦!
【编辑推荐】




