IO是java绕不过去的槛,在开发中io无处不在, 正如同 世界上本没有路,java io写多了,也就知道了大体是什么意思,在读完thinking in java 感觉就更清晰了,结合具体的业务场景,整理一下 ,什么是IO。为什么JAVA要这么设计IO。

先来一道开胃菜

我想要读取控制台输入的字符

         InputStreamReader isr = new InputStreamReader(System.in);
         BufferedReader br = new BufferedReader(isr);
         String s = null;
         try {
             System.out.println("开始输入 。。。");
             s = br.readLine();
             while (s  != null) {
                 if (s.equalsIgnoreCase("yes")) {
                     break;
                 }
                 System.out.println(s.toUpperCase());
                 System.out.println("是否停止输入(yes/no)");
                 s = br.readLine();
             }
         } catch (IOException e) {
             e.printStackTrace();
         }finally{
             if(br !=null){
                 try {
                     br.close();
                 } catch (IOException e) {
                     e.printStackTrace();
                 }
             }
             if(isr != null){
                 try {
                     isr.close();
                 } catch (IOException e) {
                     e.printStackTrace();
                 }
             }
         }
     

  解释一下:我从控制台读取一行字符,然后打印一下。这就是一个简单的流了。

  整理一下: 就是我先 得到一个用于读取 控制台输入的流,然后 我·打印我得到的东西,这里有个细节就是 流一定得关闭,这是底线,关闭的顺序:先声明的后关闭

稍微深入一点。我用Inputstream 去读取字符串并转化为想要的编码格式

 public static String getStrFormInputStream(String str,
             String encoding) throws IOException {
         InputStream inputStream = new ByteArrayInputStream(str.getBytes(encoding));
         BufferedReader bf = null;
         InputStreamReader inputStreamReader = null;
         try {
             inputStreamReader = new InputStreamReader(inputStream);
             bf = new BufferedReader(inputStreamReader);
             StringBuffer sb = new StringBuffer();
             String line = "";
             while ((line = bf.readLine()) != null) {
                 sb.append(line);
             }
             return sb.toString();
         } finally {
             if (bf != null) {
                 bf.close();
             }
             if (inputStreamReader != null) {
                 inputStreamReader.close();
             }
             if (inputStream != null) {
                 inputStream.close();
             }
         }

     }

   这就偏实际一点,当你拿到一个字符串的时候,读取的时候,有一个细节:最好加上编码格式

   解释一下:实际上读取的地方 只有这一点 line = bf.readLine() ,那么之前的是做什么呢,  我其实是在组装我想要的铲子。这也是 开发中比较常用的“包装器模式”

我想把字符串转为贴合实际的ByteArrayInputStream, 再转化为更常用的Reader(InputStreamReader)  再包装上buffer(BufferedReader)。

当我选择输出的时候:

当我查看我以前的开发记录时,发现实际业务中 绝大多数输出都是输出到文件的。想找一个简单的输出示例,并不容易

 public void sendMessage(String str,
             String host,
             int port) throws UnsupportedEncodingException, IOException {
         String sendEncoding = "GBK";
         Writer writer = null;
         Socket client = null;
         try {
             // 与服务端建立连接
             client = new Socket(host, port);
             // 建立连接后就可以往服务端写数据了
             writer = new OutputStreamWriter(client.getOutputStream(), sendEncoding);
             System.out.println(str);
             writer.write(str);
             writer.flush();// 写完后要记得flush
         } finally {
             if (writer != null) {
                 try {
                     writer.close();
                 } catch (Exception e) {
                     e.printStackTrace();
                 }
             }
             if (client != null) {
                 try {
                     client.close();
                 } catch (Exception e) {
                     e.printStackTrace();
                 }
             }
         }

     }

  输出的地方其实很简单: writer.write(str); 其他的地方 建立服务器连接,将str写到短信中和此处关系不大,需要注意 :无论是输入输出,用完了一定关闭流,这是底线

我有一文件

  文件读写才是一个真正的业务场景中占比绝大多数的。

 文件的基本操作 

String filePath = "D:\\";
        File file = new File(filePath);
        long length = 0;
        if (file.exists()){
            length = file.length();
        }else{
           file.mkdirs();
        }

在java中 File 就是 File , 它既可以是具体的文件,也可以是一个文件目录

