目录
一 数据类型
1.1 8种基本数据类型
1.2 引用数据类型
1.3 java内存机制
1.3.1 寄存器
1.3.2 栈
1.3.3 堆
1.3.4. 静态区/方法区
1.3.5. 运行时常量池(Runtime Constant Pool)
1.3.6 直接内存(Direct Memory)
二:面向对象思想
2.1封装
2.2 继承
2.3 多态
2.3.1 方法重载(Overloading)
2.3.2 方法覆盖(Overriding)
2.4 抽象
2.4.1 抽象类
2.4.2 接口
2.4.3 接口和抽象类区别
三 特殊类
3.1实名内部类
3.2 匿名内部类
3.3 内部类的好处
3.3.1 进一步实现多重继承(通过内部类实现多类继承)
3.3.2 更好解决接口和父类中的方法同名问题
3.3.3 隐藏内部细节、无条件访问外部类成员
3.4 泛型类
3.5 class类
四 数组/字符串/枚举
4.1 数组
4.2 字符串
4.3 枚举
五 JAVA常用类/接口
5.1 java.lang包
5.1.1 Object类
5.1.2 Math类
5.1.3 System类
5.1.4 Runtime类
5.2 java.util包
5.2.1 Date类
5.2.2 Calendar类
5.2.3 Random类
5.2.4 集合
六 集合
6.1 有序可重复集合:List接口和ArrayList/LinkedList/Vector类
6.1.1 Arraylist类
6.1.2 LinkedList类
6.1.3 Vector类
6.2 非重复集合:Set接口和HashSet、TreeSet、LinkedHashSet类
6.4 映射集合:Map接口和TreeMap类
6.4.1 TreeMap
七 多线程
7.1 实现多线程的两种方法
7.1.1 继承Thread类
7.1.2 实现Runable接口
7.2 线程常用的控制方法
7.4 线程互斥同步
7.4.1 同步语句块实例
7.4.2 同步化方法实例
7.4 后台线程
八:java 流和文件
8.1 字节流/字符流介绍
8.1.1 字节流
8.1.2 字符流
8.1.3 字符缓冲流
8.1.4 字节流字符流异同
8.2 文件
8.3 对象序列化
九 网络编程
9.1 URL编程
9.1.1 URL类
9.1.2 URLConnection
9.2 Socket编程
9.3 Socket和URLConnection的异同
布尔 (1字节)
char (2字节)
byte,short,int,long (1字节,2字节,4字节,8字节)
float,doule (4字节精确到7位有效数字,8字节精确到16位有效数字)
* Java常量:
正无穷 Double.POSITIVE_INFINITY
负无穷 Double.NEGATIVE_INFINITY
不是个数 Double.NaN
* 为什么分基本数据类型和引用数据类型?
答:基本数据类型使用极高,因此设计为非对象类型,放栈中,使其存取速度快于堆中类对象。
引用数据类型包括:类、接口、数组
JVM内存 = 堆内存 + 线程数量 * 栈内存
java内存可分为:
我们在程序中无法控制,分支、跳转、循环、异常处理、线程恢复都依赖程序计数器实现;每个线程都有一个单独的程序计数器,所以程序计数器是私有的;
存放局部变量(方法执行完会马上释放局部变量所占栈空间,如果是引用数据类型,同样地,句柄回收掉,堆中的具体对象失去引用会被GC掉)。
存放基本类型的数据和对象的引用,引用的对象放在堆中。
栈中的数据大小与生存期必须是确定的,因此速度快,但缺乏灵活性。
栈有一个很重要的特殊性,就是存在栈中的数据可以共享,每个线程对应着一个虚拟机栈,因此虚拟机栈也是线程私有的,虚拟机栈生命周期与线程相同。
*栈一般指虚拟机栈,还有本地方法栈。本地方法栈与java虚拟机栈所发挥的作用非常相似,它们之间的区别在于java虚拟机栈为执行java方法服务的, 本地方法栈是为虚拟机执行Native方法服务。
存放用new产生的数据
堆区也是Java GC机制所管理的主要内存区域,堆区由所有线程共享,
HotSpot虚拟机开发者习惯称为“永久代”,但本质上两者不等价,仅仅是HotSpot设计团队把GC分代收集扩展至方法区,或者说使用永久带来实现方法区而已;
被所有线程共享区域,用于存放已被虚拟机加载的类信息,常量,静态变量等数据;
被Java虚拟机描述为堆的一个逻辑部分;
垃圾回收很少光顾这个区域,不过也是需要回收的,主要针对常量池回收,类型卸载;
*jdk1.8 已移除永久代,将方法区直接放在一个与堆不相连的本地内存区域,这个区域被叫做元空间。元空间本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:
JDK 8 中永久代向元空间的转换,几点原因:
1、字符串存在永久代中,容易出现性能问题和内存溢出。
2、类及方法的信息等比较难确定其大小,
3、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
4、Oracle 可能会将HotSpot 与 JRockit 合二为一。
*static 修饰的静态变量,什么时候被回收?
答:类被加载的时候,静态变量被分配内存,并且在虚拟机中单独占用内存,静态变量在类被卸载的时候才会被销毁,而类只有在进程结束的时候才会被卸载,也就是说被static修饰的静态变量只有在进程被销毁的时候才会被回收
在JDK1.6及之前,运行时常量池是方法区的一个部分,同时方法区里面存储了类的元数据信息、静态变量、即时编译器编译后的代码(比如spring 使用IOC或者AOP创建bean时,或者使用cglib,反射的形式动态生成class信息等。
在JDK1.7及以后,JVM已经将运行时常量池从方法区中移了出来,在JVM堆开辟了一块区域存放常量池。
*位置:1.6及其之前:方法区中;1.7及之后:堆空间中;
直接内存不是java虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域,但是这部分内存也被频繁使用,甚至导致OutOfMemeory异常出现;
比如:JDJ1.4中引入了NIO(New Input/Output)类,引入了一种基于通道(Channel)和缓冲区(Buffer)的I/O方式,它可以直接使用Native函数库直接分配堆外内存,然后通过一个存在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。
private
default
peotected
public
只能单继承,只能继承父类非private成员变量/方法,父类同名成员变量/方法会被覆盖;(非要用用super关键字)
允许向上转型;
只能重写方法体;同一类的中方法只能重写不能覆盖;父私有的方法没法覆盖;父final方法没法覆盖;
抽象类:包含抽象方法的类。eg:动物就是一个抽象类,老虎狮子猴子人都是这个抽象类的具体实现的派生类。
抽象类中还可以有成员变量,具体方法,构造方法等,但是他绝对包含抽象方法,设计出来就是为了让子类继承实现的,所以不可能会有实例,也不可能用final修饰抽象类。
*在向上转型过程中,向上转型获得的对象,在调用方法时,如果子类存在与父类重名的方法,子类方法会覆盖父类方法;同样地,访问变量时,子类方法无法覆盖父类,该对象也访问不到父类方法。
接口中的方法不含方法体,只由常量和抽象方法组成;
接口中所有变量默认加public、static、final修饰符;
接口中所有方法默认加public、abstract修饰符;
接口不含构造方法;
多继承;
*接口回调:接口就是个角色,实现这些接口的类就是演员,角色的语言和行为方法等就是演员要说的台词和行为。
接口回调其本质与上转型是一样的,不同的是:接口回调是用接口句柄来得到并调用实现这个接口的子类的引用;而上转型则是用父类句柄来得到并调用继承此父类的子类的引用。
接口多继承,抽象类单继承;接口只有常量和抽象方法,抽象类还可以有成员变量和具体成员方法;接口不是类体系一部分;
实名内部类不得与外部类同名;
实名内部类可以是抽象类/接口;
static修饰的实名内部类称为静态实名内部类;
非静态内部类不能有静态方法/静态属性(java类加载顺序是1首先加载类,2执行static变量初始化,3接下来执行对象的创建。如果我们要执行代码中的变量int a 初始化,那么必须先执行加载外部类,再加载内部类,最后初始化静态变量 a ,问题就出在加载内部类上面,我们可以把内部类看成外部类的非静态成员,它的初始化必须在外部类对象创建后以后进行,要加载内部类必须在实例化外部类之后完成 ,java虚拟机要求所有的静态变量必须在对象创建之前完成,这样便产生了矛盾。所以不允许非静态内部类有静态方法/静态属性)。
若有静态属性必须+final(而java常量放在内存中常量池,它的机制与变量是不同的,编译时,加载常量是不需要加载类的,所以就没有上面那种矛盾);
静态内部类可以有静态属性和方法;
给人的感觉就好像把抽象类当成正常类来使用,只不过实现了方法体,并且实际上完成了向上转型;
class SuperA
{
public String SayHello(String s)
{
return s + " Hello";
}
}
class SuperB
{
public int calculation(int i)
{
return ++i;
}
}
public class JInnerTest
{
private class JInnerTest1 extends SuperA
{
}
private class JInnerTest2 extends SuperB
{
public int calculation(int i)
{
return super.calculation(i);
}
}
public String SayHello(String s)
{
return new JInnerTest1().SayHello(s);
}
public int calculation(int i)
{
return new JInnerTest2().calculation(i);
}
public static void main(String args[])
{
JInnerTest test = new JInnerTest();
System.out.println(test.SayHello("Lucas"));
System.out.println(test.calculation(100));
}
}
interface JPet6
{
void name();
}
class JSmallAnimal
{
public void name()
{
System.out.println("class: JSmallAnimal---method: name");
}
}
public class JCat6 extends JSmallAnimal
{
private class JCatInner implements JPet6
{
public void name()
{
System.out.println("class: JCat6.JCatInner---method: name");
}
}
public static void main(String args[])
{
JCat6 cat = new JCat6();
cat.name();
cat.new JCatInner().name();
}
}
泛型的本质是参数化类型,这种参数类型用在类中,该类就称为泛型类。
public class JGeneric{}
public class JGeneric{}
class类能很好地结合java反射技术结合。
数组对象的引用(首地址array(0)的值)存放在栈中,堆中存放数据具体值;二维数组中,一维的值存放在堆中,堆中一维数组再指向堆中二维数组的值所在的地址。
String s = 'hello', 'hello'存放在字符串常量池中;
String s2 = 'hello', s2同样指向字符串常量池中的s所指向的‘hello’;
String s3 = new String('hello'), s3会在堆内存中开辟一个空间,里面存:“hello”;
String s4 = new String('hello'), s4同样会在堆内存中开辟一个新空间,存hello,不和s3同引用;
String类型变量所指向的内存空间内容是不能被改变的;StringBuffer类型变量所指向的内存空间内容是可以被改变的;
*常见java面试题:String s4 = new String("lucas")中,创建了几个对象?
答:1 若字符串常量池有lucas了,创建一个;2 字符串常量池没有lucas了,常量池创建一个,堆内再创建一个;
枚举继承了java.lang.Enum类,所以不能继承其他父类,但是可以实现接口和添加方法;
public enum Color{
RED;
WHITE;
BLACK;
}
java.lang包包含大多数常用的基本类,默认import的。
Math类中的所有方法和属性都是静态,且该类是final(Math类只能使用,不允许更改)
Math.random(): 产生(0,1)的随机数
System.exit(),退出当前java虚拟机;
System.arraycopy(), 从一个数组复制到另一个数组;
System.gc(), 主动对垃圾内存进行回收;
System.currentTimeMillis(), 返回当前时间,毫秒为单位;
System.Nanotime(), 返回当前时间,毫微秒为单位;
System的三个静态变量:System.InputStream in;(键盘) System.PrintStream out;(终端) System.PrintStream err;(屏幕)
一个虚拟机对应一个Runtime实例对象,通过下述方式启动的进程称为该进程的子进程;
Process p = Runtime.getRuntime().exec("notepad.exe c://count.txt");
Runtime类的常用方法还有freeMemory();totalMemory();
Runtime r = Runtime.getRuntime();
空闲空间:r.freemomery()/1024+"KB"
总空间:r.totalmomery()/1024+"KB"
java.util包下包括常用的日期类Date/Calendar,随机数类Random,集合映射等;
Date d = new Date();
获得Calendar类:
Calendar c = Calendar.getInstance();
设置Calendar的日期:
c.set(Calendar.YEAR, 2019);
c.set(Calendar.MONTH, 2);
c.set(Calendar.DATE, 19);
获得Calendar中的具体日期:
c.get(Calendar.YEAR);
c.get(Calendar.MONTH)+1;
c.get(Calendar.DATE);
在当前日期上增加日期:
//计算当前时间100天后的日期
c.add(Calendar.DATE, 100);
判断日期在某日期前/后:
c.after(c1);
c.before(c2);
获得long型时间戳,/根据long型时间戳设置Calendar:
c.getTimeInMillis();
c.setTimeInMillis(long millis);
*时间类型的相互装换:
Date d = c.getTime();
long timestamp = date.getTime();
Date date = new Date(long mylong);
//获取当前的日期
Date date = new Date();
//设置要获取到什么样的时间
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//获取String类型的时间
String createdate = simpleDateFormat.format(date);
SimpleDateFormat simpleDateFormat = newSimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time="1970-01-06 11:45:55";
Date date = simpleDateFormat.parse(time);
System.out.print("Format To times:"+date.getTime());
Date date=new Date();
Calendar cal=Calendar.getInstance();
cal.setTime(date);
Random r = new Random();
r.nextBoolean();//生产50%概率的true和false;
r.nextDouble();//生成[0,1.0)之间的double类型随机数
r.nextInt(int n);//生成[0,n)区间的随机int数
详见C6
Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口。Java集合大致可以分为Set、List、Queue和Map四种体系,其中Set代表无序、不可重复的集合;List代表有序、重复的集合;而Map则代表具有映射关系的集合,Java 5 又增加了Queue体系集合,代表一种队列集合实现。
list表示有序集合,其中的元素可以重复;
ArrayList采用可变大小的“数组”实现List接口,ArrayList对象会随着元素的增加自动扩大其容器。举例的常见的三种List实现类中,该类最常见且效率最高。
常见方法:get(); set(); add(); remove();
LinkedList采用链表结构实现List接口,提供了再List开头和结尾进行get、remove和insert的操作,以便实现堆栈、队列或双端队列;LinkedList的数据结构是一个双向链式结构,没一个对象除了本身外带一个指向前一个元素和后一个元素的引用,因此特性,它相比顺序存储结构ArrayList,插入和删除比较方便,但速度会慢些。
vector采用可变体积的数组实现List接口,但向量中不能存放基本数据与类型的数据,加入的数据必须均为对象。
常见方法:addElement(Object object); removeElement(Object object); removeElementAt(int index); removeAllElements(); insertElementAt(Object object, int index);
set接口表示的集合不能有重复元素。
key-value:Map集合会把键值映射到某一个值上。
主要方法:
Object put (Object key, Oblect value);
Object remove (Object key);
Boolean containsKey(Object key);
Boolean containsValue(Object value);
int size();
boolean isEmpty();
void putAll(Map t);
TreeMap与TreeSet类似,采用有序树的结构实现了Map的子接口SortedMap,按键的升序排列元素。
Thread类实际上也是实现Runable接口实现的,继承Thread类可以轻松获取当前线程信息:this.getName();实现Runable接口获取当前线程信息:Thread.currentThread();
线程睡眠(阻塞状态):Thread.sleep(long millis);
线程唤醒:Thread.interrupt();
线程让步(让是让了,但是谁获得还是操作系统安排分配):Thread.yield();
线程等待:
Thread.join();等待这个线程运行完,再运行主程序中这行代码下面的代码
Thread.join(long millis); 主程序等待这个线程millis时长
Java中,虚拟机通过给每个对象加锁的方式实现多线程同步。Java虚拟机为每个对象准备一把锁(Lock)和等候集(Wait Set),确保任一对象同一时刻只能有一个访问与该对象相关的同步语句块和同步方法。
synchoronized(obj){同步语句块}
synchoronized void fun(){同步化方法的方法体}
public class TestSynchronizedExample1 implements Runnable {
//一共十张票
private int ticket=10;
public void run(){
//一直卖票直到余票为0
while(true) {
synchronized (this) {
if (ticket > 0) {
try {
//为了演示产生的问题,线程在这里睡眠一次
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//线程睡眠结束后,继续当前的票进行销售
System.out.println(Thread.currentThread().getName()+"ÂôƱ-->"+(this.ticket--));
} else {
break;
}
}
}
}
static public void main(String args[]) {
//建立三个售票窗口
TestSynchronizedExample1 ru = new TestSynchronizedExample1();
Thread t = new Thread(ru);
t.setName("窗口1");
Thread t1 = new Thread(ru);
t1.setName("窗口2");
Thread t2 = new Thread(ru);
t2.setName("窗口3");
t.start();
t1.start();
t2.start();
}
}
public class TestSynchronizedExample2 implements Runnable {
//一共十张票
private int ticket=10;
public void run(){
//卖票
while(ticket>0) {
sell();
}
}
public synchronized void sell() {
if (ticket > 0) {
try {
//为了演示问题,线程在这里睡眠一次
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//˯Ãß½áÊøºó£¬¼ÌÐøµ±Ç°µÄƱ½øÐÐÏúÊÛ
System.out.println(Thread.currentThread().getName()+"ÂôƱ-->"+(this.ticket--));
}
}
static public void main(String args[]) {//½¨Á¢Èý¸öÊÛƱ´°¿ÚµÄÏß³ÌÀàÀ´Ä£Äâ´°¿ÚÊÛƱ
TestSynchronizedExample2 ru = new TestSynchronizedExample2();
Thread t = new Thread(ru);
t.setName("窗口1");
Thread t1 = new Thread(ru);
t1.setName("窗口2");
Thread t2 = new Thread(ru);
t2.setName("窗口3");
t.start();
t1.start();
t2.start();
}
}
后台线程:Daemon线程。所有用Thread建立的线程都是前台线程;main就是前台线程;
前后台线程的区别:进程中只要有前台线程没退出,进程就不会终止; 所有前台线程都退出,哪怕现在还有后台线程,进程也会自动结束;
主要方法:判断是不是后台线程:isDaemon();Thread类的setDaemon()来设置为后台线程;
java数据流都在java.io包中;
字节流1字节;常以InputStream和OutputStream为基类;类名大多以Stream为结尾;
字符流2字节;以Reader和Writer为基类;类名大多以Reader和Writer为结尾;
* 所谓的in、out、 输入、输出,都是相对于内存而言的。
输入字节流,InputStream的子类:FileInputStream; BufferdInputStream; DataInputStream;
FileInputStream in = new FileInputStream("d:/java/test.txt");
// 定义一个int类型的变量b,记住每次读取的一个字节
int b = 0;
while (b = in.read() != -1) {
System.out.println((char) b);
}
in.close();
输出字节流,OutputStream的子类:FileOutputStream; BufferedOutputStream; DataOutputStream; PrintStream;
//复制的源文件file1.txt
FileInputStream rf=new FileInputStream("G:/java1/TestVector.java");
//复制的目的文件file2.txt,若不存在,则会自动创建
FileOutputStream wf=new FileOutputStream("G:/java1/file2.txt");
byte b[]=new byte[512];
int count=-1;
while((count=rf.read(b, 0, 512))!=-1)//每次读取512个字节到数组b中,read(byte[] b, int off, int len)返回实际读取数量
wf.write(b,0,count);
rf.close();
wf.close();
输入字符流,Reader的子类:BugfferdReader;FileReader;
//创建FileReader对象,用来读取字符流
FileReader fr = new FileReader("G:/aillo.txt");
//缓冲指定文件的输入
BufferedReader br = new BufferedReader(fr);
//创建FileWriter对象,用来写入字符流
FileWriter fw = new FileWriter("G:/jacky.txt");
//将缓冲对文件的输出
BufferedWriter bw = new BufferedWriter(fw);
String strLine;//定义一个String类型的变量,用来每次读取一行
while (br.ready()) {
strLine = br.readLine();//读取一行
bw.write(strLine); //写入文件
bw.newLine();
System.out.println(strLine);//在屏幕上输出
}
bw.flush(); //刷新该流的缓冲,即将该流输出到目的
bw.close();
br.close();
fw.close();
br.close();
fr.close();
输出字符流,Writer的子类:FileWriter; BufferedWriter;
char c[] = new char[512];
int n, i;
try {
FileWriter wf = new FileWriter("TestFileOutCH.txt");
// 利用InputStreamReader正确的读取中文
System.out.print("请输入中文:");
InputStreamReader isr = new InputStreamReader(System.in);
n = isr.read(c, 0, 512);
wf.write(c);
wf.close();
System.out.println("刚输入的数据为:"+String.valueOf(c,0,n));
} catch (IOException e) {
System.out.println(e);
}
BufferedReader和BufferedWriter以类似缓冲区方式对数据进行输入输出,他们分别拥有8192个字符;
/缓冲System.in输入流
//System.in是字节流,通过InputStreamReader将其转换为字符流
BufferedReader bufReader = new BufferedReader(new InputStreamReader(System.in));
//缓冲FileWriter
BufferedWriter bufWriter = new BufferedWriter(new FileWriter(args[0]));
String input = null;
//每读一行进行一次写入动作
while(!(input = bufReader.readLine()).equals("quit")){
bufWriter.write(input);
/*
newLine()方法写入与操作系统相依的换行字符,依执行环境当时的OS来决定该输出那种换行字符
*/
bufWriter.newLine();
}
bufReader.close();
bufWriter.close();
1. 字节流字符流本质区别在于byte(字节)和char(字符)的区别:字节流采用二进制编码,字符流涉及到本地的编码问题;网络通信建议字节流;
2.字节流和字符流之间的转化通过InputStreamReader和OutputStreamReader来关联,实际上通过byte[]和String来关联:
//byte转String
public String (byte[] bytes, String charsetName)
//String转byte
byte[] String.getBytes( String charsetName)
3. 一个八位传输(1字节),一个16位传输(2字节);
一些专门用于处理磁盘文件的类,常见的比如File类等。
目的:提供对象持久化的解决方案。
原理:把内存对象数据分解成字节流;反序列化就是打开字节流重构对象;
需求场景:内存对象保存到文件或者数据库;通过Socket网络传输对象;想通过RMI传输对象;
实现方法:对象继承java.iio.Serializable类;
注意事项:序列化只保存对象的数据,方法和构造函数不被序列化(所以反序列化时需要指定恢复的对象类型);声明为static和transient的变量不能序列化。
//构建一URL对象
URL tirc = new URL("http://www.tsinghua.edu.cn/publish/th/index.html");
BufferedReader in = new BufferedReader( new InputStreamReader(tirc.openStream(), "utf-8"));
//使用openStream得到一输入流并由此构造一个BufferedReader对象
String inputLine;
//从输入流不断的读数据,直到读完为止
while ((inputLine = in.readLine()) != null) {
//把读入的数据输出到屏幕上
System.out.println(inputLine);
}
in.close(); //关闭输入流
通过URL类的openStream()方法获得;
向网络输出数据:通过URL类获得URLConnection,建立了与URL的链接,而后向服务器端的CGI(公共网关接口)程序发送数据。
//创建一URL对象
URL url = new URL("http://www.javasoft.com/cgi-bin/backwards");
//由URL对象获取URLConnection对象
URLConnection connection = url.openConnection();
connection.setDoOutput(true);
//由URLConnection获取输出流,并构造PrintStream对象
PrintStream ps = new PrintStream(connection.getOutputStream());
//向服务器写出字符串
ps.println("123456");
ps.close();
//由URLConnection获取输入流,并构造DataInputStream对象
DataInputStream dis = new DataInputStream(connection.getInputStream());
String inputLine;
//从服务器持续读入,并显示
while((inputLine = dis.readLine())!=null) {
System.out.println(inputLine);
}
dis.close();
Socket工作过程步骤:
1 创建Socket;
2 打开连接到Socket的输入输出流;
3 按照一定协议对Socket进行读写操作;
4 关闭Socket;
*主要类:Socket、SocketServer;
socket通信时,在服务器端运行一个socket通信程序,服务器端不停地监听某个端口,等待客户的连接申请,接到申请后建立连接并进行通信,所以,在socket通信方式中,服务器是主动等待连接通信的到来。
URL通信时,在服务器端常驻一个CGI程序,但它一直处于休眠状态。只有在客户端要求建立连接时才被激活,然后与用户进行通信。所以,在URL 通信方式中,服务器是被动等待连接通信的到来。
利用socket进行通信时,服务器端的程序可以打开多个线程与多个客户进行通信,还可以通过服务器使各个客户之间进行通信。这种方式比较灵活,适用于一些较复杂的通信,但是服务器端的程序必须始终处于运行状态以监听端口。
利用 URL进行通信时,服务器端的程序只能与一个客户进行通信,形式比较单一。但是它不需要服务器端的CGI程序一直处于运行状态,只是在有客户申请时才被激活。所以,这种方式比较适用于客户机的浏览器与服务器之间的通信。
总之,Socket是底层实现,协议你要自己去写,不局限于http,可以是任何协议。而类似HttpClient, FtpClient,URLConnetcion之类的,是对专属协议的封装,当然由于部分实现原理,你可能无法完全控制连接操作,比如setTimeout这个参数。
Socket只是一个供上层调用的抽象接口,隐躲了传输层协议的细节。
urlconnection 基于Http协议,Http协议是应用层协议,对传输层Tcp协议进行了封装,是无状态协议,不需要你往考虑线程、同步、状态治理等。