java byte数组截取从右往左(java截取数组一部分)

软件开发生命周期:

意向 -> 立项 -> 需求 -> 概要设计 -> 数据库设计 -> 详细设计 -> 编码 -> 需求变更 -> 测试 -> 上线 -> 运维 -> 下线

JAVA基本概念Java 历史

James Gosling 1990年 Oak -> 2014 JDK 8

J2SE Java 2 Platform Standard Edition

J2EE Java 2 Platform Enterprise Edition

J2ME Java 2 Platform Micro Edition

Java 跨平台

Java语言使用JVM沟通系统与编程语言,实现一次编写,到处运行。

JDK

Java开发工具包(Java Development Kit)

包括基础类库jdk/jre/lib/rt.jar、开发工具 jdk/bin、源码 jdk/src.zip

官方网站:https://www.oracle.com/java/technologies/javase-downloads.html

官方文档:https://docs.oracle.com/en/java/javase/index.html

目前长期支持版本:jdk8,jdk11,只推荐这两个版本开发应用

JDK、JRE、JVM区别

JDK:开发者环境,包括JRE、Java工具与Java基础类库

JRE:运行时环境,包括JVM的标准实现与Java核心类库

JVM:虚拟机,实现Java在不同平台上运行

JVM在不同操作系统是不同的,从而JRE,JDK在不同平台是不同的

.java源文件会被编译器编译成.class文件,这是JAVA自行定义的中间代码,在所有平台相同,JVM针对平台进行中间代码的差异化执行。也就是,中间代码与不同指令集的对应。

环境变量变量值JAVA_HOMEC:\apps\jdkPath%JAVA_HOME%\bin集成开发环境IDE说明Eclipse免费,基于工作空间(workspace),一个工作空间存放多个项目IDEA收费,基于项目(project),一个项目可以存放多个子模块

IDEA的模块就是项目,项目还是项目,可以嵌套。IDEA目前出了官方汉化插件,否则,任何IDE都不建议第三方汉化。Eclipse的配置文件为根目录下eclipse.ini,建议Xms和Xmx(堆下限和堆上限)都改到2048以上,IDEA设置里可以修改堆大小。

编程首先应该学习的是英语,目前,很多语言是支持汉语编程的,但是支持得不彻底,输入法切换非常麻烦,某种程度上说,汉语不适合做标记语言。这个判断待验证,我正在尝试。

基本上,IDE的使用是见名知意的,只要记住一些常用单词,就可以非常顺利的使用。本地化是一件非常麻烦的事情,需要把程序的所有名称写成配置文件,工作量巨大。所以,不要奢求本地化,只有市场足够大,软件商才可能考虑适配语言。在官方汉化之前,名词翻译是不确定的,因此,不建议第三方汉化。会打断之后的学习进度。

工具本身不稳定,建议留存软件安装包。

Hello World

打开Eclipse

public class HelloWorld { // 类

public static void main(String[] args) { // 主程序

System.out.println(“Hello World! “); // 程序内容

}}

Ctrl F11 运行

IDE有很多快捷键操作,搜索一下,或者到设置里找

命名约定

字母、数字、下划线(_)、美元($)

不能数字开头

严格区分大小写(或大小写敏感)

尽量简单易懂

关键字booleanbytecharintshortlongfloatdoubleifelseforwhileswitchcasebreakcontinuetruefalsepublicprivateprotecteddefaultabstractstaticvoidreturnnewfinalthissuperpackageclassinterfaceimportimplementsextendstrycatchfinallythrowthrowsnullnativesychronizedtransientvolatileinstanceofdo基本输入输出System.out.println(“标准控制台输出”);// 接收标准控制台输入Scanner scan = new Scanner(System.in);String str = scan.nextLine();基本数据类型数据类型范围与字节数数据类型范围与字节数大约范围byte[-128,127],1int[-2^31,2^31-1],421亿boolean{true,false},1long[-2^63,2^63-1],8922亿亿char[0,65535],2float[3.4E-38,3.4E38],4short[-32768,32767],2double[1.7E-308,1.7E308],8

