Fork me on GitHub
drqblog


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 日程表

  • 站点地图

  • 公益404

  • 有料

JAVA中的流(续)

发表于 2018-12-14 | 分类于 JAVA
字数统计: 1k | 阅读时长 ≈ 4

一、流(Stream)

所谓流(Stream),就是一系列的数据。

当不同的介质之间有数据交互的时候,java就会使用流来实现。

数据源可以使文件,还可以是数据库,网络,甚至是其他的程序

不如读取文件的数据到程序中,站在程序的角度来看,就叫做输入流

  • 字节输入流:InputStream

  • 字符输入流:Reader    

  • 缓存字符输入流:BufferedReader  
  • 数据输入流 :DataInputStream

  • 字节输出流:OutputStream        

  • 字符输出流:Writer    
  • 缓存字符输出流:PrintWriter    
  • 数据输出流:DataOutputStream 

img

1、文件输入流

可以用来把数据从硬盘的文件,读取到JVM(内存)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package stream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class TestStream{
public static void main(String[] args){
try{
File f=new File("d:/lol.txt");
//创建基于文件的输入流
FileInputStream fis=new FileInputStream(f);
//通过这个输入流,就可以把数据从硬盘,读取到java的虚拟机中,也就是读取到内存中
}catch(Exception e){
e.printStackTrace();
}
}
}

2、字节流

  • InputStream:字节输入流

  • 用以字节的形式读取和写入数据

  • InputStream是字节输入流,同时也是抽象类,只提供方法声明,不提供方法的具体实现。

  • FileInputStream是InputStream的子类,可以进行直接使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package stream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class TestStream{
public static void main(String[] args){
try{
File f=new File("d:/lol.txt");
//创建基于文件的输入流
FileInputStream fis=new FileInputStream(f);
//通过这个输入流,就可以把数据从硬盘,读取到java的虚拟机中,也就是读取到内存中
//创建字节数组,其长度就是文件的长度
byte[] all=new byte[(int)f.length()];
//以字节流的形式读取文件所有内容
fis.read(all);
for(byte b:all){
System.out.println(b);
}
//每次使用完流,都应该进行关闭
fis.close()
}catch(Exception e){
e.printStackTrace();
}
}
}
  • OutputStream:字节输出流,同事也是抽象类,只提供方法声明,不提供方法的具体实现。

  • FileOutputStream是OutputStream子类,可以进行直接使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package stream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class TestStream{
public static void main(String[] args){
try{
File f=new File("d:/lol.txt");
byte data[]={88,89};
//创建基于文件的输出流
FileOutputStream fos=new FileOutputStream(f);

//把数据写入到输出流上
fos.write(data);
//每次使用完流,都应该进行关闭
fos.close()
}catch(Exception e){
e.printStackTrace();
}
}
}

注:所有的流,不论是输入流还是输出流,使用完毕之后,都应该关闭,如果不关闭,会产生对资源占用的浪费,当量比较大时,会影响业务的正常开展。

3.流的关闭方式

在finally中关闭:

  • 首先把流的引用声明在try的外面,如果声明在try里面,其作用与无法抵达finally

  • 在finally关闭之前,要先判断该引用是否为空

  • 关闭的时候,要再一次的进行try。。catch处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package stream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class TestStream{
public static void main(String[] args){
File f=new File("d/source/LOL.exe");
FileInputStream fis=null;
try{
fis=new FileInputStream(f);
byte[] all=new byte[(int)f.length()];
fis.read(all);
for(byte b:all){
System.out.println(b);
}
}catch(IOException e){
e.printStackTrace();
}finally{
//在finally里关闭流
if(null!=fis)
try{
fis.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
}

使用try()方式

  • 把流定义在try()里,try,catch或者finally结束的时候,会自动关闭

  • 这种编写代码的方式叫做try-with-resources,这是JDK7开始支持的技术

  • 所有的流,都实现了一个接口叫做AutoCloseable,任何类实现了这个接口,都可以在try()中进行实例化,并且在try,catch,finally结束的时候自动关闭,回收相关资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TestStream{
public static void main(String[] args){
File f=new File("d/source/LOL.exe");
//把流定义在try()里,try,catch或者finally结束的时候,会自动关闭
try(FileInputStream fis=new FileInputStream(f)){
byte[] all=new byte[(int)f.length()];
fis.read(all);
for(byte b:all){
System.out.println(b);
}catch(IOException e){
e.printStackTrace();
}
}
}
}

JAVA中的流

发表于 2018-12-14 | 分类于 JAVA
字数统计: 1.5k | 阅读时长 ≈ 6

一、缓存流

  以介质是硬盘为例子说明,字节流和字符流的缺点:

  • 每次读写的时候,都会访问硬盘,如果读写频率比较高的时候,性能不佳。为了解决问题,采用缓存流。

  • 缓存流在读取的时候,会一次性读较多的数据到缓存中,以后每一次的读取,都是在缓存中访问,直到缓存中的数据读取完毕,再到硬盘中区读取。

  • 缓存流在写入数据的时候,会先把数据写入到缓存区,直到缓存区达到一定的量,才把这些数据,一起写入到硬盘中去,按照这种方式,就不会像字节流,字符流那样每次写一个字节都访问硬盘,减少了I/O操作。

1、使用缓存流读取数据

BufferedReader缓存字符输入流,可以一次读取一行数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package stream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class TestStream{
public static void main(String[] args){
File f=new File("d/source/LOL.exe");
try(FileReader fr=new FileReader(f);BufferedReader br=new BufferedReader(fr);){
while(true){
String line=br.readLine();//一次读一行。
if(line==null)break;
System.out.println(line);
}
}catch(IOException e){
e.printStackTrace();
}
}
}

2、使用缓存流写入数据

PrintWriter缓存字符输出流,可以一次写出一行数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package stream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class TestStream{
public static void main(String[] args){
File f=new File("d/source/LOL.exe");
try(FileWriter fw=new FileWriter(f);PrintWriter pw=new PrintWriter(fw);){
{
pw.println("hello world");
pw.println("hello kitty");
}
}catch(IOException e){
e.printStackTrace();
}
}
}

3、使用flush

有时候,需要立即把数据写入到硬盘,而不是等缓存满了才写进去,这时候就要用flush。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package stream;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
public class TestStream{
public static void main(String[] args){
File f=new file("d/source/LOL.exe");
//创建文件字符流
//缓存流必须建立在一个存在的流的基础上
try(FileWriter fr=new FileWriter(f);PrintWriter pw=new PrintWriter(fr);){
//向文件LOL.exe中写入三行语句。
pw.println("hello world");
//强制把缓存中的数据写入硬盘,无论缓存是否已满
pw.flush();
pw.println("I love play lol");
pw.flush();
pw.println("I love play dota");
pw.flush();
}catch(IOException e){
e.printStackTrace();
}
}
}

二、数据流

  数据输入流:DataInputStream

  数据输出流:DataOutputStream

数据流的方法:writeUTF(),readUTF()可以进行数据的格式化顺序读写

要用DataInputStream读取一个文件,这个文件必须由DataOutStream写出的,不然会抛出EOFException异常。也就是说这两个是成对出现的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package stream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class TestStream{
public static void main(String[] args){
write();//先写入
read();//在读取
}
private static void read(){
File f=new File("d:/source/LOL.exe");
try(
FileInputStream fis=new FileInputStream(f);
DataInputStream dis=new DataInputStream(fis);
){
//然后在读取刚才写入到lol.exe文件里的东西。
boolean b=dis.readBoolean();
int i=dis.readInt();
String str=dis.readUTF();
System.out.println("读取到布尔值:"+b);
System.out.println("读取到整数:"+i);
System.out.println("读取到字符串"+str);
}catch(IOException e){
e.printStackTrace();
}
}
private static void write(){
File f=new File("d:/source/LOL.exe");
try(
FileOutStream fos=new FileOutputStream(f);
DataOutStream dos=new DataOutStream(fos);
){
//把布尔型,整型,字符型这三个写到lol.exe文件里去
dos.writeBoolean(true);
dos.writeInt(300);
dos.writeUTF("hello world");
}catch(IOException e){
e.printStackTrace();
}
}
}

三、对象流

  可以直接把一个对象以流的形式传输给其他的介质,比如硬盘,一个对象以流的形式进行传输,叫做序列化。

该对象所对应的类,必须是实现Serializable接口

首先序列化一个实体类Hero

1
2
3
4
5
6
7
8
package stream;
import java.io.Serializable;
public class Hero implements Serializable{
//表示这个类当前的版本,如果有了变化,比如添加了新的属性,就要修改这个版本号
private static final long serialVersionUID=1L;
private String name;
private float hp;
}

如果要把Hero对象直接保存在文件上,必须让Hero类实现Serializable接口

首先要创建对象输出流,也就是写入对象到流,首先创建一个文件写入流FileOutputStream对象,然后在创建一个对象写入流ObjectOutputStream对象,把文件写入流传到对象写入流中。写入操作,利用ObjectOutputStream的writeObject()方法进行写入。同样读取操作,还是要先创建文件读取流FileInputStream对象,然后在创建一个对象读取流ObjectInputStream对象,把文件读取流对象传到对象读取流中。然后执行对象读取流的readObject()方法进行读取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package stream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class TestStream{
public static void main(String[] args){
//创建一个Hero对象
//然后把Hero对象直接保存在文件上,必须Hero类实现Serializable接口
Hero h=new Hero();
h.name="Teemo";
h.hp=90.0;

//准备一个文件用于保存新创建的Hero对象
File f=new File("d:/Teemo.lol");

try(
//创建对象输出流(也就是写入)
FileOutputStream fos=new FileOutputStream(f);
ObjectOutputStream oos=new ObjectOutputStream(fos);
//创建对象输入流(也就是读取)
FileInputStream fis=new FileInputStream(f);
ObjectInputStream ois=new ObjectInputStream(fis);
){
oos.writeObject(h);
Hero h2=(Hero)ois.readObject();
System.out.println(h2.name);
System.out.println(h2.hp);
}catch(IOException e){
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}
}
}

四、控制台的输入输出

  • 控制台输出数据:System.out

  • 控制台输入数据:System.in

利用System.in进行控制台写入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package stream;
import java.io.IOException;
import java.io.InputStream;
publc class TestStream{
public static void main(String[] args){
//控制台输入
try(InputStream is=System.in;){
while(true){
int i=is.read();
System.out.println(i);
}
}catch(IOException e){
e.printStackTrace();
}
}
}

利用Scanner进行逐行读取

1
2
3
4
5
6
7
8
9
10
11
12
package stream;
import java.util.Scanner;
public class TestStream{
public class void main(String[] args){
//使用Scanner进行逐行读取。
Scanner s=new Scanner(System.in);
while(true){
String strLine=s.nextLine();
System.out.pirntln(strLine);
}
}
}

JAVA中的集合类

发表于 2018-12-14 | 分类于 JAVA
字数统计: 1.3k | 阅读时长 ≈ 5

一、ArrayList

  解决了数组的局限性,最常见的容器类,ArrayList容器的容量capacity会随着对象的增加,自动增长。不会出现数组边界的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package collection;

import java.util.ArrayList;

import charactor.Hero;

public class TestCollection {
@SuppressWarnings("rawtypes")
public static void main(String[] args) {
//容器类ArrayList,用于存放对象
ArrayList heros = new ArrayList();
heros.add( new Hero("盖伦"));
System.out.println(heros.size());

//容器的容量"capacity"会随着对象的增加,自动增长
//只需要不断往容器里增加英雄即可,不用担心会出现数组的边界问题。
heros.add( new Hero("提莫"));
System.out.println(heros.size());

}

}

二、ArrayList常用方法

首先创建一个重写了toString的Hero类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package charactor;

public class Hero {
public String name;
public float hp;

public int damage;

public Hero() {

}

// 增加一个初始化name的构造方法
public Hero(String name) {

this.name = name;
}

// 重写toString方法
public String toString() {
return name;
}

}

1、add增加

  • 1、直接add对象 hero.add(new Hero(“demo”));
  • 2、指定位置增加对象hero.add(3,”demo”)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package collection;

import java.util.ArrayList;

import charactor.Hero;

public class TestCollection {
public static void main(String[] args) {
ArrayList heros = new ArrayList();

// 把5个对象加入到ArrayList中
for (int i = 0; i < 5; i++) {
heros.add(new Hero("hero " + i));
}
System.out.println(heros);

// 在指定位置增加对象
Hero specialHero = new Hero("special hero");
heros.add(3, specialHero);

System.out.println(heros.toString());

}

}

2、contains():判断是否存在

3、get():获取指定位置的对象

4、indexOf():获取对象所处的位置

5、remove():删除

6、set():替换

7、size():获取大小

8、toArray():转换为数组

三、ArrayList和List

ArrayList实现了接口List,常见的写法会把引用声明为接口List类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package collection;

import java.util.ArrayList;
import java.util.List;

import charactor.Hero;

public class TestCollection {

public static void main(String[] args) {
//ArrayList实现了接口List

//常见的写法会把引用声明为接口List类型
//注意:是java.util.List,而不是java.awt.List
//接口引用指向子类对象(多态)

List heros = new ArrayList();
heros.add( new Hero("盖伦"));
System.out.println(heros.size());

}

}

四、ArrayList遍历的方法

1、使用for循环,通过获取ArrayList的size()来一一遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package collection;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import charactor.Hero;

public class TestCollection {

public static void main(String[] args) {
List<Hero> heros = new ArrayList<Hero>();

// 放5个Hero进入容器
for (int i = 0; i < 5; i++) {
heros.add(new Hero("hero name " + i));
}

// 第一种遍历 for循环
System.out.println("--------for 循环-------");
for (int i = 0; i < heros.size(); i++) {
Hero h = heros.get(i);
System.out.println(h);
}

}

}

2、使用迭代器Iterator进行遍历,迭代器每次都是从一个空的位置开始,通过hasNext()来判断,当返回false时表示后面没有数据了结束遍历,获取通过next()方法获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package collection;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import charactor.Hero;

public class TestCollection {

public static void main(String[] args) {
List<Hero> heros = new ArrayList<Hero>();

//放5个Hero进入容器
for (int i = 0; i < 5; i++) {
heros.add(new Hero("hero name " +i));
}

//第二种遍历,使用迭代器
System.out.println("--------使用while的iterator-------");
Iterator<Hero> it= heros.iterator();
//从最开始的位置判断"下一个"位置是否有数据
//如果有就通过next取出来,并且把指针向下移动
//直达"下一个"位置没有数据
while(it.hasNext()){
Hero h = it.next();
System.out.println(h);
}
//迭代器的for写法
System.out.println("--------使用for的iterator-------");
for (Iterator<Hero> iterator = heros.iterator(); iterator.hasNext();) {
Hero hero = (Hero) iterator.next();
System.out.println(hero);
}

}

}

3、常用的for循环遍历.

不能进行ArrayList初始化,也不知道到底多少个元素在ArrayList,就是所有遍历出来

五、ArrayList和LinkedList的区别

  • ArrayList:顺序结构,插入,删除数据很慢(O(N)),查找快(O(1)),就类似于数组,跟链表的关系一样。

  • LinkedList:链表结构,插入,删除数据快(O(1)),查找慢(O(N))。

六、ArrayList和HashSet区别

最大区别在于:

1、是否有顺序:

  • ArrayList:有顺序

  • HashSet:无顺序(集合的无序性),它的具体顺序既不是按照hashcode的顺序,也不是按照插入顺序,根据在JVM的不用版本中,看到的顺序也是不同的,HashSet的顺序本身不稳定的。

2、是否重复

  • List中的数据可以重复

  • Set中的数据不能重复

重复判断的标准是:首先看hashcode是否相同,不同,则肯定是不同数据

如果相同,在比较equals,如果equals相同,则是相同数据,否则是不同数据。

七、HashSet,LinkedHashSet,TreeSet比较

  • HashSet:无顺序

  • LinkedHashSet:按照插入顺序

  • TreeSet:从小到大顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.TreeSet;

public class TestCollection{
public static void main(String[] args){
HashSet<Integer> hashSet=new HashSet<Integer>();
//HashSet中的数据不是按照插入顺序存放
hashSet.add(22);
hashSet.add(33);
hashSet.add(55);
System.out.println(hashSet);

LinkedHashSet<Integer> linkedHashSet=new LinkedHashSet<Integer>();
//LinkedHashSet中的数据按照插入顺序存放
linkedHashSet.add(22);
linkedHashSet.add(56);
linkedHashSet.add(28);
System.out.println(linkedHashSet);//22,56,28

TreeSet<Integer> treeSet=new TreeSet<Integer>();
//TreeSet中的数据是进行了排序的从小到大
treeSet.add(22);
treeSet.add(56);
treeSet.add(28);
System.out.println(treeSet);//22,28,56
}
}

JAVA中的抽象类与接口

发表于 2018-12-14 | 分类于 JAVA
字数统计: 495 | 阅读时长 ≈ 2

一、类中声明一个方法,这个方法没有实现体,是一个空方法,这样的方法叫抽象方法,使用abstract修饰符号

当一个类有抽象方法时候,必须声明为抽象类

1.声明一个父类(父类类声明一个抽象方法,则父类必须声明为抽象类abstract修饰)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package property;
//为Hero增加一个抽象方法,必须把Hero声明为abstract。
public abstract class Hero{
String name;
float hp;
float armor;
int moveSpeed;

public static void main(String[] args){
}
//抽象方法attack
//Hero的子类会被要求实现attack方法
public abstract void attack();
}

2.子类一

1
2
3
4
5
6
7
8
9
10
package property;
public class ADHero extends Hero{
public void physicAttack(){
System.out.println("进行物理攻击");
}
@Override
public void attack(){//继承父类的同事,必须实现父类里的抽象类
physicAttack();
}
}

3.子类二

1
2
3
4
5
6
7
8
9
10
package property;
public class APHero extends Hero{
public void magicAttack(){
System.out.println("进行魔法攻击");
}
@Override
public void attack(){//继承父类的同事,必须实现父类里的抽象类
magicAttack();
}
}

二、类可以在不提供抽象方法的前提下,直接声明为抽象类。但是一旦被声明为抽象类,就不能狗直接实例化。

1
2
3
4
5
6
7
8
9
10
11
public class Hero extends Object{
String name;
float hp;
float armor;
int moveSpeed;

public static void main(String[] args){
//虽然没有抽象方法,但是被声明了抽象类,不能进行实例化操作。
Hero h=new Hero();
}
}

三、抽象类和接口的区别

子类只能继承一个抽象类,也印证了java只能单继承

子类可以继承多个接口

抽象类可以定义为public,protected,package,private,有静态属性,非静态属性,final和非final属性

接口中的属性只能是public,静态,final的

1
2
3
4
5
6
7
package property;
public interface AD{
public static final int moveSpeed=100;
float hp;//hp没有显式声明为public static final,但是它默认就是这个
public void magicAttack();
int moveSpeed();//方法没有显式声明为public,接口中默认方法为public
}

JAVA中的HashMap与Hashtable

发表于 2018-12-14 | 分类于 JAVA
字数统计: 136 | 阅读时长 ≈ 1

HashMap和Hashtable

  都实现了Map接口,都是以key-value形式保存数据。

  • HashMap可以存放null

  • Hashtable不能存放null

  • HashMap不是线程安全类

  • Hashtable是线程安全类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package collection;
import java.util.HashMap;
import java.util.Hashtable;
public class TestCollection{
//HashMap和Hashtable都实现了Map接口,key-value方式存数据
HashMap<string,string> hashMap=new HashMap<string,string>();
//HashMap可以用null作key,作value
hashMap.put(null,"123");
hashMap.put("123",null);
Hashtable<string,string> hashtable=new Hashtable<string,string>();
//Hashtable不能用null作key,不能用null作value
hashtable.put(null,"123");//报错
hashtable.put("123",null);//报错
}

JAVA中的深拷贝与浅拷贝

发表于 2018-12-14 | 分类于 JAVA
字数统计: 797 | 阅读时长 ≈ 3

Java中有两种类型变量,值类型和引用类型。

  • 对于值类型:copy是属于全盘复制,也就是深拷贝

  • 对于引用类型:一般的copy只是浅拷贝,相当于传递一个引用指针一样。

而当引用类型进行深拷贝的时候必须实现Cloneable接口中提供的Clone方法。

通俗的说:

  • 深拷贝:不仅复制对象的基本类型,还复制原来对象中的对象,完全产生一个新的对象。

  • 浅拷贝:只是赋值对象的基本类型,对象还是属于原来的引用。如果是值类型直接拷贝到新的对象,引用类型则是复制一个引用到目标对象而已。

在有指针的情况下,浅拷贝只是增加了一个指针指向已经存在的内存而已。

深拷贝不但增加了一个指针还申请了一个新的内存,让这个增加的指针指向新的内存。所以当采用深拷贝释放内存的时候不会出现重复释放同一个内存的错误。

浅拷贝代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class Teacher implements Cloneable{
private String name;
private int age;

Teacher(String name,int age){
this.name=name;
this.age=age;
}

public Object clone() throws CloneNotSupportedException{
return super.clone();
}
}

public class Student implements Cloneable{
private String name;
private int age;
private Teacher teacher;

Student(String name,int age,Teacher teacher){
this.name=name;
this.age=age;
this.teacher=teacher;
}
//重写clone方法
public Object clone(){
Student student=null;
try{
student=(Student)super.clone();
}catch(CloneNotSupportedException e){
e.printStack();
}
return student;
}
}

//测试浅拷贝
public class ShallowCopy{
public static void main(String[] args){
Teacher teacher=new Teacher("流浪法师",50);
Student student1=new Student("盖伦",30,teacher);
Student student2=(Student)student1.clone();

student2.teacher.name="提莫";
student2.teacher.age=20;
student2.name="卡特";
student2.age=22;
System.out.println("student1的姓名:"+student1.name+",student1的老师名字:"
+student1.teacher.name+",student1的老师年龄:"+student1.teacher.age);//盖伦,提莫,20
//发现student2变了,student1变了,说明student1和student2的teacher引用是指向同一个对象。
}
}

深拷贝代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class Teacher implements Cloneable{
private String name;
private int age;

Teacher(String name,int age){
this.name=name;
this.age=age;
}

public Object clone() throws CloneNotSupportedException{
Object o=null;
try{
object=super.clone();//复制一个对象,相当于申请一片新的内存空间用来存储对象
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return object;
}
}

public class Student implements Cloneable{
private String name;
private int age;
private Teacher teacher;

Student(String name,int age,Teacher teacher){
this.name=name;
this.age=age;
this.teacher=teacher;
}
//重写clone方法
public Object clone(){
Student student=null;
try{
student=(Student)super.clone();
}catch(CloneNotSupportedException e){
e.printStack();
}
student.teacher=(Teacher)teacher.clone();//进行深拷贝
return student;
}
}

//测试深拷贝
public class DeepCopy{
public static void main(String[] args){
Teacher teacher=new Teacher("流浪法师",50);
Student student1=new Student("盖伦",30,teacher);
Student student2=(Student)student1.clone();

student2.teacher.name="提莫";
student2.teacher.age=20;
student2.name="卡特";
student2.age=22;
System.out.println("student1的姓名:"+student1.name+",student1的老师名字:"
+student1.teacher.name+",student1的老师年龄:"+student1.teacher.age);//盖伦,流浪法师,50
//发现student2变了,student1还是没变,
//说明student1和student2的teacher引用不是指向同一个对象。
//新增了一片存储空间,各自的引用指向了存储空间
}
}

JAVA中的线程安全

发表于 2018-12-14 | 分类于 JAVA
字数统计: 1.9k | 阅读时长 ≈ 7

一、线程安全

个人理解:当多个线程访问一个对象的时候,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那么这个对象是线程安全的。

JAVA中的线程安全:限定于程序之间存在共享数据访问的这个前提,如果一段代码根本不会与其他线程共享数据,那么从线程安全的角度看,程序无论是串行执行还是多线程执行都是没区别的。

JAVA中线程安全各种操作共享数据有5类:

  • 不可变
  • 绝对线程安全
  • 相对线程安全
  • 线程兼容
  • 线程对立

1、不可变

首先不可变(Immutable)的对象一定是线程安全的,无论是对象的方法实现还是方法的调用者,都不需要采用任何的线程安全保障措施,java中,如果共享数据是一个基本数据类型,那么只要定义时使用final关键字修饰它就可以保证是不可变的。如果共享数据是一个对象,那么就需要保证对象的行为不会对其状态产生任何影响才行。比如说String类对象,是一个典型的不可变对象,那么我们调用它的substring()、replace()、concat()方法都不会影响它原来的值,只会返回一个新构造的字符串对象。这就是线程安全的。

保证对象行为不影响自己状态的途径有多种,最简单的就是把对象中带有状态的变量都声明为final,这样在构造函数结束之后,他就是不可变的。

1
2
3
4
5
6
7
8
9
10
/*
java.lang.Integer构造函数,通过内部状态变量value定义为final来保证状态不变
*/
public class Integer{
private final int value;

public Integer(int value){
this.value=value;
}
}

在java的API中符合不可变要求的类型,除了String,还有枚举类型,以及java.lang.Number的不容分子类,比如Long,Double等数值包装类型,BigInteger,BigDecimal等大数据类型。

2、绝对线程安全

也就是要求一个类要达到,不管运行时环境如何,调用者都不需要任何额外的同步措施。

比如说java.util.Vector是一个线程安全的容器,因为它的add()/get()/size()这些方法都是被synchronized关键字修饰的,虽然这样做效率低但是安全的。但是,就算它所有的方法都被修饰为同步,也不意味着调用它的时候永远都不需要同步手段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public class Test{
private static Vector<Integer> vector=new Vector<Integer>();

public static void main(String[] args){
while(true){
for(int i=0;i<10;i++){
vector.add(i);
}

Thread removeThread=new Thread(new Runnable(){
@Override
public void run(){
for(int i=0;i<vector.size();i++){
vector.remove(i);
}
}
});

Thread printThread=new Thread(new Runnable(){
@Override
public void run(){
for(int i=0;i<vector.size().i++){
System.out.println(vector.get(i));
}
}
});

removeThread.start();
printThread.start();
//不要同时产生过多的线程,否则会导致操作系统假死
while(Thread.activeCount()>20);
}
}
}
/*这段代码会报错,虽然Vector的get(),remove(),size()方法都是同步的,但是多线程的环境中
,如果不在方法调用端做额外的同步措施的话,使用这段代码仍然是不安全的。因为如果
另一个县城恰好在错误的时间里删除了一个元素,导致序号i已经不再可用的话
再用i访问数组就会抛出一个ArrayIndexOutOfBoundsException。
*/

/*修改方法如下:*/
publc class Test{
private static Vector<Integer> vector=new Vector<Integer>();
Thread removeThread=new Thread(new Runnable(){
@Override
public void run(){
synchronized(vector){
for(int i=0;i<vector.size();i++){
vector.remove(i);
}
}
}
});

Thread printThread=new Thread(new Runnable(){
@Override
public void run(){
synchronized(vector){
for(int i=0;i<vector.size();i++){
System.out.println(vector.get(i));
}
}
}
});

removeThread.start();
printThread.start();
while(Thread.activeCount()>20);
}

3、相对线程安全

我们通常意义上说的线程安全是相对线程安全。它需要保证对这个对象单独的操作是线程安全的,我们在调用的时候不需要做额外的保护措施,但是对于一些有特定顺序的连续调用,就可鞥需要在调用端使用额外的同步的手段来保证调用的正确性。

比如Vector、HashTable、Collections的synchronizedCollection()方法包装的集合等都是这样的。

4、线程兼容

线程兼容指的是对象本身不是线程安全的,但是可以通过在调用端使用正确的同步手段来保证对象在并发环境中可以安全的使用。比如与Vector对应得ArrayList,HashTable对象的HashMap都是这种情况。

5、线程对立

线程对立指的是无论调用端是否采用了同步措施,都无法在多线程环境中并发使用的代码。由于java中天生就具备多线程特性,线程对立这种排斥多线程的代码很少,而且都是有害的,应该避免。

二、线程安全的实现方法

1、互斥同步

一种常见的并发正确性保障手段。同步是指在多线程并发访问共享数据时候,保证共享的数据在同一个时刻只能被一个线程使用。而互斥实现同步的一种手段,临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)都是主要互斥的实现方式。互斥是原因,同步是结果。互斥是方法,同步是目的。

java中:

  • 最基本的互斥同步手段是synchronized关键字,这个关键字是重量级操作,耗时可能比用户代码执行的时间还要长。

  • 使用java.util.concurrent包中的重入锁来实现同步。

虚拟机在未来的性能的改进中,肯定也会更加偏向于原生的synchronized,所以还是提倡在synchronized能实现需求的情况下,优先考虑使用synchronized来实现同步。

2、非阻塞同步

互斥同步最主要的问题是进行线程阻塞和唤醒所带来的性能问题,因此这种同步也叫做阻塞同步。互斥同步是一种悲观的并发策略。总是认为只要不去做正确的同步措施比如加锁,那么肯定就会出现问题,无论共享数据是否真的会出现竞争,他都要进行枷锁,用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要唤醒等操作。

乐观并发措施:基于冲突检测,就是先进行并发操作,如果没有其他线程争用共享数据,那么操作就成功,如果共享数据有争用,就产生了冲突,那么采用其他补偿措施(常见的补偿措施就是不断地重试,直到成功为止),乐观的并发措施策略使得许多实现都不需要把线程挂起。

3、无同步方法

要保证线程安全,并不是一定要进行同步,两者没有因果关系。同步只是保证共享数据争用时正确的手段,如果一个方法本来就不涉及数据共享,那么自然不需要任何同步去保证正确性。

JAVA中的NIO与IO

发表于 2018-12-14 | 分类于 JAVA
字数统计: 953 | 阅读时长 ≈ 3

一、JAVA的NIO和IO

1、NIO:面向缓冲区(buffer)(分为非阻塞模式IO和阻塞模式IO)组成部分:Channels管道,Buffers缓冲区,Selectors选择器

2、IO:面向流(Stream)(阻塞的IO)

面向流:当一个线程调用read() 或 write()时,线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。流就像一根水管从操作系统的缓存中读取数据,而且只能顺序从流中读取数据,如果需要跳过一些字节或者再读取已经读过的字节,你必须将从流中读取的数据先缓存起来。

面向缓冲区:数据是先被读/写到buffer中的,根据需要你可以控制读取什么位置的数据。这在处理的过程中给用户多了一些灵活性,然而,你需要额外做的工作是检查你需要的数据是否已经全部到了buffer中,如果你还需要保证当有更多的数据进入buffer中时,buffer中未处理的数据不会被覆盖就行。

注:

  • 所有的JAVA的IO流都是阻塞的,JAVA NIO的阻塞模式NIO除了使用buffer存储数据以外和JAVA IO基本没区别。

  • JAVA NIO的非阻塞模式允许一条线程从管道channel中读取数据,通过返回值来判断buffer中是否有数据,如果没有数据,NIO不糊阻塞,不阻塞这个线程就意味着这个线程可以去做其他的事情,过一段时间再回来判断有没有数据。

  • JAVA NIO的写入也是如此,一个线程将buffer中的数据写入管道channel中,它不会等待数据全部写完才会返回,而是调用完write()方法就会继续向下执行。

二、JAVA NIO的Selectors(选择器)

  选择器(Selectors)允许一个线程去监控多个channels的输入,也就是说可以在一个selector上注册多个管道channel,然后通过调用selector的select()方法判断是否有新的连接进来或者已经在selector上注册的channel是否有数据进入。selector的机制让一个线程挂你多个管道变简单。

1
2
3
4
5
6
7
8
9
10
//创建选择器
Selector selector=Selector.open();
//注册通道Channel
//与选择器Selector一块使用的时候,Channel必须是非阻塞模式。
channel.configureBlocking(false);
//第二个参数:Selector监听channel时所作的事件
//SelectionKey.OP_CONNECT/SelectionKey.OP_ACCEPT/SelectionKey.OP_READ/SelectionKey.OP_WRITE
SelectionKey key=channel.register(selector,SelectionKey.OP_READ);
//SelectionKey包含了interest集合,ready集合,Channel,Selector的对象。
int interestSet=key.interestOps();

总结:

  • NIO允许用一个单独的线程或者几个线程管理很多个管道(channels),只是程序的处理会比JAVA IO更加复杂。

  • 一般来说,你如果只有少量的连接但是每个连接都会占有很高的带宽,同时发送很多的数据,传统的JAVA IO会更好。

  • 一般来说,如果你需要同时管理成千上万的连接,但是每个连接只是发送少量的数据,选择JAVA NIO会更好,比如一个聊天服务器,使用NIO更好。

三、JAVA NIO的Channel管道

主要包括了UDP,TCP,SOCKET等网络IO,以及File文件IO:

  • FileChannel  
  • DatagramChannel  
  • SocketChannel  
  • ServlerSocketChannel

读取数据:

1
int bytesRead=inChannel.read(buf);

写入数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int bytesWrite=inChannel.write(buf);
/*SocketChannel通道*/
//建立SocketChannel通道
SocketChannel socketChannel=SocketChannel.open();
socketChannel.connect(new IntSocketAddress("LOL.exe",10));
//读取数据
ByteBuffer buf=ByteBuffer.allocate(20);
int bytesRead=socketChannel.read(buf);
//写入数据
String data="HelloWorld"+System.currentTimeMills();
ByteBuffer buf=ByteBuffer.allocate(20);
buf.clear();//清空整个缓冲区。
buf.put(data.getBytes());//写入数据
buf.flip();//将Buffer从写入模式切换到读取模式
while(buf.hasRemaining()){
socketChannel.write(buf);
}

四、JAVA NIO的Buffer缓冲区

主要包括了读基本数据类型的实现Buffer:

  • ByteBuffer  
  • CharBuffer  
  • DoubleBuffer  
  • FloatBuffer  
  • IntBuffer  
  • LongBuffer  
  • ShortBuffer

数据链路层(续)

发表于 2018-12-14 | 分类于 计算机网络
字数统计: 4.2k | 阅读时长 ≈ 14

1、数据链路层的功能

数据链路层在物理层提供服务的基础上向网络层提供服务。

作用:加强物理层传输原始比特流的功能,将物理层提供的可能出错的物理连接改造成逻辑上无差错的数据链路。让它对网络层表现为一条无差错的链路。

1.1、为网络层提供的服务

  • 无确认的无连接的服务:源机器发送数据帧之前不用先建立链路连接,目的机器收到数据帧后也不要发回确认。对丢失的帧,数据链路层不负责重发而交给上层处理,用来实时通信或者误码率较低的通信信道。以太网就是这种机制服务

  • 有确认无连接的服务:源机器发送数据帧不需要建立链路连接,但是目的机器收到数据帧后必须发回确认。源机器在所规定的时间内没有收到确认信号,就会重新传丢失的帧。用来提高传输的可靠性。这种服务常用在误码率高的通信信道,比如无线通信。

  • 有确认的面向连接的服务:帧传输分为三个过程:建立数据链路,传输帧,释放数据链路。这种服务用语通信要求,可靠性,实时性较高的情况下。

注:有连接就一定要有确认

1.2数据链路层的链路管理

  • 数据链路层,连接的建立,维持,释放,三个过程叫做链路管理,主要还是面向连接的服务。两个工作栈之间进行传输信息的时候,必须将网络层的分组(package)封装成帧(Frame),然后用帧的格式进行传送。在数据的前后分别加上帧头和帧尾,就构成了帧。

  • 帧头和帧尾的作用:确定帧的界限,也就是帧定界。HDLC标准帧格式:前后都有标志位F(01111110)

  • 透明传输:不管所传数据是什么样的比特组合,都可以在连路上传送

1.3、流量控制

由于发送方和接收方的工作速率和缓存空间的差异,可能出现发送方发送能力大于接收方的能力。如果不对链路上的信息流量限制,前面来不及接收的帧就会被后面不断发送的帧淹没,造成帧的丢失而出错。

  • 流量控制:就是限制发送方的数据流量,使其发送速率不超过接收速率。其实流量控制其它层也提供这个功能,只不过控制的对象不同而已。对于数据链路层:控制的是相邻两个节点之间的数据链路上的流量。而对于传输层:控制的是从源端口到目的端口的流量。

  • 流量控制的基本方法:接收方控制发送方发送数据的速率

常见方式:停止等待协议,滑动窗口协议
  • 停止等待协议:发送方发送一帧,都要等待接收方得应答信号,才能发送下一帧。同样,接收方每接收一帧,都要反馈一个应答信号,这种传输效率很低。

  • 滑动窗口协议:任何时刻,发送方都维持一组连续的允许发送的帧的序号,这个叫发送窗口

同样接收方也维持一组连续的允许接受帧的序号,叫接收窗口。发送窗口用来对发送方进行流量控制。

可靠传输机制:确认机制,超时重传机制。

  • 多帧滑动窗口-选择重传协议SR

  • 多帧滑动窗口-后退N帧协议GBN。

  • 后退N帧式ARQ中,发送方不需要再收到上一个帧的ACK后才能开始发送下一帧,也就是可以连续发送帧。

1.4、差错控制

由于信道噪声等因素,帧传输可能出现错误。

位错:帧的某些位出现了差错,采用循环冗余校验CRC,通过自动重传请求ARQ方式来重传错误的帧

注:介质访问控制MAC(Medium Access Control)

信道划分介质访问控制有四种:频分多路复用FDM,时分多路复用TDM,波分多路复用WDM,码分多路复用CDM

2、随机访问MAC

在随机访问协议中,不采用集中控制方式解决信息发送的次序问题。所有用户都可以根据自己的意愿随机发送信息,占用信道全部速率。在总线网中,当有两个或者多个用户同时发送信息的时候,就会产生帧的冲突。这导致所有冲突用户的发送均失败。

为了解决随机接入发生的碰撞,每个用户需要按照一定的规则反复的重传他的帧。知道帧没有碰撞到通过。

这些规则就是随机访问MAC协议。

  • 重用的协议:ALOHA协议,CSMA协议,CSMA/CD协议,CSMA/CA协议

这些协议的核心思想都是:胜利者通过争用获得信道,进而获得信息的发送权,所以说随机访问MAC协议,也叫争用型协议。

MAC采用信道划分机制,那么节点之间的通信,要不就是共享空间,要不就共享时间,要不就两个都共享。

随机MAC:实质上是一种广播信道转化为点到点信道的行为。

因为交换机可以转发广播,随机访问MAC,可以将广播转化为point to point

2.1、ALOHA协议:随机接入系统协议

2.2、CSMA协议:

  • 如果每个站点在发送前都先侦听一下公用的信道,那么发送信道空闲后再发送,那么将会大大减小冲突的可能。从而提高信道的利用率。

  • 载波侦听多路访问(Carrier Sense Multiple Access,CSMA)。CSMA协议对ALOHA协议的一种改进,也就是多了一个载波侦听装置。

2.3、CSMA/CD协议:载波侦听多路访问/碰撞检测

  • 是对CSMA协议的改进方案,适用于总线型网络或者半双工网络环境

  • 载波侦听:也就是发送前先侦听,每次发送数据之前都要先检查一下总线上是否有其他站点在发送数据,如果有。则暂时不要发送数据,等待信道变为空闲的时候再发送。

  • 碰撞检测:就是一边发送一边侦听,适配器在发送数据的时候变检测信道上的信号电压的变化情况,用来判断自己在发送数据的时候其他站点是否也在发送数据。

  • CSMA/CD工作流程:先听后发,边听边发,冲突停发,随机重发

  • 总线的传播时延对CSMA/CD的影响很大,CSMA/CD中的站不能同时发送和接收。所以CSMA/CD的以太网是不进行全双工通信,只能进行半双工通信

2.4、CSMA/CA协议

  • CSMA/CD协议已经应用在使用有线连接的局域网中,但是要在无线局域网的环境下,却不能用。

  • CSMA/CD协议,尤其是碰撞部分,因为无线局域网中,接受信号的强度远远小于发送信号的强度。而且在无线介质上信号强度变化范围很广,要实现碰撞检测,那么在硬件上要花费很大。

  • 在无线通信中,并非所有的站点都可以侦听到对方,也就是隐蔽站的问题。

  • CSMA/CA协议,广泛用于无线局域网。

  • 把碰撞检测改成了碰撞避免(Collision Avoidance,CA)

  • 碰撞避免:不是指协议可以完全避免碰撞,而是指协议的设计要尽量减少碰撞的发生概率。

  • CSMA/CA采用二进制指数退避算法。通过预约信道,ACK帧,RTS/CTS帧,三种机制来实现碰撞避免RTS/CTS帧,主要用来解决无线网的隐蔽站问题。

  • 预约信道,ACK帧,都是必须要实现的

  • 预约信道:发送方在发送数据的同时想起他站点通过告知自己传输数据需要的时间长度,方便让其他站点在这段时间内部发送数据,避免碰撞。

  • ACK帧:所有站点在正确接收到发送给自己的数据帧后,都需要向发送方应答一个ACK帧。

总结:

  • CSMA/CA协议的基本思想:发送数据的时候先广播告知其他节点,让其他节点在某个时间段内不要发送数据,避免碰撞。

  • CSMA/CD协议的基本思想:发送前先侦听,边发送边侦听,一旦出现碰撞马上停止发送。

3、轮询访问MAC:令牌传递协议

  • 在轮询访问中,用户不能随机的发送信息,是通过集中控制的监控站,以循环的方式轮询每个节点。然后决定信道的分配。

  • 当某个节点使用信道的时候,其他节点都不能使用信道。典型的轮询MAC协议是令牌传递协议,令牌环局域网。

  • 令牌传递协议:一个令牌在各个节点以一个固定的次序交换。令牌是个特殊的比特组成的帧,当换上的站希望传递帧的时候,就必须等待令牌,一旦收到令牌,站点就可以启动发送帧。

  • 轮询MAC适合复杂很高的广播信道,负载很高的信道就是多个节点在同一时刻发送数据概率很大的信道。

  • 如果广播信道采用随机MAC,发生冲突的概率很大,而采用轮询MAC则可以更好满足各个节点的要求。

  • 轮序的实质:不共享时间,空间。实质上就是在随机MAC的基础上,限定了有权利发送数据的节点只能有一个

  • 即使是广播信道,都可以通过MAC使得广播信道逻辑上变成点对点的信道。所以说数据链路层研究的是点对点之间的通信。

4、局域网(Local Area Network,LAN)

4.1、局域网的特点:

  • 为一个单位所拥有的,地理范围和站点数目有限

  • 所有的站共享较高的总带宽,也就是共享较高的数据传输率

  • 较低的时延和较低的误码率

  • 各站为平等关系而不是主动关系

  • 能进行广播和组播

4.2、局域网的拓扑结构:

星型,环形,总线型,星型+总线型复杂复合

4.3、局域网的传输介质:

  • 双绞线,同轴电缆,光纤,其中双绞线是主流

  • 局域网的介质访问控制方法:CSMA/CD,令牌总线,令牌环。这是交换机常用于局域网,而局域网接入广域网要用路由器

  • CSMA/CD,令牌总线访问控制方法用于总线型网络

  • 令牌环用于环形局域网

4.4、以太网

使用范围最广的局域网,逻辑拓扑是总线型结构,物理拓扑是星型,或者扩展星型
  • 令牌环(Token Ring):逻辑拓扑是环形,物理拓扑是双环

  • FDDI(光纤分布数字接口):逻辑拓扑是环形,物理拓扑是双环

由于以太网在局域网市场中垄断地位,几乎成为局域网的代名词
  • LLC子层也叫逻辑链路控制子层,由于他的作用不大,所以现在的网卡只有MAC协议没有LLC协议。

  • 以太网逻辑上采用总线型拓扑结构,以太网中所有计算机共享同一条总线,信息以广播方式发送。

  • 以太网简化了通信并且使用了CSMA/CD方式对总线进行访问控制。

  • 计算机与外界局域网的连接是通过主机里的一块网络适配器(Adapter),也叫网卡NIC

  • 网卡上有处理器和存储器,他工作在数据链路层的网络组件。

  • 网卡是局域网连接计算机和传输介质的接口,不仅能实现于局域网传输介质之间的物理连接和电信号匹配。还涉及帧的发送与接收,帧的封装与拆封,介质访问控制MAC,数据的编码和解码,数据缓存等功能。

  • 全世界的每一块网卡在出厂的时候都有一个唯一的代码,叫做价值访问控制MAC地址。MAC地址用来控制主机在网络上的数据通信。

数据链路层的设备:网卡,网桥,交换机。
  • 网桥,交换机都是使用各个网卡的MAC地址。网卡控制着主机对介质的访问,因此网卡也工作在物理层,只关注比特流。

  • 以太网常用的传输介质:粗缆,细缆,双绞线,光纤

  • MAC地址:物理地址,6个10进制数组成。速率达到或者超过100Mb/s的以太网叫高速以太网。

5、广域网(Wide Area Network,WAN)

  • 广域网是Internet的核心部分,通过长距离运送主机所发送的数据,连接广域网各个节点的交换机的链路都是高速链路。距离是几千公里的光缆线路。广域网覆盖方位很广,远远超过一个城市。

  • 广域网的组成:节点交换机+链路

  • 节点交换机:和路由器一样都是用来转发分组的

  • 节点交换机实在单个网络中转发分组,路由器是在多个网络构成的互联网转发分组。节点之间都是点到点连接。为了提高网络的可靠性,通常一个节点交换机往往与多个节点交换机相连。

5.1、广域网和局域网的区别很大:

局域网使用的协议主要在数据链路层。

广域网使用的协议主要在网络层。

也就是说网络中的两个节点要进行数据交换,节点除了要给出数据外,还要给数据包装上一层控制信息,用来实现

检错纠错的功能。如果这层信息是数据链路层的协议控制信息,就叫做使用了数据链路的协议,如果这层控制信息是在网络层,就是使用了网络层的协议。

  • 广域网强调:资源共享

  • 局域网强调:数据传输

  • 广域网中一个重要问题:路由选择和分组转发

  • 路由选择协议:负责搜索分组从某个节点到目的节点的最佳路由,以便构成路由表。

  • 分组转发:从路由表构造出转发分组的转发表。

  • PPP协议和HDLC协议是目前最常用的两种广域网数据链路层的面向字节的协议

5.1.1、PPP协议(Point to Point Protocol):

使用串行线路通信的面向字节的协议,PPP协议应用在直接连接的两个节点的连路上。

  • 目的:通过拨号或者专线方式建立点对点的连接放松数据,让它成为各种主机,网桥,路由器之间简单连接的解决方法。

  • PPP协议:在SLIP的基础上发展而来,可以在异步线路上传输,也可以在同步线路上用。不仅用于Modem链路,还可以用于路由器和路由器之间的链路。

PPP组成:
  • 链路控制协议LCP:用来建立,配置,测试,管理数据链路

  • 网络控制协议NCP:由于PPP可以同时用多种网络层协议,每个不同的网络层协议要用一个相应的NCP来配置。一个将IP数据报封装到串行链路的方法。

  • PPP帧和HDLC帧的格式一样,收尾都是相同的标志字段为7E

  • PPP协议是点对点的,不是总线型,不用CSMA/CD协议。

5.1.2、HDLC协议

高级数据链路控制(High-level Data Link Control):面向比特的数据链路层协议。

  • HDLC协议不依赖任何一种字符集编码,数据报文可以透明传输。

  • PPP是面向字节的,HDLC协议是面向比特的

  • TCP/IP协议簇:TCP,IP,ICMP,ARP,RARP,UDP,DNS,FTP,HTTP

  • HDLC,PPP是ISO提出的数据链路层协议,不属于TCP/IP协议簇

数据链路层

发表于 2018-12-14 | 分类于 计算机网络
字数统计: 1.1k | 阅读时长 ≈ 3

1、数据链路层设备

1.1、网桥

两个,或者多个以太网通过网桥连接起来后,就成为了一个覆盖范围更大的以太网,从而原来的没个以太网就可以叫做一个网段。

网桥工作在数据链路层的MAC子层,可以使得以太网各网段成为隔离开的碰撞域或(冲突域)

如果把网桥换曾工作在物理层的转发器,就没有了这种过滤通信量的功能。网桥处理的数据对象是帧(Frame),所以工作在数据链路层。中继器,集线器Hub处理的数据对象是信号,工作在物理层。

1.2、交换机

1.2.1、局域网交换机

网桥主要是限制在任意时刻通常只能执行一个帧的转发操作。局域网交换机也叫以太网交换机,本质上说,就是一个多端口的网桥,工作在数据链路层。交换机可以经济的将网络分成小的冲突域,为每个工作栈提供更高的带宽。

以太网交换机对工作栈是透明的,管理开销低廉,简化了网络节点的增加,移动和网络变化的操作。

利用交换机还可以方便的实现虚拟局域网(Virtual LAN,VLAN),VLAN不仅可以隔离冲突域,还可以隔离广播域

1.2.2、以太网交换机的工作原理

检测从交换机端口来的数据帧和目的地的MAC地址,然后和系统内部的动态查找表进行比较,

如果数据帧的MAC地址没有在查找表中,就把这个数据帧的MAC地址加入到查找表,然后将数据帧发送给相应的目的端口。

以太网交换机:每个端口都是直接与单个主机相连,网桥的端口往往是连接到以太网的一个网段。

交换机的工作方式是全双工方式。

以太网交换机由于使用了专用的交换结构芯片,交换速率高。以太网交换机独占传输媒体的带宽。

比如普通的10Mb/s的共享式以太网,如果有N个用户,每个用于占用的平均带宽只有总带宽的N分之一不到。

但是使用了交换机,虽然每个端口到主机的带宽还是10Mb/s,但是由于一个用户在通信,因此对于拥有N对端口

的交换机总量为N*10Mb/s。

1.2.3、交换机的两种交换模式
  • 直通式交换机:只检查帧的目的地址,这种方式速度快,但是缺乏智能和安全性。无法进行不同速率的端口交换。

  • 存储转发式交换机:先接收到帧缓存到高速缓存器中,然后检查数据是否正确,确认无误后,通过查找表转换成输出端口将帧发送出去。如果检查数据帧有错,就可以丢弃掉。存储转发交换机:可靠性高,还可以支持不同速度端口的转换,但是延迟比较大。

注:冲突域(碰撞域)和广播域的区别:
当一块网卡发送信息的时候只要可能和另外一块网卡冲突,则这些可能冲突的网卡构成冲突域。
一块网卡发出一个广播,能收到这个广播的所有网卡的集合叫做广播域。一般来说一个网段就是一个冲突域
一个局域网就是一个广播域。
设备名称 工作层 能够隔离冲突域 能否隔离广播域
集线器Hub 物理层 不能 不能
中继器 物理层 不能 不能
交换机 数据链路层 能 不能
网桥 数据链路层 能 不能
路由器 网络层 能 能

总结:中继器,集线器Hub,工作在同一个冲突域(也就是同一个网段)只是对信号放大而已,也就是说能隔离冲突域的只要工作在物理层之上就可以。因为局域网通过路由器连接到广域网,而一个广播域就是一个局域网,所以说路由器可以隔离广播域

而交换机叫做以太网交换机,是在局域网当中的,不能隔离广播域。

1…4567
Victor Drq

Victor Drq

70 日志
8 分类
1 标签
RSS
GitHub E-Mail 博客园
© 2018 Victor Drq | Site words total count: 62k
本站访客数:
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4