1. Java API String类
1.1. 什么是API
API全名:Application Programming Interface,API是应用程序编程接口,指一些预先定义好的类。
例如我们想要一台电脑,并不需要自己生产每个零件,只要从各个厂商买到组装电脑的零件就可以,然后根据说明书学会使用,将零件安装在一起就得到了电脑。电脑就像是我们要的程序,而零件就是API,说明书就是帮助文档。
1.2. Java API
Java API就是Sun公司提供给我们使用的类,这些类将底层的实现封装了起来,我们不需要关心这些类是如何实现的,只需要学习这些类如何使用。
我们可以通过查帮助文档来了解Java提供的API如何使用
1.3. Java中常用API
String类
对字符串进行操作通常我们使用String类,相关的还有StringBuffer和StringBuilder
集合类
集合是一种容器,用来存取对象(Collection、Map)
包装类
Java定义了一组包装类对基本数据类型进行了包装(Integer、Double、Boolean)
时间对象
Java定义了一些类方便用户对时间、日期进行处理(Date、Calendar)
系统类
Java定义了一些类针对系统进行操作(System、Runtime)
IO流
Java定义了一些类对数据传输进行了封装(输入输出流、File文件对象)
Socket
Java定义了一些类方便用户进行网络编程(Socket、DatagramSocket)
1.4. String对象的存储
字符串是常量,一旦创建不能被修改。
字符串在程序中经常使用,虚拟机会将其缓存在String池中。
了解 String s = “abc” 和 String s = new String(“abc”) 的区别。
1.5. String类的构造函数
String(byte[] bytes)
通过指定字节数组构建字符串。
String(byte[] bytes, int offset, int length)
通过指定字节数组、数组元素偏移量和元素个数构建字符串。
String(byte[] bytes, String charsetName)
通过指定字节数组和指定码表构建字符串。
String(byte[] bytes, int offset, int length, String charsetName)
通过指定字节数组、数组元素偏移量、元素个数和指定码表构建字符串。
String(char[] value)
通过指定字符数组构建字符串。
String(char[] value, int offset, int count)
通过指定字符数组、数组元素偏移量和元素个数构建字符串。
String(StringBuffer buffer)
通过指定StringBuffer构建字符串。
String(StringBuilder builder)
通过指定StringBuffer构建字符串。
1.6. String类的常用方法
char charAt(int index)
查找指定位置的字符
int indexOf(String str)
判断字符串出现的位置
int compareTo(String anotherString)
按字典顺序比较两个字符串
String substring(int beginIndex, int endIndex)
截取子字符串
String[] split(String regex)
字符分割
String replace(CharSequence target, CharSequence replacement)
替换字符串
Ø 字符串练习
设计一个方法, 获取一个已知文件名的扩展名.
Person.java的扩展名是.java,Person.java.txt的扩展名是.txt
设计一个方法, 查找一个字符串中子字符串出现的所有位置.
“xxxabcxxxabcxxx”中abc出现了2次,索引位置是3和9
查找一个字符串中出现最多的字符.
“hello world”中L出现了3次
设计方法,使用System.in.read()读取一行.
循环读取一个字节,读取到\r\n结束。考虑中文问题
已知一个字符串. 设计一个方法, 可以从这个字符串中打印n个字节. 但不能打印出半个中文.
短信一次发送字节140个,如果超过140字节就会分为两条。这时如果第140个字节是中文的前半,那么第一条短信应该发送139字节。
查找两个字符串中最大相同子串.
“abc”和“bcd”的最大相同子串是”bc”
“xyzabcdefxyz”和“xxxabcdefooo”的最大相同子串是”abcdef”
2. 集合类
2.1. 集合概念
Ø 为什么出现集合类?
在面向对象的编程思想中,都是以对象的形式对事物进行描述的,为了保证对象的生命周期,我们需要持有对象
在很多情况下,我们不知道在程序中需要创建多少个对象,这时就不能依靠定义引用对象的变量来持有每一个对象
存储对象的容器就能帮我们解决这样的问题,而集合便是这样的容器
Ø 数组和集合类的区别
数组和集合类都是容器,都能存储对象
集合类的优势就在于长度可变
Ø 集合类的特点
集合类可用于存储对象
集合类的长度可变
一个集合可以存储多种类型的对象
2.2. 集合接口
Ø Collection接口
一个独立的元素的序列,这些元素服从一条或多条规则
Collection接口下主要分为List集合和Set集合
List集合的特点是元素有序、允许有重复元素
Set集合的特点是元素无存储顺序、不允许有重复元素
Ø Map接口
一组成对的”键值对”对象,允许根据键来查找值
Map集合的键不允许有重复,所以Map的所有键构成了一个Set集合
主要学习HashMap和TreeMap
Ø Iterable接口
JDK1.5新定义的接口作为Collection的父接口
主要为了实现增强for循环
2.3. List
Ø List特点
元素有序,可重复。
我们主要学习三种:ArrayList、Vector、LinkedList
这三种都是List接口的实现类,使用上完全一样,只是实现原理不同,效率不同。
Ø ArrayList
底层数组实现
查找快,增删慢
线程不安全
Ø Vector
与ArrayList基本一样
线程安全(线程同步),效率低
Ø LinkedList
底层链表实现
增删块,查找慢
Ø 存取元素
List集合元素存取方法一致
使用add()方法增加元素
由于List集合有序,可以使用get()方法获取元素
元素的迭代(Iterator)
通过集合对象的iterator()方法获得迭代器Iterator
通过Iterator迭代器的hasNext()方法判断是否存在下一个元素
通过Iterator迭代器的next()方法获取下一个元素
元素的迭代(Enumeration)
迭代Vector集合中的元素可以使用Enumeration
通过Enumeration的hasMoreElements()方法判断是否还有元素
通过Enumeration的nextElement()方法返回下一个元素
2.4. JDK5新特性
Ø 泛型
由于集合可以存储不同类型的数据,所以取元素时有可能会导致类型转换错误
JDK1.5增加了新特性泛型,为了减少操作集合时出错的机率
集合一旦声明了泛型,便只能存储同一类型的对象了
使用方法:ArrayList<Person> al = new ArrayList<Person>();
使用泛型的好处
提高了程序的安全性
将运行期遇到的问题转移到了编译期
省去了类型强转的麻烦
泛型类的出现优化了程序设计
Ø 增强for循环
新接口Iterable中定义了增强for循环
可以通过增强for循环对数组和集合进行遍历
语法:for(类型 变量名 : 要遍历的容器) { …… }
Ø 可变参数
有的时候在设计方法时无法确定将来别人会传入的参数个数
JDK1.5增加了新特性可变参数,在函数中只声明参数类型,不规定个数
方法接受的参数实际上是一个数组,可以在方法中遍历数组
可变参数只能被定义为函数的最后一个形参
语法格式: 返回值 函数名(参数类型… 形参名)
2.5. Set
Ø Set集合无序,不允许有重复元素
Set集合通过存入对象的equals方法来保证集合中没有重复元素
Ø HashSet
HashSet是Set的子类,因此也没有重复元素
底层使用哈希算法保证没有重复元素
存储对象时,先调用对象的hashCode()方法计算一个哈希值,在集合中查找是否有哈希值相同的对象。
如果没有哈希值相同的对象,直接存入。
如果有哈希值相同的对象,则和哈希值相同的对象进行equals()方法比较。
equals()方法比较结果相同则不存,不同就存入。
往HashSet集合里存储的对象必须正确重写hashCode和equals方法
Ø TreeSet
TreeSet集合通过二叉树算法保证无重复元素,并对元素进行排序
在使用TreeSet时必须指定比较的算法,指定的方式有两种:
自然顺序:将要存储的类实现Comparable接口,重写compareTo方法,在方法中指定算法
比较器顺序:在创建TreeSet时,传入一个比较器Comparator,在比较器的compare方法中指定算法
2.6. Map
Ø Map集合的特点
Map存储了一系列键值的映射关系
Map集合需要保证键的唯一性
可以通过键获得值,反之则不能
Map集合存储元素使用put(key,value)方法
Ø Map集合的两种遍历方式
通过keySet方法返回由键组成的集合,迭代该集合的元素就拿到了所有的键,再调用get方法根据键拿到值
通过entrySet方法返回键值映射关系组成的集合,迭代该集合就拿到了一个个的键值映射关系,通过getKey方法拿到键,通过getValue方法拿到值。
Ø HashMap
线程不安全,存取速度快,允许存放null键,null值。
通过HashSet原理保证键唯一性
Ø Hashtable
线程安全,速度慢,不允许存放null键,null值,已被HashMap替代。
Ø TreeMap
通过二叉树算法保证键唯一性
对键进行排序,排序原理与TreeSet相同。
Ø Properties
HashTable的子类,所以也是线程安全的
用于读写配置文件的,一般配置项等号两边都是String,所以该集合中的两列保存的都是String类型的数据
这个集合中只能存String,所以不需要定义泛型。
3. 其他常用类
3.1. 工具类
Ø Arrays
工具类,提供了对数组的常用操作
将数组转成List集合
对数组进行排序
对数组进行二分查找
将数组转为字符串显示形式
Ø Collections
工具类,提供了对集合的常用操作
对集合进行查找
取出集合中的最大值,最小值
对List集合进行排序
3.2. 包装类
Ø JDK提供了对所有数据类型的包装类
byte >>> Byte
short >>> Short
int >>> Integer
long >>> Long
double >>> Double
float >>> Float
char >>> Character
boolean >>> Boolean
Ø 包装类的常用方法
toString方法
parseInt方法:Integer.parseInt(String s)
valueOf方法:Double.valueOf(String s)
3.3. 系统类
Ø System类
静态属性in为标准输入流,属于InputStream类型,read方法返回一个字节
静态属性out为标准打印流,属于PrintStream类型,print方法打印字符
可以用set方法修改属性in和out
System.exit()方法退出Java虚拟机
System.gc()垃圾回收
System.getProperties()方法获得系统属性
Ø Runtime类
表示系统运行时状态
exec方法执行命令
3.4. 时间类
Ø Date类
使用new Date()创建时间对象代表当前系统时间
需要使用DateFormat类来进行格式化,才能显示想符合习惯的格式
Ø Calendar类
使用该类对时间进行操作比较方便
通过常量来表示时间的各种值,如一年中的某一天,一个月的某一天等
将对应的常量作为形参来调用相应的get、add、set方法来操作对象
Ø 练习
计算出某一年的二月份有多少天?
设计一个方法可以计算工作时间,接收一个参数(工作日),方法打印出哪天完工。
4. IO(Input Output)
4.1. IO流概念
IO流用来处理设备之间的数据传输
Java对数据的操作是通过流的方式
Java用于操作流的对象都在IO包中
流按操作对象分为两种:字节流与字符流。 字节流可以操作任何数据,字符流只能操作纯字符数据比较方便。
流按流向分为:输入流,输出流。
4.2. IO流常用基类
Ø 字节流的抽象基类:
InputStream ,OutputStream
Ø 字符流的抽象基类:
Reader , Writer
Ø 由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。
如:InputStream的子类FileInputStream。
如:Reader的子类FileReader。
InputStreamReader是Reader的子类
4.3. IO程序书写
使用前,导入IO包中的类
使用时,进行IO异常处理
使用后,释放资源
4.4. 字符流读写文件
Ø 读取文件
定义字符流关联指定文件
FileReader reader = new FileReader("Test.txt");
读取一个字符,返回int,该字符的码表值
int ch = reader.read();
关闭流,释放资源
reader.close();
Ø 写出文件
定义字符输出流关联指定文件
FileWriter writer = new FileWriter("Test.txt");
写出一个字符,接收int码表值
writer.write(97);
关闭流,释放资源
writer.close();
Ø 注意事项
文件路径
定义文件路径时Windows中的目录符号为“\”,但这个符号在Java中是特殊字符,需要转义。
可以用“\\”或“/”表示。
读取文件
读取文件时必须保证文件存在,否则将抛出FileNotFoundException。
写出文件
写出时文件如不存在时程序会创建新文件,如文件已存在则会清空原文件内容重新写入。
如需追加内容可调用FileWriter构造函数FileWriter(String fileName, boolean append)
练习
拷贝一个文件
4.5. 字符流缓冲区读写
Ø 自定义缓冲区读写
为什么定义缓冲区
由於单个字符读写需要频繁操作文件,所以效率非常低。
我们可以定义缓冲区将要读取或写出的数据缓存,减少操作文件次数。
缓冲区读取
先定义一个数组,然后调用FileReader读取一个数组的方法。
int read(char[] cbuf)
缓冲区写出
将要写出的数据存放在数组中,调用FileWriter方法,一次写出一个数组。
void write(char[] cbuf, int off, int len)
Ø 内置缓冲区的BufferedReader和BufferedWriter
Java提供了带缓冲功能的Reader和Writer类:BufferedReader,BufferedWriter
这两个类都是提供包装功能,需要提供其他流来使用,给其他流增加缓冲功能
当我们调用BufferedReader读取数据时,程序会从文件中一次读取8192个字符用来缓冲
当我们调用BufferedWriter写出数据时,程序会先将数据写出到缓冲数组,直到写满8192个才一次性刷出到文件
4.6. 装饰设计模式(Decorator)
Ø 什么情况下使用装饰设计模式
当我们需要对一个类的功能进行改进、增强的时候
Ø 装饰模式的基本格式。
含有被装饰类的引用
通过构造函数传入被装饰类对象
和被装饰类含有同样的方法,其中调用被装饰类的方法,对其进行改进、增强
和被装饰类继承同一个类或实现同一个接口,可以当做被装饰类来使用
Ø 了解BufferedReader、BufferedWriter的原理。
BufferedReader、BufferedWriter都是装饰类,他们可以装饰一个Reader或Writer,给被装饰的Reader和Writer提供缓冲的功能。
就像我们用BufferedReader、BufferedWriter装饰FileReader和FileWriter,使用的读写功能还是FileReader和FileWriter的,但给这两个类的读写添加了缓冲功能。
Ø 练习
模拟一个BufferedReader类。
模拟一个LineNumberReader类。
4.7. 字节流
基本操作与字符流相同
字节流可以操作任意类型数据
练习:拷贝一个Jpg文件
4.8. 字节流缓冲区读写
Ø 自定义缓冲区读写
原理和字符流相同,都是为了提高效率
定义数组缓冲数据,一次读取一个数组,一次写出一个数组,减少操作文件的次数
Ø BufferedInputStream、BufferedOutputStream
和BufferedReader、BufferedWriter原理相同,都是包装类
BufferedInputStream、BufferedOutputStream包装InputStream和OutputStream提供缓冲功能
4.9. 转换流
字符流与字节流之间的桥梁
方便了字符流与字节流之间的操作
字节流中的数据都是字符时,转成字符流操作更高效
练习:转换System.in
4.10. 标准输入输出流
System类中的成员变量:in,out。
它们各代表了系统标准的输入和输出设备。
默认输入设备是键盘,输出设备是显示器。
System.in的类型是InputStream.
System.out的类型是PrintStream是OutputStream的子类FilterOutputStream 的子类.
练习:通过修改标准输入输出流,使用System.in和System.out拷贝文件
4.11. 流基本应用小节
流是用来处理数据的。
处理数据时,一定要先明确数据源,或者数据目的地
数据源可以是文件,可以是键盘或者其他设备。
数据目的地可以是文件、显示器或者其他设备。
而流只是在帮助数据进行传输,并对传输的数据进行处理,比如过滤处理、转换处理等。
字符流
Reader
Writer
InputStreamReader
OutputStreamWriter
FileReader
FileWriter
BufferedReader
BufferedWriter
FileInputStream
字节流
FilterInputStream
FilterOutputStream
BufferedInputStream
BufferedOutputStream
InputStream
OutputStream
FileOutputStream
4.12. File类
用来将文件或者文件夹路径封装成对象
方便对文件与文件夹进行操作。
File对象可以作为参数传递给流的构造函数。
了解File类中的常用方法。
4.13. 递归
函数自己调用自己。
注意:递归时一定要明确结束条件。
应用场景:
当某一功能要重复使用时。
练习:
列出一个文件夹下所有的子文件夹以及子文件
思考:
删除一个目录的过程是如何进行的?
复制一个目录的过程呢?
4.14. IO包中的其他类
序列流
SequenceInputStream
可以将多个字节输入流整合成一个流,在使用这个流读取的时候,读到第一个流的末尾时继续读第二个,第二个读到末尾则继续读第三个,以此类推,直到读到最后一个流的末尾返回-1
打印流
PrintStream 、PrintWriter
相比普通的OutputStream和Writer增加了print()和println()方法,这两个方法可以输出实参的toString()方法的返回值
这两个类还提供自动flush()的功能
操作对象
ObjectOutputStream
可以将实现了Serializable的接口的对象转成字节写出到流中
ObjectInputStream
可以从流中读取一个ObjectOutputStream流写出的对象
操作内存缓冲数组
ByteArrayOutputStream: 写出到字节数组(内存)中,可以获取写出的内容装入一个字节数组。通常我们用这个流来缓冲数据。
ByteArrayInputStream:可以从一个字节数组中读取字节。
CharArrayWriter:写出字符到字符数组(内存)中,可以获取写出的内容装入一个字符数组。
CharArrayReader:可以从一个字符数组中读取字符。
管道流
PipedInputStream:管道输入流,可以从管道输出流中读取数据
PipedOutputStream:管道输出流,可以向管道输入流中写出数据
操作基本数据类型
DataInputStream、DataOutputStream
可以按照基本数据类型占用空间大小读写数据
随机访问文件
RandomAccessFile
5. 多线程
5.1. 多线程概念
Ø 线程与进程
进程就是一个运行中的程序。
一个进程中可以有多个线程,线程是CPU调度和分派的基本单位。我们可以理解为线程就是程序运行中的一条路径。
Ø 多线程存在的意义
允许多个线程并发执行,提高程序运行效率。
例如:迅雷多线程下载,QQ多个人同时聊天,凌波多个人同时共享屏幕。
5.2. 线程的使用
Ø 创建线程有两种方式
自定义一个类继承Thread类,将线程要做的事写在run()方法中,由于子类可以当父类来用,创建自定义子类对象就是创建了一个线程。
自定义一个类实现Runnable接口,将要做的事写在run()方法中。创建Thread对象时在构造函数中传入Runnable实现类对象。
Ø 线程的启动
两种创建方式都是调用Thread对象的start()方法。
当调用start()方法时,CPU会开启一条新线程,并在新线程上执行run()方法。
Ø 线程常用方法
currentThread
静态方法,用来获取当前线程
getName、setName
用来获取、设置当前线程的名字
sleep
控制线程休眠,单位为毫秒
setDeamon
将线程设置为守护线程。线程默认是非守护线程,守护线程不能单独执行。
join
当前线程暂停,等待加入的线程运行结束,当前线程继续执行。
5.3. 多线程同步
Ø 线程安全问题
多线程并发访问同一数据,有可能出现线程安全问题。
一条线程的访问还没有结束,CPU切换到另一条线程工作,导致数据访问出错。
Ø 使用同步解决线程安全问题
使用同步代码块synchronized(锁对象){需要同步的代码...}形式将访问数据的代码锁住,在同步代码块中的内容同一时间内只能一个线程执行。
使用同步方法,用synchronized修饰方法,整个方法的代码都是同步的,只能一个线程运行。同步方法使用this作为锁。
Ø 死锁
在多个线程并发执行使用多个锁来同步时,有可能互相冲突,导致程序无法继续执行。
Ø 同步的优点与缺点
同步可以解决多个线程同时访问一个共享数据的问题,只要加上同一个锁,在同一时间内只能有一条线程执行。
在执行同步代码时每次都会判断锁,非常消耗资源,效率较低。
5.4. 多线程通信
在同步代码中可以使用锁对象的wait()方法让当前线程等待
使用锁对象的notify()方法可以将正在等待的线程唤醒
如果多个线程都在等待,notify()唤醒随机1个
notifyAll()方法可以唤醒所有在等待的线程
5.5. JDK5之后的线程同步与通信
Ø 同步
使用java.util.concurrent.locks.Lock接口的实现类对象来进行同步
ReentrantLock就是Lock的实现类,可以实现synchronized的功能
在需要同步的代码块前后使用lock()和unlock()方法来完成同步
unlock()最好放在finally中,因为如果上面代码抛出异常没有解锁的话,会导致其他线程无法运行,程序卡死。
Ø 通信
使用Lock对象的newCondition()方法获取一个Condition对象,Condition对象可以控制指定线程的等待与唤醒。
await()方法可以控制线程等待。
signal()方法可以唤醒等待的线程。
signalAll()方法可以唤醒所有等待线程。
6. GUI
6.1. GUI概念
Ø 什么是GUI
GUI是Graphical User Interface的缩写,图形化用户界面
Ø awt和swing
Java为GUI提供的对象都存在java.awt,javax.swing两个包中
awt依赖于本地系统平台,如颜色样式显示
swing跨平台
Ø 组件与容器
组件 Component,是GUI图形界面的组成单元。
容器Container,可以存放组件,也可以存放容器。
6.2. 布局管理
Ø FlowLayout(流式布局管理器)
从左到右的顺序排列。
Ø BorderLayout(边界布局管理器)
东,南,西,北,中
Ø GridLayout(网格布局管理器)
规则的矩阵
Ø CardLayout(卡片布局管理器)
选项卡
Ø GridBagLayout(网格包布局管理器)
非规则的矩阵
6.3. 建立一个窗体
窗体中可以存放各种组件,所以窗体是容器Container。创建时我们使用的是它的子类
Container的常用子类有两个,Window和Panel。Window是我们常用的窗体,Panel是用来布局的不可见的。
Window也有两个常用子类,Frame和Dialog。Frame是我们常用的带有标题和边框的顶层窗口,Dialog是对话框。
所有AWT包中的类都会运行在AWT线程上
6.4. 事件处理
Ø 事件处理机制
事件:用户对组件的一个操作。
事件源:发生事件的组件。
监听器:我们需要处理某个事件,就需要在发生事件的组件上添加监听器,也就是java.awt.event包中XxxListener接口的子类。
事件处理器:监听器中的方法。监听器被添加在组件上之后,组件上发生了对应事件就会执行指定方法。
Ø 常用事件分类
窗体事件,WindowEvent,窗体打开、关闭、正在关闭、激活、最小化等。
鼠标事件,MouseEvent,鼠标按下、擡起、进入、移出等。
键盘事件,KeyEvent,键盘按下、擡起等。
动作事件,ActionEvent,在某一组件上发生了定义好的动作,例如按钮上鼠标点击或按空格,菜单上鼠标点击或按回车等。
7. Socket网络编程
7.1. 网络编程概念
Ø IP地址
每台网络终端在网络中都有一个独立的地址,我们在网络中传输数据就是使用这个地址。
ipconfig:查看本机IP
ping:测试连接
本地回路地址:127.0.0.1
IPv4:4个字节组成,4个0-255。大概42亿,30亿都在北美,亚洲4亿。已经用尽。
IPv6:8组,每组4个16进制数。
1a2b:0000:aaaa:0000:0000:0000:aabb:1f2f
1a2b::aaaa:0000:0000:0000:aabb:1f2f
1a2b:0000:aaaa::aabb:1f2f
1a2b:0000:aaaa::0000:aabb:1f2f
1a2b:0000:aaaa:0000::aabb:1f2f
Ø 端口号
每个网络程序都需要绑定一个端口号,传输数据的时候除了确定发到哪台机器上,还要明确发到哪个程序。
端口号范围从0-65535
编写网络应用就需要绑定一个端口号,尽量使用1024以上的,1024以下的基本上都被系统程序占用了。
常用端口
mysql: 3306
oracle: 1521
web: 80
tomcat: 8080
QQ: 4000
feiQ: 2425
Ø 网络协议
为计算机网络中进行数据交换而建立的规则、标准或约定的集合。
UDP
面向无连接,数据不安全,速度快。不区分客户端与服务端。
TCP
面向连接(三次握手),数据安全,速度略低。分为客户端和服务端。
Ø Socket
通信的两端都有Socket。
网络通信其实就是Socket间的通信。
数据在两个Socket间通过IO传输。
Socket在应用程序中创建,通过一种绑定机制与驱动程序建立关系,告诉自己所对应的IP和port。
7.2. UDP传输
Ø 发送
创建DatagramSocket
创建DatagramPacket
使用DatagramSocket发送DatagramPacket
关闭DatagramSocket
Ø 接收
创建DatagramSocket
创建DatagramPacket
使用DatagramSocket接收DatagramPacket
关闭DatagramSocket
7.3. TCP传输
Ø 客户端
创建Socket连接服务端
调用Socket的getInputStream()和getOutputStream()方法获取和服务端相连的管道流
输入流可以读取服务端输出流写出的数据
输出流可以写出数据到服务端的输入流
Ø 服务端
创建ServerSocket
调用ServerSocket的accept()方法接收一个客户端请求,得到一个Socket
调用Socket的getInputStream()和getOutputStream()方法获取和客户端相连的管道流
输入流可以读取客户端输出流写出的数据
输出流可以写出数据到客户端的输入流