关于数字。整数字面值是int类型;浮点数字面类型是double类型;byte/short/char可以用范围内的值直接赋值;字面值后缀 L/D/F/l/d/f;前缀:十六进制(0x)、八进制(0)、二进制(0b)、Unicode字符(\u)

关于计算。计算结果的数据类型与最大类型一致;整数运算溢出后舍弃高位;byte/short/char 计算时会自动转型为int;浮点运算不精确,科学计算用Decimal类;浮点特殊值:±Infinity 无穷大;NaN 非数字类型(Not a number)

小数的标准为IEEE-754,可查阅。实际使用基本上可以不考虑范围,只使用double,因为处理器是64位的,并不会有性能忧虑。浮点计算即小数计算,速度跟整数计算相比,要慢很多,因此,能不用小数就不用小数。

类型转换方向

自动类型转换,无损:

byte -> short -> int -> long -> float/double

char -> int -> float/double

float 与 double 虽然都是小数,但算法不同,无法无损转换

强制类型转换,有损:

long x = 12345L; int y = (int)x;运算符运算符号运算符号加减乘除次方 – * / ^取余%连接字符串 等于 不等于== !=与或非&& || !短路与或& |自增自减 –复合运算 = -= *= /= %=赋值运算=三元运算A ? B : C

整数除为整除,小数除为正常除法(因为整数除法的最大类型为整数,所以结果只能是整数)

流程控制if else / for / while / do while / switch case break continue

递归:循环调用自身,对空间要求很高,理解但尽量不使用,具体知识建议参考算法方面的书籍

跳出循环:break 跳出当前循环;continue 跳到下一次循环

循环可以命名,以控制跳出方向

public static void **main**(String[] args) { lever0: for(int i = 0; i < 100; i ) { lever1: for(int j = 0; j < 100; j ) { lever2: for(int z = 0; z < 100; z ) { if(z == 50) break lever0; } System.out.println(“跳到了lever2”); return; } System.out.println(“跳到了lever1”); return; } System.out.println(“跳到了lever0”); return;}数组

顺序存放一组数据的一种数据结构,称作数组,数组的创建后,长度不可变,a.length可获取数组长度;数组元素若不初始化,默认为零;数组下标从0开始,到a.length-1 结束;一般使用for对数组进行遍历,以int数组为例展示创建数组的语法:

int[] a = new int[5]; int[] a = {1,2,3,4,5}; int[] a = new int[]{1,2,3,4,5};

二维数组:数组的数组,即数组的元素是另一个数组

int[][] a = new int[3][2]; int[][] a = new int[3][]; int[][] a = {{1,2},{3,4},{5,6}};

随机数组排序是一件非常有趣的事情

数组工具类 java.util.Arrays。数组排序建议参考《算法导论》,冒泡,二分,并归,快排等。其他数字游戏:阶乘、斐波那契数列、汉诺塔等。

方法说明Arrays.toString(a)数组输出,自动连接元素并转化为字符串Arrays.sort(a)对于基本类型数组,优化的快速排序,对于引用类型数组,优化的合并排序Arrays.binarySearch(a,target)二分法,折半查找,找不到会返回-1Arrays.copyOf(origin, start, target, start, length)复制指定长度的数组,从origin数组到target数组作用域

Java语言以大括号作为作用域边界,定义在类中的变量为成员变量,定义在类中其他大括号中的为局部变量。可以看作,类是Java的自然边界。

成员变量,作为类的属性,会自动地初始化。通过访问控制符来控制访问(public private protected等)。允许定义同名的局部变量,在方法内,局部变量拥有比成员变量更高的优先级。

局部变量,作为代码块的传值中介,并不会自动的初始化,作用域为它所在的代码块。不能重复定义。

进制转换

1字节 = 8位,有256种不同的组合。每一位表示高低电位中的一种,约定最高位为符号位

因此,byte 可以存储数字-128到 128

二进制转十进制:Integer.parseInt(“10101011”, 2)

十进制转二进制:Integer.toBinaryString(999)

