| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 2866 人关注过本帖, 16 人收藏
标题:JAVA入门教程(2)
只看楼主 加入收藏
lampeter123
Rank: 16Rank: 16Rank: 16Rank: 16
等 级:版主
威 望:54
帖 子:2508
专家分:6424
注 册:2009-1-30
结帖率:90.32%
收藏(16)
 问题点数:0 回复次数:9 
JAVA入门教程(2)
JAVA GUI

组成Swing的类如图
图片附件: 游客没有浏览图片的权限,请 登录注册


Swing优点
    Swing具有更丰富,更方便的用户界面元素集合。
    Swing对低层平台的依赖更少;因此和平台有关的bug也少的多。
    Swing给不同平台上的用户一致的感觉。

布局管理器
由Swing开发的GUI界面通常由两种组件构成:
    容器组件:用于管理其他界面组件的组件,例如:JFrame,JPanel等。
    元素组件:用于构成各种用户界面的组件,例如:JLabel,JTextField等。
容器中组件出现的位置和组件的大小通常由布局管理器控制。每个Container(比如一个JPanel或一个JFrame)都有一个缺省布局管理器,它可以通过调用setLayout()来改变。
布局管理器负责决定布局策略以及其容器的每一个子组件的大小。
Java编程语言包含下面的布局管理器:
    FlowLayout- Panel和Applets的缺省布局管理器
    BorderLayout- Window、Dialog及Frame的缺省管理程序
    GridLayout
    CardLayout
    GridBagLayout
    GridBagLayout
下图描述了容器的默认布局管理器
图片附件: 游客没有浏览图片的权限,请 登录注册



FlowLayout
前面所用的FlowLayout布局管理器对组件逐行地定位。每完成一行,一个
新行便又开始。与其它布局管理器不一样,FlowLayout布局管理器不限制
它所管理的组件的大小,而是允许它们有自己的最佳大小。Flow布局构造函
数参数允许将组件左对齐或右对齐(缺省为居中)。如果想在组件之间创建一
个更大的最小间隔,可以规定一个界限。
当用户对由Flow布局管理的区域进行缩放时,布局就发生变化。
图片附件: 游客没有浏览图片的权限,请 登录注册


下面的例子说明如何用容器类的setLayout()方法来创建Flow布局对象并设置它们。
setLayout(new FlowLayout(int align, int hgap, int vgap));
align的值必须是FlowLayout.LEFT, FlowLayout.RIGHT,或
FlowLayout.CENTER。例如:
setLayout(new FlowLayout(FlowLayout.RIGHT, 20, 40));
下述程序构造并设置一个新Flow布局,它带有规定好的对齐方式以及一个缺
省的5单位的水平和垂直间隙。
setLayout(new FlowLayout(FlowLayout.LEFT));
下述程序构造并安装一个新Flow布局,它带有规定好的居中对齐方式和一个
缺省的5单位的水平和垂直间隙。
setLayout(new FlowLayout());
下面的代码将几个按钮添加到框架中的一个Flow布局中:
import java.awt.*;

public class MyFlow {
    private Frame f;
    private Button button1, button2, button3;
    public static void main(String args[]) {
        MyFlow mflow = new MyFlow();
        mflow.go();
    }

    public void go() {
        f = new Frame("Flow Layout");
        f.setLayout(new FlowLayout());
        button1 = new Button("Ok");
        button2 = new Button("Open");
        button3 = new Button("Close");
        f.add(button1);
        f.add(button2);
        f.add(button3);
        f.setSize(100, 100);
        f.setVisible(true);
    }
}

BorderLayout
BorderLayout布局管理器为在一个Panel或Window中放置组件提供一
个更复杂的方案。BorderLayout布局管理器包括五个明显的区域:东、南、
西、北、中。
北占据面板的上方,东占据面板的右侧,等等。中间区域是在东、南、西、
北都填满后剩下的区域。当窗口垂直延伸时,东、西、中区域也延伸;而当
窗口水平延伸时,东、西、中区域也延伸。
BorderLayout布局管理器是用于Dialog和Frame的缺省布局管理器。
其划分界面如图
图片附件: 游客没有浏览图片的权限,请 登录注册