读写文件总体步骤

当你读写文件的时候,看到直接读取字节实际上应用更广一点,当然用读取字符也是占很大比重,所以,这个问题就丢给各位读者,到底是想用哪个。反正都行。

public byte[] getBytesByFileName(String filePath) {
        byte[] buffer = null;
        try {
            File file = new File(filePath);
            FileInputStream fis = new FileInputStream(file);
            ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
            byte[] b = new byte[1000];
            int n;
            while ((n = fis.read(b)) != -1) {
                bos.write(b, 0, n);
            }
            bos.close();
            buffer = bos.toByteArray();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return buffer;
    }
public static void moveFile(String sourcePath,
            String targetPath) throws Exception {
        File sourceRoot = new File(sourcePath);
        if (!sourceRoot.exists()) {
            throw new Exception("要移动的文件不存在");
        }
        if (sourceRoot.isFile()) {
            boolean success = true;
            File targetFile = new File(targetPath);
            if (!targetFile.getParentFile().exists()) {
                if (!targetFile.getParentFile().mkdirs()) {
                    success = false;
                }
            }
            if (!targetFile.exists()) {
                if (!targetFile.createNewFile()) {
                    success = false;
                }
            }
            if (!success) {
                throw new Exception("目标目录创建失败");
            }
            BufferedInputStream bis = null;
            BufferedOutputStream bos = null;
            byte[] d = new byte[1024];
            int length = -1;
            try {
                bis = new BufferedInputStream(new FileInputStream(sourceRoot));
                bos = new BufferedOutputStream(new FileOutputStream(targetFile));
                while ((length = bis.read(d)) != -1) {
                    bos.write(d, 0, length);
                }
                bos.flush();
            } catch (IOException e) {
                e.printStackTrace();
                success = false;
            } finally {
                if (bos != null) {
                    bos.close();
                }
                if (bis != null) {
                    bis.close();
                }
                bos = null;
                bis = null;
            }
            if (success) {
                sourceRoot.deleteOnExit();
            }
        } else {
            File[] files = sourceRoot.listFiles();
            for (File file : files) {
                moveFile(file.getAbsolutePath(), targetPath + File.separator + file.getName());
            }
            sourceRoot.deleteOnExit();
        }
    }

  移动文件:实际上就是从一个文件中读取文件,然后写到另一个文件中,这算是一个非常详细的例子。分析代码:我想判断源文件是否存在,我再去创建目标文件夹和目标文件,当然,你也可以不用mkdir()

直接用 mkdirs()也行。

当然我写文件时的数据(调用write()方法传入的数据)不一定是来自文件也有可能来自一个list,一个byte[]数组。

public void writeFile(String str,
            File file) throws IOException {
        OutputStream out = null;
        try {
            out = new FileOutputStream(file, true); // 是否追加
            byte[] b = str.getBytes();
            out.write(b);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                out.close();
            }
        }
    }
public void exportByModel(List<Map<String, Object>> data,
            byte[] exportModel,
            String fileNameLocation) throws Exception {
        InputStream in = null;
        Reader reader = null;
        OutputStream out = null;
        Writer des = null;
        CharArrayWriter writer = null;
        try {
            // 读取模板
            in = new ByteArrayInputStream(exportModel);
            reader = new InputStreamReader(in);
            // 设置输出位置
            out = new FileOutputStream(fileNameLocation);
            String encoding = "GBK";
            try {
                des = new OutputStreamWriter(out, encoding);// 不设置utf-8,中文不支持
            } catch (Exception e) {
                des = new OutputStreamWriter(out, "GBK");// 编码设置异常,直接按照GBK输出
            }
            // 执行
            writer = VelocityHelper.getInstance().evaluateToWriter(data, reader);
            writer.writeTo(des);
            des.flush();
        } catch (Exception e) {
            throw new  Exception("写入文件异常");
        } finally {
            if (writer != null)
                writer.close();
            if (des != null)
                des.close();
            if (out != null)
                out.close();
            if (reader != null)
                reader.close();
            if (in != null)
                in.close();
        }
    }