更多底层处理方式的知识,参考《汇编语言》与《数字逻辑》

面向对象基础面向对象与面向过程的区别

计算机程序设计目的为完成某个目标,面向过程的编程方式将任务分解为不同的阶段,然后分别实现。面向对象将世界抽象为对象和对象行为,任务的不同阶段对应不同对象的不同行为。面向对象是比面向过程更加高级和复杂的抽象。在小规模任务时,二者效率差别不大,但在大规模任务时,面向对象可以大量降低复杂程度。

类:指拥有共同特征的一组事物的抽象形式,如人类,是具体的人的抽象表示。数字,是具体的数的抽象表示。

对象,又称实例:即类的具体形式,如具体的人是人类的对象,数字6是整数类的一个具体形式。通过类创建具体的对象的过程称作实例化。

引用:因为无法确定对象的大小,因此对象是引用值,而非顺序存放。在Java中,除了基本类型存储直接值,其他的一切都是引用值。

构造与析构

对象的初始化称作构造。Java通过类创建对象,因此需要设置对象的初始值。如通过人类创建一个具体的人,需要设置身高体重等基本信息。Java提供专门的构造函数完成对象初始化,它在对象申请到内存空间之后自动进行。对应的,对象使用完成之后,需要销毁,称作析构函数。与构造函数完全相反,负责归还所占用的内存空间。函数与方法只是称呼不同,是等价的。Java的类回收一般由系统自动进行,所以,一般只需要写构造函数。析构函数为finalize(),或称终结方法,有时候程序员也会手动编写一个destroy()方法。注意:回收资源仍是系统进行,这个方法只是通知系统可以回收资源。

构造方法可以重载,实现不同参数的初始化。如不添加构造函数,则默认添加一个无任何操作的空构造函数。构造函数形如:public 类名(参数) {// 操作}

this

一个特殊指针,指向当前对象。用于区别成员变量与局部变量重名。“this.变量名”表示当前对象的成员变量。“变量名”表示局部变量。也可用于构造函数之间调用,通常程序会有一个参数较多的全参构造函数和缺失很多参数的简单构造函数,为了减少工作内容,可以将参数较少的构造函数用“this(多参数)”的方式重定向到全参构造函数,并为缺失的内容设置默认值。

super

父类指针,指向当前对象的父类

重载

方法可以同名不同参,参数类型、个数、顺序不同。(不能参数相同返回值不同)

继承

通过一个类创建另一个类,如通过人类创建男人类和女人类,Java支持单继承、多实现。子类可以重写父类方法。创建对象时,会先创建父类对象,执行构造函数时,先执行父类构造函数,父类不存在无参构造时,必须手动调有参构造,super(参数)。

多态

子类会被当做父类处理,当使用父类状态的子类时,只能调用父类定义的成员,子类自己的东西会被隐藏。(子类被当作父类使用,通常称为向上转型,自动完成)。

被当作父类的子类重新转回子类,称作向下转型。需要手动向下转型(系统无法选择哪一个子类),向下转型的形式同强制转换。instanceof关键字,可以进行识别,意为“x的子类?”。

抽象类

即无法实例化的类,更彻底的,这是一个标记,本质它仍是普通类,但是功能被限制了。如人类就可以作为抽象类,而男人是继承了人类的具体类。抽象类应用于不需要被实例化的类,可以减少代码量,抽象类的方法无需方法体,子类必须重写抽象类的方法。

Object

Java的顶级父类,所有类都是Object的子类,如果一个类不写继承的父类,则默认继承Object。

字符串类。封装的char数组,字符串是引用对象,值存储在字符串常量池(专用)中,遇到重复的字符串时,将引用池中相同的对象。字符串不可修改,每一次修改都是新建。使用加号连接字符串时,每一次加号操作都会新建一个字符串。创建方式:

String s = new String(char[] a)String s = “New Bee”方法说明charAt(int i)取指定下标的字符,下标从0开始indexOf(子串)查找子串,返回下标substring(起点下标)截取子串,从起点到末尾substring(起点, 终点)截取子串,从起点到终点equals()判断字符的内容是否相等String.valueOf()任何数据转成字符串trim()去除两端的空白字符StringBuffer、StringBuilder

可变字符串类。可以进行字符串修改而不创建新字符串,可进行高效字符串连接。初始长度为16,放满翻倍。StringBuffer是线程安全的(锁对象),StringBuilder是线程不安全的(不锁对象),功能完全一样。

常用:append() 追加内容

Regex

正则表达式(Regular Expression)。意为符合规则的表达式,正则意为正确的规则(垃圾翻译)

方法说明match(regex)字符串正则匹配split(regex)字符串正则分割replaceAll(regex, str)字符串正则替换表达式匹配的字符串abc全字匹配,abc[abc]单字匹配,a、b或c[a-z]单字范围匹配,意为a到z[a-zA-Z_0-9]单字多范围匹配,意为字母数字或下划线[\u4E00-\u9FA5]单字范围匹配,中文范围\ddigital,数字,即[0-9]\D排除数字\wword,单词字符,即[a-zA-Z_0-9]\W排除单词字符\s空白字符\S排除空白字符.任意字符[abc]?问号,0或1个[abc]*星号,0或多个[abc] 加号,1或多个[abc]{3}3个[abc]{3,5}3到5个[abc]{3,}3到多个|或^中括号内表示排除,中括号外表示起始位置$表示结尾位置包装类

基本类型存储直接值,但是作为值,是没有方法可言的,因此,基本类型在复杂处理的时候,需要将它们转换为引用类型,如int是基本类型,只有一个数字,如果要将int转换为字符串,就需要用到Integer.toString(),因为,只有类存在方法,数字是没有方法可言的。所以,包装类可以看作基本类型的工具类。Java对此进行了优化,有时候会自动进行转换,称作自动装箱和自动拆箱,箱子就是基本类型对应的引用类型。

基本类型包装类(工具类)byteByteshortShortintIntegerlongLongfloatFloatdoubleDoublecharCharacterbooleanBoolean

其中,数字类的父类是Number。以下以int举例:

创建对象:Integer.valueOf(100) 或 new Integer(100)

Java有一个整数对象的缓存数组,存储了[-128,127]等经常使用的数值,所以,如果数字在此范围内,不会新建对象,而是直接使用这些缓存对象,在范围之外,则新建。对编程者来说,此差异可以忽略,它只是程序对运行时的优化,是额外设计而非典型设计。

方法说明Integer.parseInt(字符串数字,[进制])字符串转数字Integer.toBinaryString()数字转二进制字符串Integer.toOctalString()数字转八进制字符串Integer.toHexString()数字转十六进制字符串Double.isInfinite(double)判断无穷大Double.isNaN(double)判断非数BigDecimal、BigInteger

大实数与大整数,同属Number的子类。用于科学计算,非常精确。(Decimal:小数,由于程序没有无理数一说,所以说是实数也不为过)

没有加减乘除符号,只能使用方法操作。创建方法同Integer。

方法说明add()加substract()减multiply()乘divide()除setScale(保留位数,舍入方式)设置精度

其中,由于除法的特殊性,可能除不尽,因此,divide(除数, 保留位数, 舍入方式)。如果不设置,可能因无限位数而报错。

位运算符号说明&位与|位或^异或~求反>>带符号右移,高位补符号位,右移一位,相当于除以2>>>不带符号右移,高位补0<<左移,符号位不动,左移一位,相当于乘2

运算优先级:只记加减乘除的顺序,其他靠括号(貌似在不同的平台上,运算优先级有差异,但是先加减后乘除是一定不会错的)

final、static

final:表示最终类、属性或方法,也就是不可修改。

static:表示静态属性或方法,属于类,而不属于对象,类是静态的,只有一份,类的实例,即对象是动态的,可以有很多。用于共享的数据一般设为静态,静态方法不能直接调用非静态成员(必须通过对象.成员的方式)

方法区:存放加载的类文件(类静态资源与方法表)

栈:存放方法中的局部变量

堆:存放对象及对象的成员变量

public、protected、private、default

default,默认,包内、类内可访问

public,随处可访问、private,只有类内可访问

protected,包内、类内及子类可访问

原则:尽量使用小范围

接口

Java支持单继承,多实现,类是对一类事物的抽象,它是实际概念(现实的或抽象的),所以父类只能有一个(逻辑上只能有一个,所以C的继承,某种程度上说,是一种复杂接口)。而接口是对一组功能的封装,是功能集合。接口不需要实现方法,只需要写声明,接口的方法由实现接口的类去实现。

内部类

顾名思义,类中之类。分为非静态内部类(在类中定义,不能独立创建对象)、静态内部类(加了static,可以创建独立对象)、局部内部类(在类的方法中定义)、匿名内部类(只用一次,用完就丢)。

集合

java byte数组截取从右往左(java截取数组一部分)

常用集合说明Collection集合,包括数组(List)和集合(Set)Map映射。ArrayList顺序存放的数组LinkedList双向链表Stack栈,后进先出(LIFO)Queue队列,先进先出(FIFO)HashSet集合,哈希存放,底层是只有键不同的HashMapTreeSet集合,二叉树存放,底层是TreeMapHashMap散列表,或音译为哈希表TreeMap二叉树表

链表常用方法:add([index], data) get(index) remove(index) size() iterator() addFirst() addLast() getFirst() getLast() removeFirst() removeLast()

队列常用方法:offer()=addLast() peek()=getFirst() pool()=removeFirst()

栈常用方法:push()=addFirst() pop()=removeFirst()

哈希表常用方法:put() get() remove() size()

因此,数据少用数组,数据多用链表。(判断依据为寻址难度)

哈希表:Map,意为映射(数学集合中的概念),因此,所有的Map都是存放键值对<k,v>,即映射表,区别在于,key的计算方式。HashMap就是以Hash算法计算key的一种Map。hash算法的要求是,每一次计算出来的键都独一无二,现实情况是尽量保持独一无二,依赖于独一无二的算法,hash表有着超快的速度。

但是没有哪一个算法可以保证独一无二,因此,必然会出现重复的键,这种情况称作“碰撞”,解决办法,一、相同的键,则值存储为链表,标记偏移。二、换算法、或扩大存储空间,重算。

迭代器 Iterator<T>

用于遍历集合,具体方法是创建T类型的迭代器,T为集合中的元素类型。迭代器依次取出集合中T类型的元素。方法:hasNext() / next() / remove()。迭代器会锁定集合,迭代时集合无法增删。

foreach是迭代器的简化版本,JVM优化出来的特殊语法,本质仍是迭代器。foreach只能访问集合,不能增删。

for(T t : collection) { t.todo();}异常处理

常用类说明Exception异常。可以在catch内处理Error错误。系统错误,无法解决RuntimeException运行时异常NullPointerException空指针异常ArrayIndexOutOfBoundsException数组下标越界ArithmeticException数学运算错误,主要是除0和除不尽NumberFormatException数字转换异常ClassCastException类型转换异常ClassNotFoundException类找不到try catchtry { // 可能出错的操作} catch (Exception e) { // 提示或解决办法} finally { // 一定会做的操作}

如果异常无法立刻处理,则可以抛给调用它的对象,若一直得不到解决,则最终抛到虚拟机,虚拟机杀进程

允许自定义异常(参考Exception的写法,或继承Exception类)

输入输出流

File:文件,使用路径初始化

length() getName() getParent() getLastModified()

isFile() isDirectory() exists()

createNewFile() mkdirs() delete()

list() listFiles()

流说明InputStream OutputStream字节流FileInputStream FileOutputStream文件字节流ObjectInputStream ObjectOutputStream对象字节流,用于对象序列化,readObject() writeObject()Reader Writer字符流抽象父类InputStreamReader OutputStreamWriter字符流FileReader FileWriter文件字符流BufferedReader缓冲读,可以按行读,readLine()PrintStream PrintWriter打印流

流的基本操作为read()/write(),其中,字节流用于读写一般文件,字符流用于读写文本,可以设置文字编码

字符编码

在Java中,字符用单引号,字符串用双引号

ASCII:美国信息交换标准代码(American Standard Code for Information Interchange)

不是ASC-2,7字节,共128个字符,注意两个起点:’A’-65,’1′-49

ISO-8859-1 或 Latin-1:ASCII的扩展集,到256个字符

CJK:中日韩字符,双字节

GBK:国标扩展,双字节

Unicode:统一码,万国码,100W 字符,中文双字节或三字节

UTF-8:Unicode Transfermation Format,中文三字节,网页默认约定使用UTF-8

基本类型char采用Unicode编码

编码转换(gbk为例):

Unicode 转 gbk:byte[] bytes = str.getByte(“gbk”);

gbk 转 Unicode:String str = new String(byte[], “gbk”);

线程与并发

进程:正在执行的某个程序

线程:进程分为若干线程,并发执行,线程是操作系统调度的最小粒度

并发:即“同时”运行。计算机将CPU时间分为若干份,通常为每一份时间为几纳秒,称作时间片。所有线程轮流占用这些时间片,由于切换非常快,在外界看来,这些线程就是同时运行的,所以,同时运行不同的线程,就叫做并发。并发是系统级操作,完全由系统控制。用户启动程序和启动线程,都只是通知系统,该线程加入等待队列,至于什么时候运行,由系统决定。CPU有多个队列,用于存放不同状态的线程,分别为运行,等待,就绪,阻塞等,由系统的设计决定,在各个运行状态中,往往还分不同的优先级队列。总之,系统自动的调配这些东西,与程序无关,当系统管理不善时,就会出现卡顿(排除性能不足的原因)。程序建立线程,表示申请线程需要的资源,程序启动线程,表示加入等待队列。重要的是,这是通知系统,而不是立刻去做。

创建线程:继承Thread类或实现Runnable接口,两种方式均要重写run()

方法说明start()启动线程,自动执行run()方法(这就是为什么一定要重写run())getName() setName()获取/设置线程名,不设置的话,有默认名称interrupt()线程中断,InterruptedExceptionjoin()当前线程等待被调用的线程结束,如 t1.join(),则调用此语句的线程需要等待t1结束才能继续运行getPriority() setPriority()线程优先级setDeamon()设为守护线程、或后台线程Thread.currentThread()当前线程Thread.sleep(毫秒)线程暂停Thread.yield()线程让出当前时间片,重新排队Object.wait()线程等待,必须在同步锁内执行Object.notify()唤醒当前线程监视器上的一个线程(随机)Object.notifyAll()唤醒当前线程监视器上的所有线程

监视器:加锁时,对象自动关联一个同步监视器,用于监控对对象有想法的线程

线程同步:根据需要,手动调整线程的执行顺序。虽然线程并发执行,会让事情变得更快,但是,有些任务需要等待其他任务完成(前序作业)。如线程B需要等待线程A完成C文件的写入后读取文件才有意义。这时候,就需要将C文件上锁,写入完成前禁止线程B访问它。这种操作称作同步,锁称作同步锁。

synchronized(资源) { // 锁资源,操作完成后释放}synchronized void func() { // 方法执行时,锁住当前对象}static synchronized void func() { // 静态方法执行时,锁住所属类}反射

reflect 反射,指的是在运行时,找到正在运行的对象的信息

设类为C,对象为c

获得类对象:C.class Class.forName(“C”) c.getClass()

获得包名:c.getPackage().getName()

获得类名:c.getName() c.getSimpleName() 全限定类名与简单类名

获取所有可见成员变量:getFields()

获取所有成员变量,包括私有:getDeclaredFields()

获取指定可见成员变量:getField(name)

获取指定成员变量,包括私有:getDeclaredField(name)

获取构造方法:

getConstructors()

getDeclaredConstructors()

getConstructor(参数类型列表)

getDeclaredConstructor(参数类型列表)

新建对象:

c.newInstance()

c.newInstance(构造方法参数)

使私有变量可访问:

field.setAccessible(true)

获取与修改变量:

field.set(对象, 21)

field.get(对象)

获取方法:

c.getMethod(方法名, 参数类型列表)

使私有方法可访问:

method.setAccessible(true)

调用方法:

method.invoke(对象,参数数据)

网络初步控制台操作命令含义ipconfigip config,获取ip配置。ipconfig /all 获取更详细的ip配置ping ip本意乒乓球声。引申为测试IP连通性,即“敲”另一台主机,只要写ip,不要写协议,http://ip 是错误的Socket本意插座插孔,译为套接字,指两个主机使用“IP:端口”进行TCP/IP协议连接通信,即插了一个插头到目的主机的某端口上ServerSocket服务器套接字,在某端口启动服务,则Socket可以向某端口发送消息

以下通过一个小型聊天室介绍用法,收信器即服务器。由于没有两个主机,通过服务端口不同以作区别。

import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.OutputStreamWriter;import java.net.Socket;import java.util.Scanner;public class 消息发送器 extends Thread { int port; OutputStreamWriter osw; BufferedReader br; Socket socket; Scanner scan = new Scanner(System.in); public 消息发送器(int port) { this.port = port; } public void run() { super.run(); try { while(!isAccess(port)) { Thread.sleep(1000); } br = new BufferedReader(new InputStreamReader(socket.getInputStream())); osw = new OutputStreamWriter(socket.getOutputStream()); while(true) { System.out.print(“发送消息到 ” port ” 端口:\n”); String str = scan.nextLine(); osw.write(str “\r\n”); osw.flush(); while(true) { String bring = br.readLine(); if(bring != null) { System.out.println(port ” 端口回复:” bring); break; } } } } catch(Exception e) { e.printStackTrace(); } finally { close(); } } public boolean isAccess(int port) { try { socket = new Socket(“localhost”, port); } catch(IOException e) { System.out.println(“消息发送器正在等待端口 ” port ” 的接收器启动…”); return false; } return true; } public void close() { try { if(br != null) { br.close(); } if(osw != null) { osw.close(); } if(socket != null) { socket.close(); } } catch(IOException e) { e.printStackTrace(); } }}import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.OutputStreamWriter;import java.net.ServerSocket;import java.net.Socket;public class 消息接收器 extends Thread { int port; OutputStreamWriter osw; BufferedReader br; Socket socket; ServerSocket server; public 消息接收器(int port) { this.port = port; } @Override public void run() { super.run(); try { while(!isAccess(port)) { Thread.sleep(1000); } System.out.println(“消息服务已在 ” port ” 端口上启动”); socket = server.accept(); br = new BufferedReader(new InputStreamReader(socket.getInputStream())); osw = new OutputStreamWriter(socket.getOutputStream()); // 循环等待 while(true) { System.out.print(“收到消息:” br.readLine() “\n”); osw.write(“已阅,狗屁不通!\r\n”); osw.flush(); } } catch(Exception e) { System.out.println(“服务器异常”); e.printStackTrace(); } finally { close(); } } public boolean isAccess(int port) { try { server = new ServerSocket(port); } catch(IOException e) { System.out.println(“消息接收器 ” port ” 端口被占用”); return false; } return true; } public void close() { try { if(br != null) { br.close(); } if(osw != null) { osw.close(); } if(socket != null) { socket.close(); } if(server != null) { server.close(); } } catch(IOException e) { System.out.println(“流关闭异常”); e.printStackTrace(); } }}public class 消息收发机1 { public static void main(String[] args) { int port1 = 8999; // 收信器端口 int port2 = 9000; // 发送目标端口 Thread t1; Thread t2; t1 = new 消息接收器(port1); t1.start(); t2 = new 消息发送器(port2); t2.start(); }}public class 消息收发机2 { public static void main(String[] args) { int port1 = 9000; // 收信器端口 int port2 = 8999; // 发送目标端口 Thread t1; Thread t2; t1 = new 消息接收器(port1); t1.start(); t2 = new 消息发送器(port2); t2.start(); }}

发表评论

登录后才能评论