標籤: 台中搬家

  • 又大又漂亮SUV僅5.89萬?11月上市的這幾款性價比特高

    又大又漂亮SUV僅5.89萬?11月上市的這幾款性價比特高

    4T渦輪增壓發動機,最大功率達到143馬力,匹配6速手自一體變速箱或6速手動變速箱。編者點評:新款創酷設計一下子變得年輕、運動。這樣的設計風格無疑對20多歲的朋友們更有吸引力。而且自動擋車型的起售價僅11。99萬,還採用了1。

    在11月份,因為廣州車展的緣故,各大汽車廠商都在這個月里推出了今年最後一波重點新車!今天編者就和大家聊聊這個月上市的重點高性價比新SUV,要買SUV不妨多看看這些新車!

    奇瑞汽車-瑞虎3x

    指導價:5.89-8.09萬

    奇瑞全新的小型SUV–瑞虎3x也在本月上市,5.89萬的起售價,讓很多粉絲都在後台跟我們聊過這款車。

    其實瑞虎3x的車身長寬高為4200*1760*1570mm,軸距為2555mm。編者此前親身體驗過這款車的空間,它的乘坐空間夠用,基本上頭部、腿部空間都有富餘。而且後備廂的空間也不錯。

    動力方面,其採用1.5L發動機最大功率106馬力,最大扭矩為135牛米。搭配4擋自動變速箱或5擋手動變速箱。

    編者點評:

    如果你想要買人生的第一輛車的話,瑞虎3x這一類的低價、年輕化的SUV車型可以說是比較合適的。它賣點在於不錯的外觀內飾設計、不錯的空間實用性,還有較高的性價比。另外較高的坐姿,在行車過程中也更方便察看前方的車流。

    上汽通用雪佛蘭-創酷

    指導價:9.99—14.99萬

    在前幾天,雪佛蘭2017款創酷正式上市了。這次上市的為中期改款車型,它的外觀運用了雪佛蘭全新家族化設計,在前臉造型的變化尤為明顯!

    其採用立體雙格柵的造型、修長的大燈,較老款車型更有運動感。大燈中還帶有U型LED日間行車燈,配置方面,新車提供胎壓監測、智能啟停功能、ESp、7英寸觸摸屏等亮點配置。

    動力系統方面,它搭載的是1.4T渦輪增壓發動機,最大功率達到143馬力,匹配6速手自一體變速箱或6速手動變速箱。

    編者點評:

    新款創酷設計一下子變得年輕、運動。這樣的設計風格無疑對20多歲的朋友們更有吸引力。而且自動擋車型的起售價僅11.99萬,還採用了1.4T發動機,動力表現十分充沛。喜歡這個價位SUV車型的朋友可以多關注它!

    東風標緻-標緻4008

    指導價:18.57-27.37萬

    因為漂亮、科幻的外觀而備受期待的法系全新SUV,標緻4008也在這個月上市!18.57-27.37萬的售價區間讓它比起翼虎、途觀這些對手車型的指導價要稍低一些。

    在外觀和內飾設計方面,標緻4008足夠前衛、時尚。而在動力方面它搭載1.6T、1.8T渦輪增壓發動機,最大功率分別為167馬力、204馬力。匹配6擋手自一體變速箱!

    pSA集團採用的這兩套動力系統其實在動力、平順性、油耗方面都有比較出色的表現,如果你需求動力充沛、提速給力的車型,標緻4008可以成為你重點考慮的車型之一。

    編者點評:

    外觀和內飾的超前的設計、三大件不錯的性能,讓標緻4008的競爭力表現不錯。雖然低配車型的舒適性配置有些缺失,但是其安全性配置還是很厚道的,所以不失為一款競爭力出色的歐系SUV。

    最後總結:

    上面提到的車型都是本月已經上市的高關注度SUV車型,在性價比方面都表現不錯,而我個人比較喜歡標緻4008,前衛的設計和不錯的操控性是它能打動我的地方!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※回頭車貨運收費標準

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    ※推薦評價好的iphone維修中心

    ※教你寫出一流的銷售文案?

    台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

    台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

  • 天哪!手動編寫mybatis雛形竟然這麼簡單

    天哪!手動編寫mybatis雛形竟然這麼簡單

    前言

    mybaits 在ORM 框架中,可算是半壁江山了,由於它是輕量級,半自動加載,靈活性和易拓展性。深受廣大公司的喜愛,所以我們程序開發也離不開mybatis 。但是我們有對mabtis 源碼進行研究嗎?或者想看但是不知道怎麼看的苦惱嗎?

    歸根結底,我們還是需要知道為什麼會有mybatis ,mybatis 解決了什麼問題?
    想要知道mybatis 解決了什麼問題,就要知道傳統的JDBC 操作存在哪些痛點才促使mybatis 的誕生。
    我們帶着這些疑問,再來一步步學習吧。

    原始JDBC 存在的問題

    所以我們先來來看下原始JDBC 的操作:
    我們知道最原始的數據庫操作。分為以下幾步:
    1、獲取connection 連接
    2、獲取preparedStatement
    3、參數替代佔位符
    4、獲取執行結果resultSet
    5、解析封裝resultSet 到對象中返回。

    如下是原始JDBC 的查詢代碼,存在哪些問題?

    public static void main(String[] args) {
            String dirver="com.mysql.jdbc.Driver";
            String url="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8";
            String userName="root";
            String password="123456";
    
            Connection connection=null;
            List<User> userList=new ArrayList<>();
            try {
                Class.forName(dirver);
                connection= DriverManager.getConnection(url,userName,password);
    
                String sql="select * from user where username=?";
                PreparedStatement preparedStatement=connection.prepareStatement(sql);
                preparedStatement.setString(1,"張三");
                System.out.println(sql);
                ResultSet resultSet=preparedStatement.executeQuery();
    
                User user=null;
                while(resultSet.next()){
                    user=new User();
                    user.setId(resultSet.getInt("id"));
                    user.setUsername(resultSet.getString("username"));
                    user.setPassword(resultSet.getString("password"));
                    userList.add(user);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
    
            if (!userList.isEmpty()) {
                for (User user : userList) {
                    System.out.println(user.toString());
                }
            }
    
        }
    

    小夥伴們發現了上面有哪些不友好的地方?
    我這裏總結了以下幾點:
    1、數據庫的連接信息存在硬編碼,即是寫死在代碼中的。
    2、每次操作都會建立和釋放connection 連接,操作資源的不必要的浪費。
    3、sql 和參數存在硬編碼。
    4、將返回結果集封裝成實體類麻煩,要創建不同的實體類,並通過set方法一個個的注入。

    存在上面的問題,所以mybatis 就對上述問題進行了改進。
    對於硬編碼,我們很容易就想到配置文件來解決。mybatis 也是這麼解決的。
    對於資源浪費,我們想到是用連接池,mybatis 也是這個解決的。
    對於封裝結果集麻煩,我們想到是用JDK的反射機制,好巧,mybatis 也是這麼解決的。

    設計思路

    既然如此,我們就來寫一個自定義吃持久層框架,來解決上述問題,當然是參照mybatis 的設計思路,這樣我們在寫完之後,再來看mybatis 的源碼就恍然大悟,這個地方這樣配置原來是因為這樣啊。
    我們分為使用端和框架端兩部分。

    使用端

    我們在使用mybatis 的時候是不是需要使用SqlMapConfig.xml 配置文件,用來存放數據庫的連接信息,以及mapper.xml 的指向信息。mapper.xml 配置文件用來存放sql 信息。
    所以我們在使用端來創建兩個文件SqlMapConfig.xml 和mapper.xml。

    框架端

    框架端要做哪些事情呢?如下:
    1、獲取配置文件。也就是獲取到使用端的SqlMapConfig.xml 以及mapper.xml的 文件
    2、解析配置文件。對獲取到的文件進行解析,獲取到連接信息,sql,參數,返回類型等等。這些信息都會保存在configuration 這個對象中。
    3、創建SqlSessionFactory,目的是創建SqlSession的一個實例。
    4、創建SqlSession ,用來完成上面原始JDBC 的那些操作。

    那在SqlSession 中 進行了哪些操作呢?
    1、獲取數據庫連接
    2、獲取sql,並對sql 進行解析
    3、通過內省,將參數注入到preparedStatement 中
    4、執行sql
    5、通過反射將結果集封裝成對象

    使用端實現

    好了,上面說了一下,大概的設計思路,主要也是仿照mybatis 主要的類實現的,保證類名一致,方便我們後面閱讀源碼。我們先來配置好使用端吧,我們創建一個maven 項目。
    在項目中,我們創建一個User實體類

    public class User {
        private Integer id;
        private String username;
        private String password;
        private String birthday;
        //getter()和setter()方法
    }
    

    創建SqlMapConfig.xml 和Mapper.xml
    SqlMapConfig.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <configuration>
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&amp;characterEncoding=utf8&amp;useUnicode=true&amp;useSSL=false"></property>
        <property name="userName" value="root"></property>
        <property name="password" value="123456"></property>
        
        <mapper resource="UserMapper.xml">
        </mapper>
    </configuration>
    

    可以看到我們xml 中就配置了數據庫的連接信息,以及mapper 一個索引。mybatis中的SqlMapConfig.xml 中還包含其他的標籤,只是豐富了功能而已,所以我們只用最主要的。

    mapper.xml
    是每個類的sql 都會生成一個對應的mapper.xml 。我們這裏就用User 類來說吧,所以我們就創建一個UserMapper.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <mapper namespace="cn.quellanan.dao.UserDao">
        <select id="selectAll" resultType="cn.quellanan.pojo.User">
            select * from user
        </select>
        <select id="selectByName" resultType="cn.quellanan.pojo.User" paramType="cn.quellanan.pojo.User">
            select * from user where username=#{username}
        </select>
    </mapper>
    

    可以看到有點mybatis 裏面文件的味道,有namespace表示命名空間,id 唯一標識,resultType 返回結果集的類型,paramType 參數的類型。
    我們使用端先創建到這,主要是兩個配置文件,我們接下來看看框架端是怎麼實現的。

    加油哈哈。

    框架端實現

    框架端,我們按照上面的設計思路一步一步來。

    獲取配置

    怎麼樣獲取配置文件呢?我們可以使用JDK自帶自帶的類Resources加載器來獲取文件。我們創建一個自定義Resource類來封裝一下:

    import java.io.InputStream;
    public class Resources {
        public  static InputStream getResources(String path){
            //使用系統自帶的類Resources加載器來獲取文件。
            return Resources.class.getClassLoader().getResourceAsStream(path);
        }
    }
    

    這樣通過傳入路徑,就可以獲取到對應的文件流啦。

    解析配置文件

    上面獲取到了SqlMapConfig.xml 配置文件,我們現在來解析它。
    不過在此之前,我們需要做一點準備工作,就是解析的內存放到什麼地方?
    所以我們來創建兩個實體類Mapper 和Configuration。

    Mapper
    Mapper 實體類用來存放使用端寫的mapper.xml 文件的內容,我們前面說了裏面有.id、sql、resultType 和paramType .所以我們創建的Mapper實體如下:

    public class Mapper {
        private String id;
        private Class<?> resultType;
        private Class<?> parmType;
        private String sql;
        //getter()和setter()方法
    }
    

    這裏我們為什麼不添加namespace 的值呢?
    聰明的你肯定發現了,因為mapper裏面這些屬性表明每個sql 都對應一個mapper,而namespace 是一個命名空間,算是sql 的上一層,所以在mapper中暫時使用不到,就沒有添加了。

    Configuration
    Configuration 實體用來保存SqlMapConfig 中的信息。所以需要保存數據庫連接,我們這裏直接用JDK提供的 DataSource。還有一個就是mapper 的信息。每個mapper 有自己的標識,所以這裏採用hashMap來存儲。如下:

    public class Configuration {
    
        private DataSource dataSource;
        HashMap <String,Mapper> mapperMap=new HashMap<>();
        //getter()和setter方法
        }
    

    XmlMapperBuilder

    做好了上面的準備工作,我們先來解析mapper 吧。我們創建一個XmlMapperBuilder 類來解析。通過dom4j 的工具類來解析XML 文件。我這裏用的dom4j 依賴為:

    		<dependency>
                <groupId>org.dom4j</groupId>
                <artifactId>dom4j</artifactId>
                <version>2.1.3</version>
            </dependency>
    

    思路:
    1、獲取文件流,轉成document。
    2、獲取根節點,也就是mapper。獲取根節點的namespace屬性值
    3、獲取select 節點,獲取其id,sql,resultType,paramType
    4、將select 節點的屬性封裝到Mapper 實體類中。
    5、同理獲取update/insert/delete 節點的屬性值封裝到Mapper 中
    6、通過namespace.id 生成key 值將mapper對象保存到Configuration實體中的HashMap 中。
    7、返回 Configuration實體
    代碼如下:

    
    public class XmlMapperBuilder {
        private Configuration configuration;
        public XmlMapperBuilder(Configuration configuration){
            this.configuration=configuration;
        }
    
        public Configuration loadXmlMapper(InputStream in) throws DocumentException, ClassNotFoundException {
            Document document=new SAXReader().read(in);
    
            Element rootElement=document.getRootElement();
            String namespace=rootElement.attributeValue("namespace");
    
            List<Node> list=rootElement.selectNodes("//select");
    
            for (int i = 0; i < list.size(); i++) {
                Mapper mapper=new Mapper();
                Element element= (Element) list.get(i);
                String id=element.attributeValue("id");
                mapper.setId(id);
                String paramType = element.attributeValue("paramType");
                if(paramType!=null && !paramType.isEmpty()){
                    mapper.setParmType(Class.forName(paramType));
                }
                String resultType = element.attributeValue("resultType");
                if (resultType != null && !resultType.isEmpty()) {
                    mapper.setResultType(Class.forName(resultType));
                }
                mapper.setSql(element.getTextTrim());
                String key=namespace+"."+id;
                configuration.getMapperMap().put(key,mapper);
            }
            return configuration;
        }
    
    }
    

    上面我只解析了select 標籤。大家可以解析對應insert/delete/uupdate 標籤,操作都是一樣的。

    XmlConfigBuilder

    我們再來解析一下SqlMapConfig.xml 配置信息思路是一樣的,
    1、獲取文件流,轉成document。
    2、獲取根節點,也就是configuration。
    3、獲取根節點中所有的property 節點,並獲取值,也就是獲取數據庫連接信息
    4、創建一個dataSource 連接池
    5、將連接池信息保存到Configuration實體中
    6、獲取根節點的所有mapper 節點
    7、調用XmlMapperBuilder 類解析對應mapper 並封裝到Configuration實體中
    8、完
    代碼如下:

    public class XmlConfigBuilder {
        private Configuration configuration;
        public XmlConfigBuilder(Configuration configuration){
            this.configuration=configuration;
        }
    
        public Configuration loadXmlConfig(InputStream in) throws DocumentException, PropertyVetoException, ClassNotFoundException {
    
            Document document=new SAXReader().read(in);
    
            Element rootElement=document.getRootElement();
    
            //獲取連接信息
            List<Node> propertyList=rootElement.selectNodes("//property");
            Properties properties=new Properties();
    
            for (int i = 0; i < propertyList.size(); i++) {
                Element element = (Element) propertyList.get(i);
                properties.setProperty(element.attributeValue("name"),element.attributeValue("value"));
            }
    		//是用連接池
            ComboPooledDataSource dataSource = new ComboPooledDataSource();
            dataSource.setDriverClass(properties.getProperty("driverClass"));
            dataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
            dataSource.setUser(properties.getProperty("userName"));
            dataSource.setPassword(properties.getProperty("password"));
            configuration.setDataSource(dataSource);
    
            //獲取mapper 信息
            List<Node> mapperList=rootElement.selectNodes("//mapper");
            for (int i = 0; i < mapperList.size(); i++) {
                Element element= (Element) mapperList.get(i);
                String mapperPath=element.attributeValue("resource");
                XmlMapperBuilder xmlMapperBuilder = new XmlMapperBuilder(configuration);
                configuration=xmlMapperBuilder.loadXmlMapper(Resources.getResources(mapperPath));
            }
            return configuration;
        }
    }
    

    創建SqlSessionFactory

    完成解析后我們創建SqlSessionFactory 用來創建Sqlseesion 的實體,這裏為了盡量還原mybatis 設計思路,也也採用的工廠設計模式。
    SqlSessionFactory 是一個接口,裏面就一個用來創建SqlSessionf的方法。
    如下:

    public interface SqlSessionFactory {
        public SqlSession openSqlSession();
    }
    

    單單這個接口是不夠的,我們還得寫一個接口的實現類,所以我們創建一個DefaultSqlSessionFactory。
    如下:

    public class DefaultSqlSessionFactory implements SqlSessionFactory {
    
        private Configuration configuration;
    
        public DefaultSqlSessionFactory(Configuration configuration) {
            this.configuration = configuration;
        }
        public SqlSession openSqlSession() {
            return new DefaultSqlSeeion(configuration);
        }
    }
    

    可以看到就是創建一個DefaultSqlSeeion並將包含配置信息的configuration 傳遞下去。DefaultSqlSeeion 就是SqlSession 的一個實現類。

    創建SqlSession

    在SqlSession 中我們就要來處理各種操作了,比如selectList,selectOne,insert.update,delete 等等。
    我們這裏SqlSession 就先寫一個selectList 方法。
    如下:

    public interface SqlSession {
    
        /**
         * 條件查找
         * @param statementid  唯一標識,namespace.selectid
         * @param parm  傳參,可以不傳也可以一個,也可以多個
         * @param <E>
         * @return
         */
        public <E> List<E> selectList(String statementid,Object...parm) throws Exception;
    
    

    然後我們創建DefaultSqlSeeion 來實現SqlSeesion 。

    public class DefaultSqlSeeion implements SqlSession {
        private Configuration configuration;
    	private Executer executer=new SimpleExecuter();
    	
        public DefaultSqlSeeion(Configuration configuration) {
            this.configuration = configuration;
        }
    
    	@Override
        public <E> List<E> selectList(String statementid, Object... parm) throws Exception {
            Mapper mapper=configuration.getMapperMap().get(statementid);
            List<E> query = executer.query(configuration, mapper, parm);
            return query;
        }
    
    }
    

    我們可以看到DefaultSqlSeeion 獲取到了configuration,並通過statementid 從configuration 中獲取mapper。 然後具體實現交給了Executer 類來實現。我們這裏先不管Executer 是怎麼實現的,就假裝已經實現了。那麼整個框架端就完成了。通過調用Sqlsession.selectList() 方法,來獲取結果。

    感覺我們都還沒有處理,就框架搭建好了?騙鬼呢,確實前面我們從獲取文件解析文件,然後創建工廠。都是做好準備工作。下面開始我們JDBC的實現。

    SqlSession 具體實現

    我們前面說SqlSeesion 的具體實現有下面5步
    1、獲取數據庫連接
    2、獲取sql,並對sql 進行解析
    3、通過內省,將參數注入到preparedStatement 中
    4、執行sql
    5、通過反射將結果集封裝成對象

    但是我們在DefaultSqlSeeion 中將實現交給了Executer來執行。所以我們就要在Executer中來實現這些操作。

    我們首先來創建一個Executer 接口,並寫一個DefaultSqlSeeion中調用的query 方法。

    public interface Executer {
    
        <E> List<E> query(Configuration configuration,Mapper mapper,Object...parm) throws Exception;
    
    }
    

    接着我們寫一個SimpleExecuter 類來實現Executer 。
    然後SimpleExecuter.query()方法中,我們一步一步的實現。

    獲取數據庫連接

    因為數據庫連接信息保存在configuration,所以直接獲取就好了。

    //獲取連接
            connection=configuration.getDataSource().getConnection();
    

    獲取sql,並對sql 進行解析

    我們這裏想一下,我們在Usermapper.xml寫的sql 是什麼樣子?

    select * from user where username=#{username}
    

    {username} 這樣的sql 我們改怎麼解析呢?

    分兩步
    1、將sql 找到#{***},並將這部分替換成 ?號

    2、對 #{***} 進行解析獲取到裏面的參數對應的paramType 中的值。

    具體實現用到下面幾個類。
    GenericTokenParser類,可以看到有三個參數,開始標記,就是我們的“#{” ,結束標記就是 “}”, 標記處理器就是處理標記裏面的內容也就是username。

    public class GenericTokenParser {
    
      private final String openToken; //開始標記
      private final String closeToken; //結束標記
      private final TokenHandler handler; //標記處理器
    
      public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
        this.openToken = openToken;
        this.closeToken = closeToken;
        this.handler = handler;
      }
    
      /**
       * 解析${}和#{}
       * @param text
       * @return
       * 該方法主要實現了配置文件、腳本等片段中佔位符的解析、處理工作,並返回最終需要的數據。
       * 其中,解析工作由該方法完成,處理工作是由處理器handler的handleToken()方法來實現
       */
      public String parse(String text) {
     	 //具體實現
     	 }
      }
    

    主要的就是parse() 方法,用來獲取操作1 的sql。獲取結果例如:

    select * from user where username=?
    

    那上面用到TokenHandler 來處理參數。
    ParameterMappingTokenHandler實現TokenHandler的類

    
    public class ParameterMappingTokenHandler implements TokenHandler {
    	private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
    
    	// context是參數名稱 #{id} #{username}
    
    	@Override
    	public String handleToken(String content) {
    		parameterMappings.add(buildParameterMapping(content));
    		return "?";
    	}
    
    	private ParameterMapping buildParameterMapping(String content) {
    		ParameterMapping parameterMapping = new ParameterMapping(content);
    		return parameterMapping;
    	}
    
    	public List<ParameterMapping> getParameterMappings() {
    		return parameterMappings;
    	}
    
    	public void setParameterMappings(List<ParameterMapping> parameterMappings) {
    		this.parameterMappings = parameterMappings;
    	}
    
    }
    
    

    可以看到將參數名稱存放 ParameterMapping 的集合中了。
    ParameterMapping 類就是一個實體,用來保存參數名稱的。

    public class ParameterMapping {
    
        private String content;
    
        public ParameterMapping(String content) {
            this.content = content;
        }
    	//getter()和setter() 方法。
    }
    

    所以我們在我們通過GenericTokenParser類,就可以獲取到解析后的sql,以及參數名稱。我們將這些信息封裝到BoundSql實體類中。

    public class BoundSql {
    
        private String sqlText;
        private List<ParameterMapping> parameterMappingList=new ArrayList<>();
        public BoundSql(String sqlText, List<ParameterMapping> parameterMappingList) {
            this.sqlText = sqlText;
            this.parameterMappingList = parameterMappingList;
        }
        ////getter()和setter() 方法。
      }
    

    好了,那麼分兩步走,先獲取,后解析
    獲取
    獲取原始sql 很簡單,sql 信息就存在mapper 對象中,直接獲取就好了。

    String sql=mapper.getSql()
    

    解析
    1、創建一個ParameterMappingTokenHandler 處理器
    2、創建一個GenericTokenParser 類,並初始化開始標記,結束標記,處理器
    3、執行genericTokenParser.parse(sql);獲取解析后的sql‘’,以及在parameterMappingTokenHandler 中存放了參數名稱的集合。
    4、將解析后的sql 和參數封裝到BoundSql 實體類中。

    /**
         * 解析自定義佔位符
         * @param sql
         * @return
         */
        private BoundSql getBoundSql(String sql){
            ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
            GenericTokenParser genericTokenParser = new GenericTokenParser("#{","}",parameterMappingTokenHandler);
            String parse = genericTokenParser.parse(sql);
            return new BoundSql(parse,parameterMappingTokenHandler.getParameterMappings());
    
        }
    

    將參數注入到preparedStatement 中

    上面的就完成了sql,的解析,但是我們知道上面得到的sql 還是包含 JDBC的 佔位符,所以我們需要將參數注入到preparedStatement 中。
    1、通過boundSql.getSqlText()獲取帶有佔位符的sql.
    2、接收參數名稱集合 parameterMappingList
    3、通過mapper.getParmType() 獲取到參數的類。
    4、通過getDeclaredField(content)方法獲取到參數類的Field。
    5、通過Field.get() 從參數類中獲取對應的值
    6、注入到preparedStatement 中

    		BoundSql boundSql=getBoundSql(mapper.getSql());
            String sql=boundSql.getSqlText();
            List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
    
            //獲取preparedStatement,並傳遞參數值
            PreparedStatement preparedStatement=connection.prepareStatement(sql);
            Class<?> parmType = mapper.getParmType();
    
            for (int i = 0; i < parameterMappingList.size(); i++) {
                ParameterMapping parameterMapping = parameterMappingList.get(i);
                String content = parameterMapping.getContent();
                Field declaredField = parmType.getDeclaredField(content);
                declaredField.setAccessible(true);
                Object o = declaredField.get(parm[0]);
                preparedStatement.setObject(i+1,o);
            }
            System.out.println(sql);
            return preparedStatement;
    

    執行sql

    其實還是調用JDBC 的executeQuery()方法或者execute()方法

    //執行sql
     ResultSet resultSet = preparedStatement.executeQuery();
    

    通過反射將結果集封裝成對象

    在獲取到resultSet 后,我們進行封裝處理,和參數處理是類似的。
    1、創建一個ArrayList
    2、獲取返回類型的類
    3、循環從resultSet中取數據
    4、獲取屬性名和屬性值
    5、創建屬性生成器
    6、為屬性生成寫方法,並將屬性值寫入到屬性中
    7、將這條記錄添加到list 中
    8、返回list

    /**
         * 封裝結果集
         * @param mapper
         * @param resultSet
         * @param <E>
         * @return
         * @throws Exception
         */
        private <E> List<E> resultHandle(Mapper mapper,ResultSet resultSet) throws Exception{
            ArrayList<E> list=new ArrayList<>();
            //封裝結果集
            Class<?> resultType = mapper.getResultType();
            while (resultSet.next()) {
                ResultSetMetaData metaData = resultSet.getMetaData();
                Object o = resultType.newInstance();
                int columnCount = metaData.getColumnCount();
                for (int i = 1; i <= columnCount; i++) {
                    //屬性名
                    String columnName = metaData.getColumnName(i);
                    //屬性值
                    Object value = resultSet.getObject(columnName);
                    //創建屬性描述器,為屬性生成讀寫方法
                    PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName,resultType);
                    Method writeMethod = propertyDescriptor.getWriteMethod();
                    writeMethod.invoke(o,value);
                }
                list.add((E) o);
            }
            return list;
        }
    

    創建SqlSessionFactoryBuilder

    我們現在來創建一個SqlSessionFactoryBuilder 類,來為使用端提供一個人口。

    public class SqlSessionFactoryBuilder {
    
        private Configuration configuration;
    
        public SqlSessionFactoryBuilder(){
            configuration=new Configuration();
        }
    
        public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException, ClassNotFoundException {
            XmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder(configuration);
            configuration=xmlConfigBuilder.loadXmlConfig(in);
    
            SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
            return sqlSessionFactory;
        }
    }
    

    可以看到就一個build 方法,通過SqlMapConfig的文件流將信息解析到configuration,創建並返回一個sqlSessionFactory 。

    到此,整個框架端已經搭建完成了,但是我們可以看到,只實現了select 的操作,update、inster、delete 的操作我們在我後面提供的源碼中會有實現,這裏只是將整體的設計思路和流程。

    測試

    終於到了測試的環節啦。我們前面寫了自定義的持久層,我們現在來測試一下能不能正常的使用吧。
    見證奇迹的時刻到啦

    我們先引入我們自定義的框架依賴。以及數據庫和單元測試

    <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.11</version>
            </dependency>
            <dependency>
                <groupId>cn.quellanan</groupId>
                <artifactId>myself-mybatis</artifactId>
                <version>1.0.0</version>
            </dependency>
    
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.10</version>
            </dependency>
    

    然後我們寫一個測試類
    1、獲取SqlMapperConfig.xml的文件流
    2、獲取Sqlsession
    3、執行查找操作

    @org.junit.Test
        public void test() throws Exception{
            InputStream inputStream= Resources.getResources("SqlMapperConfig.xml");
            SqlSession sqlSession = new SqlSessionFactoryBuilder().build(inputStream).openSqlSession();
            List<User> list = sqlSession.selectList("cn.quellanan.dao.UserDao.selectAll");
    
            for (User parm : list) {
                System.out.println(parm.toString());
            }
            System.out.println();
    
            User user=new User();
            user.setUsername("張三");
            List<User> list1 = sqlSession.selectList("cn.quellanan.dao.UserDao.selectByName", user);
            for (User user1 : list1) {
                System.out.println(user1);
            }
    
        }
    

    可以看到已經可以了,看來我們自定義的持久層框架生效啦。

    優化

    但是不要高興的太早哈哈,我們看上面的測試方法,是不是感覺和平時用的不一樣,每次都都寫死statementId ,這樣不太友好,所以我們接下來來點騷操作,通用mapper 配置。
    我們在SqlSession中增加一個getMapper方法,接收的參數是一個類。我們通過這個類就可以知道statementId .

    /**
         * 使用代理模式來創建接口的代理對象
         * @param mapperClass
         * @param <T>
         * @return
         */
        public <T> T getMapper(Class<T> mapperClass);
    

    具體實現就是利用JDK 的動態代理機制。
    1、通過Proxy.newProxyInstance() 獲取一個代理對象
    2、返回代理對象
    那代理對象執行了哪些操作呢?
    創建代理對象的時候,會實現一個InvocationHandler接口,重寫invoke() 方法,讓所有走這個代理的方法都會執行這個invoke() 方法。那這個方法做了什麼操作?
    這個方法就是通過傳入的類對象,獲取到對象的類名和方法名。用來生成statementid 。所以我們在mapper.xml 配置文件中的namespace 就需要制定為類路徑,以及id 為方法名。
    實現方法:

    @Override
        public <T> T getMapper(Class<T> mapperClass) {
    
            Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSeeion.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
                    //獲取到方法名
                    String name = method.getName();
                    //類型
                    String className = method.getDeclaringClass().getName();
                    String statementid=className+"."+name;
    
                    return selectList(statementid,args);
                }
            });
    
    
            return (T) proxyInstance;
        }
    

    我們寫一個UserDao

    public interface UserDao {
        List<User> selectAll();
    
        List<User> selectByName(User user);
    }
    

    這個是不是我們熟悉的味道哈哈,就是mapper層的接口。
    然後我們在mapper.xml 中指定namespace 和id

    接下來我們在寫一個測試方法

    @org.junit.Test
        public void test2() throws Exception{
            InputStream inputStream= Resources.getResources("SqlMapperConfig.xml");
            SqlSession sqlSession = new SqlSessionFactoryBuilder().build(inputStream).openSqlSession();
    
            UserDao mapper = sqlSession.getMapper(UserDao.class);
            List<User> users = mapper.selectAll();
            for (User user1 : users) {
                System.out.println(user1);
            }
    
            User user=new User();
            user.setUsername("張三");
            List<User> users1 = mapper.selectByName(user);
            for (User user1 : users1) {
                System.out.println(user1);
            }
    
        }
    

    番外

    自定義的持久層框架,我們就寫完了。這個實際上就是mybatis 的雛形,我們通過自己手動寫一個持久層框架,然後在來看mybatis 的源碼,就會清晰很多。下面這些類名在mybatis 中都有體現。

    這裏拋磚引玉,祝君閱讀源碼愉快。
    覺得有用的兄弟們記得收藏啊。

    厚顏無恥的求波點贊!!!

    本文由博客一文多發平台 OpenWrite 發布!

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※別再煩惱如何寫文案,掌握八大原則!

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    ※回頭車貨運收費標準

    台中搬家公司費用怎麼算?

  • 日本研究發現大氣污染導致心臟驟停者增加

    摘錄自2020年4月18日共同社報導

    日本川崎醫科大學(岡山縣)等團隊17日在美國醫學雜誌上發表研究結果稱,如果大氣污染物之一、細顆粒物PM2.5在大氣中的濃度上升,因在家中、室外等地心臟驟停被緊急送醫的人就會增加。

    研究團隊分析了2011年至2016年的約6年期間全國47個都道府縣被送醫的10萬人數據與各地點PM2.5濃度之間的關係。團隊成員、川崎醫科大學循環器內科學教授小島淳表示:「在首次全國範圍的研究中,弄清了大氣污染與心臟驟停有關。」

    據該團隊分析,若PM2.5在大氣中的濃度從某一標準上升每立方米10微克(1微克為百萬分之一克),因心臟驟停被送醫的人在全國增加1.6%。

    將日本分為三個大區進行調查後發現,愛知到大阪、高知的中央地區14個府縣上升5.9%。此外若僅限於5至10月溫暖的時期,全國增加2.3%。據稱均不清楚詳細原因。進行調查的6年間全國PM2.5平均濃度為每立方米13.9微克。各地濃度使用了各都道府縣政府所在地的觀測數值。

    空氣污染
    公害污染
    污染治理
    國際新聞
    日本
    心臟病
    PM2.5

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※超省錢租車方案

    ※教你寫出一流的銷售文案?

    網頁設計最專業,超強功能平台可客製化

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

  • IUCN:亞當峰朝聖者腳下 斯里蘭卡瀕危兩棲類正在消失

    環境資訊中心綜合外電;黃鈺婷 翻譯;林大利 審校;稿源:Mongabay

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計最專業,超強功能平台可客製化

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    ※回頭車貨運收費標準

    ※推薦評價好的iphone維修中心

    ※教你寫出一流的銷售文案?

    台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

    台中搬家公司費用怎麼算?

  • 英封城期間 販賣機賣肉和菜、直接向漁夫買 掀新商機

    環境資訊中心綜合外電;姜唯 編譯;林大利 審校

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    ※回頭車貨運收費標準

    ※推薦評價好的iphone維修中心

    ※超省錢租車方案

    台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

    ※推薦台中搬家公司優質服務,可到府估價

  • 印度全國封鎖後 恆河上游部分河段可飲用

    摘錄自2020年4月24日中央社報導

    一般帶大量超級細菌的恆河,在印度全國封鎖近一個月後,因為幾乎沒有人類污染,瑜伽聖城瑞詩凱詩(Rishikesh)這段恆河河水,經政府檢測水質達到可飲用的標準。

    印度北部北阿坎德省(Uttarakhand)污染控制局最近從瑞詩凱詩及哈里德瓦(Haridwar)的恆河河段抽樣進行化驗,發現水質可以飲用。

    官方報告表示,哈基寶里河壇段的恆河水,生化需氧量(Biochemical oxygen demand)也下降20%。專家說,這意味著恆河水含氧量變高,水族可舒適地在河中呼吸。

    印度中央污染控制局(Central Pollution Control Board)近日發表的調查報告顯示,恆河36個監測站中,有27個監測站測得恆河水質已變為可安全沐浴和水中生物可安全生活的水準。

    生活環境
    土地水文
    土地利用
    國際新聞
    印度
    封城
    武漢肺炎
    恆河

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※回頭車貨運收費標準

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    ※推薦評價好的iphone維修中心

    ※教你寫出一流的銷售文案?

    台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

    台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

  • 廣汽傳祺打出SUV“組合拳”,2017款GS4蛻變成全能选手

    廣汽傳祺打出SUV“組合拳”,2017款GS4蛻變成全能选手

    與此同時,還能通過手機遠程控制安防系統(T-BOX),隨時隨地了解控制車內外各種信息。新增的愛信6AT變速箱, 最大的優點就是平順性處於行業領先水平,質量穩定可靠,駕駛舒適性得到了提升。i-4WD智能適時四驅系統,具有兩驅、智能適時四驅及強制四驅等三種模式,極大地提高了傳祺GS4的越野性能。

    如果說自主SUV的崛起,是得益於精品車型的話,那麼就不得不提傳祺GS4了,不俗的原創外觀+成熟的動力組合+大空間,正好符合了我們對一輛緊湊SUV的所有需求。

    縱使傳祺GS4一經推出就取得了成功,但作為一款新車,還存在着很多進步的空間 。比如當時呼聲很高的“動力如果能有更多選擇就好了”,在今年初的時候,傳祺GS4 就順勢推出了235T(1.5T)動力配置,繼續鞏固了GS4銷量地位,在10月銷量中,傳祺GS4的成績已突破3.5萬。

    後來,有更高要求的消費者又提出 “能不能增配6AT+四驅+後排出風口呢”?傳祺這次依舊沒讓我們失望!在半個月前,傳祺GS4就發布了2017新款,上述的功能都如我們所願,總共超過15項性能與配置升級!

    2017款 傳祺GS4 235T 6AT版

    指導價格:13.38-16.18萬

    在廣州車展期間,小編也前往了傳祺展台目睹了新款GS4,但那個人山人海的場景依然歷歷在目,作為本地車企,在主場的人氣還是非常火爆的。

    傳祺GS4 2017款15項配置升級中

    最大的變化主要有以下3點

    ●車載互聯繫統

    ●愛信6AT+i-4WD

    ●後排出風口+USB接口

    2017款GS4整合了百度CarLife車載互聯繫統,理論上支持所有Andriod系統手機,可實現語音控制,觸摸屏控制電話、音樂、地圖及其它App應用。

    與此同時,還能通過手機遠程控制安防系統(T-BOX),隨時隨地了解控制車內外各種信息。

    新增的愛信6AT變速箱, 最大的優點就是平順性處於行業領先水平,質量穩定可靠,駕駛舒適性得到了提升。

    i-4WD智能適時四驅系統,具有兩驅、智能適時四驅及強制四驅等三種模式,極大地提高了傳祺GS4的越野性能。只需輕轉控制旋鈕,即可自由切換,兼顧燃油經濟性和通過性,最大爬坡度輕鬆≥40%。

    後排新增出風口、雙USB接口與手機儲物盒,對於後排乘客而言,也豐富了乘車樂趣,因為都是非常實用的配置。同時,後排中央扶手及中央頭枕與後排隱私玻璃在2017款GS4中配車型即有所體現。

    除這幾項大的升級之外,其餘的功能、性能升級在這就一一闡述了,想了解的朋友可以自己查一下產品資料。

    小編最後點評

    放眼所有汽車品牌,幾乎每個品牌都有屬於自己的品牌符號,像高爾夫之於大眾、卡羅拉之於豐田、3系之於寶馬。GS4之于于傳祺正是這樣,一款細分市場明星車型全面打響了品牌,成為了家喻戶曉的標杆產品。隨後又推出了GS8這款豪華大七座SUV,據小編了解,到目前為止已經收穫2萬個訂單了!在國產高端SUV銷量普遍不濟的情況下,GS8的勢頭非常奪人眼球。

    在國產車裡,小編也不止一次推薦過GS4了,它和其他品牌不一樣,傳祺的原創設計、做工和自主核心技術非常成熟,不僅有了與合資同台競技的硬實力,還有了以後長久發展的根基,小編對它還是比較看好的。

    2017款GS4的改款是成功的,豐富的產品線也趨於完善,配置升級非常實用,滿足了更多人的選車訴求。

    更重要的是,價格方面也沒想象中大幅上漲,而是與1.5T 7速雙離合配置的價格保持了一致,聰明的定價相信也能俘虜不少新的消費者,繼續鞏固傳祺GS4在銷量榜的領先地位。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※別再煩惱如何寫文案,掌握八大原則!

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    ※回頭車貨運收費標準

    台中搬家公司費用怎麼算?

  • HashMap源碼閱讀(java1.8.0)

    HashMap源碼閱讀(java1.8.0)

    1.1 背景知識

    1.1.1 紅黑樹

      二叉查找樹可能因為多次插入新節點導致失去平衡,使得查找效率低,查找的複雜度甚至可能會出現線性的,為了解決因為新節點的插入而導致查找樹不平衡,此時就出現了紅黑樹。

    紅黑樹它一種特殊的二叉查找樹。紅黑樹的每個節點上都有存儲位表示節點的顏色,可以是紅(Red)或黑(Black)。它具有以下特點:

    (1)每個節點或者是黑色,或者是紅色。

    (2)根節點是黑色。

    (3)每個恭弘=叶 恭弘子節點(恭弘=叶 恭弘子節點,是指為空(NIL或NULL)的恭弘=叶 恭弘子節點)是黑色。

    (4)如果一個節點是紅色的,則它的子節點一定是黑色(即從根節點到恭弘=叶 恭弘子節點的路徑上不能有兩個重複的紅色節點)。

    (5)從一個節點到其上每一個恭弘=叶 恭弘節點的所有路徑都具有相同的黑色節點個數。

    紅黑樹的基本操作–添加

    ① 將紅黑樹當作一顆二叉查找樹,將節點插入。

    ② 將插入的節點着色為”紅色”。(因為條件5,從一個節點到其中每一個節點的的所有路徑都具有相同的黑色節點)。

    ③通過一系列的旋轉(左旋或右旋操作)或着色等操作,使之重新成為一顆紅黑樹。

                           

    1.2 源碼

      在java 1.7之前是用數組和鏈表一起組合構成HashMap,在java1.8之後就使用當鏈表長度超過8之後,就會將鏈錶轉化為紅黑樹,縮小查找的時間(紅黑樹維護也會花費大量時間,包含左旋、右旋和變色過程)。

    1.2.1 HashMap的初始化

    hashmap構造函數會初始化三個值:

    • 初始容量initialCapacity:默認值是16,當儲存的數據越來越多的時候,就必須進行擴容操作。
    • 閾值threshold:hashmap的數組結構中所能存放的最大數量,超過該數量,則會對數組進行擴容。閾值的計算方式為:容量(initialCapacity)*負載因子(loadFactor)。
    • 負載因子loadFactor:當負載因子很大時,閾值會很大,table數組擴容的可能性比較小,會使得一個數組中的鏈表(紅黑樹)存放過多的數據,雖然節省了一定的空間,但會導致查詢時間很長。相反負載因子很小時,擴容的可能性會很高,使得數組中的數據變得相對少,查詢時間會縮短,但會花費較長的時間。

      在初始化一個hashmap對象的時候,指定鍵值對的同時,也可以指定初始map的容量大小,假設此處我們指定大小為11,則會在構造函數中調用tableSizeFor將容量改為2的n冪次,即比當前容量大,而且必須是2的指數次冪的最小數,就會變成16。這是因為2的指數次冪便於計算進行位運算操作,提升運行效率問題(位運算>加法>乘法>除法>取模)。

      hashmap的的默認值是16,負載因子默認是0.75,代碼如下:

    //HashMap<String,String> hashMap = new HashMap<String, String>(11);
    
    /**
     * Returns a power of two size for the given target capacity.
     **/
    static final int tableSizeFor(int cap) {
        int n = cap - 1;   //10 防止在cap已經是2的n次冪的情況下
        // >>> 表示不關心符號位,對數據的二進制形式進行右移  |表示或運算
        n |= n >>> 1;	  //15
        n |= n >>> 2;     //15
        n |= n >>> 4;     //15
        n |= n >>> 8;     //15
        n |= n >>> 16;    //15
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; //16
    }
    

    1.2.2 HashMap的put操作

       這裏可以介紹一下&位運算,當我們將一對KV存儲到hashmap當中時,會通過(n – 1) & hash運算來定位將要插入的鍵值對放入到哈希表的某個桶中。其中n表示哈希表的長度,通常n為2的倍數,通過n-1即可n所表示的二進制數,除最高位外,全部轉化為1,藉助與運算即可快速完成取模操作。

     //hashMap.put("2020", "good luck");
    
     /**
      * Implements Map.put and related methods.
      *
      * @param hash hash for key
      * @param key the key
      * @param value the value to put
      * @param onlyIfAbsent if true, don't change existing value
      * @param evict if false, the table is in creation mode.
      * @return previous value, or null if none
      */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //如果hashtable沒有初始化,則初始化該table數組
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length; 
        //通過位運算找到數組中的下標位置,如果數組中對應下標為空,則可以直接存放下去
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            //數組元素對應的位置已經有元素,產生碰撞
            Node<K,V> e; K k;
            //如果插入的元素key是已經存在的,則將新的value替換掉原來的舊值
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //如果此時table數組對應的位置是紅黑樹結構,則將該節點插入紅黑樹中
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                //如果此時table數組對應的位置是鏈表結構
                for (int binCount = 0; ; ++binCount) {
    				//遍歷到數組尾端,沒有與插入鍵值對相同的key,則將新的鍵值對插入鏈表尾部
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //鏈表過長,將鏈錶轉化為紅黑樹
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //發現鏈表中的某個節點有與插入鍵值對相同的key,則跳出循環,在循環外部重新賦值
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //該key在hashmap已存在,更新與在鏈表跳出循環節點對應的值
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        //超過閾值則更新
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
    

    1.2.3 HashMap的get操作

    /**
         * Implements Map.get and related methods.
         *
         * @param hash hash for key
         * @param key the key
         * @return the node, or null if none
         */
        final Node<K,V> getNode(int hash, Object key) {
            Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
            //table數組不為空,且對應的下標位置也不為空。
            if ((tab = table) != null && (n = tab.length) > 0 &&
                (first = tab[(n - 1) & hash]) != null) {
                //如果第一個位置是對應的key,則返回
                if (first.hash == hash && // always check first node
                    ((k = first.key) == key || (key != null && key.equals(k))))
                    return first;
                //遍歷其他元素
                if ((e = first.next) != null) {
                    //紅黑樹
                    if (first instanceof TreeNode)
                        return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                    //鏈表
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            return e;
                    } while ((e = e.next) != null);
                }
            }
            return null;
        }

    1.2.4 HashMap的擴容操作

        /**
         * Initializes or doubles table size.  If null, allocates in
         * accord with initial capacity target held in field threshold.
         * Otherwise, because we are using power-of-two expansion, the
         * elements from each bin must either stay at same index, or move
         * with a power of two offset in the new table.
         *
         * @return the table
         */
        final Node<K,V>[] resize() {
            Node<K,V>[] oldTab = table;
            int oldCap = (oldTab == null) ? 0 : oldTab.length;
            int oldThr = threshold;
            int newCap, newThr = 0;
            //table不為空,且容量大於0
            if (oldCap > 0) {
                //如果舊的容量到達閾值,則不再擴容,閾值直接設置為最大值
                if (oldCap >= MAXIMUM_CAPACITY) {
                    threshold = Integer.MAX_VALUE;
                    return oldTab;
                }
                //如果舊的容量沒有到達閾值,直接操作
                else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                         oldCap >= DEFAULT_INITIAL_CAPACITY)
                    newThr = oldThr << 1; // double threshold
            }
            //閾值大於0,直接使用舊的閾值
            else if (oldThr > 0) // initial capacity was placed in threshold
                newCap = oldThr;
            //如果閾值為零,則使用默認的初始化值
            else {               // zero initial threshold signifies using defaults
                newCap = DEFAULT_INITIAL_CAPACITY;
                newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
            }
            if (newThr == 0) {
                float ft = (float)newCap * loadFactor;
                newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                          (int)ft : Integer.MAX_VALUE);
            }
            threshold = newThr;
            //更新數組桶
            @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
            table = newTab;
            //將之前舊數組桶的數據重新移到新數組桶中
            if (oldTab != null) {
                //依次遍歷舊table中每個數組桶的元素
                for (int j = 0; j < oldCap; ++j) {
                    Node<K,V> e;
                    //如果數組桶中含有元素
                    if ((e = oldTab[j]) != null) {
                        //將下標數據清空
                        oldTab[j] = null;
                        //如果元組的某一桶中只有一個元素,則直接將該元素移到新的位置去
                        if (e.next == null)
                            newTab[e.hash & (newCap - 1)] = e;
                        //如果是紅黑樹結構
                        else if (e instanceof TreeNode)
                            ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                         //鏈表 -- 對舊桶里鏈表中的每一個元素重新計算哈希值得到下標
                        else { // preserve order
                            //將原先桶中的鏈表分為兩個鏈表
                            Node<K,V> loHead = null, loTail = null;
                            Node<K,V> hiHead = null, hiTail = null;
                            Node<K,V> next;
                            do {
                                next = e.next;
                                /*
                                 * e.hash & oldCap 對hash取模運算,
                                 * 雖然數組大小擴大了一倍,
                                 * 但是同一個key在新舊table中對應的index卻存在一定聯繫: 
                                 * 要麼一致,要麼相差一個 oldCap。
                                 */
                                if ((e.hash & oldCap) == 0) {
                                    if (loTail == null)
                                        loHead = e;
                                    else
                                        loTail.next = e;
                                    loTail = e;
                                }
                                else {
                                    if (hiTail == null)
                                        hiHead = e;
                                    else
                                        hiTail.next = e;
                                    hiTail = e;
                                }
                            } while ((e = next) != null);
                            if (loTail != null) {
                                loTail.next = null;
                                newTab[j] = loHead;
                            }
                            if (hiTail != null) {
                                hiTail.next = null;
                                newTab[j + oldCap] = hiHead;
                            }
                        }
                    }
                }
            }
            return newTab;
        }
    

      此處在處理鏈表的時候,如何將鏈表中的節點重新分配到新的哈希表需要做一些解釋。在擴容的時候,將原來的哈希表擴大了一倍,原來屬於同一個桶中的數據會被重新分配,此時取模運算時(a mod b),會注意到,b會擴大兩倍(a mod 2b),此時如果該桶中的某一個數據的哈希值是c1(0<c<b),則它必定還是會落入原來的位置,而如果桶中的某一個數據的哈希值是c2(b<c2<2b),則它會被重新分配到一個新的位置(這個位置是原先的哈希桶位置+舊桶的大小)。

    HashMap在多線程的情況下出現的死循環現象

      在某些java版本中擴容機制如果使用鏈表,且再插入時使用尾插法會出現死循環,具體原因可以參考老生常談,HashMap的死循環,在本文中所參考的java版本使用了頭插法的方式將元素添加到鏈表當中,可以避免死循環的出現,但是會出現一部分節點丟失的問題。如圖:

      假設原始的哈希map的某個桶的數據如下,此時線程開始擴容,將桶中的數據分配到lo和hi桶的鏈表中。

       初始時刻線程1和線程2開始運行,線程1在執行完以下代碼后,線程1的時間片運行結束。線程1運行的結果如圖所示

      線程2與線程1同時運行,線程2的時間片未用完,還在繼續執行,根據代碼的分配策略,線程2直到時間片運行結束,出現如圖所示的結果:

       此時CPU的時間片又被分配到了線程1,線程1繼續運行,因為此時A所在的鏈表結構已經發生了變化,只能處理A,B,D三個元素。此時線程1創建的hashmap如圖:

     

     參考資料

      教你初步了解紅黑樹

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※超省錢租車方案

    ※教你寫出一流的銷售文案?

    網頁設計最專業,超強功能平台可客製化

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

  • 一次找出範圍內的所有素數,埃式篩法是什麼神仙算法?

    一次找出範圍內的所有素數,埃式篩法是什麼神仙算法?

    本文始發於個人公眾號:TechFlow,原創不易,求個關注

    今天這篇是算法與數據結構專題的第23篇文章,我們繼續數論相關的算法,來看看大名鼎鼎的埃式篩法。

    我們都知道在數學領域,素數非常重要,有海量的公式和研究關於素數,比如那個非常著名至今沒有人解出來的哥德巴赫猜想。和數學領域一樣,素數在信息領域也非常重要,有着大量的應用。舉個簡單的例子,很多安全加密算法也是利用的質數。我們想要利用素數去進行各種計算之前,總是要先找到素數。所以這就有了一個最簡單也最不簡單的問題,我們怎麼樣來尋找素數呢?

    判斷素數

    尋找素數最樸素的方法當然是一個一個遍歷,我們依次遍歷每一個數,然後分別判斷是否是素數。所以問題的核心又回到了判斷素數上,那麼怎麼判斷一個數是不是素數呢?

    素數的性質只有一個,就是只有1和它本身這兩個因數,我們要判斷素數也只能利用這個性質。所以可以想到,假如我們要判斷n是否是素數,可以從2開始遍歷到n-1,如果這n-1個數都不能整除n,那麼說明n就是素數。這個我沒記錯在C語言的練習題當中出現過,總之非常簡單,可以說是最簡單的算法了。

    def is_prime(n):
        for i in range(2, n):
            if n % i == 0:
                return False
        return n != 1
    

    顯然,這個算法是可以優化的,比如當n是偶數的時候,我們根本不需要循環,除了2意外的偶數一定是合數。再比如,我們循環的上界其實也沒有必要到n-1,到就可以了。因為因數如果存在一定是成對出現的,如果存在小於根號n的因數,那麼n除以它一定大於根號n。

    這個改進也很簡單,稍作改動即可:

    def is_prime(n):
        if n % 2 == 0 and n != 2:
            return False
        for i in range(3, int(math.sqrt(n) + 1)):
            if n % i == 0:
                return False
        return n != 1
    

    這樣我們把O(n)的算法優化到了O()也算是有了很大的改進了,但是還沒有結束,我們還可以繼續優化。數學上有一個定理,只有形如6n-1和6n+1的自然數可能是素數,這裏的n是大於等於1的整數。

    這個定理乍一看好像很高級,但其實很簡單,因為所有自然數都可以寫成6n,6n+1,6n+2,6n+3,6n+4,6n+5這6種,其中6n,6n+2,6n+4是偶數,一定不是素數。6n+3可以寫成3(2n+1),顯然也不是素數,所以只有可能6n+1和6n+5可能是素數。6n+5等價於6n-1,所以我們一般寫成6n-1和6n+1。

    利用這個定理,我們的代碼可以進一步優化:

    def is_prime(n):
        if n % 6 not in (1, 5) and n not in (2, 3):
            return False
        for i in range(3, int(math.sqrt(n) + 1)):
            if n % i == 0:
                return False
        return n != 1
    

    雖然這樣已經很快了,但仍然不是最優的,尤其是當我們需要尋找大量素數的時候,仍會消耗大量的時間。那麼有沒有什麼辦法可以批量查找素數呢?

    有,這個方法叫做埃拉托斯特尼算法。這個名字念起來非常拗口,這是一個古希臘的名字。此人是個古希臘的大牛,是大名鼎鼎的阿基米德的好友。他雖然沒有阿基米德那麼出名,但是也非常非常厲害,在數學、天文學、地理學、文學、歷史學等多個領域都有建樹。並且還自創方法測量了地球直徑、地月距離、地日距離以及黃赤交角等諸多數值。要知道他生活的年代是兩千五百多年前,那時候中國還是春秋戰國時期,可以想見此人有多厲害。

    埃式篩法

    我們今天要介紹的埃拉托斯特尼算法就是他發明的用來篩選素數的方法,為了方便我們一般簡稱為埃式篩法或者篩法。埃式篩法的思路非常簡單,就是用已經篩選出來的素數去過濾所有能夠被它整除的數。這些素數就像是篩子一樣去過濾自然數,最後被篩剩下的數自然就是不能被前面素數整除的數,根據素數的定義,這些剩下的數也是素數。

    舉個例子,比如我們要篩選出100以內的所有素數,我們知道2是最小的素數,我們先用2可以篩掉所有的偶數。然後往後遍歷到3,3是被2篩剩下的第一個數,也是素數,我們再用3去篩除所有能被3整除的數。篩完之後我們繼續往後遍歷,第一個遇到的數是7,所以7也是素數,我們再重複以上的過程,直到遍歷結束為止。結束的時候,我們就獲得了100以內的所有素數。

    如果還不太明白,可以看下下面這張動圖,非常清楚地還原了這整個過程。

    不見圖 請翻牆

    這個思想非常簡單,理解了之後寫出代碼來真的很容易:

    def eratosthenes(n):
        primes = []
        is_prime = [True] * (n + 1)
        for i in range(2, n+1):
            if is_prime[i]:
                primes.append(i)
                # 用當前素數i去篩掉所有能被它整除的數
                for j in range(i * 2, n+1, i):
                    is_prime[j] = False
        return primes
    

    我們運行一次代碼看看:

    和我們的預期一樣,獲得了小於100的所有素數。我們來分析一下篩法的複雜度,從代碼當中我們可以看到,我們一共有了兩層循環,最外面一層循環固定是遍歷n次。而裏面的這一層循環遍歷的次數一直在變化,並且它的運算次數和素數的大小相關,看起來似乎不太方便計算。實際上是可以的,根據素數分佈定理以及一系列複雜的運算(相信我,你們不會感興趣的),我們是可以得出篩法的複雜度是

    極致優化

    篩法的複雜度已經非常近似了,因為即使在n很大的時候,經過兩次ln的計算,也非常近似常數了,實際上在絕大多數使用場景當中,上面的算法已經足夠應用了。

    但是仍然有大牛不知滿足,繼續對算法做出了優化,將其優化到了的複雜度。雖然從效率上來看並沒有數量級的提升,但是應用到的思想非常巧妙,值得我們學習。在我們理解這個優化之前,先來看看之前的篩法還有什麼可以優化的地方。比較明顯地可以看出來,對於一個合數而言,它可能會被多個素數篩去。比如38,它有2和19這兩個素因數,那麼它就會被置為兩次False,這就帶來了額外的開銷,如果對於每一個合數我們只更新一次,那麼是不是就能優化到了呢?

    怎麼樣保證每個合數只被更新一次呢?這裏要用到一個定理,就是每個合數分解質因數只有的結果是唯一的。既然是唯一的,那麼一定可以找到最小的質因數,如果我們能夠保證一個合數只會被它最小的質因數更新為False,那麼整個優化就完成了。

    那我們具體怎麼做呢?其實也不難,我們假設整數n的最小質因數是m,那麼我們用小於m的素數i乘上n可以得到一個合數。我們將這個合數消除,對於這個合數而言,i一定是它最小的質因數。因為它等於i * n,n最小的質因數是m,i 又小於m,所以i是它最小的質因數,我們用這樣的方法來生成消除的合數,這樣來保證每個合數只會被它最小的質因數消除。

    根據這一點,我們可以寫出新的代碼:

    def ertosthenes(n):
        primes = []
        is_prime = [True] * (n+1)
        for i in range(2, n+1):
            if is_prime[i]:
                primes.append(i)
            for j, p in enumerate(primes):
                # 防止越界
                if p > n // i:
                    break
                # 過濾
       is_prime[i * p] = False
                # 當i % p等於0的時候說明p就是i最小的質因數
                if i % p == 0:
                    break
                    
        return primes
    

    總結

    到這裏,我們關於埃式篩法的介紹就告一段落了。埃式篩法的優化版本相對來說要難以記憶一些,如果記不住的話,可以就只使用優化之前的版本,兩者的效率相差並不大,完全在可以接受的範圍之內。

    篩法看着代碼非常簡單,但是非常重要,有了它,我們就可以在短時間內獲得大量的素數,快速地獲得一個素數表。有了素數表之後,很多問題就簡單許多了,比如因數分解的問題,比如信息加密的問題等等。我每次回顧篩法算法的時候都會忍不住感慨,這個兩千多年前被發明出來的算法至今看來非但不過時,仍然還是那麼巧妙。希望大家都能懷着崇敬的心情,理解算法當中的精髓。

    如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

    本文使用 mdnice 排版

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計最專業,超強功能平台可客製化

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    ※回頭車貨運收費標準

    ※推薦評價好的iphone維修中心

    ※教你寫出一流的銷售文案?

    台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

    台中搬家公司費用怎麼算?

  • 容器技術之Docker私有鏡像倉庫docker-distribution

    容器技術之Docker私有鏡像倉庫docker-distribution

      在前邊的博客中我們說到docker的架構由docker客戶端、服務端以及倉庫組成;docker倉庫就是用來存放鏡像的地方;其實docker registry我們理解為存放docker鏡像倉庫的倉庫比較準確吧;因為docker的鏡像倉庫通常是把同一類的鏡像用不同的版本來區別,而registry則是用來存放這些倉庫的倉庫;默認安裝docker都是從dockerhub鏡像倉庫下載鏡像;其實在生產環境中,我們很少去公有倉庫上下載鏡像,原因之一是公有倉庫中的鏡像在生產環境中使用,有些不適配,通常我們是去公有倉庫下載基礎鏡像,然後基於基礎鏡像構建適合自己生產環境中的鏡像;其次公有倉庫鏡像有很多都不是安全的鏡像,這麼說吧,我們不確定自己下載的鏡像是否有後門,是否有挖礦代碼,所以基於種種因素,我們還是有必要搭建自己私有的鏡像倉庫;今天我們就來聊一聊docker的私有鏡像倉庫的搭建;

      1、查看docker-distribution包簡介

    [root@docker_registry ~]# yum info docker-distribution
    Loaded plugins: fastestmirror
    Loading mirror speeds from cached hostfile
     * base: mirrors.aliyun.com
     * extras: mirrors.aliyun.com
     * updates: mirrors.aliyun.com
    Available Packages
    Name        : docker-distribution
    Arch        : x86_64
    Version     : 2.6.2
    Release     : 2.git48294d9.el7
    Size        : 3.5 M
    Repo        : extras/7/x86_64
    Summary     : Docker toolset to pack, ship, store, and deliver content
    URL         : https://github.com/docker/distribution
    License     : ASL 2.0
    Description : Docker toolset to pack, ship, store, and deliver content
    
    [root@docker_registry ~]# 
    

      提示:docker-distribution這個包就是提供簡單倉庫服務軟件實現;

      2、安裝docker-distribution

    [root@docker_registry ~]# yum install -y docker-distribution
    Loaded plugins: fastestmirror
    Loading mirror speeds from cached hostfile
     * base: mirrors.aliyun.com
     * extras: mirrors.aliyun.com
     * updates: mirrors.aliyun.com
    Resolving Dependencies
    There are unfinished transactions remaining. You might consider running yum-complete-transaction, or "yum-complete-transaction --cleanup-only" and "yum history redo last", first to finish them. If those don't work you'll have to try removing/installing packages by hand (maybe package-cleanup can help).
    The program yum-complete-transaction is found in the yum-utils package.
    --> Running transaction check
    ---> Package docker-distribution.x86_64 0:2.6.2-2.git48294d9.el7 will be installed
    --> Finished Dependency Resolution
    
    Dependencies Resolved
    
    ===================================================================================================================
     Package                         Arch               Version                               Repository          Size
    ===================================================================================================================
    Installing:
     docker-distribution             x86_64             2.6.2-2.git48294d9.el7                extras             3.5 M
    
    Transaction Summary
    ===================================================================================================================
    Install  1 Package
    
    Total download size: 3.5 M
    Installed size: 12 M
    Downloading packages:
    docker-distribution-2.6.2-2.git48294d9.el7.x86_64.rpm                                       | 3.5 MB  00:00:03     
    Running transaction check
    Running transaction test
    Transaction test succeeded
    Running transaction
      Installing : docker-distribution-2.6.2-2.git48294d9.el7.x86_64                                               1/1 
      Verifying  : docker-distribution-2.6.2-2.git48294d9.el7.x86_64                                               1/1 
    
    Installed:
      docker-distribution.x86_64 0:2.6.2-2.git48294d9.el7                                                              
    
    Complete!
    [root@docker_registry ~]# 
    

      3、查看docker-distribution安裝了那些文件

    [root@docker_registry ~]# rpm -ql docker-distribution
    /etc/docker-distribution/registry/config.yml
    /usr/bin/registry
    /usr/lib/systemd/system/docker-distribution.service
    /usr/share/doc/docker-distribution-2.6.2
    /usr/share/doc/docker-distribution-2.6.2/AUTHORS
    /usr/share/doc/docker-distribution-2.6.2/CONTRIBUTING.md
    /usr/share/doc/docker-distribution-2.6.2/LICENSE
    /usr/share/doc/docker-distribution-2.6.2/MAINTAINERS
    /usr/share/doc/docker-distribution-2.6.2/README.md
    /var/lib/registry
    [root@docker_registry ~]# 
    

      提示:/etc/docker-distribution/registry/config.yml這個文件用於配置registry的配置文件;/usr/bin/registry是二進制應用程序;/usr/lib/systemd/system/docker-distribution.service 這個文件是docker-distribution的unit file;/var/lib/registry這個目錄用於存放我們上傳到registry上的鏡像存放地;

      4、查看配置文件

    [root@docker_registry ~]# cat /etc/docker-distribution/registry/config.yml
    version: 0.1
    log:
      fields:
        service: registry
    storage:
        cache:
            layerinfo: inmemory
        filesystem:
            rootdirectory: /var/lib/registry
    http:
        addr: :5000
    [root@docker_registry ~]# 
    

      提示:這個配置文件是一個yml語法的配置文件,從上面的信息可以看到,默認情況docker-distribution監聽在tcp的5000端口;存放鏡像的目錄是/var/lib/registry/目錄下;

      5、啟動docker-distribution

    [root@docker_registry ~]# systemctl start docker-distribution
    [root@docker_registry ~]# ss -tnl
    State       Recv-Q Send-Q            Local Address:Port                           Peer Address:Port              
    LISTEN      0      128                           *:22                                        *:*                  
    LISTEN      0      100                   127.0.0.1:25                                        *:*                  
    LISTEN      0      128                          :::22                                       :::*                  
    LISTEN      0      100                         ::1:25                                       :::*                  
    LISTEN      0      128                          :::5000                                     :::*                  
    [root@docker_registry ~]# 
    

      提示:可以看到5000端口已經處於監聽狀態了;到此docker-distribution就啟動起來了;這個倉庫服務很簡陋,沒有用戶認證功能,默認是基於http通信而非https,所以從某些角度講,不是一個安全的倉庫;所以一般不見在互聯網上使用,在自己的內外環境中可以使用;

      這裏補充一點,docker的鏡像通常是 registry地址加repository名稱加版本這三部分組成,registry可以是域名,可以是ip地址加端口,也可以說域名加端口,默認https是443端口,http是80端口,如果不寫端口默認是443而非80(原因是docker默認不支持從http協議的倉庫下載/上傳鏡像);例如 quay.io/coreos/flannel:v0.12.0-s390x  從這個鏡像名我們就可以知道registry是https://quay.io;repository名稱為coreos/flannel 版本是v0.12.0-s390x;

      示例:下載第三方倉庫鏡像到本地

      提示:可以看到下載下來的鏡像名稱就是我們剛才說的registry+repository+版本;從上面的信息我們可以總結一點,docker鏡像的名稱(標籤)反應了該鏡像來自哪個registry的那個倉庫;所以我們要下載私有鏡像倉庫中的鏡像就需要把加上私有registry的名稱或地址+repository+版本來下載私有鏡像倉庫中的鏡像;同理上傳鏡像也需要寫明上傳到那個registry中的那個repository中去;

      示例:上傳本地鏡像到私有倉庫

      提示:要把本地倉庫鏡像傳到私有倉庫中去,首先我們要把本地鏡像打一個新的標籤,按照我們剛才上面說的邏輯,然後在上傳新打到標籤的鏡像到私有倉庫就可以了;從上面的信息我們看到當我們打好標籤后,上傳鏡像時報錯了,提示我們倉庫不是https的;默認情況docker不支持http明文上傳/下載鏡像;如果我們非要用http上傳下載鏡像我們需要在配置文件中明確的告訴docker非安全倉庫地址;

      配置docker支持私有倉庫上傳下載鏡像

    [root@docker_registry ~]# cat /etc/docker/daemon.json
    {
            "registry-mirrors": ["https://registry.docker-cn.com","https://cyr1uljt.mirror.aliyuncs.com"],
            "insecure-registries": ["192.168.0.99:5000"]
    }
    
    [root@docker_registry ~]# systemctl daemon-reload    
    [root@docker_registry ~]# systemctl restart docker 
    

      提示:我們通過在配置文件中配置insecure-registries來告訴docker192.168.0.99:5000這個registry是不安全的,但是我們信任這個倉庫,大概就是這個意思嘛;通常我們是寫主機名然後配合hosts文件來解析的方式來對registry解析;從而把鏡像命名為主機名+倉庫名+版本的形式;如下所示;這裏還需要注意一點insecure-registries後面的列表中的倉庫如果有域名,域名不能有下劃線(“_”),否則重啟docker會起不來;

    [root@docker_registry ~]# cat /etc/docker/daemon.json 
    {
            "registry-mirrors": ["https://registry.docker-cn.com","https://cyr1uljt.mirror.aliyuncs.com"],
            "insecure-registries": ["192.168.0.99:5000","docker-registry.io:5000"]
    
    }
    [root@docker_registry ~]# cat /etc/hosts
    127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
    ::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
    192.168.0.99 docker-registry.io registry
    192.168.0.22 docker-node01.io node01
    192.168.0.23 docker-node02.io node02
    [root@docker_registry ~]# systemctl restart docker
    [root@docker_registry ~]# docker info
    Client:
     Debug Mode: false
    
    Server:
     Containers: 0
      Running: 0
      Paused: 0
      Stopped: 0
     Images: 1
     Server Version: 19.03.11
     Storage Driver: overlay2
      Backing Filesystem: xfs
      Supports d_type: true
      Native Overlay Diff: true
     Logging Driver: json-file
     Cgroup Driver: cgroupfs
     Plugins:
      Volume: local
      Network: bridge host ipvlan macvlan null overlay
      Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
     Swarm: inactive
     Runtimes: runc
     Default Runtime: runc
     Init Binary: docker-init
     containerd version: 7ad184331fa3e55e52b890ea95e65ba581ae3429
     runc version: dc9208a3303feef5b3839f4323d9beb36df0a9dd
     init version: fec3683
     Security Options:
      seccomp
       Profile: default
     Kernel Version: 3.10.0-693.el7.x86_64
     Operating System: CentOS Linux 7 (Core)
     OSType: linux
     Architecture: x86_64
     CPUs: 4
     Total Memory: 1.785GiB
     Name: docker_registry
     ID: R34V:IG2F:23I6:6WG6:FFQ4:75SV:3UKZ:RFH7:DGCO:QS7V:CS7K:NSH6
     Docker Root Dir: /var/lib/docker
     Debug Mode: false
     Registry: https://index.docker.io/v1/
     Labels:
     Experimental: false
     Insecure Registries:
      192.168.0.99:5000
      docker-registry.io:5000
      127.0.0.0/8
     Registry Mirrors:
      https://registry.docker-cn.com/
      https://cyr1uljt.mirror.aliyuncs.com/
     Live Restore Enabled: false
    
    [root@docker_registry ~]#
    

      提示:重啟docker后,如果在docker info 中能夠看到我們配置的內容說明配置生效了;現在我們再來傳我們新打的標籤的鏡像,看看是否能夠傳到我們的私有倉庫呢?

    [root@docker_registry ~]# docker images
    REPOSITORY                       TAG                 IMAGE ID            CREATED             SIZE
    docker-registry.io:5000/centos   7                   b5b4d78bc90c        4 weeks ago         203MB
    centos                           7                   b5b4d78bc90c        4 weeks ago         203MB
    192.168.0.99:5000/flannel        v0.12.0-s390x       57eade024bfb        2 months ago        56.9MB
    quay.io/coreos/flannel           v0.12.0-s390x       57eade024bfb        2 months ago        56.9MB
    [root@docker_registry ~]# docker push 192.168.0.99:5000/flannel:v0.12.0-s390x
    The push refers to repository [192.168.0.99:5000/flannel]
    b67de7789e55: Pushed 
    4c4bfa1b47e6: Pushed 
    3b7ae8a9c323: Pushed 
    fbd88a276dca: Pushed 
    271ca11ef489: Pushed 
    1f106b41b4d6: Pushed 
    v0.12.0-s390x: digest: sha256:3ce5b8d40451787e1166bf6b207c7834c13f7a0712b46ddbfb591d8b5906bfa6 size: 1579
    [root@docker_registry ~]# docker push docker-registry.io:5000/centos:7
    The push refers to repository [docker-registry.io:5000/centos]
    edf3aa290fb3: Pushed 
    7: digest: sha256:c2f1d5a9c0a81350fa0ad7e1eee99e379d75fe53823d44b5469eb2eb6092c941 size: 529
    [root@docker_registry ~]# 
    

      提示:可以看到我們上傳的兩個鏡像都完成了上傳沒有報錯,接下來我們去/var/lib/registry/這個目錄,看看是否有這兩個鏡像相關目錄?

    [root@docker_registry ~]# tree /var/lib/registry/
    /var/lib/registry/
    └── docker
        └── registry
            └── v2
                ├── blobs
                │   └── sha256
                │       ├── 13
                │       │   └── 13b80a37370b57f558a2e06092c39224e5d1ebac50e48df0afdeb43cf2303e60
                │       │       └── data
                │       ├── 17
                │       │   └── 176bad61a3a435da03ec603d2bd8f7a69286d92f21f447b17f21f0bc4e085bde
                │       │       └── data
                │       ├── 1b
                │       │   └── 1b56fbc8a8e10830867455c0794a70f5469c154cdc013554daf501aeda3f30fe
                │       │       └── data
                │       ├── 26
                │       │   └── 266247e2e603e1c840f97cb4d97a08b9184344e9802966cb42c93d21c407839f
                │       │       └── data
                │       ├── 3c
                │       │   └── 3ce5b8d40451787e1166bf6b207c7834c13f7a0712b46ddbfb591d8b5906bfa6
                │       │       └── data
                │       ├── 42
                │       │   └── 42d8e66fa893de4beb5d136b787cf182b24b7f4972c4212b9493b661ad1d7e85
                │       │       └── data
                │       ├── 52
                │       │   └── 524b0c1e57f8ee5fee01a1decba2f301c324a6513ca3551021264e3aa7341ebc
                │       │       └── data
                │       ├── 57
                │       │   └── 57eade024bfbd48c45ef2bad996c4d6a0fa41b692247294745265af738066813
                │       │       └── data
                │       ├── 85
                │       │   └── 85ecb68de4693bb4093d923f6d1062766e4fa7cbb3bf456a2bc19dd3e6c5e6c6
                │       │       └── data
                │       ├── b5
                │       │   └── b5b4d78bc90ccd15806443fb881e35b5ddba924e2f475c1071a38a3094c3081d
                │       │       └── data
                │       └── c2
                │           └── c2f1d5a9c0a81350fa0ad7e1eee99e379d75fe53823d44b5469eb2eb6092c941
                │               └── data
                └── repositories
                    ├── centos
                    │   ├── _layers
                    │   │   └── sha256
                    │   │       ├── 524b0c1e57f8ee5fee01a1decba2f301c324a6513ca3551021264e3aa7341ebc
                    │   │       │   └── link
                    │   │       └── b5b4d78bc90ccd15806443fb881e35b5ddba924e2f475c1071a38a3094c3081d
                    │   │           └── link
                    │   ├── _manifests
                    │   │   ├── revisions
                    │   │   │   └── sha256
                    │   │   │       └── c2f1d5a9c0a81350fa0ad7e1eee99e379d75fe53823d44b5469eb2eb6092c941
                    │   │   │           └── link
                    │   │   └── tags
                    │   │       └── 7
                    │   │           ├── current
                    │   │           │   └── link
                    │   │           └── index
                    │   │               └── sha256
                    │   │                   └── c2f1d5a9c0a81350fa0ad7e1eee99e379d75fe53823d44b5469eb2eb6092c941
                    │   │                       └── link
                    │   └── _uploads
                    └── flannel
                        ├── _layers
                        │   └── sha256
                        │       ├── 13b80a37370b57f558a2e06092c39224e5d1ebac50e48df0afdeb43cf2303e60
                        │       │   └── link
                        │       ├── 176bad61a3a435da03ec603d2bd8f7a69286d92f21f447b17f21f0bc4e085bde
                        │       │   └── link
                        │       ├── 1b56fbc8a8e10830867455c0794a70f5469c154cdc013554daf501aeda3f30fe
                        │       │   └── link
                        │       ├── 266247e2e603e1c840f97cb4d97a08b9184344e9802966cb42c93d21c407839f
                        │       │   └── link
                        │       ├── 42d8e66fa893de4beb5d136b787cf182b24b7f4972c4212b9493b661ad1d7e85
                        │       │   └── link
                        │       ├── 57eade024bfbd48c45ef2bad996c4d6a0fa41b692247294745265af738066813
                        │       │   └── link
                        │       └── 85ecb68de4693bb4093d923f6d1062766e4fa7cbb3bf456a2bc19dd3e6c5e6c6
                        │           └── link
                        ├── _manifests
                        │   ├── revisions
                        │   │   └── sha256
                        │   │       └── 3ce5b8d40451787e1166bf6b207c7834c13f7a0712b46ddbfb591d8b5906bfa6
                        │   │           └── link
                        │   └── tags
                        │       └── v0.12.0-s390x
                        │           ├── current
                        │           │   └── link
                        │           └── index
                        │               └── sha256
                        │                   └── 3ce5b8d40451787e1166bf6b207c7834c13f7a0712b46ddbfb591d8b5906bfa6
                        │                       └── link
                        └── _uploads
    
    65 directories, 26 files
    [root@docker_registry ~]# 
    

      提示:可以看到對應目錄下有兩個子目錄就是以我們上傳的鏡像名稱命名的;

      示例:查看私有倉庫中存在的進行列表

    [root@docker_registry ~]# curl docker-registry.io:5000/v2/_catalog
    {"repositories":["centos","flannel"]}
    [root@docker_registry ~]# 

      示例:下載私有倉庫中的鏡像到本地

    [root@docker_node01 ~]# ip a l ens33
    2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
        link/ether 00:0c:29:22:36:7f brd ff:ff:ff:ff:ff:ff
        inet 192.168.0.22/24 brd 192.168.0.255 scope global ens33
           valid_lft forever preferred_lft forever
        inet6 fe80::20c:29ff:fe22:367f/64 scope link 
           valid_lft forever preferred_lft forever
    [root@docker_node01 ~]# docker images
    REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
    linux1874/myimg     v0.1                e408b1c6e04f        2 weeks ago         1.22MB
    wordpress           latest              c3fa1c8546fb        5 weeks ago         540MB
    mysql               5.7                 f965319e89de        5 weeks ago         448MB
    alpine              v3                  f70734b6a266        6 weeks ago         5.61MB
    nginx               1.14-alpine         8a2fb25a19f5        14 months ago       16MB
    httpd               2.4.37-alpine       dfd436f9a5d8        17 months ago       91.8MB
    [root@docker_node01 ~]# docker pull 192.168.0.99:5000/flannel:v0.12.0-s390x
    v0.12.0-s390x: Pulling from flannel
    176bad61a3a4: Pull complete 
    13b80a37370b: Pull complete 
    42d8e66fa893: Pull complete 
    266247e2e603: Pull complete 
    1b56fbc8a8e1: Pull complete 
    85ecb68de469: Pull complete 
    Digest: sha256:3ce5b8d40451787e1166bf6b207c7834c13f7a0712b46ddbfb591d8b5906bfa6
    Status: Downloaded newer image for 192.168.0.99:5000/flannel:v0.12.0-s390x
    192.168.0.99:5000/flannel:v0.12.0-s390x
    [root@docker_node01 ~]# docker images
    REPOSITORY                  TAG                 IMAGE ID            CREATED             SIZE
    linux1874/myimg             v0.1                e408b1c6e04f        2 weeks ago         1.22MB
    wordpress                   latest              c3fa1c8546fb        5 weeks ago         540MB
    mysql                       5.7                 f965319e89de        5 weeks ago         448MB
    alpine                      v3                  f70734b6a266        6 weeks ago         5.61MB
    192.168.0.99:5000/flannel   v0.12.0-s390x       57eade024bfb        2 months ago        56.9MB
    nginx                       1.14-alpine         8a2fb25a19f5        14 months ago       16MB
    httpd                       2.4.37-alpine       dfd436f9a5d8        17 months ago       91.8MB
    [root@docker_node01 ~]# 
    

      提示:下載私有倉庫中的鏡像,默認情況docker也是不支持直接訪問http協議的倉庫,需要我們手動去配置insecure-registries,然後重啟docker才可以;

      示例:刪除私有倉庫中的鏡像

      1、獲取對應鏡像的sha256的值 curl –header “Accept:application/vnd.docker.distribution.manifest.v2+json” -I -X GET http://<registry addr>/v2/<image name>/manifests/<image tag>

      2、刪除對應鏡像版本元數據 curl -I -X DELETE http://<registry addr>/v2/<image name>/manifests/<image digest>

      提示:如果響應405方法不被允許;我們需要修改私有倉庫的配置文件,將其配置為允許刪除;如下

    [root@docker_registry ~]# cat /etc/docker-distribution/registry/config.yml
    version: 0.1
    log:
      fields:
        service: registry
    storage:
        delete:
            enabled: true
        cache:
            layerinfo: inmemory
        filesystem:
            rootdirectory: /var/lib/registry
    http:
        addr: :5000
    [root@docker_registry ~]# systemctl restart docker-distribution           
    [root@docker_registry ~]# curl -IX DELETE http://docker-registry.io:5000/v2/centos/manifests/sha256:c2f1d5a9c0a81350fa0ad7e1eee99e379d75fe53823d44b5469eb2eb6092c941
    HTTP/1.1 202 Accepted
    Docker-Distribution-Api-Version: registry/2.0
    Date: Sat, 06 Jun 2020 19:55:52 GMT
    Content-Length: 0
    Content-Type: text/plain; charset=utf-8
    
    [root@docker_registry ~]#
    

      提示:degest值包含”sha256:”

      3、垃圾回收清理

    [root@docker_registry ~]# registry garbage-collect /etc/docker-distribution/registry/config.yml 
    centos
    flannel
    flannel: marking manifest sha256:3ce5b8d40451787e1166bf6b207c7834c13f7a0712b46ddbfb591d8b5906bfa6 
    flannel: marking blob sha256:57eade024bfbd48c45ef2bad996c4d6a0fa41b692247294745265af738066813
    flannel: marking blob sha256:176bad61a3a435da03ec603d2bd8f7a69286d92f21f447b17f21f0bc4e085bde
    flannel: marking blob sha256:13b80a37370b57f558a2e06092c39224e5d1ebac50e48df0afdeb43cf2303e60
    flannel: marking blob sha256:42d8e66fa893de4beb5d136b787cf182b24b7f4972c4212b9493b661ad1d7e85
    flannel: marking blob sha256:266247e2e603e1c840f97cb4d97a08b9184344e9802966cb42c93d21c407839f
    flannel: marking blob sha256:1b56fbc8a8e10830867455c0794a70f5469c154cdc013554daf501aeda3f30fe
    flannel: marking blob sha256:85ecb68de4693bb4093d923f6d1062766e4fa7cbb3bf456a2bc19dd3e6c5e6c6
    myweb
    myweb: marking manifest sha256:aaf04cf567a776e36eb3b0bafaec17ed8d9e0a743bdb897dca13f251250ae493 
    myweb: marking blob sha256:4f406abeaab7f848178867409142090d1a551b22b968be6a6dae733c8403738e
    myweb: marking blob sha256:524b0c1e57f8ee5fee01a1decba2f301c324a6513ca3551021264e3aa7341ebc
    myweb: marking blob sha256:c941076f9075280c41b502283f37ab8bafef3a66f4a7ba299838dce07641a48d
    test
    test: marking manifest sha256:5ecad23ab8a52e55f93968f708d325261032dd613287aec92e7cf8ddd6426635 
    test: marking blob sha256:370e3a843c3cb12700301e3f87f929939146cd8b676260bedcd83aaa7fcc2939
    test: marking blob sha256:524b0c1e57f8ee5fee01a1decba2f301c324a6513ca3551021264e3aa7341ebc
    test: marking manifest sha256:da8b53210bf1f4dc4873bbd5589abad616663cda45205ae3a4fffb0729d2730d 
    test: marking blob sha256:461f6ceabc885e2e90b5f9ee82aefc9a30a39510c40e7cd8fb7436a4d340fe1d
    test: marking blob sha256:524b0c1e57f8ee5fee01a1decba2f301c324a6513ca3551021264e3aa7341ebc
    test: marking blob sha256:035e026f1d6b0acba3413ba616dcbabf75d20e945778c52716e601255452b7b7
    
    17 blobs marked, 2 blobs eligible for deletion
    blob eligible for deletion: sha256:b5b4d78bc90ccd15806443fb881e35b5ddba924e2f475c1071a38a3094c3081d
    INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/b5/b5b4d78bc90ccd15806443fb881e35b5ddba924e2f475c1071a38a3094c3081d  go.version=go1.9.4 instance.id=b3029d7f-99e8-4941-8c87-989514b584ea
    blob eligible for deletion: sha256:c2f1d5a9c0a81350fa0ad7e1eee99e379d75fe53823d44b5469eb2eb6092c941
    INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/c2/c2f1d5a9c0a81350fa0ad7e1eee99e379d75fe53823d44b5469eb2eb6092c941  go.version=go1.9.4 instance.id=b3029d7f-99e8-4941-8c87-989514b584ea
    [root@docker_registry ~]# 
    

      測試:下載docker-registry.io:5000/centos:7 看看是否還能下載?

    [root@docker_node01 ~]# docker pull 192.168.0.99:5000/centos:7
    Error response from daemon: manifest for 192.168.0.99:5000/centos:7 not found: manifest unknown: manifest unknown
    [root@docker_node01 ~]# 
    

      提示:以上提示告訴我們沒有對應鏡像的元數據信息;說明我們私有倉庫沒有對應鏡像;以上方法適合精準刪除某個鏡像的某個版本,如果是刪除一個倉庫,直接刪除 /var/lib/registry/docker/registry/v2/repositories/下對應倉庫的目錄,然後在用registry命令做垃圾回收;如下

    [root@docker_registry ~]# ll /var/lib/registry/docker/registry/v2/repositories/
    total 0
    drwxr-xr-x 5 root root 55 Jun  6 14:16 centos
    drwxr-xr-x 5 root root 55 Jun  6 14:15 flannel
    drwxr-xr-x 5 root root 55 Jun  6 15:25 myweb
    drwxr-xr-x 5 root root 55 Jun  6 15:24 test
    [root@docker_registry ~]# rm -rf /var/lib/registry/docker/registry/v2/repositories/test
    [root@docker_registry ~]# rm -rf /var/lib/registry/docker/registry/v2/repositories/myweb
    [root@docker_registry ~]# ll /var/lib/registry/docker/registry/v2/repositories/
    total 0
    drwxr-xr-x 5 root root 55 Jun  6 14:16 centos
    drwxr-xr-x 5 root root 55 Jun  6 14:15 flannel
    [root@docker_registry ~]# registry garbage-collect /etc/docker-distribution/registry/config.yml 
    centos
    flannel
    flannel: marking manifest sha256:3ce5b8d40451787e1166bf6b207c7834c13f7a0712b46ddbfb591d8b5906bfa6 
    flannel: marking blob sha256:57eade024bfbd48c45ef2bad996c4d6a0fa41b692247294745265af738066813
    flannel: marking blob sha256:176bad61a3a435da03ec603d2bd8f7a69286d92f21f447b17f21f0bc4e085bde
    flannel: marking blob sha256:13b80a37370b57f558a2e06092c39224e5d1ebac50e48df0afdeb43cf2303e60
    flannel: marking blob sha256:42d8e66fa893de4beb5d136b787cf182b24b7f4972c4212b9493b661ad1d7e85
    flannel: marking blob sha256:266247e2e603e1c840f97cb4d97a08b9184344e9802966cb42c93d21c407839f
    flannel: marking blob sha256:1b56fbc8a8e10830867455c0794a70f5469c154cdc013554daf501aeda3f30fe
    flannel: marking blob sha256:85ecb68de4693bb4093d923f6d1062766e4fa7cbb3bf456a2bc19dd3e6c5e6c6
    
    8 blobs marked, 9 blobs eligible for deletion
    blob eligible for deletion: sha256:370e3a843c3cb12700301e3f87f929939146cd8b676260bedcd83aaa7fcc2939
    INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/37/370e3a843c3cb12700301e3f87f929939146cd8b676260bedcd83aaa7fcc2939  go.version=go1.9.4 instance.id=1a15f1e7-194f-4a82-b79d-9f437d975f6e
    blob eligible for deletion: sha256:524b0c1e57f8ee5fee01a1decba2f301c324a6513ca3551021264e3aa7341ebc
    INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/52/524b0c1e57f8ee5fee01a1decba2f301c324a6513ca3551021264e3aa7341ebc  go.version=go1.9.4 instance.id=1a15f1e7-194f-4a82-b79d-9f437d975f6e
    blob eligible for deletion: sha256:5ecad23ab8a52e55f93968f708d325261032dd613287aec92e7cf8ddd6426635
    INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/5e/5ecad23ab8a52e55f93968f708d325261032dd613287aec92e7cf8ddd6426635  go.version=go1.9.4 instance.id=1a15f1e7-194f-4a82-b79d-9f437d975f6e
    blob eligible for deletion: sha256:aaf04cf567a776e36eb3b0bafaec17ed8d9e0a743bdb897dca13f251250ae493
    INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/aa/aaf04cf567a776e36eb3b0bafaec17ed8d9e0a743bdb897dca13f251250ae493  go.version=go1.9.4 instance.id=1a15f1e7-194f-4a82-b79d-9f437d975f6e
    blob eligible for deletion: sha256:035e026f1d6b0acba3413ba616dcbabf75d20e945778c52716e601255452b7b7
    INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/03/035e026f1d6b0acba3413ba616dcbabf75d20e945778c52716e601255452b7b7  go.version=go1.9.4 instance.id=1a15f1e7-194f-4a82-b79d-9f437d975f6e
    blob eligible for deletion: sha256:461f6ceabc885e2e90b5f9ee82aefc9a30a39510c40e7cd8fb7436a4d340fe1d
    INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/46/461f6ceabc885e2e90b5f9ee82aefc9a30a39510c40e7cd8fb7436a4d340fe1d  go.version=go1.9.4 instance.id=1a15f1e7-194f-4a82-b79d-9f437d975f6e
    blob eligible for deletion: sha256:4f406abeaab7f848178867409142090d1a551b22b968be6a6dae733c8403738e
    INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/4f/4f406abeaab7f848178867409142090d1a551b22b968be6a6dae733c8403738e  go.version=go1.9.4 instance.id=1a15f1e7-194f-4a82-b79d-9f437d975f6e
    blob eligible for deletion: sha256:c941076f9075280c41b502283f37ab8bafef3a66f4a7ba299838dce07641a48d
    INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/c9/c941076f9075280c41b502283f37ab8bafef3a66f4a7ba299838dce07641a48d  go.version=go1.9.4 instance.id=1a15f1e7-194f-4a82-b79d-9f437d975f6e
    blob eligible for deletion: sha256:da8b53210bf1f4dc4873bbd5589abad616663cda45205ae3a4fffb0729d2730d
    INFO[0000] Deleting blob: /docker/registry/v2/blobs/sha256/da/da8b53210bf1f4dc4873bbd5589abad616663cda45205ae3a4fffb0729d2730d  go.version=go1.9.4 instance.id=1a15f1e7-194f-4a82-b79d-9f437d975f6e
    [root@docker_registry ~]# 
    

      提示:這種方式比較粗暴簡單,通常是一個倉庫里只有一個版本鏡像可以使用這種方式刪除,如果一個倉庫有多個版本,那麼還是建議使用第一種方式;

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    ※回頭車貨運收費標準

    ※推薦評價好的iphone維修中心

    ※超省錢租車方案

    台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

    ※推薦台中搬家公司優質服務,可到府估價