读写图片

  public void createImage(BufferedImage image,
             String fileLocation) throws IOException {
         FileOutputStream fos = null;
          BufferedOutputStream bos = null;
          try {
              fos = new FileOutputStream(fileLocation);
             bos = new BufferedOutputStream(fos);
            // JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(bos);
             // encoder.encode(image);
             ImageIO.write(image, "JPEG", bos);
          } catch (Exception e) {
              e.printStackTrace();
         } finally {
              if (fos != null) {
                  fos.close();
              }
              if (bos != null) {
                 bos.close();
              }
       }
      }
     boolean isJob = etlTrans.getFromKjb();
         byte[] xml = etlTrans.getXml();
         ByteArrayInputStream bais = new ByteArrayInputStream(xml);
         TransMeta transMeta = null;
         JobMeta jobMeta = null;
         int imageWidth = 1400;// 图片的宽度
         int imageHeight = 900;// 图片的高度
         int wordWidth = 6;
         BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);
         Graphics graphics = image.getGraphics();
         // 字体不支持时,中文就显示口口口了
         graphics.setFont(new java.awt.Font("宋体", java.awt.Font.BOLD, 20));
         // Font oldFont = graphics.getFont();
         graphics.setColor(Color.WHITE);
         graphics.fillRect(0, 0, imageWidth, imageHeight);

读写xml文件

public static void objectXmlEncoder(Object obj,
            String fileName) throws FileNotFoundException, IOException, Exception {
        // 创建输出文件
        File fo = new File(fileName);
        // 文件不存在,就创建该文件
        if (!fo.exists()) {
            // 先创建文件的目录
            String path = fileName.substring(0, fileName.lastIndexOf('.'));
            File pFile = new File(path);
            pFile.mkdirs();
        }
        // 创建文件输出流
        FileOutputStream fos = new FileOutputStream(fo);
        // 创建XML文件对象输出类实例
        XMLEncoder encoder = new XMLEncoder(fos);
        // 对象序列化输出到XML文件
        encoder.writeObject(obj);
        encoder.flush();
        // 关闭序列化工具
        encoder.close();
        // 关闭输出流
        fos.close();
    }

xml文件读写,实际上也是一样的,只不过调用的是xml提供的读写工具类

分文件写数据

 private void writeLog(FileOutputStream writer,
             List<ExceptionLog> cellExceptionLogs) throws IOException {
         StringBuilder builder = new StringBuilder();
         int len = cellExceptionLogs.size();
         for (int i = 0; i < len; i++) {
             ExceptionLog exceptionLog = cellExceptionLogs.get(i);
             processSystemLogData(exceptionLog, builder);
             if ((i + 1) % 500 == 0) {
                 writer.write(builder.toString().getBytes("UTF-8"));
                 writer.flush();
                 builder = null;
                 builder = new StringBuilder();
             }
         }
         if (len % 500 != 0) {
             writer.write(builder.toString().getBytes("UTF-8"));
             writer.flush();
         }
     }
     private void processSystemLogData(ICellExceptionLog exception,
             StringBuilder builder) {
         builder.append(exception.getId() + Constant.REPORTUNITSEPARATOR);
         builder.append(exception.getCode() + Constant.REPORTUNITSEPARATOR);
         builder.append(exception.getName() + Constant.REPORTUNITSEPARATOR);
         builder.append(exception.getDescription()+ Constant.REPORTUNITSEPARATOR);
         builder.append("\r\n");
     }

综合应用:编写一个页面,提供给用户下载数据。用户选择下载位置

本质上是一样,只不过输出的流特殊一点(参考于java web文件下载功能实现

第一步:写一个提供给用户的按钮

<a href="/day06/ServletDownload?filename=cors.zip">压缩包</a>  

第二步:编写对应的servlet方法。一共分两步:把文件读入文件流,把文件流内的数据写入到response中(response.getOutputStream().write(bytes, 0, bytes.length);)

 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
         //获得请求文件名
        String filename = request.getParameter("filename");
        System.out.println(filename);
        //设置文件MIME类型
        response.setContentType(getServletContext().getMimeType(filename));
        //设置Content-Disposition
        response.setHeader("Content-Disposition", "attachment;filename="+filename);
        //读取目标文件,通过response将目标文件写到客户端
        //获取目标文件的绝对路径
        String fullFileName = getServletContext().getRealPath("/download/" + filename);
        //System.out.println(fullFileName);
        //读取文件
        InputStream in = new FileInputStream(fullFileName);
        OutputStream out = response.getOutputStream();
        //写文件
        int b;
        while((b=in.read())!= -1)
        {
            out.write(b);
        }
     · in.close();
        out.close();
    }  