下面这一行构造并安装一个新Border布局管理器,在组件之间没有间隙:
setLayout(new BorderLayout());
这一行构造并安装一个Border布局,在组件之间有由hgap和 vgap 规定的间隙:
setLayout(new BorderLayout(int hgap, int vgap);            
在向使用BorderLayout布局管理器管理的界面添加组件时,默认的将加入
到中间,如果添加的其它组件未指定添加方位将互相覆盖,只有最后添加上
去的组件方可看见。
下面的代码对前例进行了修改,表示出了Border布局管理器的特性。可以
用从Container类继承的setLayout()方法来将布局设定为Border布
局。
import java.awt.*;
public class ExGui2 {
    private Frame f;
    private Button bn, bs, bw, be, bc;
    public static void main(String args[]) {
        ExGui2 guiWindow2 = new ExGui2();
        guiWindow2.go();
    }

    public void go() {
        f = new Frame("Border Layout");
        bn = new Button("B1");
        bs = new Button("B2");
        be = new Button("B3");
        bw = new Button("B4");
        bc = new Button("B5");
        f.add(bn, BorderLayout.NORTH);
        f.add(bs, BorderLayout.SOUTH);
        f.add(be, BorderLayout.EAST);
        f.add(bw, BorderLayout.WEST);
        f.add(bc, BorderLayout.CENTER);
        f.setSize(200, 200);
        f.setVisible(true);
    }
}
这段代码产生如图效果
图片附件: 游客没有浏览图片的权限,请 登录注册


GridLayout
GridLayout布局管理器为放置组件提供了灵活性。用行和列来创建管
理器。然后组件就填充到由管理器规定的单元中。比如,由语句new
GridLayout(3,2)创建的有三行两列的GridLayout布局能产生如图
的六个单元:
图片附件: 游客没有浏览图片的权限,请 登录注册


像BorderLayout布局管理器一样,GridLayout布局管理器中的组件相应的位置不随区域的缩放而改变。只是组件的大小改变。GridLayout布局管理器总是忽略组件的最佳大小。所有单元的宽度是相同的,是根据单元数对可用宽度进行平分而定的。同样地,所有单元的高度是相同的,是根据行数对可用高度进行平分而定的。将组件添加到网格中的命令决定它们占有的单元。单元的行数是从左到右填充,就象文本一样,而列是从上到下由行填充。
程序行:setLayout(new GridLayout());创建并安装一个Grid布局,
仅有一行一列。
程序行:setLayout(new GridLayout(int rows, int cols));创
建并安装一个带有规定好行数和栏数的Grid布局。
程序行:setLayout(new GridLayout(int rows, int cols,  int
hgap, int vgap); 创建并安装一个带有规定好行数和栏数的网格布局。
hgap和vgap规定组件间各自的间隙。水平间隙放在左右两边及栏与栏之间。
垂直间隙放在顶部、底部及每行之间。            
import java.awt.*;
public class GridEx {
    private Frame f;
    private Button b1, b2, b3, b4, b5, b6;
    public static void main(String args[]) {
        GridEx grid = new GridEx();
        grid.go();
    }
    public void go() {
        f = new Frame("Grid example");
        f.setLayout(new GridLayout(3, 2));
        b1 = new Button("1");
        b2 = new Button("2");
        b3 = new Button("3");
        b4 = new Button("4");
        b5 = new Button("5");
        b6 = new Button("6");
        f.add(b1);
        f.add(b2);
        f.add(b3);
        f.add(b4);
        f.add(b5);
        f.add(b6);
        f.pack();
        f.setVisible(true);
    }
}

CardLayout
Card布局管理器能将界面看作一系列的卡片,在任何时候都仅能看到其中的
一个。用add()方法来将卡添加到Card布局中。Card布局管理器的show()
方法将请求转换到一个新卡中。下图就是一个带有5张卡片的框架。

图片附件: 游客没有浏览图片的权限,请 登录注册


鼠标点击左面板将视图转换到右面板,等等。
用来创建上图框架的代码段如下所示:
import java.awt.*;
import java.awt.event.*;

public class CardExample implements MouseListener {
    Panel p1, p2, p3, p4, p5;
    Label l1, l2, l3, l4, l5;   
    private CardLayout myCard;
    private Frame f;
    public CardExample() {
        f = new Frame("Card Test");
        myCard = new CardLayout();
        p1 = new Panel();
        p2 = new Panel();
        p3 = new Panel();
        p4 = new Panel();
        p5 = new Panel();
        l1 = new Label("This is the first Panel");
        l2 = new Label("This is the second Panel");
        l3 = new Label("This is the third Panel");
        l4 = new Label("This is the fourth Panel");
        l5 = new Label("This is the fifth Panel");
    }

    public void launchFrame() {
        f.setLayout(myCard);        
        p1.setBackground(Color.yellow);
        p1.add(l1);
        p2.setBackground(Color.green);
        p2.add(l2);
        p3.setBackground(Color.magenta);
        p3.add(l3);
        p4.setBackground(Color.white);
        p4.add(l4);
        p5.setBackground(Color.cyan);
        p5.add(l5);

        p1.addMouseListener(this);
        p2.addMouseListener(this);
        p3.addMouseListener(this);
        p4.addMouseListener(this);
        p5.addMouseListener(this);

        f.add(p1, "First");
        f.add(p2, "Second");
        f.add(p3, "Third");
        f.add(p4, "Fourth");
        f.add(p5, "Fifth");

        myCard.show(f, "First");

        f.setSize(200, 200);
        f.setVisible(true);
    }
    //用于处理鼠标点击事件
    public void mousePressed(MouseEvent e) {
        myCard.next(f);
    }
    public void mouseReleased(MouseEvent e) {}
    public void mouseClicked(MouseEvent e) {}
    public void mouseEntered(MouseEvent e) {}
    public void mouseExited(MouseEvent e) {}
    public static void main(String args[]) {
        CardExample ct = new CardExample();
        ct.launchFrame();
    }
}

GridBagLayout
除了Flow、Border、Grid和Card布局管理器外,核心Java.awt也提
供GridBag布局管理器。
GridBag布局管理器在网格的基础上提供复杂的布局,但它允许单个组件在
一个单元中而不是填满整个单元那样地占用它们的最佳大小。网格包布局

理器也允许单个组件扩展成不止一个单元。

实例分析
例1:组成用户登录的界面包括用户名和密码输入并可以确认提交,请使用Swing组件编写
解决方案
n    问题分析
n    使用组件分析
n    编写代码
n    编译并运行
问题分析
可以使用Swing中的GUI类构建登录界面,注意区别元素组件和容器组件
使用组件分析
基本元素组件
n    JLabel: 用于短文本字符串或图像或二者的显示区。
n    JTextField: 用于单行输入文本。
n    JButton: 按钮的实现。
n    JTextArea: 显示纯文本的多行区域。
n    JComboBox: 下拉列表组合的组件。
n    JRadioButton: 实现一个单选按钮,此按钮项可被选择或取消选择.
n    JCheckBox: 复选框的实现               
创建框架
Java中的顶层窗口(即:那些没有包含在其他窗口中的窗口)被称为框架,
也称为容器组件。
n    JPanel: 一般轻量级容器,不能直接显示.
n    JFrame: 是带有标题和边界的顶层窗口.
n    JApplet:一种不适合单独运行但可嵌入在其他应用程序中的小程序。
注解:大部分Swing组件类的名字都是以"J"开头,如JButton,JFrame,JTextField等等。Java中也有着Button,Frame类,不过它们都是AWT组件。如果你不小心忘了写Swing组件前的"J",程序还是很可能能够编译和运行,不过由于混杂了Swing和AWT组件,在视觉和响应上可能会有不一致的地方。
给框架定位
JFrame类本身只有几个用来改变框架外观的类。当然,通过继承,JFrame
从不同的超类中继承来很多用于处理框架大小和位置的方法。下面列出几个
可能最为重要的方法:
n    dispose方法----关闭窗口并回收用于创建窗口的任何资源;
n    setIconImage方法----当窗口最小化时,把一个Image对象用作图标。
n    setTitle方法-----改变标题栏中的文字。
n    setResizable方法-使用boolean参数来决定框架大小是否能被用户改变。
n    setLocation方法----用来显示组件在容器中的位置(对于框架来说,方法中的参数坐标是相对于整个屏幕而言的,对于容器内的组件,坐标是相对于容器的)。
n    setBounds方法----同上,只不过它带有四个参数(其中前两个参数同上,都是设置组件的位置,后两个参数用来设置组件的大小)。
在面板中显示信息
JFram与Frame不同的是,在JFrame中加组件是加在内容窗格里的。如:
Container contentPane=frame.getContentPane();  //用上例中的 frame对象
JComponent c=…;
contentPane.add(c);
如果你只需要在框架中显示一个Swing组件,那么你可以用下面的方式把组
件放置到内容窗格中。
Frame.setContentPane(c);
面板是也是个容器。它可以再放其他的组件,我们可以设计自己的面板:
n    定义一个扩展JPanel的新类
n    覆盖paintComponent方法
注意:paintComponent方法实际上定义在JComponent中,这个类是所有非窗口Swing组件的父类。该方法有一个Graphics类型的参数。Graphics 对象存储了一个用于绘制图形和文本的设置集合(比如字体和当前颜色)。Java中的所有绘制都必须用Graphics对象。它拥有绘制,图案,图象和文本的方法。
例如:SimpleJFrameTest.java
import javax.swing.*;
import java.awt.*;

public class SimpleJFrameTest {
    public static void main(String[] args) {
        SimpleJFrame fram = new SimpleJFrame();
        fram.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        fram.show();
        // fram.setVisible(true);
    }
}

class SimpleJFrame extends JFrame {
    public static final int WIDTH = 400;
    public static final int HEIGHT = 300;
    public SimpleJFrame() {
        setSize(WIDTH, HEIGHT);
        Container contentPane = getContentPane();
        contentPane.add(new JPanelOne());
    }
}
class JPanelOne extends JPanel {
    public void paint(Graphics g) {
        super.paint(g);
        g.fillRect(30, 10, 200, 100);
        g.clearRect(60, 30, 80, 40);
        g.drawRect(70, 45, 35, 20);
        g.drawLine(10, 60, 250, 60);
    }
}
结果
定义元素组件:
JLabel lblUsername;
JLable lblPassword;
JTextField txtUsername;
JPasswordField txtPassword;
JButton bLogin;
定义容器组件:
JPanel panel;
编写代码
import java.awt.*;
import javax.swing.*;

public class UserLogin extends JFrame {
    JLabel lblUsername;
    JLabel lblPassword;
    JTextField txtUsername;
    JPasswordField txtPassword;
    JButton bLogin;
    JPanel panel;

    public UserLogin() {
        panel = (JPanel) getContentPane();
        // 设置布局管理器
        panel.setLayout(new FlowLayout());

        lblUsername = new JLabel("Usernanme:");
        lblPassword = new JLabel("Password:");

        txtUsername = new JTextField(10);
        txtPassword = new JPasswordField(10);

        bLogin = new JButton("Login");

        panel.add(lblUsername);
        panel.add(txtUsername);

        panel.add(lblPassword);
        panel.add(txtPassword);

        panel.add(bLogin);

        setTitle("User Login");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(250, 150);
        setVisible(true);
    }

    public static void main(String[] args) {
        new UserLogin();
    }
}
编译并运行
图片附件: 游客没有浏览图片的权限,请 登录注册


例2:组成用户登录的界面已经设计完成,使用合理的布局管理器管理界面元
素,请使用Swing组件编写
解决方案
n    问题分析
n    使用布局管理器分析
n    编写代码
n    编译并运行
问题分析
可以使用使用GridBagLayout管理布局
使用布局管理器分析
GridBagLayout
与GridLayout类似,GridBagLayout将组件在一个矩形的"单元"网格中对齐。GridLayout和GridBagLayout的区别在于,GridBagLayout使用了一种称为GridBagConstraints的对象。
通过在GridBagConstraints对象中设置值,组件可水平或垂直地对齐(带
有或不带有小插图和填充),被告知扩展以填充给定的区域,以及被指示如何
对窗口大小的改变采取相应的行动。
GridBagConstraints
GridBagConstraints的成员变量用于控制组件布局它们是:
n    gridx和gridy
这些变量指定了位于组件显示区域左上方的单元的特征(也就是组件出现的
x,坐标值),其中,最左上方的单元具有地址gridx=0,gridy=0。
n    gridwidth和gridheight
这些变量指定了在组件的显示区域中,行(gridwidth)或列(gridheight)
中的单元数目。缺省值为1。你可用GridBagConstraints.REMAINDER
来指定某组件在其行(gridwidth)或列(gridheight)中为最后一个。
用GridBagConstraints.RELATIVE可指定某组件在其行(gridwidth)
或列(gridheight)中与最后一个相邻。
n    fill
fill在某组件的显示区域大于它所要求的大小时被使用;fill决定了是否
(和怎样)改变组件的大小。有效值为GridBagConstraints.NONE(缺
省),GridBagConstraints.HORIZONTAL(使该组件足够大以填充其显
示区域的水平方向,但不改变其高度),GridBagConstraints.VERTICAL
(使该组件足够大,以填充其显示区域的垂直方向,但不改变其宽度),
GridBagConstraints.BOTH(使该组件填充其整个显示区域)。
n    ipadx和ipady
这些变量指定了内部填充;即,在该组件的最小尺寸基础上还需增加多少。
组件的宽度必须至少为其最小宽度加ipadx*2个象素(因为填充作用于组件
的两边)。同样地,组件的高度必须至少为其最小高度加ipady*2个象素。
n    insets
insets指定了组件的外部填充;即,组件与其显示区域边界之间的最小空间大小。
n    anchor
本变量在组件小于其显示区域时使用;anchor决定了把组件放置在该区域中的位置。有效值为GridBagConstraints.CENTER(缺省),.NORTH,.NORTHEAST,.EAST,.SOUTHEAST,.SOUTH,.SOUTHWEST,.WEST,和.NORTHWEST。
n    weightx和weighty
这些变量用来决定如何分布空白和改变它的大小。除非你为一行(weightx)
和一列(weighty)中至少一个组件指定了重量,否则,所有组件都会集中
在容器的中央。这是因为,当重量为0(缺省值)时,GridBagLayout在其
单元网格间以及容器边界加入一些额外的空白。
结果
GridBagLayout gb;
GridBagConstraints gbc;
....
gb = new GridBagLayout();
gbc = new GridBagConstraints();
panel.setLayout(gb);
...
gbc.gridx = 1;
gbc.gridy = 1;
gb.setConstraints(lblUsername,gbc);
panel.add(lblUsername);
编写代码
import java.awt.*;
import javax.swing.*;
public class UserLoginFrame extends JFrame {
    JLabel lblUsername;
    JLabel lblPassword;
    JTextField txtUsername;
    JPasswordField txtPassword;
    JButton bLogin;
    JPanel panel;
    GridBagLayout gb;
    GridBagConstraints gbc;
    public UserLoginFrame() {
        panel = (JPanel) getContentPane();
        gb = new GridBagLayout();
        gbc = new GridBagConstraints();
        // 设置布局管理器
        panel.setLayout(gb);
        lblUsername = new JLabel("Usernanme:");
        lblPassword = new JLabel("Password:");
        txtUsername = new JTextField(10);
        txtPassword = new JPasswordField(10);
        bLogin = new JButton("Login");

        gbc.anchor = GridBagConstraints.NORTHWEST;
        gbc.gridx = 1;
        gbc.gridy = 1;
        gb.setConstraints(lblUsername, gbc);
        panel.add(lblUsername);

        gbc.gridx = 2;
        gbc.gridy = 1;
        gb.setConstraints(txtUsername, gbc);
        panel.add(txtUsername);

        gbc.gridx = 1;
        gbc.gridy = 2;
        gb.setConstraints(lblPassword, gbc);
        panel.add(lblPassword);

        gbc.gridx = 2;
        gbc.gridy = 2;
        gb.setConstraints(txtPassword, gbc);
        panel.add(txtPassword);

        gbc.gridx = 2;
        gbc.gridy = 3;
        gb.setConstraints(bLogin, gbc);
        panel.add(bLogin);

        setTitle("User Login");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(250, 250);
        setVisible(true);
    }
    public static void main(String[] args) {
        new UserLoginFrame();
    }
}
编译并运行
图片附件: 游客没有浏览图片的权限,请 登录注册



[ 本帖最后由 lampeter123 于 2010-7-21 11:27 编辑 ]
搜索更多相关主题的帖子: JAVA 入门 教程 
2010-07-21 08:38
lampeter123
Rank: 16Rank: 16Rank: 16Rank: 16
等 级:版主
威 望:54
帖 子:2508
专家分:6424
注 册:2009-1-30
收藏
得分:0 
线程

1.线程的概念
一个关于计算机的简化的视图是:它有一个执行计算的处理机、包含处理机所执行的程序的ROM(只读存储器,在JAVA中也叫堆栈)、包含程序所要操作的数据的RAM(随机存储器,在JAVA中也叫堆)。在这个简化视图中,只能执行一个作业。一个关于最现代计算机比较完整的视图允许计算机在同时执行一个以上的作业。
你不需关心这一点是如何实现的,只需从编程的角度考虑就可以了。如果你要执行一个以上的作业,这类似有一台以上的计算机。在这个模型中,线程(或执行上下文),被认为是带有自己的程序代码和数据的虚拟处理机的封装。java.lang.Thread类允许用户创建并控制他们的线程。在单CPU的情况下,一个时刻只能运行一个进程,那么进程在运行时,也只能运行一个线程来代表该进程的执行。
进程是正在执行的程序。一个或更多的线程构成了一个进程(操作系统是以进程为单位的,而进程是以线程为单位的,进程中必须有一个主线程)。一个线程(执行上下文)由三个主要部分组成:
    一个虚拟CPU
    CPU执行的代码
    代码操作的数据

代码可以由多个线程共享,它不依赖数据。如果两个线程执行同一个类的实例的代码时,则它们可以共享相同的代码。
类似地,数据可以由多个线程共享,而不依赖代码。如果两个线程共享对一个公共对象的访问,则它们可以共享相同的数据。
在Java编程中,虚拟处理机封装在Thread类的一个实例里。构造线程时,定义其上下文的代码和数据是由传递给它的构造函数的对象指定的。Java线程分守护线程和用户线程,由创建时设置。

线程状态和调度
在Java中,线程的调度是基于时间片基础上的优先级优先原则  
抢占式调度模型(优先级优先 )是指可能有多个线程是可运行的,但只有一个线程在实际运行。这个线程会一直运行,直至它不再是可运行的(运行时间到,时间片原则,或者,另一个具有更高优先级的线程抢占,优先级优先原则)。 对于后面一种情形,低优先级线程被高优先级线程抢占了运行的机会。
线程 的代码可能执行了一个Thread.sleep()调用,要求这个线程暂停一段固定的时间。这个线程可能在等待访问某个资源,而且在这个资源可访问之前,这个线程无法继续运行。
所有可运行线程根据优先级保存在池中。当一个被阻塞的线程变成可运行时,它会被放回相应的可运行池。优先级最高的非空池中的线程会得到处理机时间(被运行)。
一个Thread对象在它的生命周期中会处于各种不同的状态。
下图形象地说明了这点:
图片附件: 游客没有浏览图片的权限,请 登录注册

图片附件: 游客没有浏览图片的权限,请 登录注册


线程进入"可运行"状态,并不意味着它立即开始运行。在一个只有一个CPU的机器上,在一个时刻只能进行一个动作。(下节将描述:如果有一个以上可运行线程时,系统如何分配CPU。)
因为Java线程是抢占式的,所以你必须确保你的代码中的线程会不时地给其它线程运行的机会。这可以通过在各种时间间隔中发出sleep()调用来做到。
class ThreadA implements Runnable {
    public void run() {
        while (true) {
            //线程的执行代码部分            
            try {
                //给其他的线程提供机会运行
                Thread.sleep(7);
            } catch (Exception e) {
            }
        }
    }
}
try和catch块的使用。Thread.sleep()和其它使线程暂停一段时间的方法是可中断的。线程可以调用另外一个线程的interrupt()方法,这将向暂停的线程发出一个InterruptedException。
Thread类的sleep()方法对当前线程操作,因此被称作Thread.sleep(x),它是一个静态方法。sleep()的参数指定以毫秒为单位的线程最小休眠时间。除非线程因为中断而提早恢复执行,否则它不会在这段时间之前恢复执行。使用该方法只是使当前线程中断多少毫秒,并不是创建多线程。
例如:
class TestTS {//Thread sleep
    public static void main(String[] args) {
        try {
            Thread.sleep(5000);//中断当前线程(main)5秒,并没有创建新的线程。
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Hello World!");
    }
}
 
线程中断/恢复的几种方式
一个线程可能因为各种原因而不再是可运行的。
    该线程调用Thread.sleep() 进入中断状态必须经过规定的毫秒数才能从中断状态进入可运行状态
    该线程进行了IO操作 而进入中断状态必须等待IO操作完成,才能 进入可运行状态
    该线程调用了其它线程的join()方法,而使自己进入中断状态必须等待调用的线程执行完,才能进入可运行状态
    该线程试图访问被另一个线程锁住的对象 而进入中断状态必须等待另一个线程释放对象锁,该线程才能进入可运行状态该线程调用wait()方法而进入中断状态必须通过其他线程调用notify()或者notifyAll()方法才能进入可运行状态
 
创建线程的两种方式(实现接口的方式请看实例分析5)
    实现Runnable接口
    继承Thread类
实现Runnable的优点
从面向对象的角度来看,Thread类是一个虚拟处理机的严格封装,因此只有当处理机模型修改或扩展时,才应该继承类。
由于Java技术只允许单一继承,所以如果你已经继承了Thread,你就不能再继承其它任何类。
继承Thread的优点
当一个run()方法体出现在继承Thread的类中,用this指向实际控制运行的Thread实例。因此,代码不再需要使用如下控制:
Thread.currentThread().join();而可以简单地用:join();
因为代码简单了一些,许多Java编程语言的程序员使用扩展Thread的机制。   
 
线程的控制
终止一个线程:
当一个线程结束运行并终止时,它就不能再运行了。可以用一个标志来指示run()方法,必须退出一个线程。
public class Runner implements Runnable {
    private boolean timeToQuit = false; //终止标志

    public void run() {
                    while(! timeToQuit) { //当结束条件为假时运行
                    ...
                    }
                }

    //停止运行的方法
    public void stopRunning() {
        timeToQuit = true;
    }
}

//控制线程类
public class ControlThread {
    private Runnable r = new Runner();
    private Thread t = new Thread(r);
    public void startThread() {
        t.start();
    }
    public void stopThread() {
        r.stopRunning();
    }
}

线程的优先级
使用getPriority方法测定线程的当前优先级。使用setPriority方法设定线程的当前优先级。线程优先级是一个整数(1到10)。Thread类包含下列常数:
Thread.MIN_PRIORITY      1       (最低级别)
Thread.NORM_PRIORITY     5       (默认的级别)
Thread.MAX_PRIORITY      10      (最高级别)
延迟线程
sleep()方法是使线程停止一段时间的方法。在sleep时间间隔期满后,线程不一定立即恢复执行。这是因为在那个时刻,其它线程可能正在运行而且没有被调度为放弃执行,除非
(a)"醒来"的线程具有更高的优先级
(b)正在运行的线程因为其它原因而阻塞
 
线程同步
为了保证共享数据在任何线程使用它完成某一特定任务之前是一致的,Java使用关键字synchronized,允许程序员控制共享数据的线程。
对象锁   
在Java技术中,每个对象都有一个和它相关联的标志。这个标志可以被认为是"锁标志"。 synchronized关键字使线程能和这个标志的交互,即允许独占地存取对象。
当线程运行到synchronized语句,它检查作为参数传递的对象,并在继续执行之前试图从对象获得锁标志。
持有锁标志的线程执行到synchronized()代码块末尾时将释放锁。即使出现中断或异常而使得执行流跳出synchronized()代码块,锁也会自动返回。此外,如果一个线程对同一个对象两次发出synchronized调用,则在跳出最外层的块时,标志会正确地释放,而最内层的将被忽略。
关键字synchronized
public void push(char c) {
        synchronized(this) {// synchronized语句块
            . . .
            }
        }
        // synchronized方法
        public synchronized void push(char c) {
        . . .
        }
死锁
当一个线程等待由另一个线程持有的锁,而后者正在等待已被第一个线程持有的锁时,就会发生死锁。
避免死锁的一个通用的经验法则是:决定获取锁的次序并始终遵照这个次序。按照与获取相反的次序释放锁。
 
实例分析
例1:    创建四个线程对同一个数据操作,其中两个线程对该数据执行加1操作,两个线程对该数据减1操作
    创建数据类
//数据类
class Data {
    private int k;
    public void add() {
        k++;
    }

    public void sub() {
        k--;
    }

    public int getK() {
        return k;
    }
}
    创建加数据的线程
//加数据的线程
class ThreadAdd extends Thread {
    //线程操作的数据
    Data data;

    public ThreadAdd(Data data, String name) {
        //给当前线程命名
        super(name);
        this.data = data;
    }

    //线程执行时所调用的方法
    public void run() {
        for (int i = 0; i < 20; i++) {
            data.add();
            //打印出哪个线程执行的加操作
            System.out.println(Thread.currentThread().getName() + "  "
                    + data.getK());
            //每循环一次,让该线程中断5毫秒
            try {
                Thread.sleep(5);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
};

    创建减数据的线程
//减数据的线程
class ThreadSub extends Thread {
    //线程所操作的关键数据
    Data data;

    public ThreadSub(Data data, String name) {
        //给当前线程命名
        super(name);
        this.data = data;
    }

    //线程执行时所调用的方法,即线程所执行的代码
    public void run() {
        for (int i = 0; i < 20; i++) {
            data.sub();
            //打印出哪个线程执行的加操作
            System.out.println(Thread.currentThread().getName() + "  "
                    + data.getK());
            //每循环一次,让该线程中断5毫秒
            try {
                Thread.sleep(5);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
};
    启动四个线程运行
class TestThread {
    public static void main(String[] args) {
        Data data = new Data();
        //创建四个线程
        Thread thadd1 = new ThreadAdd(data, "thadd1");
        Thread thadd2 = new ThreadAdd(data, "thadd2");
        Thread thsub1 = new ThreadSub(data, "thsub1");
        Thread thsub2 = new ThreadSub(data, "thsub2");
        //启动四个线程
        thadd1.start();
        thadd2.start();
        thsub1.start();
        thsub2.start();
    }
}

 

例2    通过join()方法中断一个线程
需要修改上例的代码(只修改main()方法):
class TestThread {
    public static void main(String[] args) {
        Data data = new Data();
        //创建四个线程
        Thread thadd1 = new ThreadAdd(data, "thadd1");
        Thread thadd2 = new ThreadAdd(data, "thadd2");
        Thread thsub1 = new ThreadSub(data, "thsub1");
        Thread thsub2 = new ThreadSub(data, "thsub2");
        //启动四个线程
        thadd1.start();
        try {
            thadd1.join(); //thadd1执行完后才输出“join() 已经执行完毕”
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("join() 已经执行完毕");
        thadd2.start();
        thsub1.start();
        thsub2.start();
    }
}

 

例3    通过IO中断线程
class TestThread {
    public static void main(String[] args) {
        Data data = new Data();
        //创建四个线程
        Thread thadd1 = new ThreadAdd(data, "thadd1");
        Thread thadd2 = new ThreadAdd(data, "thadd2");
        Thread thsub1 = new ThreadSub(data, "thsub1");
        Thread thsub2 = new ThreadSub(data, "thsub2");
        //启动四个线程
        thadd1.start();
        try {
            //等待用户从控制台输入数据
            int k = System.in.read();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("io 已经执行完毕");
        thadd2.start();
        thsub1.start();
        thsub2.start();
    }
}


 
例4    线程的同步
先看线程不同步的情况:
//数据类
package com.itjob;

public class Person {
    private String name = "王非";
    private String sex = "女";   
   
    public void put(String name, String sex)
    {            
        this.name = name;        
        this.sex = sex;        
    }
   
    public void get()
    {        
        System.out.println(name + "----->" + sex);
    }   
}

//用来显示Person数据
package com.itjob;
public class Consumer implements Runnable {
    Person person;
   
    public Consumer(Person person)
    {
        this.person = person;
    }

    public void run() {
        while(true)
        {
            person.get();
        }        
    }
}

// 用来修改Person数据
package com.itjob;
public class Producer implements Runnable {   
    Person person;
   
    public Producer(Person person)
    {
        this.person = person;
    }
   
    public void run()
    {
        int i = 0;
        while(true)
        {            
                if(i==0)
                {
                    person.put("刘祥", "男");                    
                }else{
                    person.put("王非", "女");                    
                }            
            i = (i+1)%2;
        }
    }
}
//主程序类
package com.itjob;
public class ThreadCom {
    public static void main(String[] args)
    {
        Person person = new Person();
        new Thread(new Producer(person)).start();
        new Thread(new Consumer(person)).start();
    }
}
 

修改为线程同步
只需要修改Person类就OK了,同步只是对数据加锁,与线程无关,当多个线程访问同一个数据的时候,就要对数据加锁(同步)。
修改后的代码如:
//数据类
package com.itjob;
public class Person {
    private String name = "王非";
    private String sex = "女";   
   
    public synchronized void put(String name, String sex)
    {            
        this.name = name;        
        this.sex = sex;        
    }
   
    public synchronized void get()
    {        
        System.out.println(name + "----->" + sex);
    }   
}
运行的结果不会有男女不分的情况!
 


例5:    通过wait()和notify()/notifyAll()方法进行同步
Object类中提供了用于线程通信的方法:wait()和notify(),notifyAll()。如果线程对一个指定的对象x发出一个wait()调用,该线程会暂停执行,此外,调用wait()的线程自动释放对象的锁,直到另一个线程对同一个指定的对象x发出一个notify()调用。
为了让线程对一个对象调用wait()或notify(),线程必须锁定那个特定的对象。也就是说,只能在它们被调用的实例的同步块内使用wait()和notify()。
根据以上内容,修改Person类如下:
package com.itjob;
public class Person {
    private String name = "王非";
    private String sex = "女";
   
    //为true时,修改数据;为false时,显示数据
    boolean flag = false;
   
    public synchronized void put(String name, String sex)
    {
        if(flag)
        {
            try {
                wait();
            } catch (InterruptedException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
        }
            
        this.name = name;
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        this.sex = sex;
        flag = true;
        notifyAll();
    }
   
    public synchronized void get()
    {
        if(!flag)
        {
            try {
                wait();
            } catch (InterruptedException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
        }   
        System.out.println(name + "----->" + sex);
        flag = false;
        notifyAll();
    }   
}


[ 本帖最后由 lampeter123 于 2010-7-21 08:50 编辑 ]

你的优秀和我的人生无关!!!!
    
    我要过的,是属于我自己的生活~~~
2010-07-21 08:47
lampeter123
Rank: 16Rank: 16Rank: 16Rank: 16
等 级:版主
威 望:54
帖 子:2508
专家分:6424
注 册:2009-1-30
收藏
得分:0 
高级I/O流

I/O基础知识
Java语言中数据流是发送或接收数据的管道。通常,你的程序是流的一个端点,其它程序或文件是流的另一个端点。
流是:one dimension one direction 一维单向的。
数据 源端点和数据 目的端点分别叫做input stream(输入流)和output stream(输出流)。你可以从输入流读,但你不能对它写;同样,你可以向输出流写,但不能从输出流读。
数据流的分类如图
图片附件: 游客没有浏览图片的权限,请 登录注册

InputStream和OutputStream:字节流。其它字节流都是InputStream或OutputStream的子类。
Reader和 Writer:字符流。其它字符流都是Reader或Writer的子类。

字节流
InputStream
InputStream有三个方法访问它的数据:
int read():简单读方法,返回一个int值,它是从流里读出的一个字节。如果遇到文件结束则返回-1。
int read(byte []):将数据读入到字节数组中,并返回所读的字节数。
int read(byte[], int offset,int length) 将数据读入到字节数组中,并返回所读的字节数。Offset是数组的偏移量,length是读取的长度。
void  close() 你完成流操作之后,就关闭这个流。如果你有一个流所组成的栈,使用过滤器流,就关闭栈顶部的流。这个关闭操作会关闭其余的流。
int available()
这个方法报告立刻可以从流中读取的字节数。在这个调用之后的实际读操作可能返回更多的字节数。
skip(long)这个方法丢弃了流中指定数目的字符。
boolean markSupported()
void mark(int)   
void reset()
如果流支持"回放"操作,则这些方法可以用来完成这个操作。如果mark()和reset()方法可以在特定的流上操作,则markSupported()方法将返回ture。mark(int)方法用来指明应当标记流的当前点和分配一个足够大的缓冲区,它最少可以容纳参数所指定数量的字节。在随后的read()操作完成之后,调用reset()方法来返回你标记的输入点。

OutputStream
void write(int)
void write(byte[])
void write(byte[], int, int)
这些方法写输出流。和输入一样,总是尝试以实际最大的块进行写操作。
void close()当你完成写操作后,就关闭输出流。如果你有一个流所组成的栈,就关闭栈顶部的流。这个关闭操作会关闭其余的流。
void flush()
有时一个输出流在积累了若干次之后才进行真正的写操作。flush()方法允许你强制执行写操作。

字符流
Reader
int read()
int read(char[])
int read(char[], int offset, int length)
简单读方法返回一个int值,它包含从流里读出的一个字符或者-1,其中-1表明文件结束。其它两种方法将数据读入到字符数组中,并返回所读的字符数。第三个方法中的两个int参数指定了所要填入的数组的子范围。
void close()
boolean ready()
void skip(long)
boolean markSupported()
void mark(int)   
void reset()
这些方法与InputStream中的对应方法相似

Writer
void write(int c)
void write(char [])
void write(char [], int offset, int length)
void write(String string)
void write(String string, int offset, int length)
void close()
void flush()
所有这些方法与OutputStream中的方法类似。

节点流
Java 2 SDK中有三种基本类型的节点:文件(file)、内存(memory)、管道(pipe)。如
图片附件: 游客没有浏览图片的权限,请 登录注册


过程流
过程流在其它流之上,完成排序、变换等操作。过程流也被称做过滤流。当你需要改变输入流的原始数据时,你可以将一个过滤输入流连接到一个原始的输入流上。用过滤流将原始数据变换成你需要的格式。
其分类如图
 
图片附件: 游客没有浏览图片的权限,请 登录注册

基本字节流类
分类如图
图片附件: 游客没有浏览图片的权限,请 登录注册

图片附件: 游客没有浏览图片的权限,请 登录注册


FileInputStream和FileOutputStream
这两个节点流用来操纵磁盘文件。这些类的构造函数允许你指定它们所连接的文件。要构造一个FileInputStream,所关联的文件必须存在而且是可读的。如果你要构造一个FileOutputStream而输出文件已经存在,则它将被覆盖。主要用于操作二进制或者带有格式的文件:如压缩文件,可执行文件等。
FileInputStream infile = new FileInputStream("myfile.dat");
FileOutputStream outfile = new FileOutputStream("results.dat");

BufferInputStream和BufferOutputStream
带有缓冲区的流,BufferInputStream一次可以读入一定长度的数据(默认2048字节),BufferOutputStream一次可以一定长度的数据(默认512字节),可以提高I/O操作的效率。需要和其它的流类配合使用。
BufferOutputStream在使用时,为了确保把数据写出去,建议最后执行flush()将缓冲区中的数据全部写出去。

PipedInputStream和PipedOutputStream
管道流用来在线程间进行通信。一个线程的PipedInputStream对象从另一个线程的PipedOutputStream对象读取输入。要使管道流有用,必须有一个输入方和一个输出方。

DataInputStream和DataOutputStream
用来对java的基本数据类型读写的类
DataInputStream方法
    byte readByte()
    long readLong()
    double readDouble()
    String readUTF(DataInput in)

DataOutputStream方法
    void writeByte(byte)
    void writeLong(long)
    void writeDouble(double)
    void writeUTF(String str)

PrintStream
可以自动进行字符转换的动作,默认会使用操作系统的编码处理对应的字符。
import *;

public class PrintStreamDemo {
    public static void main(String[] args) throws FileNotFoundException {
        PrintStream out = new PrintStream(new FileOutPutStream("1.txt"));
        out.println(1);
        out.close();
    }
}

基本字符流类
阐述了Reader和Writer字符流的体系结构。
图片附件: 游客没有浏览图片的权限,请 登录注册

图片附件: 游客没有浏览图片的权限,请 登录注册


InputStreamReader 和 OutputStreamWriter
用于字节流与字符流之间的转换接口。
当你构造一个InputStreamReader或OutputStreamWriter时,转换规则定义了16位Unicode和其它平台的特定表示之间的转换。
InputStreamReader从一个数据源读取字节,并自动将其转换成Unicode字符。如果你特别声明,InputStreamReade会将字节流转换成其它种类的字符流。
OutputStreamWriter将字符的Unicode编码写到输出流,如果你的使用的不是Unicode字符,OutputStreamWriter会将你的字符编码转换成Unicode编码。

BufferedReader和BufferedWriter
因为在各种格式之间进行转换和其它I/O操作很类似,所以在处理大块数据时效率最高。在InputStreamReader和OutputStreamWriter的结尾链接一个BufferedReader和BufferedWriter是一个好主意。记住对BufferedWriter使用flush()方法。

FileReader和FileWriter
以字符的方式操作文件的类,主要用于操作文本文件。
PrintWriter
与PrintStream相类似,使用println()输出内容。

URL输入流
除了基本的文件访问之外,Java技术提供了使用统一资源定位器(URL)来访问网络上的文件。当你使用Applet的getDocumentBase()方法来访问声音和图象时,你已经隐含地使用了URL对象。
    String imageFile = new String ("images/Duke/T1.gif");
    images[0] = getImage(getDocumentBase(), imageFile);
当然,你也可以直接使用URL如下:
imageSource;
try{
    imageSource = new URL("http://);
}catch(MalformedURLException e) {}
    images[0] = getImage(imageSource, "Duke/T1.gif");

使用RandomAccessFile随机访问文件
你经常会发现你只想读取文件的一部分数据,而不需要从头至尾读取整个文件。你可能想访问一个作为数据库的文本文件,此时你会移动到某一条记录并读取它的数据,接着移动到另一个记录,然后再到其他记录――每一条记录都位于文件的不同部分。Java编程语言提供了一个RandomAccessFile类来处理这种类型的输入输出。
创建一个随机访问文件
你可以用如下两种方法来打开一个随机存取文件:
用文件名
myRAFile = new RandomAccessFile(String name, String mode);
用文件对象
myRAFile = new RandomAccessFile(File file, String mode);
mode参数决定了你对这个文件的存取是只读(r)还是读/写(rw)。
例如,你可以打开一个数据库文件并准备更新:
RandomAccessFile myRAFile;
myRAFile = new RandomAccessFile("db/stock.dbf","rw");
存取信息
RandomAccessFile对象按照与数据输入输出对象相同的方式来读写信息。你可以访问在DataInputStrem和DataOutputStream中所有的read()和write()操作。
Java编程语言提供了若干种方法,用来帮助你在文件中移动。
long getFilePointer();返回文件指针的当前位置。
void seek(long pos);    设置文件指针到给定的绝对位置。这个位置是按照从文件开始的字节偏移量给出的。位置0标志文件的开始。
long length()返回文件的长度。位置length()标志文件的结束。

添加信息
你可以使用随机存取文件来得到文件输出的添加模式。
myRAFile = new RandomAccessFile("java.log","rw");
myRAFile.seek(myRAFile.length());
对象串行化
接口支持将一个Java技术对象存放到一个流中。
将一个对象存放到某种类型的永久存储器上称为"保持"。如果一个对象可以被存放到磁盘或磁带上,或者可以发送到另外一台机器并存放到存储器或磁
盘上,那么这个对象就被称为可保持的。
接口没有任何方法,它只作为一个"标记",用来表明实现了这个接口的类可以串行化。类中没有实现Serializable接口的对象不能被保持。
当一个对象被串行化时,只有对象的数据被保存;方法和构造函数不属于串行化流。如果一个数据变量是一个对象引用,那么这个对象的数据成员也会被串行化。树或者对象数据的结构,包括这些子对象,构成了对象图。
因为有些对象类所表示的数据在不断地改变,所以它们不会被串行化;例如,和java.lang.Thread等流。如果一个可串行化对象包含某个不可串行化元素的引用,那么整个串行化操作就会失败,而且会抛出一个NotSerializableException。
如果对象包含 一个不可串行化的引用,只要这个引用已经用transient关键字进行了标记,那么对象仍然可以被串行化。
public class MyClass implements Serializable {
    public transient Thread myThread;

    private String customerID;

    private int total;
}
域存取修饰符对于被串行化的对象没有任何作用。写入到流的数据是字节格式,而且字符串被表示为UTF(文件系统安全的通用字符集转换格式)。transient关键字防止对象被串行化。
public class MyClass implements Serializable {
    public transient Thread myThread;
    private transient String customerID;
    private int total;
}

实例分析
例1:从第一个命令行参数代表的文件中读字符,然后写入第二个参数代表的文件。
问题分析
本题中需要从文件读,写数据,需要使用到与文件有关的流FileReader/FileWriter。
可以通过运行时参数提供文件的名称。
使用带有Buffer功能的流
为了提高读写数据的效率,可以使用带有buffer功能的流完成文件读写,并且可以以行为单位读写数据。
使用类BufferedReader,BufferedWriter
I/O流的链
在程序中很少使用单独一个流对象,实际做法是将几个流对象串联起来处共同理数据。这样做会提高程序的效率。
数据源-> FileInputStream -> BufferedInputStream -> DataInputStream -> 程序
数据源<- DataOutputStream <- BufferedOutputStream <- FileOutputStream <-程序
编写代码
import *;
public class TestBufferedStreams {
    public static void main(String[] args) {
        try {
            FileReader input = new FileReader(args[0]);
            BufferedReader bufInput = new BufferedReader(input);
            FileWriter output = new FileWriter(args[1]);
            BufferedWriter bufOutput = new BufferedWriter(output);
            String line = bufInput.readLine();

            while (line != null) {
                bufOutput.write(line, 0, line.length());
                bufOutput.newLine();
                line = bufInput.readLine();
            }

            bufInput.close();
            bufOutput.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
编译运行
    javac TestBufferedStreams.java
    java TestBufferedStreams user.bat userbak.bat

例2:使用管道流完成线程之间的通讯。
1 问题分析
本题中需要一个线程向管道写入数据,另外一个线程从管道中读出数据,需要使用到与管道有关的流PipedInputStream和PipedOutputStream。
2 使用管道流
PipedInputStream和PipedOutputStream
管道流用来在线程间进行通信。一个线程的PipedInputStream对象从另一个线程的PipedOutputStream对象读取输入。要使管道流有用,必须有一个输入方和一个输出方。
    PipedInputStream  in=new PipedInputStream();
    PipedOutputStream out=new PipedOutputStream(in);
3 编写代码
import *;

public class TestPipeStream {
    public static void main(String[] args) {
        try {
            PipedInputStream in1 = new PipedInputStream();
            PipedOutputStream out1 = new PipedOutputStream(in1);

            PipedInputStream in2 = new PipedInputStream();
            PipedOutputStream out2 = new PipedOutputStream(in2);

            ThreadC tc = new ThreadC(out1);
            ThreadZ tz = new ThreadZ(out2, in1);
            ThreadQ tq = new ThreadQ(in2);
            tc.start();
            tz.start();
            tq.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static class ThreadC extends Thread {
        DataOutputStream dos = null;

        public ThreadC(OutputStream os) {
            dos = new DataOutputStream(os);
        }

        public void run() {
            while (true) {
                try {
                    double d = Math.random();
                    dos.writeDouble(d);
                    sleep(2);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    };

    static class ThreadZ extends Thread {
        DataOutputStream dos = null;

        DataInputStream dis = null;

        public ThreadZ(OutputStream os, InputStream is) {
            dos = new DataOutputStream(os);
            dis = new DataInputStream(is);
        }

        public void run() {
            while (true) {
                try {
                    double d = dis.readDouble();
                    dos.writeDouble(d);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    };

    static class ThreadQ extends Thread {
        DataInputStream dis = null;

        public ThreadQ(InputStream is) {
            dis = new DataInputStream(is);
        }

        public void run() {
            while (true) {
                try {
                    double d = dis.readDouble();
                    System.out.println(d);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    };
}
编译运行
            javac TestPipeStream.java
            java TestPipeStream

例3:保存所有的Person对象到文件并以对象的方式读出来
1 问题分析
本题中需要对文件读,写对象数据,需要使用到与对象有关的流ObjectInputStream/ObjectOutputStream。        
2 使用对象的读写流
ObjectOutputStream用于将一个对象输出,输出对象使用的方法为writeObject(Object obj)
ObjectInputStream用于读取一个对象,读取对象使用的方法为readObject()
注意:    被读写的对象必须是已序列化的类的对象,即要实现要Serializable接口。
3 编写代码
import *;
import java.util.*;

class Person implements Serializable {
    String name = null;

    public Person(String s) {
        name = s;
    }

    public String toString() {
        return name;
    }
}

public class TestObjectStream {
    public static void main(String[] args) {
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        try {
            File f = new File("date.ser");
            oos = new ObjectOutputStream(new FileOutputStream(f));
            oos.writeObject(new Person("andy"));
            oos.close();

            ois = new ObjectInputStream(new FileInputStream(f));
            Person d = (Person) ois.readObject();
            System.out.println(d);
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
4 编译运行
            javac TestObjectStream.java
            java TestObjectStream

例4:创建GZIP压缩格式文件
在JDK API中,定义了多种类型用于创建和解除GZIP压缩格式数据文件的通用对象和方法,用于基于JDK编写GZIP压缩数据管理程序。
由于在数据压缩过程中可以采用多种类型的压缩算法,因此,压缩文件的压缩比很高。在JDK API中,只定义了GZIPInputStream和GZIPOutputStream两种类型的流(Stream)对象,用于在基于流的数据传输过程中实现数据压缩。
以下程序完成压缩功能
package com.itjob;

import *;
import java.util.zip.*;

public class GZIPDemo {
    public static void main(String[] args) {
        if (args.length != 2) {
            System.out.println("Usage:java GZIPDemo SourceFile DestnFile"
                    + args.length);
            System.exit(1);
        }
        try {
            int number;
            // 建立需压缩文件的输入流
            FileInputStream fin = new FileInputStream(args[0]);
            // 建立压缩文件输出流
            FileOutputStream fout = new FileOutputStream(args[1]);
            // 建立GZIP压缩输出流
            GZIPOutputStream gzout = new GZIPOutputStream(fout);
            // 设定读入缓冲区尺寸
            byte[] buf = new byte[1024];
            while ((number = fin.read(buf)) != -1)
                gzout.write(buf, 0, number);
            gzout.close();
            fout.close();
            fin.close();
        } catch (IOException e) {
            System.out.println(e);
        }
    }
}
以下程序完成解压缩:
package com.itjob;

import *;
import java.util.zip.*;

public class UnGZIPDemo {
    public static void main(String[] args) {
        if (args.length != 2) {
            System.out.println("Usage:java UnGZIPDemo GZIPFile DestnFile");
            System.exit(1);
        }
        try {
            int number;
            // 建立GZIP压缩文件输入流
            FileInputStream fin = new FileInputStream(args[0]);
            // 建立GZIP解压工作流
            GZIPInputStream gzin = new GZIPInputStream(fin);
            // 建立解压文件输出流
            FileOutputStream fout = new FileOutputStream(args[1]);
            // 设定读入缓冲区尺寸
            byte[] buf = new byte[1024];
            while ((number = gzin.read(buf, 0, buf.length)) != -1)
                fout.write(buf, 0, number);
            gzin.close();
            fout.close();
            fin.close();
        } catch (IOException e) {
            System.out.println(e);
        }
    }
}
对象通过内存压缩,解压缩的例子:
package com.itjob;

import

public class Data implements Serializable {
    String name = "张三";
    int age = 12;
    float height = 1.83f;
}


package com.itjob;

import java.util.zip.*;
import *;

public final class CompressObject {
    // 将Data类型数据对象序列化对象压缩,返回字节数组,压缩后的对象数组可写入文件保存或用于网络传输
    public static byte[] writeCompressObject(Data object_) {
        byte[] data_ = null;
        try {
            // 建立字节数组输出流
            ByteArrayOutputStream o = new ByteArrayOutputStream();
            // 建立gzip压缩输出流
            GZIPOutputStream gzout = new GZIPOutputStream(o);
            // 建立对象序列化输出流
            ObjectOutputStream out = new ObjectOutputStream(gzout);
            out.writeObject(object_);            
            out.close();
            gzout.close();
            // 返回压缩字节流
            data_ = o.toByteArray();
            o.close();
        } catch (IOException e) {
            System.out.println(e);
        }
        return (data_);
    }

    // 将压缩字节数组还原为Data类型数据对象
    public static Data readCompressObject(byte[] data_) {
        Data object_ = null;
        try {
            // 建立字节数组输入流
            ByteArrayInputStream i = new ByteArrayInputStream(data_);
            // 建立gzip解压输入流
            GZIPInputStream gzin = new GZIPInputStream(i);
            // 建立对象序列化输入流
            ObjectInputStream in = new ObjectInputStream(gzin);
            // 按制定类型还原对象
            object_ = (Data) in.readObject();
            i.close();
            gzin.close();
            in.close();
        } catch (ClassNotFoundException e) {
            System.out.println(e);
        } catch (IOException e) {
            System.out.println(e);
        }
        return (object_);
    }
}

package com.itjob;

import *;
import java.util.zip.*;

public class Test {
    public static void main(String[] args) {
        Data testData_ = new Data();
        // 未压缩数据对象内容
        System.out.println("name=" + testData_.name + " age=" + testData_.age
                + " height=" + testData_.height);
        // 压缩
        byte[] i_ = CompressObject.writeCompressObject(testData_);

        /*
         * 可执行保存或网络传输,需要时还原或在对端还原
         */

        // 解压缩
        Data o_ = CompressObject.readCompressObject(i_);
        // 解压缩后对象内容
        System.out.println("name=" + o_.name + " age=" + o_.age + " height="
                + o_.height);
    }
}


[ 本帖最后由 lampeter123 于 2010-7-21 09:01 编辑 ]

你的优秀和我的人生无关!!!!
    
    我要过的,是属于我自己的生活~~~
2010-07-21 08:51
lampeter123
Rank: 16Rank: 16Rank: 16Rank: 16
等 级:版主
威 望:54
帖 子:2508
专家分:6424
注 册:2009-1-30
收藏
得分:0 
网络

TCP/IP协议模型
计算机网络的组成部分:计算机系统、数据通信系统、网络软件。其中,网络软件充当网络管理者的角色,它提供并实现各种网络服务,包括网络协议软件(TCP/IP、IPX等协议驱动)、网络服务软件、网络工具软件、网络操作系统(Netware、NT局域网系统)。
网络通信协议
网络协议是构成网络的基本组件之一,协议是若干规则和协定的组合,一般指机器1的第n层与机器2的第n层的对话,这种对话中所使用的若干规则和约束便称为第n层网络协议。TCP/IP网络体系结构模型就是遵循TCP/IP协议进行通信的一种分层体系,现今,Internet和Intranet所使用的协议一般都为TCP/IP协议。
TCP/IP协议是一个工业标准协议套件,专为跨大广域网(WAN)的大型互联网络而设计。在了解该协议之前,我们必须掌握基于该协议的体系结构层次,而TCP/IP体系结构分为四层,具体结构如下图:

图片附件: 游客没有浏览图片的权限,请 登录注册


可以看出,TCP/IP体系模型分为4层结构,其中有3层对应于ISO参考模型中的相应层。这4层概述如下:
第一层 网络接口层
包括用于协作IP数据在已有网络介质上传输的协议,提供TCP/IP协议的数据结构和实际物理硬件之间的接口。比如地址解析协议(Address Resolution Protocol, ARP )等。
第二层 网络层
对应于ISO模型的网络层,主要包含了IP、RIP等相关协议,负责数据的打包、寻址及路由。还包括网间控制报文协议(ICMP)来提供网络诊断信息。
第三层 传输层
对应于ISO的传输层,提供了两种端到端的通信服务,分别是TCP和UDP协议。
第四层 应用层
对应于ISO的应用层和表达层,提供了网络与应用之间的对话接口。包含了各种网络应用层协议,比如Http、FTP等应用协议。
TCP/IP体系模型相对于ISO模型的7层结构来说更简单更实用!现已成为因特网之间的标准协议模型。
TCP/IP网络体系主要包含两种协议:TCP/IP、UDP协议。其中,IP(Internet Protocol)协议是一种低级路由协议,该协议主要实现将传输数据分解成许多小数据包,接着通过网络将这些数据包传到一个指定地址,但是,请注意,IP协议并不会保证传输的数据包一定到达目的地,或者是数据包的完整性!
TCP(Thransfer Control Protocol)协议正好弥补了IP协议的不足,属于一种较高级的协议,它实现了数据包的有力捆绑,通过排序和重传来确保数据传输的可靠(即数据的准确传输以及完整性)。排序可以保证数据的读取是按照正确的格式进行,重传则保证了数据能够准确传送到目的地!
UDP协议与TCP协议类似,它们之间的区别在于TCP协议是面向连接的可靠数据传输协议,而UDP协议是面向数据报的不可靠数据传输协议;UDP协议可以要求数据传输的目的地可以没有连接甚至不存在,数据传输效率更快,但可靠性低,TCP正好相反。
注意,TCP与UDP协议均属于传输层协议,而IP协议属于网络层协议。
应用层各种协议提供了应用程序访问其他层的服务,并定义应用程序用于交换数据的协议。以下应用协议是广泛被使用的交换用户信息的协议:
    超文本传输协议(HTTP): 用于传输组成万维网Web页面的文件,大部分Web项目都是基于该协议实现用户数据的传输。
    文件传输协议(FTP): 交互式文件传输
    简单邮件传输协议(SMTP): 用于传输邮件消息和连接
    终端访问协议(Telnet): 远程登录到网络主机
    域名系统(DNS)
    路由选择信息协议(RIP)

通信端口
每一个应用协议都有其特定的端口,通过端口实现服务器同时能够服务于很多不同的客户端,服务器进程通过监听端口来捕获客户端连接。一个服务器允许在同个端口接受多个客户,一个服务器也能开启多个端口接受客户请求。能够接受并管理多个客户连接的服务器进程必须是支持多线程的(或者采用同步输入/输出处理多路复用技术的其他方法)。
每一个端口都有特定的端口号,TCP/IP系统中的端口号是一个16位的数字,它的范围是0~65535。同时该号一般对应一些特定协议,TCP/IP为特定协议保留了低端的1024个端口,其中,端口80是HTTP协议的,21是FTP协议的,23是Telnet协议的等等,客户和服务器必须事先约定所使用的端口。如果系统两部分所使用的端口不一致,那就不能进行通信。
HTTP是网络浏览器及服务器用来传输超文本网页和图像的协议,一般的Web服务器也提供了HTTP服务器的功能,当客户端向HTTP服务器请求一个资源时,HTTP协议会将相关数据以特定格式传给其缺省端口80,服务器在该端口接受请求并将处理结果返回给客户。可以这样理解,端口是基于某种特定协议的一个点(一般是服务器)与另一个点之间的对话窗口,在通话的过程中两点必须在同一窗口才能实现数据对话。

 
基于Java的网络技术
TCP/IP套接字
    套接字是网络软件中的一个抽象概念,套接字允许单个计算机同时服务于很多不同客户,并能够提供不同类型信息的服务。该技术由引入的端口处理,该端口既是一个特定机器上的一个被编号的套接字---通信端口.TCP/IP套接字用于在主机和Internet之间建立的可靠、双向、点对点、持续的流式连接。
在java中,TCP/IP Socket连接是用包中的类实现的,这些类实现了建立网络连接和通过连接发送数据的复杂过程,我们只需使用其简单接口就能实现网络通信!在java中有两类套接字,一种是服务器端套接字,另一种是客户端套接字

ServerSocket
其中,ServerSocket被设计成在等待客户建立连接之前不做任何事情的监听器,构造方法的版本如下:
public ServerSocekt(int port) throws IOException
--在服务器指定端口port创建队列长度为50的服务器套接字,当port为0则代表创建一个基于任意可用端口的服务器套接字。队列长度告诉系统多少与之连接的客户在系统拒绝连接之前可以挂起。
public ServerSocekt(int port, int maxQueue) throws IOException
--在指定端口创建指定队列长度的服务器套接字
public ServerSocket(int port, int maxQueue, InetAddress bindAddr ) throws IOException
在多地址主机上,我们除了可以指定端口之外,还可以通过InetAddress类来指定该套接字约束的IP地址。InetAddress在后面将学习。
ServerSocket还定义了以下一些常用的方法:
public Socket accept() throws IOException
--该方法用于告诉服务器不停地等待,直到有客户端连接到该ServerSocket指定的端口,一旦有客户端通过网络向该端口发送正确的连接请求,该方法就会返回一个表示服务器与客户端连接已建立的Socket对象,接下来我们就可以通过这个返回的Socket对象实现服务器与指定客户端的通信。注意:accept()方法会让服务器中的当前线程暂停下来,直到有客户端的正确连接发送过来。
public void bind(SocketAddress endpoint) throws IOException
--绑定该ServerSocket到指定的endpoint地址(IP地址和端口)
public void close() throws IOException
--关闭当前ServerSocket。任何当前被锁定的线程将在accept()方法中抛出IOException。
从jdk1.4开始,java提供了关于ServerSocket的ServerSocketChannel,jdk建议用管道来实现客户端连接的监听以及关闭服务器套接字会更安全,因此,现在我们应该通过ServerSocket来得到其套接字管道,通过管道来实现服务监听以及关闭,可以通过ServerSocket的getChannel()方法来得到当前ServerSocket的相关管道。

Socket
该类为建立连向服务器套接字及启动协议交换而设计,当进程通过网络进行通信的时候,java技术使用流模型来实现数据的通信。一个Socket包括两个流,分别为一个输入流和一个输出流,一个进程如果要通过网络向另一个进程发送数据,只需简单地写入与Socket相关联的输出流,同样,一个进程通过从与Socket相关联的输入流来读取另一个进程所写的数据。如果通过TCP/IP协议建立连接,则服务器必须运行一个单独的进程来等待连接,而某一客户机必须试图到达服务器,就好比某人打电话,必须保证另一方等待电话呼叫,这样才能实现两人之间的通信。
分析以下代码:
    import *;
    import *;
    ......
    try{
        // 在服务器的8000端口创建一个ServerSocket
        ServerSocket server = new ServerSocket(8000);
        
// accept()方法使当前服务器线程暂停,之前创建的server套接字
//将通过该方法不停的监听指定端口是否有客户端请求连接发送过来,
//如果有正确连接发送过来,
        // 则该方法返回一个表示连接已建立的Socket对象
        Scoket fromSocket = server.accept();
   
    if (fromSocket != null){
        // 通过套接字实现数据传输,得到套接字的输入流输出流
        InputStream input = fromSocket.getInputStream();
        OutputStream out = fromSocket.getOutputStream();   
    }
      }catch(IOException e){
        e.printStackTrace();
      }
     ......
通过上面的代码,我们分析得知,套接字只是实现数据传输的接口,真正实现数据传输的是封装在套接字中的输入、输出流。以上代码是服务器端程序,该程序开启了一个端口号为8000的特定端口,并且开启了服务器套接字在该端口上监听客户请求,accept()方法阻塞当前线程直到有客户端请求发送过来,当请求正确时,该方法将客户端套接字引用传递出来,这样,网络之间的数据发送就好像是本地数据调用。
Socket类的相关方法:
Socket()创建一个无参数套接字,该套接字会随即取一个可用端口、可用IP来建立连接。
Socket(String host, int port)创建一个指定远程服务器、端口号的套接字
Socket(InetAddress net, int port)创建一个指定InetAddress类封装IP、指定端口号的套接字。即该套接字只能往指定IP的服务器以及服务器指定端口发送数据。
public OutputStream getOutputStream() throws IOException 得到套接字的输出流,接下来就可以使用得到的输出流去写数据至服务器或客户端
public InputStream getInputStream() throws IOException得到套接字的输入流,接下来就可以使用得到的输入流去读取来自于服务器或客户端的数据。
public SocketChannel getChannel()得到套接字的管道,在1.4之后新增了io功能,在java.nio包中定义,通过流中的SocketChannel来实现数据的读取会比直接使用流来读取更安全可靠。(扩展内容)
public void close() throws IOException关闭当前套接字,注意,关闭套接字的同时也会关闭该套接字中的所有流。
下面是客户端的部分代码:
          import *;
          import *;
      ......
          try{
            // 创建套接字,该套接字连接IP地址为192.168.0.2的服务器,端口为8000            Socket server = new Socket("192.168.0.2",8000);

            if(server != null){
                // 通过套接字实现数据传输输入流               
                InputStream input = fromSocket.getInputStream();        
                OutputStream out = fromSocket.getOutputStream();
            }
          }catch(IOException e){
             e.printStackTrace();
          }
以上即是简单的基于Tcp/IP协议、使用套接字实现数据传输的服务器和客户端代码(注意:在运行时,先执行服务器端代码,接着执行客户端代码),但是我们发现,通过以上代码,客户端和服务器端只能进行一次数据对话,通过更改以上代码,我们才能实现一个客户端与服务器端的真正对话,直到某一方终止通话为止,修改后代码如下:
服务器端:
......
    ServerSocket server = new ServerSocket(8000);
    while (true){        
        Socket fromSocket = server.accept();
        if (fromSocket != null){            
            InputStream input = fromSocket.getInputStream();
            OutputStream out = fromSocket.getOutputStream();            
            .....
        }
    }
    .....

客户端:
      ......
      Socket server = new Socket("192.168.0.2",8000);
      InputStream input = fromSocket.getInputStream();
      OutputStream out = fromSocket.getOutputStream();

      while (true){
        // 数据的读写操作
        ......
      }
      ......

实际上,我们只要将服务器端每一个客户的请求套接字的获取以及流的获取、数据的读写放入至一个无限循环,这样就可以保证服务器可以随时接受任意客户发送过来的套接字,从而实现对话。客户端也可以通过一个无限循环实现对服务器端的任意时刻的数据传输。
当然,这只是实现了一个客户和服务器之间的对话,但是服务器一般是对应许多客户的,因此,为了实现服务器对应多个客户,必须将每一次accept()方法返回的每一个客户套接字保存起来,这样才可以实现服务器与多个客户对应,具体代码在后面讨论。
注意,数据的读写应该是在一个独立于主线程的单独线程中运行,因为accept方法会阻塞当前线程,为了不影响主线程的其余功能,我们应该启用多线程来实现高效的数据传输。
当通过代码实现服务器与客户之间的套接字发送之后,我们就可以使用流的IO操作来实现服务器与客户端之间的基于特定协议的远程数据传输。注意,基于TCP/IP协议的数据传输,前提是必须得保证接受数据的一方是连通的,也就是说,如果服务器端没有运行,那么客户端是无法对其发送信息,反之亦然。那有时候我们只在乎信息的发送,并不理会接受的人是否连通,甚至不理会接受人是否存在,那就不能使用TCP/IP协议,而得通过我们下面所学的UDP协议来实现。
UDP套接字
UDP(User Datagrams Protocol)用户数据报协议,是一种使用数据报的机制来传递信息的协议。数据报(Datagrams)是一种在不同机器之间传递的信息包,该信息包一旦从某一机器被发送给指定目标,那么该发送过程并不会保证数据一定到达目的地,甚至不保证目的地的存在真实性。反之,数据报被接受时,不保证数据没有受损,也不保证发送该数据报的机器仍在等待响应。
由此可见,UDP协议是一种基于数据报的快速的(因为它无需花时间去保证数据是否损坏,无需花时间确定接受方是否存在并等待响应)、无连接的、不可靠的信息包传输协议。
在java中,通过两个特定类来实现UDP协议顶层数据报,分别是DatagramPacket和DatagramSocket,其中DatagramPacket是一个数据容器,用来保存即将要传输的数据;而DatagramSocket实现了发送和接收DatagramPacket的机制,即实现了数据报的通信方式。 1 、atagramPacket
该类主要有四个常用构造方法,分别如下:
DatagramPacket(byte[] buff, int length)
DatagramPacket(byte[] buf, int offset, int length)
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)
第一个构造方法用于创建一个指定数据缓冲区大小和信息包的容量大小的DatagramPacket,第二个构造方法用于创建一个长度大小为length的缓冲区,并指定数据存储(读取)的偏移地址为offset的DatagramPacket。第三个创建一个指定缓冲区大小、传送(接受)IP地址、端口号的DatagramPacket。一般情况下,发送地址是由DatagramSocket指定。
常用的几个方法如下:
byte[] getData()
用于得到发送过来的DatagramPacket中的数据
void setData(byte[] buf)
用于将buf中的数据写入DatagramPacket中,以备发送。
InetAddress getAddress()
返回目标的InetAddress,一般用于发送。

DatagramSocket
DatagramSocket()
创建一个以当前计算机的任意可用端口为发送端口的数据报连接
DatagramSocket(int port)
创建一个以当前计算机的port端口为发送端口的数据报连接
DatagramScoket(int port, InetAddress address)
创建一个以当前计算机的port端口为发送端口,向IP地址为address的计算机发送数据报连接

常用的几个方法:
void close() throws IOException
关闭数据报连接
void recieve(DatagramPacket packet)
接收来自于packet数据报的信息
void send(DatagramPacket packet)
发送packet数据报
void connect(InetAddress address, int port)
以当前计算机指定端口port为发送端口,向IP地址为address的计算机发送数据报连接
void disconnect()
断开连接
DatagramChannel getChannel()
和SocketChannel类似
结合以上两个类的方法,创建一个简单的UDP服务器端如下:
      ....
      // 指定连接端口
      DatagramSocket socket = new DatagramSocket(8001)
      byte[] buff = new byte[256];
      // 指定接受数据报缓冲区大小为字节数组buff大小
      DatagramPacket fromPacket = new DatagramPacket(buff,buf.length);
      // 接受客户端请求,并将请求数据存储在数据报packet中
      packet.recieve(fromPacket);
      // 读取数据报中数据
      byte rec = packet.getData();
      .....
   
      buff = "hello world".toBytes();
      InetAddress addr = .....
      // 指定发送数据报缓冲区大小为字节数组buff大小,
//发送目的地为addr,端口为8001,数据报内容为数组buff内容
    DatagramPacket toPacket = new DatagramPacket(buff,buf.length,addr,8001);
      // 发送服务器端数据报toPacket
      packet.send(toPacket);
      .......
   
客户端代码与服务器段类似,只要注意接受与发送应该分别是两个不同的数据报。
 
InetAddress类的使用
类是java的IP地址封装类,内部隐藏了IP地址,可以通过它很容易的使用主机名以及IP地址。一般共各种网络类使用。直接由Object类派生并实现了序列化接口。该类用两个字段表示一个地址:hostName与address。hostName包含主机名,address包含IP地址。InetAddress支持ipv4与ipv6地址。
一些常用方法如下:
byte[] getAddress()
返回指定对象的IP地址的以网络字节为顺序的4个元素的字节数组
static InetAddress getByName(String hostname)
使用DNS查找hostname的IP地址,并返回
static InetAddress getLocalHost()
返回本地计算机的InetAddress。
String getHostName()
返回指定InetAddress对象的主机名。
String getHostAddress()
返回指定InetAddress对象的主机地址的字符串形式
分析:
InetAddress addr = InetAddress.getByName("java.);
System.out.println(addr);
以上代码将打印网址域名为java.的对应IP地址
因此,在网络编程中,我们可以很方便的使用InetAddress类实现Ip地址的各种操作。
例题1:服务器端:
package com.

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import *;
import javax.swing.border.*;
import *;

public class ServerApp extends JFrame implements Runnable, ActionListener {
    JPanel mainPanel;
    JPanel bottomPanel;
    GridBagLayout gbl;
    GridBagConstraints gbc;
    Border border;
    JTextArea txtChatMess;
    JScrollPane scroll;
    JTextField txtMess;
    JLabel lblEnterMess;
    JButton cmdSend;
    JButton cmdReset;

    ServerSocket server = null;
    Socket socket = null;
    Scanner read = null;

    public ServerApp() {
        super("服务器");
        mainPanel = new JPanel();
        bottomPanel = new JPanel();
        mainPanel.setLayout(new BorderLayout());
        mainPanel.add(bottomPanel, BorderLayout.SOUTH);
        gbl = new GridBagLayout();
        gbc = new GridBagConstraints();
        bottomPanel.setLayout(gbl);

        txtChatMess = new JTextArea(15, 20);
        txtChatMess.setEditable(false);
        scroll = new JScrollPane(txtChatMess);
        mainPanel.add(scroll);

        border = BorderFactory.createRaisedBevelBorder();
        txtMess = new JTextField(15);
        lblEnterMess = new JLabel("请输入消息: ");
        cmdSend = new JButton("发 送");
        cmdSend.setPreferredSize(new Dimension(50, 20));
        cmdSend.setEnabled(false);
        cmdSend.addActionListener(this);
        cmdReset = new JButton("清 空");
        cmdReset.setPreferredSize(new Dimension(50, 20));
        cmdReset.addActionListener(this);
        cmdSend.setBorder(border);
        cmdReset.setBorder(border);

        gbc.gridx = 3;
        gbc.gridy = 10;
        gbl.setConstraints(lblEnterMess, gbc);
        bottomPanel.add(lblEnterMess);

        gbc.gridx = 10;
        //gbc.fill = gbc.BOTH;
        gbl.setConstraints(txtMess, gbc);
        bottomPanel.add(txtMess);

        gbc.gridx = 3;
        gbc.gridy = 30;
        gbl.setConstraints(cmdSend, gbc);
        bottomPanel.add(cmdSend);

        gbc.gridx = 10;
        gbl.setConstraints(cmdReset, gbc);
        bottomPanel.add(cmdReset);

        getContentPane().add(mainPanel);
        pack();
        setVisible(true);

    }

    public void actionPerformed(ActionEvent evt) {
        if (evt.getSource() == cmdSend) {
            try {
                write.println(txtMess.getText());
                write.flush();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        if (evt.getSource() == cmdReset) {
            txtMess.setText("");
        }

    }

    public void run() {
        try {
            server = new ServerSocket(2005);
            //反复接收客户端请求
            while (true) {

                socket = server.accept();

                if (socket != null) {
                    txtChatMess.append("服务器消息:客户已连接!" + "\n");
                    cmdSend.setEnabled(true);
                } else {
                    txtChatMess.append("服务器消息:客户未能连接!" + "\n");
                    cmdSend.setEnabled(false);
                }

                //为每一个客户启动一个读取数据的单独线程
                Connections con = new Connections(socket);
                Thread thread = new Thread(con);
                thread.start();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    class Connections implements Runnable {
        Socket clientSock = null;

        Connections(Socket s) {
            clientSock = s;
        }

        public void run()    {
                    try    {
                        read = new Scanner(socket.getInputStream()));
                        write = new
                         PrintWriter(socket.getOutputStream());
                        
                        String result = read.readLine();
                        if (result == null){
                            return;
                        }
                        
                        while(!result.trim().equals("Exit!")) {
                            txtChatMess.append("客户端消息: " + result + "\n");
                            if (read.hasNextLine()){
                                result = read.nextLine();
                            }else{
                                Thread.sleep(100);
                            }
                           
                        }
                    }catch(Exception e){
                        e.printStackTrace();
                    }finally{
                        try {
                            read.close();
                            write.close();
                            socket.close();
                            server.close();
                            cmdSend.setEnabled(false);
                        }catch(Exception e){
                        }   
                    }
                }
    }

    public static void main(String[] args) {
        ServerApp serverApp = new ServerApp();
        Thread thread = new Thread(serverApp);
        thread.start();
    }

}

客户端:
package com.

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import *;
import javax.swing.border.*;
import *;

public class ClientApp extends JFrame implements Runnable, ActionListener {
    JPanel mainPanel;
    JPanel bottomPanel;
    GridBagLayout gbl;
    GridBagConstraints gbc;
    Border border;
    JTextArea txtChatMess;
    JScrollPane scroll;
    JTextField txtMess;
    JLabel lblEnterMess;
    JButton cmdSend;
    JButton cmdReset;

    ServerSocket server = null;
    Socket socket = null;
    Scanner read = null;
    PrintWriter write = null;

    public ClientApp() {
        super("客户端");
        mainPanel = new JPanel();
        bottomPanel = new JPanel();
        mainPanel.setLayout(new BorderLayout());
        mainPanel.add(bottomPanel, BorderLayout.SOUTH);
        gbl = new GridBagLayout();
        gbc = new GridBagConstraints();
        bottomPanel.setLayout(gbl);
        //new GBC();

        txtChatMess = new JTextArea(15, 20);
        txtChatMess.setEditable(false);
        scroll = new JScrollPane(txtChatMess);
        mainPanel.add(scroll);

        border = BorderFactory.createRaisedBevelBorder();
        txtMess = new JTextField(15);
        lblEnterMess = new JLabel("请输入消息: ");
        cmdSend = new JButton("发 送");
        cmdSend.setPreferredSize(new Dimension(50, 20));
        cmdSend.setEnabled(false);
        cmdSend.addActionListener(this);
        cmdReset = new JButton("清 空");
        cmdReset.setPreferredSize(new Dimension(50, 20));
        cmdReset.addActionListener(this);
        cmdSend.setBorder(border);
        cmdReset.setBorder(border);

        gbc.gridx = 3;
        gbc.gridy = 10;
        gbl.setConstraints(lblEnterMess, gbc);
        bottomPanel.add(lblEnterMess);

        gbc.gridx = 10;
        //gbc.fill = gbc.BOTH;
        gbl.setConstraints(txtMess, gbc);
        bottomPanel.add(txtMess);

        gbc.gridx = 3;
        gbc.gridy = 30;
        gbl.setConstraints(cmdSend, gbc);
        bottomPanel.add(cmdSend);

        gbc.gridx = 10;
        gbl.setConstraints(cmdReset, gbc);
        bottomPanel.add(cmdReset);

        getContentPane().add(mainPanel);
        pack();
        setVisible(true);

    }

    public void actionPerformed(ActionEvent evt) {
        if (evt.getSource() == cmdSend) {
            try {
                write.println(txtMess.getText());
                write.flush();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        if (evt.getSource() == cmdReset) {
            txtMess.setText("");
        }

    }

    public void run() {
        try {
            InetAddress add = InetAddress.getLocalHost();
            socket = new Socket(add, 2005);
            read = new Scanner(socket.getInputStream());
            write = new PrintWriter(socket.getOutputStream());

            if (socket != null) {
                txtChatMess.append("客户消息:服务器已连接!" + "\n");
                cmdSend.setEnabled(true);
            } else {
                txtChatMess.append("客户消息:服务器未能连接!" + "\n");
                cmdSend.setEnabled(false);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        Connections con = new Connections();
        Thread thread = new Thread(con);
        thread.start();
    }

    class Connections implements Runnable {
        public void run() {
            try {
                String result = read.readLine();
                if (result == null) {
                    return;
                }

                while (!result.trim().equals("Exit!")) {

                    txtChatMess.append("服务器端消息: " + result + "\n");
                    if (read.hasNextLine()) {
                        result = read.nextLine();
                    } else {
                        Thread.sleep(100);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    read.close();
                    write.close();
                    socket.close();
                    server.close();
                    //cmdSend.setEnabled(false);
                } catch (Exception e) {
                }
            }
        }
    }

    public static void main(String[] args) {
        ClientApp client = new ClientApp();
        Thread thread = new Thread(client);
        thread.start();
    }

}
注意:以上代码是基于客户端与服务器在同一台服务器上的,而且服务器不能实现对应多个客户,请大家思考并修改,从而实现一个服务器对应多个客户端。

扩展知识:
套接字超时
在使用套接字的时候,读取数据之前当前线程会被阻塞,直到数据的到达为止,在这个过程中间可能由于被访问的主机不可达,从而使你的应用将等待很长的时间,最终受操作系统的底层影响而导致超时产生。也就是说,虽然最终会导致超时的产生,但我们在设计一个套接字的时候应该给定一个合理的超时时间,从而保证在读、写操作未完成之前可以有一个限定时间,当超出该限定时间时操作仍未完成,那么套接字就会抛出SocketTimeoutException异常,这样,我们就可以捕获该异常并编写超时后的程序逻辑。这样,通过设置套接字的超时时间(对套接字内所有的操作而言)就可以保证程序因为某些原因而导致程序长时间的无作用等待。
通过调用套接字的setSoTimeout(int milliseconds)方法来设置套接字的超时时间,比如:
    Socket s  = new Socket(...);
    s.setSoTimeout(10000);
上面方法将设置套接字的超时时间为10秒,当10秒内数据仍未读取时,套接字将抛出异常并停止操作。注意,setSoTimeout方法会抛出SocketTimeoutException异常,并且实现了线程同步。
在上面我们所说的是当操作被阻塞的情况下,还有一种情况也会导致程序长期阻塞直至建立服务器的连接为止。回顾一下套接字的构造方法,其中一构造方法如下:Socket(String host, int port),如果该构造方法在连接制定的host由于某些特殊原因(网络)导致阻塞,那么该套接字将无限阻塞,直到建立和主机的连接为止。为了避免这种可能,我们还应该设置套接字的连接超时,当在指定时间内仍不能建立和主机的连接,则抛出异常并终止操作。解决步骤如下:
    //先建立一个无连接套接字
    Socket s = new Socket();
    //在调用connect方法去指定套接字连接主机时给定连接超时参数,这里超时时间设置为10秒
    s.connect(new InetSocketAddress(host,port),10000);
    (InetSocketAddress类是一个专门封装套接字地址的特定类,可以通过该类将一个Scoket与指定主机及端口连接)
SocketChannel类
    不知大家有没有发现,虽然给线程设置中断标志可以终止一个被阻塞线程,但是当线程是被套接字等网络因素而阻塞的情况下,设置中断标志是无法终止阻塞的。在1.4之后提供的java.nio.channels包中SocketChannel类则很好的解决了这个问题,即如果我们是通过该类来创建一个套接字连接,那么就可以使用线程的中断标志来终止被阻塞的线程,具体操作如下:
//首先,我们应该获取套接字连接对象,通过SocketChannel类的静态方法open可以得到一个指定主机连接对象
SocketChannel channel = SocketChannel.open(new InetSocketAddress("192.168.0.1",8001));
//对SocketChannel的对象进行的操作和对Socket的操作类似,可以通过Channels类的静态方法newOutputStream()于newInputStream()来分别得到通道中的输出和输入流
OutputStream out = Channels.newOutputStream(channel);
InputStream input = Channels.newInputStream(channel);
//或者通过Scanner来得到输入流
Scanner scan = new Scanner(channel);
注意,通过Scanner来得到输入流于通过Channels.newInputStream(channel)得到的输入流有所不同,实际上channel并没有与之关联的流,其所拥有的read和write方法都是通过Buffer对象来实现的。也就是说,Channels类的newInputStream方法得到的流实际上是通过Buffer来读取数据,而Scanner则是直接去读数据而不使用缓存。因此,读取大数据时使用缓冲可能更高效,否则反之。
SocketChannel实现了两个接口:ReadableByteChannel和WritableByteChannel,分别实现了read与write方法,这两个方法均接收ByteBuffer(java.nio)对象并对其操作,即使用SocketChannel的同时我们需结合nio包中的字节缓冲对象共同实现数据的读写操作。在使用SocketChannel时,假设线程正在执行打开、读取、写入操作,此时如果对线程进行中断,那么这些操作不会导致线程阻塞而是以抛出异常的方式结束。
收到的鲜花
  • gameohyes2010-07-21 19:53 送鲜花  49朵   附言:好文章.辛苦了!!!

你的优秀和我的人生无关!!!!
    
    我要过的,是属于我自己的生活~~~
2010-07-21 09:03
gameohyes
Rank: 16Rank: 16Rank: 16Rank: 16
来 自:湖南
等 级:版主
威 望:53
帖 子:1275
专家分:3629
注 册:2009-3-5
收藏
得分:0 
支持...

C#超级群 74862681,欢迎大家的到来!
2010-07-21 19:55
liuya0001
Rank: 1
等 级:新手上路
帖 子:6
专家分:0
注 册:2010-8-6
收藏
得分:0 
perfect
2010-08-07 06:41
qq1023569223
Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19
来 自:湖南科技大学
等 级:贵宾
威 望:26
帖 子:2753
专家分:13404
注 册:2010-12-22
收藏
得分:0 
不是一个一个字地打上去的吧,

   唯实惟新 至诚致志
2011-01-08 22:55
笨笨dě活着
Rank: 1
等 级:新手上路
帖 子:1
专家分:0
注 册:2012-12-20
收藏
得分:0 
学习了
2013-07-08 17:46
tsy1985
Rank: 1
等 级:新手上路
帖 子:1
专家分:0
注 册:2013-9-23
收藏
得分:0 
支持,顶
2013-09-23 16:25
快速回复:JAVA入门教程(2)
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.052039 second(s), 11 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved