文章目录
- Java基础知识
- 1.Java源码、字节码、编译过程/编译过程
- 2.在一个 java 文件中,是否可以定义多个类,有什么注意事项?各种java包的作用
- 3.标识符的组成,规则是什么?判断哪些标识符是合法的
- 4.一些特殊运算符的应用: ? 、++ 、- 放到变量前/后的区别;Instanceof的作用是什么?如何使用?
- 5.能让语句不用顺序执行,而是可以跳转/转移的语句有哪些?
- 6.算术运算符、关系运算符的运用 : 能否读一段代码(运算符、判断、循环等综合应用)写出程序运行结果?
- 7.Java 中的垃圾回收机制是怎样的?
- 8.构造方法的特征是什么?
- 9.有参和无参构造方法的编程与应用
- 10.方法重载与方法重写的区别、各自的特征?
- 11.成员方法、类方法如何定义?它们的作用范围有何区别?
- 12.final、static、this、super 等关键字的理解与应用
- 13.package 语句、import 语句的作用是什么?如果 2 个类不在同一个包中,一个类要访问另一个类,怎么做?
- 14.类的继承:子类继承父类,到底继承什么——变量、方法,哪些不被继承?能否读一段相关的程序代码,写出结果?
- 15.能否定义一个类、为其定义成员变量、方法,通过方法来实现某种功能,然后在测试类中创建类的对象,并完成对方法的调用,输出某些值?当变量被设置为私有的,如何读取它/为其赋值?
- 16.如何通过定义有参构造方法来实现创建对象时即对其进行初始化?
- 17.成员变量定义、方法创建、测试程序中创建对象、调用方法
- 18.抽象类与接口的区别;为何要定义抽象类、接口?它们各自有何特殊的作用?
- 19.Java 中一些常用的集合类,在实际编程时各有什么区别/各有什么作用,这些类及其父类都有哪些?
- 20.创建抽象类、接口,然后创建另外的类继承抽象类、实现接口,重写抽象类、接口中的方法;最后在测试类中创建类的对象,完成对方法的调用、输出结果
- 21.public、protected、default、private 这些访问权限在相同包内、不同包内、父子类之间,接口中使用各有什么区别?
- 22.什么是异常?常见的异常类相关的父类、子类有哪些?
- 23.自定义异常类如何去做:能否写一段代码、完成异常类的创建、抛出异常,并在测试程序中,通过 try-catch 来对自定义异常进行处理?
- 24.throw、throws 的作用分别是什么?
- 25.try-catch-finally 结构一各部分的作用分别是什么?
- 26.一些标准的输入、输出流,对应哪些类?字节/字符输入输出流、字节/字符缓冲流等都对应哪些类?字节流如何读取数据?字符流如何读取数据?Java 中的字符对应什么字符集?
- 27.缓冲流的读/写机制是怎样的?
- 28.随机读写与顺序读写有何区别?对应的类分别有哪些?
- 29.awt、swing两种组件包,它们有何区别?各自的特点是什么?
- 30.布局管理器有哪些?各有什么特征?不同容器组件对应的默认布局各是什么?
- 31.GUI编程时,涉及到顶层容器、中间容器、各种组件,它们各自的作用是什么、各对应什么类?
- 32.start()、 run()方法各自的作用是什么?
- 33.线程编程的实际应用——2种方式、run()方法的重写、线程休眠等操作如何实现?
- 34.线程的5种或者4种状态如何理解?多线程如何来访问共享资源?
- 35.URL 信息中包含哪些部分? DNS 的作用是什么?
- 36.Socket 通信机制、步骤是怎样的?TCP、UDP 两种通信原理、编写相关应用的过程/步骤/使用到哪些类?TCP、UDP 两种协议的区别是什么?
Java基础知识
1.Java源码、字节码、编译过程/编译过程
-
Java源码
- Java编程语言书写的程序文件
- 通常以
.java
为扩展名
-
字节码
- 是Java编译器将Java源代码编译生成的中间代码
- 它是**针对Java虚拟机(JVM)**的指令集
- 常以
.class
为扩展名,它是一种平台无关的中间表示,可以在任何支持Java虚拟机的平台上运行。
-
编译过程
- 源代码到字节码:Java源代码(.java文件)首先被Java编译器(javac)编译成字节码。
- 字节码到机器码:当运行Java程序时,Java虚拟机(JVM)加载字节码。
-
编译过程是怎样的
1 . 编写源代码
2 . 使用Java编译器编译源代码
3 . 生成并输出字节码文件
2.在一个 java 文件中,是否可以定义多个类,有什么注意事项?各种java包的作用
- 一个Java文件中可以定义多个类,但是最多只能有一个类被public修饰,并且这个类的类名与文件名必须相同。
- 若这个文件中没有public的类(也可以一个也没有),则文件名随便是一个类的名字即可。
- 需要注意的是,当用javac指令编译有多个类的Java文件时,它会给每一个类生成一个对应的.class 文件。
3.标识符的组成,规则是什么?判断哪些标识符是合法的
- 标识符由字母、下画线、美元符号、和数字构成,但不能以数字开头
- 标识符长度不受限制
- 区分字母的大小写
- 不能与Java语言关键字重名,不能与Java类库中的类名重名
- 标识符中不能由空格、@、#、+、-、/等符号
4.一些特殊运算符的应用: ? 、++ 、- 放到变量前/后的区别;Instanceof的作用是什么?如何使用?
-
一个二元操作符,用于检查它左边的对象是否是它右边的类的实例
-
返回boolean的数据类型
-
null用instanceof跟任何类型比较时都是false
package test01; class Animal {} class Dog extends Animal {} public class Main { public static void main(String arg[]) { Animal animal = new Animal(); if( animal instanceof Animal ) { System.out.println("1111"); // 代码输出结果为1111 } else { System.out.println("2222"); } } }
5.能让语句不用顺序执行,而是可以跳转/转移的语句有哪些?
- 条件语句:
- if-else
- switch
- 循环语句:
- for循环
- while循环
- do-while循环
- 跳转语句:
- break语句
- continue语句
- return语句
- 异常处理语句
- try-catch
- throw
6.算术运算符、关系运算符的运用 : 能否读一段代码(运算符、判断、循环等综合应用)写出程序运行结果?
算术运算符
算术运算符用于执行基本的数学运算,如加法、减法、乘法、除法和取模。这些运算符通常用于数值类型(整型、浮点型)。
+
(加法):用于两个数值相加。-
(减法):从一个数值中减去另一个数值。*
(乘法):用于两个数值相乘。/
(除法):用于一个数值除以另一个数值。%
(取模):用于两个数值相除后的余数。
关系运算符
关系运算符用于比较两个值,结果是布尔值(true或false)。这些运算符通常用于条件语句和循环。
==
(等于):检查两个值是否相等。!=
(不等于):检查两个值是否不相等。>
(大于):检查左边的值是否大于右边的值。<
(小于):检查左边的值是否小于右边的值。>=
(大于或等于):检查左边的值是否大于或等于右边的值。<=
(小于或等于):检查左边的值是否小于或等于右边的值。
这些运算符在编程中非常基础且常用,用于执行各种计算和比较操作。
7.Java 中的垃圾回收机制是怎样的?
Java虚拟机提供了一个系统级的垃圾回收线程,它负责自动回收那些无用对象所占用的内存
8.构造方法的特征是什么?
- 方法名和类名一致
- 没有返回类型
- 可以有多个
- 构造方法的参数必须不同
9.有参和无参构造方法的编程与应用
一个类可以定义多个构造函数
有参构造方法通常用于确保对象在使用前被正确设置
无参构造方法在创建具有默认状态的对象时很有用
-
有参构造方法
-
允许在创建对象时传递参数,以便在对象创建时设置其初始状态
-
public class Person { String name; int age; // 有参构造方法 public Person(String n, int a) { name = n; age = a; } } public class Main { public static void main(String[] args) { Person person = new Person("Alice", 30); System.out.println(person.name); // 输出: Alice System.out.println(person.age); // 输出: 30 } }
-
在这个例子中,
Person
类有一个接受两个参数的构造方法。当我们创建一个Person
对象时,我们必须提供这两个参数。
-
-
无参构造方法
-
不接收任何参数,通常用于初始化对象的一些默认状态
-
public class Car { String color; // 无参构造方法 public Car() { color = "红色"; } } public class Main { public static void main(String[] args) { Car car = new Car(); System.out.println(car.color); // 输出: 红色 } }
-
在这个例子中,
Car
类有一个无参构造方法,它将汽车的颜色设置为默认的“红色”。
-
10.方法重载与方法重写的区别、各自的特征?
方法重载:
- 方法重载发生在同一个类中,当多个方法具有相同的名字但不同的参数列表(参数的数量、类型或顺序不同)时,就发生了方法重载。
- 可以改变访问修饰符
方法重写:
- 方法重写发生在继承关系中,子类重写父类的方法,以实现不同的或更具体的行为。
- 方法名、参数列表和返回类型必须与父类中被重写的方法完全相同。
- 访问修饰符不能比父类中被重写的方法的访问权限更低
总结区别:
- 发生位置:重载发生在同一个类中,而重写发生在继承关系中的子类中。
- 参数和返回类型:重载方法的参数列表必须不同,而重写方法的参数列表必须相同。重载方法的返回类型可以不同,而重写方法的返回类型必须相同。
- 访问修饰符和异常:重载方法可以有不同的访问修饰符和抛出的异常,而重写方法必须遵守特定的规则,如不能降低访问权限,不能抛出更宽泛的异常。
11.成员方法、类方法如何定义?它们的作用范围有何区别?
成员方法(实例方法)
-
定义:成员方法是在类中定义的,不属于类本身,而是属于类的实例(对象)。它们操作对象的状态(属性)。
-
语法:
访问修饰符 返回类型 方法名(参数列表) { // 方法体 }
-
调用:通过类的实例(对象)来调用。
-
作用范围:成员方法可以访问类的所有成员(包括成员变量和成员方法),包括私有成员。
类方法(静态方法)
-
定义:类方法是使用
static
关键字定义的,属于类本身,而不是类的实例。它们通常用于执行不依赖于对象状态的操作。 -
语法:
访问修饰符 static 返回类型 方法名(参数列表) { // 方法体 }
-
调用:通过类名直接调用,无需创建类的实例。
-
作用范围:类方法只能直接访问类的其他静态成员(静态变量和静态方法)。它们不能直接访问类的非静态成员,因为非静态成员需要通过类的实例来访问。
区别
- 调用方式:成员方法通过对象调用,类方法通过类名调用。
- 访问权限:成员方法可以访问类中的所有成员,类方法只能访问静态成员。
- 与对象状态的关系:成员方法通常与对象的状态有关,类方法通常与对象的状态无关。
示例
public class MyClass {
int x; // 成员变量
// 成员方法
void printX() {
System.out.println(x);
}
// 类方法
static void printMessage() {
System.out.println("Hello, World!");
}
}
public class Main {
public static void main(String[] args) {
MyClass myObj = new MyClass();
myObj.x = 10;
myObj.printX(); // 调用成员方法
MyClass.printMessage(); // 调用类方法
}
}
在这个例子中,printX
是一个成员方法,它操作对象的状态(成员变量 x
)。printMessage
是一个类方法,它不依赖于任何对象的状态,可以直接通过类名调用。
12.final、static、this、super 等关键字的理解与应用
final
-
定义:
final
关键字用于限定类、方法和变量。 -
用途:
- 类:一旦一个类被声明为
final
,它就不能被继承。 - 方法:一旦一个方法被声明为
final
,它就不能被重写。 - 变量:
final
变量必须被初始化,并且在第一次被赋值之后就不能再改变。
- 类:一旦一个类被声明为
-
示例:
final class MyFinalClass { } // 不可被继承 public final void myFinalMethod() { } // 不可被重写 final int myFinalVariable = 10; // 一旦赋值,不能改变
static
-
定义:
static
关键字用于定义静态变量、静态方法和类方法。 -
用途:
- 静态变量:属于类本身,而不是类的实例。可以通过类名直接访问。
- 静态方法:属于类本身,可以通过类名直接访问。不能访问非静态变量。
-
示例:
static int myStaticVariable = 20; // 可以通过类名直接访问 public static void myStaticMethod() { } // 可以通过类名直接访问
this
-
定义:
this
关键字用于引用当前对象的成员变量和方法。 -
用途:
- 在构造方法中,
this
可以用来区分局部变量和实例变量。 - 在实例方法中,
this
可以用来引用当前对象本身。
- 在构造方法中,
-
示例:
public class MyClass { int x; int y; public MyClass(int x, int y) { this.x = x; // 引用当前对象的x变量 this.y = y; // 引用当前对象的y变量 } public void display() { System.out.println("x = " + this.x + ", y = " + this.y); // 引用当前对象的x和y变量 } }
super
-
定义:
super
关键字用于引用当前对象的父类。 -
用途:
- 访问父类的构造方法。
- 访问父类的成员变量和方法。
-
示例:
public class ChildClass extends ParentClass { ChildClass() { super(); // 调用父类的构造方法 } void myMethod() { super.myMethod(); // 调用父类的方法 } }
13.package 语句、import 语句的作用是什么?如果 2 个类不在同一个包中,一个类要访问另一个类,怎么做?
package 语句
-
定义:
package
语句用于声明一个类属于哪个包。一个包可以包含多个类文件,而一个类文件只能属于一个包。 -
作用:
- 组织:帮助将相关联的类组织在一起。
- 访问控制:通过包可以控制类的外部访问。
-
语法:
package 包名;
-
示例:
package com.example; // 表示这个类属于com.example包
import 语句
-
定义:
import
语句用于导入包中的类,以便可以直接使用类名而无需完整限定名(包名+类名)。 -
作用:
- 简化访问:简化代码中类的访问,避免每次都使用完整的限定名。
- 避免命名冲突:如果不同包中有相同名称的类,
import
可以避免编译错误。
-
语法:
import 包名.类名;
-
示例:
import com.example.MyClass; // 导入com.example包中的MyClass类
不同包中的类访问
如果两个类不在同一个包中,一个类要访问另一个类,通常需要使用import
语句来导入另一个类的包。例如:
// 假设在com.example包中有MyClass类
package com.example;
public class MyClass {
// ...
}
// 另一个包com.another包中的类想要访问MyClass
package com.another;
import com.example.MyClass; // 导入MyClass类
public class AnotherClass {
public void useMyClass() {
MyClass myInstance = new MyClass();
// ...
}
}
通过这种方式,AnotherClass
类就可以直接使用MyClass
类,而无需完整限定名。这是Java中包机制和import
语句的基本用法。
14.类的继承:子类继承父类,到底继承什么——变量、方法,哪些不被继承?能否读一段相关的程序代码,写出结果?
在Java中,子类继承父类时,会继承父类的非私有(public、protected)成员,包括变量和方法。然而,并非所有的父类成员都会被继承。
继承的成员:
- 变量:
- 非私有变量:子类会继承父类的非私有(public、protected)变量。
- 私有变量:私有变量不会被继承。子类不能直接访问父类的私有变量。
- 方法:
- 非私有方法:子类会继承父类的非私有(public、protected)方法。
- 私有方法:私有方法不会被继承。子类不能直接访问父类的私有方法。
- 构造方法:
- 子类会继承父类的构造方法。子类构造方法可以调用父类的构造方法,使用
super()
关键字。
- 子类会继承父类的构造方法。子类构造方法可以调用父类的构造方法,使用
- 接口:
- 如果父类实现了接口,子类会继承这些接口,并且必须实现这些接口中的所有方法。
不被继承的成员:
- 私有成员:父类的私有成员(包括变量和方法)不会被继承。
- 静态成员:父类的静态成员(包括变量和方法)会被继承,但它们属于类本身,而不是类的实例。因此,在子类中,这些静态成员不能直接访问父类的实例成员。
- final 修饰的成员:如果父类的成员被声明为
final
,它们不能被重写,但子类仍然会继承这些成员。 - 内部类:父类的内部类(如静态内部类或成员内部类)不会被继承。
示例
// 父类
public class ParentClass {
public int publicVar;
protected int protectedVar;
private int privateVar;
public void publicMethod() {
// ...
}
protected void protectedMethod() {
// ...
}
private void privateMethod() {
// ...
}
}
// 子类
public class ChildClass extends ParentClass {
// 子类可以访问父类的非私有成员
}
在这个例子中,ChildClass
继承了 ParentClass
的 publicVar
和 protectedVar
,以及 publicMethod
和 protectedMethod
,但不能继承 privateVar
和 privateMethod
。
15.能否定义一个类、为其定义成员变量、方法,通过方法来实现某种功能,然后在测试类中创建类的对象,并完成对方法的调用,输出某些值?当变量被设置为私有的,如何读取它/为其赋值?
当然可以。在Java中,您可以定义一个类,为它定义成员变量和方法,然后在测试类中创建该类的对象,并调用其方法。如果成员变量被设置为私有的,您可以通过提供公共的方法来读取它或为其赋值。
示例
- 定义一个类:
- 创建一个名为
MyClass
的类。 - 定义一个私有成员变量
privateVar
。 - 定义一个公共方法
getPrivateVar()
来读取privateVar
。 - 定义一个公共方法
setPrivateVar(int value)
来设置privateVar
的值。
- 创建一个名为
- 创建测试类:
- 创建一个名为
TestClass
的类。 - 在
main
方法中创建MyClass
的对象。 - 调用
MyClass
对象的方法来读取或设置privateVar
的值。
- 创建一个名为
- 输出结果:
- 输出通过方法获取或设置的值。
代码示例
// MyClass.java
public class MyClass {
private int privateVar;
public int getPrivateVar() {
return privateVar;
}
public void setPrivateVar(int value) {
privateVar = value;
}
}
// TestClass.java
public class TestClass {
public static void main(String[] args) {
MyClass myObject = new MyClass();
// 设置私有变量的值
myObject.setPrivateVar(10);
// 读取私有变量的值
int value = myObject.getPrivateVar();
// 输出值
System.out.println("Private variable value: " + value);
}
}
在这个例子中,MyClass
有一个私有变量privateVar
,以及两个公共方法getPrivateVar()
和setPrivateVar(int value)
。TestClass
中创建了MyClass
的对象,并调用了这些方法来读取和设置privateVar
的值。通过这种方式,即使privateVar
是私有的,我们仍然可以对其进行读取和赋值。
16.如何通过定义有参构造方法来实现创建对象时即对其进行初始化?
在Java中,通过定义有参构造方法可以在创建对象的同时对其进行初始化。有参构造方法是一种特殊的方法,它在对象创建时自动被调用,用于初始化对象的属性。下面是一个简单的例子来说明这个过程:
假设我们有一个名为Person
的类,它有两个属性:name
和age
。我们想要在创建Person
对象时立即设置这两个属性的值。
public class Person {
// 定义类的属性
String name;
int age;
// 定义有参构造方法
public Person(String n, int a) {
name = n;
age = a;
}
// 一个方法来展示Person的信息
public void displayInfo() {
System.out.println("Name: " + name + ", Age: " + age);
}
public static void main(String[] args) {
// 使用有参构造方法创建Person对象
Person person1 = new Person("Alice", 30);
Person person2 = new Person("Bob", 25);
// 显示Person对象的信息
person1.displayInfo(); // 输出: Name: Alice, Age: 30
person2.displayInfo(); // 输出: Name: Bob, Age: 25
}
}
在这个例子中,Person
类有一个接受两个参数的构造方法:name
和age
。当我们创建Person
对象时,我们传递相应的参数给构造方法,如new Person("Alice", 30)
。构造方法内部使用这些参数来初始化对象的属性。这样,每个Person
对象在创建时就有了自己的name
和age
值。
17.成员变量定义、方法创建、测试程序中创建对象、调用方法
在Java编程中,成员变量的定义、方法的创建、以及测试程序中对象的创建和方法的调用是面向对象编程的核心概念。
成员变量定义
成员变量是定义在类中的变量,它们代表对象的属性或状态。成员变量可以是任何类型,包括基本数据类型(如int
、float
)和对象类型。
public class Car {
// 成员变量
String color;
int doors;
double price;
}
在这个例子中,Car
类有三个成员变量:color
、doors
和 price
。
方法创建
方法是一系列执行特定任务的语句。在Java中,方法必须定义在类中。方法可以接受参数,也可以返回值。
public class Car {
// 成员变量
String color;
int doors;
double price;
// 方法
public void displayInfo() {
System.out.println("Color: " + color);
System.out.println("Doors: " + doors);
System.out.println("Price: " + price);
}
}
在这个例子中,Car
类有一个方法 displayInfo
,它打印出车的颜色、门数和价格。
测试程序中创建对象
在Java程序中,你可以通过创建类的实例(对象)来使用类。这通常在 main
方法中完成。
public class TestCar {
public static void main(String[] args) {
// 创建Car对象
Car myCar = new Car();
myCar.color = "Red";
myCar.doors = 4;
myCar.price = 25000.0;
// 调用方法
myCar.displayInfo();
}
}
在这个例子中,我们创建了一个 Car
类的实例 myCar
,并设置了它的成员变量。然后,我们调用了 displayInfo
方法来显示车的信息。
调用方法
一旦创建了对象,就可以通过对象来调用其方法。在上面的例子中,myCar.displayInfo();
就是调用 displayInfo
方法的方式。
这些是Java编程中最基础的概念,它们构成了面向对象编程的核心。通过组合使用成员变量和方法,你可以创建具有复杂行为的对象模型。
18.抽象类与接口的区别;为何要定义抽象类、接口?它们各自有何特殊的作用?
抽象类和接口在Java中都是用来定义抽象层次和实现多态的。它们之间有一些关键的区别,并且各自有不同的用途。
抽象类示例
假设我们有一个关于动物的抽象概念,动物都有名字和年龄,但它们的行为(如移动和发声)因动物而异。我们可以创建一个抽象类Animal
来定义这些共通属性和行为。
public abstract class Animal {
private String name;
private int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public abstract void makeSound(); // 抽象方法,子类必须实现
public void move() {
System.out.println("Animal moves");
}
public void displayInfo() {
System.out.println("Name: " + name + ", Age: " + age);
}
}
public class Dog extends Animal {
public Dog(String name, int age) {
super(name, age);
}
@Override
public void makeSound() {
System.out.println("Woof! Woof!");
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog("Buddy", 3);
dog.displayInfo(); // 输出: Name: Buddy, Age: 3
dog.makeSound(); // 输出: Woof! Woof!
dog.move(); // 输出: Animal moves
}
}
在这个例子中,Animal
是一个抽象类,它定义了makeSound
这个抽象方法,并实现了move
方法。Dog
类继承自Animal
,并实现了makeSound
方法。
接口示例
现在,假设我们想要定义一个可以飞翔的动物的概念。我们可以创建一个接口Flyable
来描述这个行为。
public interface Flyable {
void fly(); // 接口中所有方法默认是抽象的
}
public class Bird extends Animal implements Flyable {
public Bird(String name, int age) {
super(name, age);
}
@Override
public void makeSound() {
System.out.println("Chirp! Chirp!");
}
@Override
public void fly() {
System.out.println("Bird is flying");
}
}
public class Main {
public static void main(String[] args) {
Bird bird = new Bird("Sparrow", 1);
bird.displayInfo(); // 输出: Name: Sparrow, Age: 1
bird.makeSound(); // 输出: Chirp! Chirp!
bird.fly(); // 输出: Bird is flying
}
}
在这个例子中,Flyable
是一个接口,它定义了fly
方法。Bird
类实现了Animal
类和Flyable
接口,因此它既有动物的基本属性和行为,也能飞翔。
抽象类(Abstract Class)
- 定义:抽象类是不能被实例化的类,它可能包含抽象方法(没有具体实现的方法)和具体实现的方法。
- 特点:
- 可以包含具体实现的方法和抽象方法。
- 可以包含成员变量,包括非final变量。
- 可以继承另一个类(抽象类或具体类)并添加新的抽象方法或实现。
- 一个类只能继承一个抽象类,因为Java不支持多重继承。
接口(Interface)
- 定义:接口是一种完全抽象的类,用于定义公共的方法和常量。Java 8之后,接口也可以包含默认方法和静态方法。
- 特点:
- 只能包含抽象方法和默认方法(Java 8+)。
- 所有方法默认是
public
的。 - 所有变量默认是
public static final
的。 - 一个类可以实现多个接口。
为什么使用抽象类和接口?
抽象类的使用场景:
- 部分实现:当你想在一个类中实现一些方法,但让子类实现其他方法时,可以使用抽象类。
- 共享代码:如果多个相关的类共享代码,可以将这些共享的代码放在抽象类中。
- 构造逻辑:抽象类可以有构造器,因此可以提供初始化逻辑。
接口的使用场景:
- 多继承:Java不支持类的多重继承,但通过实现多个接口,一个类可以继承多个接口的属性和行为。
- 解耦:接口提供了一种解耦的方式,使得实现类可以改变而不影响使用接口的代码。
- 功能扩展:接口可以用来定义一个类应该实现的方法,而不需要关心这些方法是如何实现的。
总结
- 抽象类更适合于那些部分实现或共享代码的情况,而接口则适合于定义完全抽象的API和实现多继承。
- 在设计系统时,选择抽象类还是接口取决于你要实现的功能和设计需求。抽象类更接近于传统的面向对象设计,而接口则提供了更大的灵活性和解耦。
19.Java 中一些常用的集合类,在实际编程时各有什么区别/各有什么作用,这些类及其父类都有哪些?
- List
- ArrayList: 适用于频繁访问元素,基于动态数组。
- LinkedList: 适用于频繁插入和删除元素,基于双向链表。
- Set
- HashSet: 快速访问元素,无序,不允许重复。
- LinkedHashSet: 保留插入顺序,稍慢于HashSet。
- TreeSet: 元素排序,较慢。
- Queue
- PriorityQueue: 优先级队列,基于堆。
- LinkedList: 可用作队列或栈。
- ArrayDeque: 高效的栈和队列实现。
- Map
- HashMap: 快速访问键值对,无序。
- LinkedHashMap: 保留插入顺序。
- TreeMap: 键排序。
20.创建抽象类、接口,然后创建另外的类继承抽象类、实现接口,重写抽象类、接口中的方法;最后在测试类中创建类的对象,完成对方法的调用、输出结果
步骤 1: 创建抽象类
首先,我们创建一个名为Animal
的抽象类,它有一个抽象方法makeSound
。
public abstract class Animal {
public abstract void makeSound();
}
步骤 2: 创建接口
接下来,我们创建一个名为Flyable
的接口,它有一个抽象方法fly
。
public interface Flyable {
void fly();
}
步骤 3: 创建继承抽象类并实现接口的类
然后,我们创建一个名为Bird
的类,它继承Animal
类并实现Flyable
接口。我们还需要重写makeSound
和fly
方法。
public class Bird extends Animal implements Flyable {
@Override
public void makeSound() {
System.out.println("Chirp! Chirp!");
}
@Override
public void fly() {
System.out.println("The bird is flying.");
}
}
步骤 4: 创建测试类并调用方法
最后,我们创建一个名为Test
的测试类,在其中创建Bird
类的对象,并调用makeSound
和fly
方法。
public class Test {
public static void main(String[] args) {
Bird bird = new Bird();
bird.makeSound(); // 输出: Chirp! Chirp!
bird.fly(); // 输出: The bird is flying.
}
}
这个例子展示了如何在Java中创建抽象类、接口,以及如何创建其他类来继承抽象类和实现接口,并重写它们的方法。最后,我们在测试类中创建了对象并调用了这些方法。
21.public、protected、default、private 这些访问权限在相同包内、不同包内、父子类之间,接口中使用各有什么区别?
在Java中,访问权限修饰符(public
、protected
、default
和 private
)决定了类、方法或变量的可见性。这些修饰符在不同的上下文(相同包内、不同包内、父子类之间,接口中使用)中有着不同的作用。
相同包内
public
:同一包内的任何类都可以访问。protected
:同一包内的任何类都可以访问。default
(包私有):同一包内的任何类都可以访问。private
:仅在同一类中可以访问。
不同包内
public
:任何包中的任何类都可以访问。protected
:只有子类(即使在不同的包中)和同一包内的类可以访问。default
(包私有):只有同一包内的类可以访问。private
:仅在同一类中可以访问。
父子类之间
public
:子类可以访问父类的public
成员。protected
:子类可以访问父类的protected
成员。default
(包私有):如果子类和父类在同一个包内,子类可以访问;否则,不能访问。private
:子类不能访问父类的private
成员。
接口中使用
接口中的成员默认是public
的,因此,接口中的方法不能使用private
、protected
或default
访问修饰符。接口中的方法总是public
的,而接口中的字段总是public static final
的。
总结
public
:最宽松的访问权限,任何地方都可以访问。protected
:允许同一包内的类访问,以及不同包内的子类访问。default
(包私有):仅允许同一包内的类访问。private
:最严格的访问权限,仅允许在同一类中访问。
理解这些访问权限对于设计良好的Java程序结构至关重要,它们帮助确保封装性和数据的安全性。
22.什么是异常?常见的异常类相关的父类、子类有哪些?
异常是程序运行时所遇到的非正常情况或意外行为。
- Java中所有异常的类型都是Throwable类的子类
- Error
- Exception
- IOException
- RuntimeException
23.自定义异常类如何去做:能否写一段代码、完成异常类的创建、抛出异常,并在测试程序中,通过 try-catch 来对自定义异常进行处理?
在Java中,自定义异常类通常是通过扩展Exception
类来实现的。下面将演示如何创建一个自定义异常类、如何抛出这个异常,以及如何在测试程序中使用try-catch
块来处理这个异常。
步骤1:创建自定义异常类
首先,你需要创建一个继承自Exception
类的自定义异常类。
public class MyCustomException extends Exception {
public MyCustomException(String message) {
super(message);
}
}
在这个例子中,MyCustomException
是一个自定义异常类,它有一个接收字符串参数的构造函数,这个参数是异常的消息。
步骤2:在方法中抛出异常
接下来,你可以在一个方法中抛出这个自定义异常。
public class MyClass {
public void doSomething(int value) throws MyCustomException {
if (value < 0) {
throw new MyCustomException("Negative value is not allowed: " + value);
}
// 正常的逻辑
}
}
在这个例子中,doSomething
方法在传入的值小于0时会抛出 MyCustomException
。
步骤3:在测试程序中使用 try-catch 处理异常
最后,你可以在测试程序中使用try-catch
块来处理这个异常。
public class TestMyException {
public static void main(String[] args) {
MyClass myClass = new MyClass();
try {
myClass.doSomething(-1);
} catch (MyCustomException e) {
System.out.println("Caught an exception: " + e.getMessage());
}
}
}
在这个例子中,当 doSomething
方法抛出 MyCustomException
时,catch
块会捕获这个异常,并打印出异常消息。
这样,你就完成了一个自定义异常类的创建、抛出异常,并在测试程序中通过 try-catch
来处理自定义异常的整个过程。
24.throw、throws 的作用分别是什么?
throw
关键字
-
作用:
throw
关键字用于在代码中显式抛出一个异常。 -
用法:在方法内部,当检测到某个条件或错误时,可以通过
throw
语句抛出一个异常。这个异常可以是系统提供的异常,也可以是自定义的异常。 -
示例:
if (someCondition) { throw new MyCustomException("Some error occurred"); }
throws
关键字
-
作用:
throws
关键字用于在方法声明中声明该方法可能抛出的异常。 -
用法:
throws
关键字放在方法签名后的()
中,后面跟着一个或多个异常类的名称。这告诉调用者这个方法可能会抛出这些异常,因此调用者需要处理这些异常。 -
示例:
public void myMethod() throws MyCustomException { // 方法实现 }
区别
- 抛出位置:
throw
用于在代码中抛出异常,而throws
用于在方法签名中声明可能抛出的异常。 - 作用范围:
throw
用于单个代码块或方法中的特定条件,而throws
用于方法签名,影响整个方法的调用。 - 异常处理:
throw
抛出的异常需要立即处理(通常使用try-catch
块),而throws
声明的异常在方法调用链中向上传递,直到找到一个合适的catch
块来处理它。
25.try-catch-finally 结构一各部分的作用分别是什么?
try
块包含可能抛出异常的代码。catch
块用于捕获和处理try
块中抛出的异常。finally
块用于执行清理或资源释放等代码,无论是否发生异常。
26.一些标准的输入、输出流,对应哪些类?字节/字符输入输出流、字节/字符缓冲流等都对应哪些类?字节流如何读取数据?字符流如何读取数据?Java 中的字符对应什么字符集?
-
字节流(Byte Streams)
- 标准输入输出流
System.in
: 标准输入流,用于读取键盘输入。System.out
: 标准输出流,用于打印输出到控制台。System.err
: 标准错误输出流,用于打印错误信息到控制台。
- 字节流类
InputStream
: 所有字节输入流的超类。OutputStream
: 所有字节输出流的超类。
- 字节缓冲流
BufferedInputStream
: 提供带缓冲的输入流。BufferedOutputStream
: 提供带缓冲的输出流。
字符流(Character Streams)
- 字符流类
Reader
: 所有字符输入流的超类。Writer
: 所有字符输出流的超类。
- 字符缓冲流
BufferedReader
: 提供带缓冲的字符输入流。BufferedWriter
: 提供带缓冲的字符输出流。
读取数据
- 字节流读取数据:使用
read()
方法从输入流中读取数据。例如,int read()
方法从输入流中读取一个字节,并返回它的整数形式。 - 字符流读取数据:使用
read()
方法从输入流中读取字符。例如,int read()
方法从输入流中读取一个字符,并返回它的整数形式(使用char
的编码)。
Java中的字符集
Java中的字符是由
char
类型表示的,它占用16位,可以表示16位Unicode字符。Java使用UTF-16字符集,这意味着Java中的字符通常占用两个字节,但有些字符(如Surrogate Pair)可能需要四个字节来表示。示例
-
字节流读取:
InputStream in = new FileInputStream("example.txt"); int data = in.read(); while ((data = in.read()) != -1) { System.out.print((char) data); } in.close();
-
字符流读取:
Reader reader = new FileReader("example.txt"); int ch; while ((ch = reader.read()) != -1) { System.out.print((char) ch); } reader.close();
这些是Java中处理输入输出的基本概念和类。在实际应用中,选择合适的输入输出流类型取决于具体的需求和数据类型。
- 标准输入输出流
27.缓冲流的读/写机制是怎样的?
读取机制
- 缓冲区:缓冲流在内存中维护一个缓冲区(Buffer)。缓冲区是一个固定大小的数组,用于临时存储数据。
- 读取操作:当从底层流(如文件、网络等)读取数据时,数据首先被读取到缓冲区中。缓冲区满了之后,再从底层流中读取新的数据。
- 使用缓冲区:在读取数据时,程序首先从缓冲区中获取数据,而不是直接从底层流中获取。这样可以减少对底层流的访问次数。
- 刷新缓冲区:通过调用缓冲流的
flush()
方法,可以将缓冲区中的数据写入底层流。 - 关闭缓冲流:关闭缓冲流时,会先刷新缓冲区,确保所有数据都被写入底层流,然后关闭底层流。
写入机制
- 缓冲区:缓冲流同样使用一个缓冲区来存储写入的数据。
- 写入操作:当向底层流写入数据时,数据首先被写入缓冲区。缓冲区满了之后,再写入新的数据到底层流。
- 使用缓冲区:在写入数据时,程序首先将数据写入缓冲区,而不是直接写入底层流。这样可以减少对底层流的写入次数。
- 刷新缓冲区:通过调用缓冲流的
flush()
方法,可以将缓冲区中的数据写入底层流。 - 关闭缓冲流:关闭缓冲流时,会先刷新缓冲区,确保所有数据都被写入底层流,然后关闭底层流。
28.随机读写与顺序读写有何区别?对应的类分别有哪些?
随机读写
- 定义:随机读写允许直接访问文件中的任意位置,而不需要按顺序读取。
- 特点:
- 快速:可以快速访问文件中的任意部分。
- 灵活:不需要从文件的开始位置读取。
- 适用场景:
- 读取或写入文件的特定部分。
- 更新文件中的数据。
- 类:
RandomAccessFile
:提供了随机访问文件的能力,可以读取和写入文件中的任意位置。
顺序读写
- 定义:顺序读写要求按顺序从文件的开始位置读取或写入数据。
- 特点:
- 简单:只需按顺序读取或写入。
- 高效:适用于大量数据的顺序处理。
- 适用场景:
- 读取或写入文件的全部内容。
- 需要按顺序处理大量数据。
- 类:
FileInputStream
和FileOutputStream
:用于顺序读写文件。BufferedInputStream
和BufferedOutputStream
:提供了带缓冲的顺序读写流,提高了效率。
29.awt、swing两种组件包,它们有何区别?各自的特点是什么?
AWT
- 历史背景:AWT是Java早期版本中的GUI库,它是Java图形界面工具包的基础。
- 底层实现:AWT依赖于本地窗口系统,这意味着AWT组件的外观和行为会受到平台的影响。
- 组件类型:AWT包含一些基础的GUI组件,如按钮、文本框、窗口等。
- 性能:由于AWT依赖于本地窗口系统,它的性能通常不如Swing。
- 功能:AWT组件的功能相对较少,主要用于创建基础的GUI应用程序。
Swing
- 历史背景:Swing是AWT的扩展,由Sun Microsystems开发,提供了更加丰富的GUI组件和功能。
- 独立实现:Swing是纯Java实现的,与本地窗口系统无关,因此具有更好的跨平台一致性。
- 组件类型:Swing提供了大量的GUI组件,包括高级组件如菜单、对话框、表格、树等。
- 性能:Swing的性能通常优于AWT,因为它避免了频繁的本地系统调用。
- 功能:Swing组件功能丰富,可以创建更复杂和更专业的GUI应用程序。
总结
- AWT:适合创建基础的GUI应用程序,易于使用,但性能和功能有限,受限于平台。
- Swing:适合创建跨平台、功能丰富的GUI应用程序,性能较好,组件丰富,但学习和使用成本相对较高。
30.布局管理器有哪些?各有什么特征?不同容器组件对应的默认布局各是什么?
在Java的Swing库中,布局管理器(Layout Manager)用于控制容器内组件的布局。不同的布局管理器有不同的特征和用途。以下是一些常用的布局管理器及其特征:
- 边界布局
- 特征:将容器分为5个区域:北(North)、南(South)、东(East)、西(West)、中(Center)。每个区域只能放置一个组件。
- 适用场景:需要将容器分成几个独立区域的简单布局。
- 流式布局
- 特征:组件按照从上到下、从左到右的顺序排列。当一行排满时,自动换行。
- 适用场景:需要简单、一致的布局,如标签页或简单对话框。
- 网格布局
- 特征:将容器分为等大的网格,每个网格只能放置一个组件。
- 适用场景:需要均匀分布组件的情况。
不同容器组件对应的默认布局
- JFrame:
BorderLayout
- JPanel:
FlowLayout
31.GUI编程时,涉及到顶层容器、中间容器、各种组件,它们各自的作用是什么、各对应什么类?
- 顶层容器
- JFrame
- 中间容器
- JPanel
- 各类组件
- JButton
- JTextArea
- JList
32.start()、 run()方法各自的作用是什么?
start()方法
- 作用:启动一个线程,并调用该线程的
run()
方法。 - 特点:
start()
方法是线程的入口点,当调用一个线程的start()
方法时,Java虚拟机(JVM)会启动一个新的线程,并执行run()
方法中的代码。start()
方法会自动调用run()
方法,因此不需要显式调用run()
方法。start()
方法是创建多线程的关键,它允许线程执行并发操作。
run()方法
- 作用:执行线程的特定任务。
- 特点:
run()
方法包含线程要执行的代码。- 每个线程都有其自己的
run()
方法,当线程启动时,它会执行这个方法。 run()
方法不能直接启动线程,它只是定义了线程启动后要执行的任务。- 即使没有调用
start()
方法,也可以单独调用run()
方法,但这不会在线程中执行任务,而是在当前线程中执行。
33.线程编程的实际应用——2种方式、run()方法的重写、线程休眠等操作如何实现?
线程编程在实际应用中广泛用于并发处理、网络通信、多用户界面响应等场景。Java提供了多种方式来创建和控制线程。下面是两种常见的方式来创建线程,以及如何使用run()
方法重写和实现线程休眠等操作。
1. 继承Thread
类
这种方式是最简单直接的,通过继承Thread
类并重写其run()
方法来创建线程。
class MyThread extends Thread {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
try {
Thread.sleep(1000); // 线程休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadExample {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("Thread-1");
t2.setName("Thread-2");
t1.start();
t2.start();
}
}
2. 实现Runnable
接口
这种方式创建线程更为通用,因为Java不支持多重继承,所以如果你已经有一个类,无法继承Thread
类,就可以通过实现Runnable
接口来实现线程功能。
class MyRunnable implements Runnable {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
try {
Thread.sleep(1000); // 线程休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadExample {
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
Thread t2 = new Thread(new MyRunnable());
t1.setName("Thread-1");
t2.setName("Thread-2");
t1.start();
t2.start();
}
}
线程休眠操作
使用Thread.sleep(milliseconds)
方法可以让线程暂停执行指定的毫秒数。这个方法可能会抛出InterruptedException
,因此通常需要捕获这个异常。
try {
Thread.sleep(1000); // 线程休眠1000毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
线程的其他操作
- 设置线程优先级:
setPriority(int priority)
方法可以设置线程的优先级,范围从1到10,其中1是最低优先级,10是最高优先级。 - 线程等待:
wait()
方法可以使线程进入等待状态,直到其他线程调用notify()
或notifyAll()
方法。 - 线程同步:通过同步代码块或同步方法来确保多个线程访问共享资源时的数据一致性。
线程编程在多线程环境中非常重要,它可以提高程序的效率和响应速度。在实际应用中,需要根据具体需求选择合适的线程创建和管理方式。
34.线程的5种或者4种状态如何理解?多线程如何来访问共享资源?
线程的5种状态
在Java中,线程可以处于以下五种状态之一:
- 新建(New):当线程被创建但尚未启动时,它处于这种状态。即,已经创建了线程对象,但还没有调用
start()
方法。 - 就绪(Runnable):线程已经准备好运行,等待被线程调度器选中。一旦被选中,它将进入运行状态。线程进入就绪状态的情况包括:新建线程调用
start()
方法、当前运行线程让出CPU(比如调用yield()
方法)、或者当前运行线程完成了一些特定操作(如等待I/O操作完成)。 - 运行(Running):线程调度器选中了就绪状态的线程,该线程正在执行其
run()
方法中的代码。 - 阻塞(Blocked):线程因为某些原因放弃CPU,暂时停止运行。阻塞状态通常发生在线程等待获取一个排他锁,或者等待某些条件成立(如等待另一个线程的通知)。
- 终止(Terminated):线程完成了它的执行,或者因为异常退出了
run()
方法。一旦线程进入终止状态,它就不能再次启动。
多线程访问共享资源
在多线程环境中,多个线程可能需要访问共享资源,如共享变量或共享对象。为了确保线程安全,避免数据不一致或竞态条件,通常需要采取以下措施:
- 同步(Synchronization):使用
synchronized
关键字来同步方法或代码块。这确保同一时间只有一个线程可以执行同步代码。 - 锁(Locks):使用
java.util.concurrent.locks.Lock
接口的实现,如ReentrantLock
,提供比传统synchronized
更灵活的锁定机制。 - 原子操作:使用原子类(如
AtomicInteger
、AtomicLong
等)来执行原子操作,这些类通过使用底层的CAS(Compare-And-Swap)操作来保证原子性。 - volatile关键字:对于一些简单的场景,使用
volatile
关键字可以确保变量的可见性,即一个线程对volatile
变量的修改对其他线程立即可见。 - 线程安全的数据结构:使用Java并发包(java.util.concurrent)中提供的线程安全的数据结构,如
ConcurrentHashMap
、CopyOnWriteArrayList
等。 - 线程局部变量(ThreadLocal):使用
ThreadLocal
变量为每个使用该变量的线程提供一个独立的变量副本,从而避免共享。
通过这些机制,可以有效地管理多线程对共享资源的访问,确保程序的正确性和稳定性。
35.URL 信息中包含哪些部分? DNS 的作用是什么?
URL 信息包含的部分
URL(Uniform Resource Locator,统一资源定位符)是互联网上用来指定信息位置的字符串。一个标准的URL包含以下几个部分:
- 协议(Scheme):指定用于访问资源的协议,如
http
、https
、ftp
等。 - 主机名(Host):资源所在的服务器的域名或IP地址。
- 端口号(Port):可选,用于访问服务的端口号。如果不指定,则使用协议的默认端口(如HTTP的默认端口是80,HTTPS的默认端口是443)。
- 路径(Path):指定资源在服务器上的路径。
- 查询字符串(Query):可选,以
?
开始,用于传递参数给服务器。 - 片段标识符(Fragment):可选,以
#
开始,用于指定网页中的一个位置。
例如,在URLhttps://www.example.com:443/path/to/resource?query=param#fragment
中:
- 协议是
https
- 主机名是
www.example.com
- 端口号是
443
- 路径是
/path/to/resource
- 查询字符串是
query=param
- 片段标识符是
fragment
DNS 的作用
DNS(Domain Name System,域名系统)是一个分布式数据库,用于将域名和IP地址相互映射。它的主要作用包括:
- 域名解析:将人类可读的域名(如
www.example.com
)转换为机器可读的IP地址(如192.0.2.1
)。 - 提高效率:用户只需记住域名,无需记住复杂的IP地址。
- 容错和高可用性:DNS系统是分布式的,即使某些DNS服务器不可用,其他服务器也可以提供服务。
- 负载均衡:DNS可以分配流量到多个服务器,提高网站的可用性和性能。
- 易于管理:当网站的IP地址更改时,只需更新DNS记录,而不是通知所有用户更改他们的书签或配置。
DNS是互联网基础设施的关键部分,使得用户能够通过简单的域名访问网站,而不是记住每个网站的IP地址。
36.Socket 通信机制、步骤是怎样的?TCP、UDP 两种通信原理、编写相关应用的过程/步骤/使用到哪些类?TCP、UDP 两种协议的区别是什么?
Socket通信机制和步骤
- 创建Socket:在服务器端和客户端分别创建Socket。服务器端Socket通常监听特定端口,客户端Socket用于连接到服务器的Socket。
- 绑定和监听:服务器端Socket需要绑定到一个端口,并进入监听状态,等待客户端的连接。
- 连接:客户端Socket通过指定服务器的IP地址和端口号来连接服务器端的Socket。
- 通信:一旦建立连接,客户端和服务器端就可以通过Socket进行数据传输。
- 关闭Socket:通信完成后,关闭Socket以释放资源。
TCP和UDP通信原理及应用过程
TCP(传输控制协议)
-
原理:提供面向连接的、可靠的、基于字节流的通信服务。TCP协议通过三次握手来建立连接,确保数据传输的可靠性。
-
应用过程
:
- 创建Socket:在服务器端和客户端分别创建Socket。
- 绑定和监听:服务器端Socket绑定到特定端口并监听。
- 连接:客户端Socket连接到服务器端Socket。
- 通信:通过Socket发送和接收数据。
- 关闭Socket:通信完成后关闭Socket。
UDP(用户数据报协议)
- 原理:提供无连接的、不可靠的数据报服务。UDP协议没有建立连接的握手过程,因此速度较快但可靠性较低。
- 应用过程:
- 创建Socket:在服务器端和客户端分别创建Socket。
- 绑定:服务器端Socket绑定到特定端口。
- 发送和接收数据:通过Socket发送和接收数据报。
- 关闭Socket:通信完成后关闭Socket。
使用的类
- Socket类:用于创建Socket。
- ServerSocket类:用于创建服务器端Socket。
- DatagramSocket类:用于创建UDP Socket。
- InetAddress类:用于获取或设置IP地址。
- InputStream和OutputStream类:用于在Socket上读写数据。
- DataInputStream和DataOutputStream类:用于读写原始数据类型。
TCP和UDP的区别
- 连接方式:TCP是面向连接的,而UDP是无连接的。
- 可靠性:TCP提供可靠的数据传输,而UDP不保证数据的可靠性。
- 速度:UDP通常比TCP快,因为它没有建立连接和维护连接的额外开销。
- 适用场景:TCP适用于对数据传输可靠性要求较高的场景,如文件传输、电子邮件等;UDP适用于对速度要求较高的场景,如实时视频、音频传输等。
示例代码
TCP示例
// 服务器端
ServerSocket serverSocket = new ServerSocket(8888);
Socket clientSocket = serverSocket.accept();
DataInputStream in = new DataInputStream(clientSocket.getInputStream());
DataOutputStream out = new DataOutputStream(clientSocket.getOutputStream());
String message = in.readUTF();
out.writeUTF("Received: " + message);
clientSocket.close();
serverSocket.close();
// 客户端
Socket socket = new Socket("localhost", 8888);
DataOutputStream out = new DataOutputStream(socket.getOutputStream());
out.writeUTF("Hello, Server!");
DataInputStream in = new DataInputStream(socket.getInputStream());
String response = in.readUTF();
System.out.println("Server says: " + response);
socket.close();
UDP示例
// 服务器端
DatagramSocket serverSocket = new DatagramSocket(8888);
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
serverSocket.receive(packet);
String message = new String(packet.getData()).trim();
System.out.println("Received: " + message);
// 客户端
DatagramSocket clientSocket = new DatagramSocket();
buffer = "Hello, Server!".get
P和UDP的区别
- **连接方式**:TCP是面向连接的,而UDP是无连接的。
- **可靠性**:TCP提供可靠的数据传输,而UDP不保证数据的可靠性。
- **速度**:UDP通常比TCP快,因为它没有建立连接和维护连接的额外开销。
- **适用场景**:TCP适用于对数据传输可靠性要求较高的场景,如文件传输、电子邮件等;UDP适用于对速度要求较高的场景,如实时视频、音频传输等。
##### 示例代码
##### TCP示例
```java
// 服务器端
ServerSocket serverSocket = new ServerSocket(8888);
Socket clientSocket = serverSocket.accept();
DataInputStream in = new DataInputStream(clientSocket.getInputStream());
DataOutputStream out = new DataOutputStream(clientSocket.getOutputStream());
String message = in.readUTF();
out.writeUTF("Received: " + message);
clientSocket.close();
serverSocket.close();
// 客户端
Socket socket = new Socket("localhost", 8888);
DataOutputStream out = new DataOutputStream(socket.getOutputStream());
out.writeUTF("Hello, Server!");
DataInputStream in = new DataInputStream(socket.getInputStream());
String response = in.readUTF();
System.out.println("Server says: " + response);
socket.close();
UDP示例
// 服务器端
DatagramSocket serverSocket = new DatagramSocket(8888);
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
serverSocket.receive(packet);
String message = new String(packet.getData()).trim();
System.out.println("Received: " + message);
// 客户端
DatagramSocket clientSocket = new DatagramSocket();
buffer = "Hello, Server!".get