断点续传

   所谓断点续传 就是文件已经下载的地方开始继续下载。所以在客户端浏览器传给 Web服务器的时候要多加一条信息--从哪里开始。

断点续传的简单实例:

这是浏览器想要断点续传的时候发给服务器的信息

//        GET /down.zip HTTP/1.0
//        User-Agent: NetFox
//        RANGE: bytes=2000070-
//        Accept: text/html, image/gif, image/jpeg, *; q=.2, *; q=.2
//        上边的信息,是浏览器发给web用户的断点续传的信息

服务器的操作: 这是一段简单的模拟从一定位置些文件的代码

//假设从 2000070 处开始保存文件,代码如下:
        RandomAccess oSavedFile = new  RandomAccessFile("down.zip","rw");
        long nPos = 2000070;
        // 定位文件指针到 nPos 位置
        oSavedFile.seek(nPos);
        byte[] b = new byte[1024];
        int nRead;
        // 从输入流中读入字节流,然后写到文件中
        while((nRead=input.read(b,0,1024)) > 0)
        {
        oSavedFile.write(b,0,nRead);   

实际上,sevlet断点续传肯定不是这么简单,但是总体思路一致,我犹豫好久,最终还是把真正用于web项目的断点续传的服务器操作贴出(虽然我已经去了大多数和断点续传无关的业务逻辑,但是代码还是稍显复杂)

         public static final int buf_size = 8192;
         ServletOutputStream out = response.getOutputStream();
         RandomAccessFile raf = null;
         File file = null;
         try {
             file = new File(filefullname);//这是我们写文件的数据来源
             raf = new RandomAccessFile(file, "rw");
             long pos = 0;
             long end = 0;//这写开始写的位置,结束的位置可以从请求报文得到
             long fLength = file.length();
             //如果有断点续传的信息
             if (queryStringMap.get("Range") != null) {
                 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);//设置返回信息为206
                 String range = ((String) queryStringMap.get("Range")).replaceAll("bytes=", "");
                 pos = Long.valueOf(range.split("-")[0]);
                 end = Long.valueOf(range.split("-")[1]);
             } else {
                 end = fLength - 1;
             }
             //是我这次请求需要写多少长度的文件
             long length = end - pos + 1;
             //下面是返回报文信息
             response.setHeader("Content-Length", String.valueOf(length));
             response.setContentType("application/x-msdownload");
             response.addHeader("content-disposition", "attachment;filename=\"" + filename + "\"");
             //下边是调用RandomAccess方法进行断点续传
             byte[] buf = new byte[buf_size];
             int n = 0;
             int i = 0;//用来for循环中统计写的次数
             raf.seek(pos);// 定位文件指针到 pos 位置
             // buf_size 是定义的大文件写的快,如果你的文件还没一块大,直接写就好
             int p = (int) (length / buf_size);//我要写多少块(byte[])
             int b_size = (int) (length % buf_size);
             if (b_size > 0)
                 p++;
             while (i < p) {
                 i++;
                 if (i == p && b_size > 0) {
                     buf = new byte[b_size];
                     n = raf.read(buf, 0, b_size);
                 } else {
                     n = raf.read(buf, 0, buf_size);
                 }
                 out.write(buf);
                 pos += n;
                 raf.seek(pos);
             }
         }catch(Exception e){
             e.printStackTrace();
         }finally{
 //            ....
         }
             //...
     

服务器操作完成之后返回的相应信息: 可以清楚的看到比普通的返回信息多了一个content-range

//Content-Length=106786028
//Content-Range=bytes 2000070-106786027/106786028
//Date=Mon, 30 Apr 2001 12:55:20 GMT
//ETag=W/"02ca57e173c11:95b"
//Content-Type=application/octet-stream
//Server=Microsoft-IIS/5.0
//Last-Modified=Mon, 30 Apr 2001 12:55:20 GMT  

当然实际项目都是多线程的,可以参考Java--使用多线程下载,断点续传技术原理(RandomAccessFile)

RandomAccessFile的常用方法介绍 可以参考java的RandomAccessFile类

 

给你一数据库

 protected Map<String, Object> getRecord(ResultSet rset,
             ResultSetMetaData metaData,
             int colnum) throws SQLException {
         Map<String, Object> resultMap = new HashMap<String, Object>();
         for (int columnCount = 1; columnCount <= colnum; columnCount++) {
             String aliasName = metaData.getColumnLabel(columnCount);
             Object columnValue = null;
             String columnName = aliasName != null ? aliasName : metaData.getColumnName(columnCount);
             int columnType = metaData.getColumnType(columnCount);
             //...
             if (columnType == Types.BLOB || columnType == Types.LONGVARCHAR
                     || columnType == Types.LONGVARBINARY) {
                 if (rset.getBlob(columnName) != null) {

                     InputStream res = rset.getBlob(columnName).getBinaryStream();
                     int BUFFER_SIZE = 4096;
                     ByteArrayOutputStream outStream = new ByteArrayOutputStream();
                     try {
                         byte[] data = new byte[4096];
                         int count = -1;
                         while ((count = res.read(data, 0, BUFFER_SIZE)) != -1)
                             outStream.write(data, 0, count);

                         data = null;
                         columnValue = new String(outStream.toByteArray());

                     } catch (Exception e) {
                         throw new SQLException("GenericDaoImpl.jdbc.error");
                     } finally {
                         try {
                             if (outStream != null)
                                 outStream.close();
                             if (res != null)
                                 res.close();
                         } catch (IOException e) {
                             throw new SQLException("GenericDaoImpl.jdbc.error");
                         }
                     }
                 }
             } else if (columnType == Types.CLOB) {
                 Clob clob = rset.getClob(columnName);
                 if (clob != null) {
                     columnValue = clob.getSubString((long) 1, (int) clob.length());
                 }
             } else {
                 columnValue = rset.getObject(columnName);
             }
             resultMap.put(columnName.toUpperCase(), columnValue);
         }
         return resultMap;
     }

数据库中读取记录代码

一般情况下,web项目采用一些orm框架足以支撑,数据库字段的读写,但是,有时候为了效率或者是特有的业务要求。会自己编写dao层支持。

而在读取数据库记录,写入Map的时候,如何读取一些特殊字段,比如Blob, 上述代码就是描述如何读取blob字段

IO概括:

 文件的读写 ,在java中是非常常用。

 而设计者设计读写控制的时候,也整理的颇为详细,可能的结果就是,给调用者带来很大的困惑,这么多类,咋用。

 其实完全不用担心:java写文件只有三步,百变不离其宗

    1:我找到三样东西:   铲子(IO工具类); 沙子(我要读取的数据); 篓子(我要放的东西)

    2:用铲子把沙子放到篓子里

    3:我把铲子还给人家

至于我用 何种铲子,我要铲的是沙子 还是面粉,我要放到篓子还是框子里。先别管,也别上来就看那个javaIIO的类示意图   

按铲子区分:一般单一的铲子不适用,需要组合多种功能

  java中有读写字符的(reader/writer) 读写字节的(inputstream,outputstream)。自由选择

  java按照功能 还会有 filereader xmlreder imgio   buffereader 等

按照沙子来看:

  字符串,byte[] , List<T>,数据库查询的结果集

按照篓子来看

  可以放到Map String 图片 文件 xml

  

  

  

