当前位置:编程学习 > JAVA >>

《新手干中学》文本编辑器(附上一些牛人建议及后续改进方向)


本人新手,某天开始学习JAVA,看了一上午JAVA语法书,晕头晕脑,中午吃饭的时候决定,还是从项目开始学习,直接写个小程序,切入JAVA语法之类方面的知识。

决定写一个文本编辑器,难度不大,以前C++(Qt)也写过,能运行即可。于是边网络搜索,边写,于下午5点多完成吧,夜里上网发到CSDN.NET,

本来计划,这个文本编辑器一些完,JAVA语法大体知道关键字等之类了,转入串口读写及数据处理方面,可以切入多线程。结果好多朋友提出了非常有价值的建议,我感觉利用这个文本编辑器我还可以学习到更多东西,于是计划,根据牛人建议,尝试对这个文本编辑器进行改进,望朋友们帮助。

下面先附上当前编辑器界面



代码
package study.com.notebook;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FileDialog;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

import javax.swing.JButton;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTextArea;
import javax.swing.JToolBar;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.DefaultEditorKit;

class MainForm extends JFrame implements ActionListener, DocumentListener {

private static final long serialVersionUID = 1L;
// 界面元素变量
JMenuBar menuBar; // 菜单
JPanel panel;
JTextArea textEdit;
JLabel statusLabel;

// 控制变量
boolean TextChangeNoSave;
// 当前文档的改变是否已经保存
boolean DocIsNew; 
// 当前文档是否为新建

// 当前文件名次、路径
String filePath;
String fileName;

// 系统粘贴板
Clipboard clip;

public MainForm() {
// 界面初始化
InitGUI();
InitPara();
}

// 界面初始化
public void InitGUI() {
// 菜单栏初始化
menuBar = new JMenuBar();
menuBar.setBackground(Color.LIGHT_GRAY);
final JMenu[] menu = {new JMenu("File"),// "File"菜单
new JMenu("Edit"),// "Edit"菜单
new JMenu("Font"), //"Font"菜单
new JMenu("Help")// "Help"菜单
};

final JMenuItem[][] menuItem = {
{new JMenuItem("New"), new JMenuItem("Open"),
new JMenuItem("Save"), new JMenuItem("Exit")},
{new JMenuItem("Cut"), new JMenuItem("Copy"),
new JMenuItem("Paste")},
{},
{new JMenuItem("Help"), new JMenuItem("About")}};
for (int i = 0; i < menu.length; i++) {
for (int j = 0; j < menuItem[i].length; j++) 
{
menuItem[i][j].setBackground(Color.LIGHT_GRAY);
menu[i].add(menuItem[i][j]);
menuItem[i][j].addActionListener(this);
}
menuBar.add(menu[i]);
}
menu[0].add(new JSeparator(), 3); // 特定地方补分隔符
// 工具栏初始化

  JToolBar toolBar = new JToolBar(); 
  String[] toolButtonText =
  {"New","Open","Exit"}; 
  JButton[] toolButton = new JButton[toolButtonText.length]; 
  for(int i=0;i<toolButtonText.length;i++)
  { 
  toolButton[i] = new JButton(toolButtonText[i]); 
  }
  for(int i=0;i<toolButton.length;i++) 
  {
  toolButton[i].setActionCommand(toolButtonText[i]);
  toolButton[i].addActionListener(this); 
  toolBar.add(toolButton[i]);
  }

// 文本框

textEdit = new JTextArea();

textEdit.setBackground(Color.DARK_GRAY);
textEdit.setForeground(Color.LIGHT_GRAY);

textEdit.getDocument().addDocumentListener(this);
// 为文本域添加滑动块
JScrollPane scrollPane = new JScrollPane(textEdit);

// 初始化状态栏
JToolBar statusBar = new JToolBar();
statusBar.setFloatable(false);
statusBar.setBackground(Color.LIGHT_GRAY);
statusLabel = new JLabel("  ");
statusBar.add(statusLabel);

// 初始化面板
panel = new JPanel();
panel.setLayout(new BorderLayout());
//panel.add(toolBar,BorderLayout.PAGE_START);
panel.add(scrollPane, BorderLayout.CENTER);
panel.add(statusBar, BorderLayout.PAGE_END);

this.add(panel);
this.setJMenuBar(menuBar);
this.setTitle("NoteBook");
this.setSize(640, 480);
this.setVisible(true);
}
// 参数初始化
public void InitPara() {
Toolkit toolkit = Toolkit.getDefaultToolkit();
clip = toolkit.getSystemClipboard();
TextChangeNoSave = false;
DocIsNew = true;
}
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
switch (e.getActionCommand()) {
// File菜单的事件响应
case "New" :
newFile();
break;
case "Open" :
openFile();
break;
case "Save" :
saveFile();
break;
case "Exit" :
exit();
break;
// Edit菜单事件响应
case "Cut" :
cutText();
break;
case "Copy" :
copyText();
break;
case "Paste" :
pasteText();
break;

// Help菜单事件响应
case "Help" :
help();
break;
case "About" :
about();
break;
default :
break;
}
}

// ////菜单功能函数
public void newFile() {
if (TextChangeNoSave) {
int n = JOptionPane.showConfirmDialog(this, "文件已更改,是否保存?", "提示!",
JOptionPane.YES_NO_OPTION);
if (n == JOptionPane.YES_OPTION) {
saveFile();
}
}
textEdit.setText("");
statusLabel.setText("Created a new file");
DocIsNew = true;
TextChangeNoSave = true;
filePath = null;
fileName = null;
}
public void openFile() {
if (TextChangeNoSave) {
int n = JOptionPane.showConfirmDialog(this, "文件已更改,是否保存?", "提示!",
JOptionPane.YES_NO_OPTION);
if (n == JOptionPane.YES_OPTION) {
saveFile();
}

}
FileDialog openFileDlg = new FileDialog(this, "打开文件", FileDialog.LOAD);
openFileDlg.setVisible(true);
filePath = openFileDlg.getDirectory();
fileName = openFileDlg.getFile();
ReadFile();
TextChangeNoSave = false;
DocIsNew = false;

}
public void saveFile() {
if (DocIsNew) {
FileDialog saveFileDlg = new FileDialog(this, "保存文件",
FileDialog.SAVE);
saveFileDlg.setVisible(true);
filePath = saveFileDlg.getDirectory();
fileName = saveFileDlg.getFile();
}
WriteFile();
}
public void exit() {
if (TextChangeNoSave) {
int n = JOptionPane.showConfirmDialog(this, "文件已更改,是否保存?", "提示!",
JOptionPane.YES_NO_OPTION);
if (n == JOptionPane.YES_OPTION) {
saveFile();
}
}
System.exit(0);
}
public void cutText() {
textEdit.cut();
}
public void copyText() {
textEdit.copy();
}
public void pasteText() {
Transferable contents = clip.getContents(this);
if (contents != null
&& contents.isDataFlavorSupported(DataFlavor.stringFlavor)) {
try {
String str = (String) contents
.getTransferData(DataFlavor.stringFlavor);
int pos = textEdit.getSelectionStart();
textEdit.cut();
textEdit.insert(str, pos);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
public void help() {
//File dir = new File("..");
//filePath = dir.getAbsolutePath();
fileName = "readme.txt";
ReadFile();
}
public void about() {
String info = "作者 :nudt_tony \n" + "日期 :2013 06 28\n"
+ "邮箱 :basic8@163.com";
JOptionPane.showMessageDialog(this, info);
}
// /底层文件读写函数
public void WriteFile() {
if (fileName != null) {
try {
File file1 = new File(filePath, fileName);
FileWriter fw = new FileWriter(file1);
BufferedWriter out = new BufferedWriter(fw);
out.write(textEdit.getText());
out.close();
fw.close();
statusLabel.setText("文件保存成功!");
} catch (IOException e) {
statusLabel.setText("文件保存出错!");
}
}
}

public void ReadFile() {
if (fileName != null) {
try {
File file1 = new File(filePath, fileName);
FileReader fr = new FileReader(file1);
BufferedReader in = new BufferedReader(fr);
StringBuilder text = new StringBuilder();
String str="";
while ((str = in.readLine()) != null)
{
text.append(str+ "\r\n");
}
textEdit.setText(text.toString());
in.close();
fr.close();
statusLabel.setText(filePath + fileName);

} catch (IOException e) {
statusLabel.setText("打开文件出错!");
}
}
}

@Override
public void insertUpdate(DocumentEvent e) {
// TODO Auto-generated method stub
// statusLabel.setText("Text Changed!!");
TextChangeNoSave = true;
statusLabel.setText("Text insertUpdate!");
}
@Override
public void removeUpdate(DocumentEvent e) {
// TODO Auto-generated method stub
TextChangeNoSave = true;
statusLabel.setText("Text removeUpdate!!");
}
@Override
public void changedUpdate(DocumentEvent e) {
// TODO Auto-generated method stub
TextChangeNoSave = true;
statusLabel.setText("Text changedUpdate!!");
}
}


package study.com.notebook;

public class TestClass {

public static void main(String[] args) {
MainForm mainform = new MainForm();
}

}


几点建议:
  1:读写使用DefaultEditorKit类的read/write方法。
  2:没有一个private方法,逻辑和界面没有分离(新建一个类,只处理逻辑,然后由构造函数传入该类实例。这里文件存取逻辑可以拆分,作为一个通用类,这样其他的程序或者本程序的其他功能也可以使用。
避免在构造函数里调用任何可以被重写的方法) 文本编辑 String Java 类 --------------------编程问答-------------------- 首先你需要三个文件,分别是两个接口和一个类:
public interface TextFileReadEventListener {
  void onFileRead(final String text, final String path);
}


public interface TextFileWriteEventListener {
  void onFileWritten();
}

public class TextFileIOHelper {
  public static void readFile(final String path, final TextFileReadEventListener listener) throws IOException {
    if (path != null) {
      File file1 = new File(path);
      FileReader fr = new FileReader(file1);
      BufferedReader in = new BufferedReader(fr);
      StringBuilder text = new StringBuilder();
      String str = "";
      while ((str = in.readLine()) != null) {
        text.append(str + "\r\n");
      }
      in.close();
      fr.close();

      listener.onFileRead(text.toString(), path);
    }
  }

  public static void writeFile(final String path, final String text, final TextFileWriteEventListener listener)
          throws IOException {
    if (path != null) {
      File file1 = new File(path);
      FileWriter fw = new FileWriter(file1);
      BufferedWriter out = new BufferedWriter(fw);
      out.write(text);
      out.close();
      fw.close();
      listener.onFileWritten();
    }
  }
}


然后稍微改一下你的save/open函数:
public void openFile() {
    if (TextChangeNoSave) {
      int n = JOptionPane.showConfirmDialog(this, "文件已更改,是否保存?", "提示!", JOptionPane.YES_NO_OPTION);
      if (n == JOptionPane.YES_OPTION) {
        saveFile();
      }

    }
    FileDialog openFileDlg = new FileDialog(this, "打开文件", FileDialog.LOAD);
    openFileDlg.setVisible(true);
    filePath = openFileDlg.getDirectory();
    fileName = openFileDlg.getFile();
    ReadFile();
    TextChangeNoSave = false;
    DocIsNew = false;

    if (fileName != null) {
      try {
        TextFileIOHelper.readFile(filePath + System.lineSeparator() + fileName, new TextFileReadEventListener() {
          @Override
          public void onFileRead(final String text, final String path) {
            textEdit.setText(text);
            statusLabel.setText(path);
          }
        });
      }
      catch (IOException e) {
        statusLabel.setText("打开文件出错!");
      }
    }
  }


public void saveFile() {
    if (!DocIsNew)
      return;

    FileDialog saveFileDlg = new FileDialog(this, "保存文件", FileDialog.SAVE);
    saveFileDlg.setVisible(true);
    filePath = saveFileDlg.getDirectory();
    fileName = saveFileDlg.getFile();

    if (fileName != null) {
      try {
        TextFileIOHelper.writeFile(filePath + System.lineSeparator() + fileName, textEdit.getText(),
                new TextFileWriteEventListener() {
                  @Override
                  public void onFileWritten() {
                    statusLabel.setText("文件保存成功!");
                  }
                });
      }
      catch (IOException e) {
        statusLabel.setText("文件保存出错!");
      }
    }
  }


这样如果你以后有程序想用read/write file函数,就可以直接调用。当然还有很多很多其他的方法能做到,这里只是举一个比较“飘逸”的例子 --------------------编程问答--------------------
final DefaultEditorKit kit = new DefaultEditorKit();
final JTextArea editor = new JTextArea();
final Document document = editor.getDocument();

//read
try(FileReader reader = new FileReader(file)){
    kit.read(reader, document, 0);
}catch(IOException|BadLocationException xe){
    // process(xe);
}

//save
try(FileWriter reader = new FileWriter(file)){
    kit.write(writer, document, 0, document.getLength());
}catch(IOException|BadLocationException xe){
    // process(xe);
}
--------------------编程问答-------------------- 晕,复制后少修改一个地方。
try(FileWriter writer = new FileWriter(file)){
--------------------编程问答-------------------- 1、下午根据使用“两个接口和一个类”的建议,针对编辑器进行了改进,在读写文件时,实际执行代码写入单独的类,成了一个版本,这个实现方法开拓的眼界和思路,的确飘逸!!(但使用的实际代码与作者给出了略有不同,修正了部分),代码可以从我资源列表下载(无需资源分),这里不贴了。
2、刚才看到楼上给出了一种思路,感觉使用了比较成熟的库,直接解决,我决定采用这种方式再写一个版本,并将该编辑器功能进行一些增加,如字体设置等方面。 --------------------编程问答-------------------- 应该使用Action类,可以添加到主菜单,工具栏,右键菜单。

import static javax.swing.text.DefaultEditorKit.CutAction;

CutAction cutAction = new CutAction();

editMenu.add(cutAction);
toolbar.add(cutAction);


--------------------编程问答-------------------- 首先感谢朋友的建议,讲文件读写利用已有库的读写
1、采用了DefaultEditorKit类实现读写。


我发现自己先前的版本对于更改菜单栏很不方便,要改动好几个地方,于是做出下面更改
2、实现利用一个String[]和一个String[][]初始化生成菜单栏。

利用上面思路,菜单UI生成方便了,但是事件响应函数还是很麻烦,要添加分支。想到 e.getActionCommand()可以返回按钮动作命令,也就是上面的String,于是思考是否可以利用函数名的string,实现调用该函数,很走运,百度到  http://zhidao.baidu.com/question/242015200.html,里面提到这个。于是
3、在事件响应上,采用下述代码

public void actionPerformed(ActionEvent e) 
{
// TODO Auto-generated method stub
// (e.getActionCommand())
try
{
Method m = MainForm.class.getDeclaredMethod(e.getActionCommand(), null);
m.invoke(this);
}
catch (Exception e2)
{
e2.printStackTrace();
}

}


但是这样一来,我"File"菜单下的“New”,那我还得新建一个New()函数,这样不合适。
于是我把生成菜单的字符串全部中文化,其响应函数名也对应为中文,于是就有了这个样子的界面和代码


package study.com.notebook;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FileDialog;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;

import javax.swing.JButton;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTextArea;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.Document;
import javax.swing.text.StyleConstants;

class MainForm extends JFrame implements ActionListener, DocumentListener {

private static final long serialVersionUID = 1L;

// 定制界面菜单变量(由这两个变量,初始化菜单栏)
String[] menuNames = {"文件(F)","编辑(E)","格式(O)","帮助(H)"};
String[][] menuItemNames = 
{
{"新建","打开","保存","另存为","页面设置","打印","退出"},
{"剪切","复制","粘贴"},
{"自动换行","字体"},
{"查看帮助","关于"}
};

private JMenuBar menuBar;
private JTextArea textEdit;
private JLabel statusLabel;

// 控制变量
private boolean TextChangeNoSave; // 当前文档的改变是否已经保存
private boolean DocIsNew; // 当前文档是否为新建

// 当前文件名次、路径
private String filePath;
private String fileName;
//用于文件读写
DefaultEditorKit editorKit ;
Document document ;

// 系统粘贴板
Clipboard clip;

private JPanel panel;

public MainForm() {
// 界面初始化
InitGUI();
InitPara();
}

//获取菜单栏参数(这里手动设定,也可从配置文件读取)
//菜单栏名称设为中文,便于反射其执行函数(不存在与关键字重叠可能)

//根据文本数组 生成菜单栏
private void InitJMenuBar()
{
menuBar = new JMenuBar();
JMenu[] menu = new JMenu[menuNames.length];
for(int i=0;i<menu.length;i++)
{
menu[i] = new JMenu(menuNames[i]);
for(int j=0;j<menuItemNames[i].length;j++)
{
JMenuItem jmi = new JMenuItem(menuItemNames[i][j]);
jmi.addActionListener(this);
menu[i].add(jmi);
}
menuBar.add(menu[i]);
}
}
// 界面初始化
private void InitGUI() {

//初始化菜单栏
InitJMenuBar();

// 文本框

textEdit = new JTextArea();
textEdit.setBackground(Color.DARK_GRAY);
textEdit.setForeground(Color.LIGHT_GRAY);
textEdit.getDocument().addDocumentListener(this);
// 为文本域添加滑动块
JScrollPane scrollPane = new JScrollPane(textEdit);

// 初始化状态栏
JToolBar statusBar = new JToolBar();
statusBar.setFloatable(false);
statusBar.setBackground(Color.LIGHT_GRAY);
statusLabel = new JLabel("  ");
statusBar.add(statusLabel);

// 初始化面板
panel = new JPanel();
panel.setLayout(new BorderLayout());
//panel.add(toolBar,BorderLayout.PAGE_START);
panel.add(scrollPane, BorderLayout.CENTER);
panel.add(statusBar, BorderLayout.PAGE_END);

this.add(panel);
this.setJMenuBar(menuBar);
this.setTitle("NoteBook");
this.setSize(640, 480);
this.setVisible(true);
}
// 参数初始化

private void InitPara() {
Toolkit toolkit = Toolkit.getDefaultToolkit();
clip = toolkit.getSystemClipboard();
TextChangeNoSave = false;
DocIsNew = true;
editorKit = new DefaultEditorKit();
document = textEdit.getDocument();


}
@Override
public void actionPerformed(ActionEvent e) 
{
// TODO Auto-generated method stub
// (e.getActionCommand())
try
{
Method m = MainForm.class.getDeclaredMethod(e.getActionCommand(), null);
m.invoke(this);
}
catch (Exception e2)
{
e2.printStackTrace();
}

}

// ////菜单功能函数
private void 新建() {
if (TextChangeNoSave) {
int n = JOptionPane.showConfirmDialog(this, "文件已更改,是否保存?", "提示!",
JOptionPane.YES_NO_OPTION);
if (n == JOptionPane.YES_OPTION) {
保存();
}
}
textEdit.setText("");
statusLabel.setText("Created a new file");
DocIsNew = true;
TextChangeNoSave = true;
filePath = null;
fileName = null;
}
private void 打开() {
if (TextChangeNoSave) {
int n = JOptionPane.showConfirmDialog(this, "文件已更改,是否保存?", "提示!",
JOptionPane.YES_NO_OPTION);
if (n == JOptionPane.YES_OPTION) {
保存();
}

}
FileDialog openFileDlg = new FileDialog(this, "打开文件", FileDialog.LOAD);
openFileDlg.setVisible(true);
filePath = openFileDlg.getDirectory();
fileName = openFileDlg.getFile();
ReadFile(filePath+fileName);
TextChangeNoSave = false;
DocIsNew = false;

}
private void 保存() {
if (DocIsNew) {
FileDialog saveFileDlg = new FileDialog(this, "保存文件",
FileDialog.SAVE);
saveFileDlg.setVisible(true);
filePath = saveFileDlg.getDirectory();
fileName = saveFileDlg.getFile();
}
WriteFile(filePath+fileName);
}
private void 另存为(){statusLabel.setText("save as!");}
private void 退出() {
if (TextChangeNoSave) {
int n = JOptionPane.showConfirmDialog(this, "文件已更改,是否保存?", "提示!",
JOptionPane.YES_NO_OPTION);
if (n == JOptionPane.YES_OPTION) {
保存();
}
}
System.exit(0);
}
private void 剪切() {
textEdit.cut();
}
private void 复制() {
textEdit.copy();
}
private void 粘贴() {
Transferable contents = clip.getContents(this);
if (contents != null
&& contents.isDataFlavorSupported(DataFlavor.stringFlavor)) {
try {
String str = (String) contents
.getTransferData(DataFlavor.stringFlavor);
int pos = textEdit.getSelectionStart();
textEdit.cut();
textEdit.insert(str, pos);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
private void 查看帮助() {

fileName = "readme.txt";
ReadFile(fileName);
}
public  void 关于() {
String info = "作者 :nudt_tony \n" + "日期 :2013 06 28\n"
+ "邮箱 :basic8@163.com";
JOptionPane.showMessageDialog(this, info);
}
private void WriteFile(String file) 
{
if (file!= null) 
{
try(FileWriter writer = new FileWriter(file))
{
editorKit.write(writer, document, 0, document.getLength());
statusLabel.setText("文件保存成功!");
}
catch(IOException|BadLocationException xe)
{
    statusLabel.setText("文件保存出错!");
}
}
}
private void ReadFile(String file) 
{
if (file != null) 
{
try(FileReader reader = new FileReader(file))
{
editorKit.read(reader,document,0);
statusLabel.setText(filePath + fileName);
}
catch(IOException|BadLocationException xe)
{
statusLabel.setText("打开文件出错!");
}
}
}
@Override
public void insertUpdate(DocumentEvent e) {
// TODO Auto-generated method stub
// statusLabel.setText("Text Changed!!");
TextChangeNoSave = true;
statusLabel.setText("Text insertUpdate!");
}
@Override
public void removeUpdate(DocumentEvent e) {
// TODO Auto-generated method stub
TextChangeNoSave = true;
statusLabel.setText("Text removeUpdate!!");
}
@Override
public void changedUpdate(DocumentEvent e) {
// TODO Auto-generated method stub
TextChangeNoSave = true;
statusLabel.setText("Text changedUpdate!!");
}
}


通过这个,初步接触到反射,感觉很神奇的一个功能----,菜单中有的功能还没有去实现,朋友们有兴趣没有呢?
希望新手小白们一起来学习探讨下,有想法的朋友也帮帮我。
--------------------编程问答-------------------- 精益求精,精神可嘉
补充:Java ,  Java SE
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,