Java IO在实际项目开发中应用的更多相关文章

  1. Java 容器在实际项目开发中应用

    前言:在java开发中我们离不开集合数组等,在java中有个专有名词:"容器" ,下面会结合Thinking in Java的知识和实际开发中业务场景讲述一下容器在Web项目中的用 ...

  2. 《Maven在Java项目开发中的应用》论文笔记(十七)

    标题:Maven在Java项目开发中的应用 一.基本信息 时间:2019 来源:山西农业大学 关键词:Maven:Java Web:仓库:开发人员:极限编程; 二.研究内容 1.Maven 基本原理概 ...

  3. Java项目开发中实现分页的三种方式一篇包会

    前言   Java项目开发中经常要用到分页功能,现在普遍使用SpringBoot进行快速开发,而数据层主要整合SpringDataJPA和MyBatis两种框架,这两种框架都提供了相应的分页工具,使用 ...

  4. java.io.IOException: 您的主机中的软件中止了一个已建立的连接解决办法

    问题现象和http://hi.baidu.com/cara_cloud/item/193a3ee327546d395a2d64be描述的一样,就是在eclipse的console栏中一直显示java. ...

  5. 团队项目开发中,常见的版本控制有svn,git

    团队项目开发中,常见的版本控制有svn,git

  6. java.io.IOException: 你的主机中的软件中止了一个已建立的连接。

    1.异常表现:我在jsp文件中有一个<form>表单,里面有一个<button>保存事件按钮.<button  onclick="addOrUPdate()&q ...

  7. 如何解决tomcat中的应用报java.io.IOException: 您的主机中的软件中止了一个已建立的连接

    转载: 施勇: https://blog.csdn.net/shiyong1949/article/details/72845634 这两天突然看到日志文件中有“java.io.IOException ...

  8. 项目开发中的一些注意事项以及技巧总结 基于Repository模式设计项目架构—你可以参考的项目架构设计 Asp.Net Core中使用RSA加密 EF Core中的多对多映射如何实现? asp.net core下的如何给网站做安全设置 获取服务端https证书 Js异常捕获

    项目开发中的一些注意事项以及技巧总结   1.jquery采用ajax向后端请求时,MVC框架并不能返回View的数据,也就是一般我们使用View().PartialView()等,只能返回json以 ...

  9. tomcat报错:java.io.IOException: 您的主机中的软件中止了一个已建立的连接。

    tomcat报错: org.apache.catalina.connector.ClientAbortException: java.io.IOException: 您的主机中的软件中止了一个已建立的 ...

随机推荐

  1. One day one cf,Keep Wa away from me.

    Codeforces Round #379 (Div. 2) A水,算字符个数 B水,贪心优先组成后者 C贪心尺取,以消耗排序change那个,然后贪心另一个 D对角线就是x0+y0 == x1+y1 ...

  2. struts2.1.6教程十、类型转换

    建立struts2conversion项目,并搭建好struts2的基本开发环境 1.基于Action的直接属性转换 建立t.jsp页面,内容如下: <s:form action="p ...

  3. javase基础回顾(四) 自定义注解与反射

    本篇文章将从元注解.自定义注解的格式.自定义注解与反射结合的简单范例.以及自定义注解的应用来说一说java中的自定义注解. 一.元注解 元注解也就是注解其他注解(自定义注解)的java原生的注解,Ja ...

  4. Sampling Distributions and Central Limit Theorem in R(转)

    The Central Limit Theorem (CLT), and the concept of the sampling distribution, are critical for unde ...

  5. 正则表达式入门案例C#

    ---恢复内容开始--- 在网上百度了好多关于正则表达式的,不过好多都是关于语法的,没有一个具体的案例,有点让人难以入门,毕竟我还是喜欢由具体到抽象的认识.所以我就在这先提供了一个入门小案例(学了了6 ...

  6. Javaweb---服务器Tomcat配置

    1.文件下载 Tomcat官方地址:http://tomcat.apache.org/ 2.文件解压 将下载好文件解压在你想放置的位置即可 解压后的文件: 3.进行配置 一般都要配置这两个参数: 1) ...

  7. opencv 删除二值化图像中面积较小的连通域

    对于上图的二值化图像,要去除左下角和右上角的噪点,方法:使用opencv去掉黑色面积较小的连通域. 代码 CvSeq* contour = NULL; double minarea = 100.0; ...

  8. 在ie下,a标签包被img的时候,为什么有个蓝色的边线

    效果像下图这样 那是由于<img>在ie下有默认边框,只要清除边框就可以了,在style中定义 img{ border:none } 显示效果就变成下面这样了 完!

  9. javascript代码的小小重构

    写js也有那么段时间了,也看过几本关于js的书,从最初的<锋利的jquery><高性能javasrcipt>到<javascript设计模式>等,虽然看了些书,看到 ...

  10. 关于HBuilder的一些使用技巧。

    在HBuilder中一个名为扩展代码块的功能. 扩展代码块 看,它就在上方工具栏的工具选项中,分为自定义html代码块, 自定义js代码块, 自定义css代码块, 自定义jquery代码块. 以下便是 ...