這兩天在看《編程人生》,這本書確實非常不錯。而且看得也特別的輕松。其中有幾個人都談到了如何學習新的語言,但是給我最深刻的是google的首席java架構師joshua bloch。正好最近我也在學習python,所以順便總結一下如何學習一門新的語言。希望你能補充一些。
心態
這不但是學習一門新的語言最重要的,而是對任何的學習都是最重要的。下面是書中的描述,非常的精彩,特別是那個比喻:
“學習一門新的語言的時候,要利用以前所學的語言的功底,但是也要保持開放的心態。有些人執著于一種理念:“這就是寫所有程序必須遵循的方法”。我不是說那種語言,但是某些語言,令人執著于這樣的理念。當開始學習新語言的時候,他們會批評這種語言跟真正神的語言的所有的不同之處。當使用新語言時,他們極力使用神的語言的方法去寫。這樣,你就會錯過這個新語言真正的獨特之處。
這就像你本來只有一個榔頭,有人給了你一個螺絲刀,你說“哎,這不是一把好榔頭,但是我應該可以倒著拿螺絲刀,用螺絲刀來砸東西?!蹦愕玫搅艘粋€很爛的榔頭,但事實上它確實一把很不錯的螺絲刀。所以你應該對所有的事物保持開放和積極的心態?!?/span>
如果你的杯子滿了,那他永遠再也裝不進水了。如果你認為你找到了銀彈,那么你可能就要固步自封了。
對新的事物,方法保持一個開發而積極的心態,才能真正了解他,了解他的獨特之處。
這一點相對來說比較難,程序員一般對他們的語言有一種近乎固執的偏愛。Paul Graham在《黑客與畫家》中好像提到過,開發語言是程序員的宗教信仰,貶低一種語言對使用這種語言的程序員是一種侮辱。
了解他的歷史,哲學觀
選擇一門語言,往往選擇了一種思維方式和哲學觀。所以,了解一門語言的歷史和哲學觀非常重要。你要知道這門語言是誰創建的,為什么創建,如何發展起來的,適合那些領域,以及解決問題的哲學是什么。
那python來說,他的設計哲學是“用一種方法,最好是只有一種方法來做一件事”,而perl的設計哲學是“總有多種方法來做同一件事”。所以,我選擇的是python。
了解這方面的知識的一個非常好的來源是百科網站。
代碼,代碼,還是代碼
代碼是學習一門語言的必經之路,可能也是最快的一種方法。
你不但要找一些優秀的代碼來閱讀,還要親自動手來寫代碼。這個過程對學習語言來說是非??斓摹A硗猓阋欢ㄒ谜Z言去解決實際的問題,而不僅僅是寫代碼來驗證語法。在解決問題的過程中,你可以學習它是如何解決問題的,而且會積累語言的經驗。
在工作中使用一門新的語言來開發新項目的風險相對較大,所以,如果再工作中嘗試使用新的語言,可以選擇一些小的項目來積累經驗。如果工作中無法使用這個語言,那么就在業余使用這個語言解決問題吧。
社區
多去這個語言的社區逛逛吧,這里有很多人在討論這種語言,和他們一起討論你能夠學到更多。
本文轉自CSDN博客
/**
* Java + MongoDB in Secure Mode
*
* @author <a href="mailto:gerald.chen@qq.com">Gerald Chen</a>
* @version $Id: AuthTest.java,v 1.1 2011/05/27 07:24:04 gerald.chen Exp $
*/
public class AuthTest {
?
/** 數據庫連接IP */
public static final String DB_HOST ="192.168.35.101";
?
/** 數據庫連接端口 */
public static final int DB_PORT =27017;
?
public static void main(String[] args) throws Exception, MongoException {
????? Mongo mongo =new Mongo(DB_HOST, DB_PORT);
????? DB db = mongo.getDB("test_db");
?
????? boolean auth = db.authenticate("gerald", "123456".toCharArray());
????? System.out.println(auth);
?
????? DBCollection collection = db.getCollection("test_collection");
????? System.out.println(collection.getFullName());
?
}
?
}
1.進入mongodb命令行管理
C:\Documents and Settings\Administrator>mongo
MongoDB shell version: 1.8.1
connecting to: test
?
2.顯示數據庫
> show dbs
admin?? (empty)
local?? (empty)
test_db 0.03125GB
?
3.使用數據庫
> use test_db
switched to db test_db
?
4.添加數據庫用戶
> db.users.save({username:"gerald"})
?
5.查找數據庫用戶
> db.users.find()
{ "_id" : ObjectId("4ddf396e641b4986d346fe89"), "username" : "gerald" }
?
6.添加隸屬于某個數據庫的用戶
> use test_db
switched to db test_db
> db.addUser("gerald","123456")
{
??????? "user" : "gerald",
??????? "readOnly" : false,
??????? "pwd" : "f528f606b8635241c7f060408973b5b9"
}
?
7.以用戶驗證的身份登錄數據庫
> use test_db
switched to db test_db
> db.auth("gerald","123456")
1
PS: 1代表驗證成功, 0代表驗證失敗
?
關鍵詞:數據庫? NoSQL? MongoDB? Database
?
1.準備
???? 下載Mongo Java Driver,下載地址:https://github.com/downloads/mongodb/mongo-java-driver/mongo-2.5.3.jar
???? 如果是使用maven編譯,可在pom.xml文件中加入如下依賴
???? <dependency>
????????? <groupId>org.mongodb</groupId>
????????? <artifactId>mongo-java-driver</artifactId>
????????? <version>2.5.3</version>
???? </dependency>
2.上程序
/**
?* MongoDB學習之HelloWorld
?*
?* @author <a href="mailto:gerald.chen@qq.com">GeraldChen</a>
?* @version $Id: HelloWorldTest.java,v 1.1 2011/05/26 12:42:45 gerald.chen Exp $
?*/
public class HelloWorldTest {
????? /** 數據庫連接IP */
???? public static final String DB_HOST = "192.168.35.101";
???? /** 數據庫連接端口 */
???? public static final int DB_PORT = 27017;
???? public static void main(String[] args) throws Exception {
???????? // connect to mongoDB, ip and port number
???????? Mongo mongo = new Mongo(DB_HOST, DB_PORT);
???????? // get database from MongoDB,
???????? // if database doesn't exists, mongoDB will create it automatically
???????? DB db = mongo.getDB("test_db");
???????? // Get collection from MongoDB, database named "yourDB"
???????? // if collection doesn't exists, mongoDB will create it automatically
???????? DBCollection collection = db.getCollection("test_collection");
???????? // create a document to store key and value
???????? BasicDBObject document = new BasicDBObject();
???????? document.put("id", 1001);
???????? document.put("message", "hello world mongoDB in Java");
???????? // save it into collection named "yourCollection"
???????? collection.insert(document);
???????? // search query
???????? BasicDBObject searchQuery = new BasicDBObject();
???????? searchQuery.put("id", 1001);
???????? // query it
???????? DBCursor cursor = collection.find(searchQuery);
???????? // loop over the cursor and display the retrieved result
???????? while (cursor.hasNext()) {
????????????????? System.out.println(cursor.next());
???????? }
???????? System.out.println("Done");
???}
}
2.程序輸出
關鍵詞:HelloWorld?? MongoDB?? NoSQL?? JAVA??? 程序?? 軟件?? 數據庫?? 程序員
?
???????繼上一篇MongoDB學習——安裝與配置?,我們接著來看下如何將MongoDB安裝為Windows的服務:
1.服務化
???????在命令窗口運行如下命令即可:
???????#>mongod --dbpath "G:\database\mongodb\data" --logpath "G:\database\mongodb\logs.txt" --install --serviceName "MongoDB"
???????其中"G:\database\mongodb\data"為MongoDB的數據目錄
2.卸載服務
???????執行命令:
???????#>mongod --remove --serviceName "MongoDB"
?
關鍵詞:HelloWorld?? MongoDB?? NoSQL?? JAVA??? 程序?? 軟件?? 數據庫?? 程序員
?
1.MongoDB介紹
MongoDB是一個基于分布式文件存儲的數據庫。由C++語言編寫。旨在為WEB應用提供可擴展的高性能數據存儲解決方案。
它的特點是高性能、易部署、易使用,存儲數據非常方便。主要功能特性有:
*面向集合存儲,易存儲對象類型的數據。
*模式自由。
*支持動態查詢。
*支持完全索引,包含內部對象。
*支持查詢。
*支持復制和故障恢復。
*使用高效的二進制數據存儲,包括大型對象(如視頻等)。
*自動處理碎片,以支持云計算層次的擴展性
*支持RUBY,PYTHON,JAVA,C++,PHP等多種語言。
*文件存儲格式為BSON(一種JSON的擴展)
*可通過網絡訪問
所謂“面向集合”(Collenction-Oriented),意思是數據被分組存儲在數據集中,被稱為一個集合(Collenction)。每個集合在數據庫中都有一個唯一的標識名,并且可以包含無限數目的文檔。集合的概念類似關系型數據庫(RDBMS)里的表(table),不同的是它不需要定義任何模式(schema)。
模式自由(schema-free),意味著對于存儲在mongodb數據庫中的文件,我們不需要知道它的任何結構定義。如果需要的話,你完全可以把不同結構的文件存儲在同一個數據庫里。
存儲在集合中的文檔,被存儲為鍵-值對的形式。鍵用于唯一標識一個文檔,為字符串類型,而值則可以是各種復雜的文件類型。我們稱這種存儲形式為BSON(Binary Serialized dOcument Format)。
2.下載MongoDB
下載地址http://www.mongodb.org/downloads?至本文成文之時,版本號為:1.8.1
?http://downloads.mongodb.org/win32/mongodb-win32-i386-1.8.1.zip
3.安裝
將zip文件解壓至某個磁盤目錄,如:G:\database\mongodb
4.配置
建立數據存儲目錄G:\database\mongodb\data
啟動命令窗口,進到目錄G:\>cd database\mongodb\bin
執行命令mongod --dbpath G:\database\mongodb\data,出現如下信息:
G:\database\mongodb\bin>mongod --dbpath G:\database\mongodb\data
Wed May 25 20:03:06 [initandlisten] MongoDB starting : pid=3244 port=27017 dbpath=G:\database\mongodb\data 32-bit
** NOTE: when using MongoDB 32 bit, you are limited to about 2 gigabytes of data
**?????? seehttp://blog.mongodb.org/post/137788967/32-bit-limitations
**?????? with --dur, the limit is lower
Wed May 25 20:03:06 [initandlisten] db version v1.8.1, pdfile version 4.5
Wed May 25 20:03:06 [initandlisten] git version: a429cd4f535b2499cc4130b06ff7c26f41c00f04
Wed May 25 20:03:06 [initandlisten] build sys info: windows (5, 1, 2600, 2, 'Service Pack 3') BOOST_LIB_VERSION=1_35
Wed May 25 20:03:06 [initandlisten] waiting for connections on port 27017
Wed May 25 20:03:06 [websvr] web admin interface listening on port 28017
5.查看管理后臺
打開瀏覽器,登入地址:http://localhost:28017
出現如下內容:
關鍵詞:HelloWorld?? MongoDB?? NoSQL?? JAVA??? 程序?? 軟件?? 數據庫?? 程序員
?
???????由于MySQL目前字段的默認值不支持函數的形式設置默認值是不可能的。
?????? 代替的方案是使用TIMESTAMP類型代替DATETIME類型。
?????? CURRENT_TIMESTAMP :當我更新這條記錄的時候,這條記錄的這個字段不會改變。
?????? CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP :當我更新這條記錄的時候,這條記錄的這個字段將會改變。即時間變為了更新時候的時間。(注意一個UPDATE設置一個列為它已經有的值,這將不引起TIMESTAMP列被更新,因為如果你設置一個列為它當前的值,MySQL為了效率而忽略更改。)如果有多個TIMESTAMP列,只有第一個自動更新。?
??????TIMESTAMP列類型自動地用當前的日期和時間標記INSERT或UPDATE的操作。?
??????如果有多個TIMESTAMP列,只有第一個自動更新。?
??????自動更新第一個TIMESTAMP列在下列任何條件下發生:?
??????列值沒有明確地在一個INSERT或LOAD DATA INFILE語句中指定。?
??????列值沒有明確地在一個UPDATE語句中指定且另外一些的列改變值。(注意一個UPDATE設置一個列為它已經有的值,這將不引起TIMESTAMP列被更新,因為如果你設置一個列為它當前的值,MySQL為了效率而忽略更改。)?
??????你明確地設定TIMESTAMP列為NULL.?
??????除第一個以外的TIMESTAMP列也可以設置到當前的日期和時間,只要將列設為NULL,或NOW()。?
??????另外在5.0以上版本中也可以使用trigger來實現此功能。
??????create table test_time (?
??????id int(11),?
??????create_time datetime?
??????);?
??????delimiter |?
????????????create trigger default_datetime before insert on test_time?
??????for each row?
????????????if new.create_time is null then?
????????????set new.create_time = now();?
??????end if;|?
??????delimiter ;
?????????單庫單表是最常見的數據庫設計,例如,有一張用戶(user)表放在數據庫db中,所有的用戶都可以在db庫中的user表中查到。
?
隨著用戶數量的增加,user表的數據量會越來越大,當數據量達到一定程度的時候對user表的查詢會漸漸的變慢,從而影響整個DB的性能。如果使用mysql,?還有一個更嚴重的問題是,當需要添加一列的時候,mysql會鎖表,期間所有的讀寫操作只能等待。
可以通過某種方式將user進行水平的切分,產生兩個表結構完全一樣的user_0000,user_0001等表,user_0000 + user_0001 + …的數據剛好是一份完整的數據。
?
?????????隨著數據量增加也許單臺DB的存儲空間不夠,隨著查詢量的增加單臺數據庫服務器已經沒辦法支撐。這個時候可以再對數據庫進行水平區分。
?
?????????設計表的時候需要確定此表按照什么樣的規則進行分庫分表。例如,當有新用戶時,程序得確定將此用戶信息添加到哪個表中;同理,當登錄的時候我們得通過用戶的賬號找到數據庫中對應的記錄,所有的這些都需要按照某一規則進行。
?????????通過分庫分表規則查找到對應的表和庫的過程。如分庫分表的規則是user_id mod 4的方式,當用戶新注冊了一個賬號,賬號id的123,我們可以通過id mod 4的方式確定此賬號應該保存到User_0003表中。當用戶123登錄的時候,我們通過123 mod 4后確定記錄在User_0003中。
1. ? 分庫分表維度的問題
假如用戶購買了商品,需要將交易記錄保存取來,如果按照用戶的緯度分表,則每個用戶的交易記錄都保存在同一表中,所以很快很方便的查找到某用戶的購買情況,但是某商品被購買的情況則很有可能分布在多張表中,查找起來比較麻煩。反之,按照商品維度分表,可以很方便的查找到此商品的購買情況,但要查找到買人的交易記錄比較麻煩。
?
所以常見的解決方式有:
?? ? a.通過掃表的方式解決,此方法基本不可能,效率太低了。
?? ? b.記錄兩份數據,一份按照用戶緯度分表,一份按照商品維度分表。
?? ? c.通過搜索引擎解決,但如果實時性要求很高,又得關系到實時搜索。
?
2. ? 聯合查詢的問題
聯合查詢基本不可能,因為關聯的表有可能不在同一數據庫中。
?
3. ??避免跨庫事務
避免在一個事務中修改db0中的表的時候同時修改db1中的表,一個是操作起來更復雜,效率也會有一定影響。
?
4. ? 盡量把同一組數據放到同一DB服務器上
例如將賣家a的商品和交易信息都放到db0中,當db1掛了的時候,賣家a相關的東西可以正常使用。也就是說避免數據庫中的數據依賴另一數據庫中的數據。
?
?
?????????在實際的應用中,絕大部分情況都是讀遠大于寫。Mysql提供了讀寫分離的機制,所有的寫操作都必須對應到Master,讀操作可以在Master和Slave機器上進行,Slave與Master的結構完全一樣,一個Master可以有多個Slave,甚至Slave下還可以掛Slave,通過此方式可以有效的提高DB集群的QPS.???????????????????????????????????????????????????????
? 所有的寫操作都是先在Master上操作,然后同步更新到Slave上,所以從Master同步到Slave機器有一定的延遲,當系統很繁忙的時候,延遲問題會更加嚴重,Slave機器數量的增加也會使這個問題更加嚴重。
? 此外,可以看出Master是集群的瓶頸,當寫操作過多,會嚴重影響到Master的穩定性,如果Master掛掉,整個集群都將不能正常工作。
? 所以,1.?當讀壓力很大的時候,可以考慮添加Slave機器的分式解決,但是當Slave機器達到一定的數量就得考慮分庫了。?2.?當寫壓力很大的時候,就必須得進行分庫操作。
?
?????????另外,可能會因為種種原因,集群中的數據庫硬件配置等會不一樣,某些性能高,某些性能低,這個時候可以通過程序控制每臺機器讀寫的比重,達到負載均衡。
1,保證線程安全的三種方法:
??? a,不要跨線程訪問共享變量
??? b,使共享變量是final類型的
??? c,將共享變量的操作加上同步
2,一開始就將類設計成線程安全的,比在后期重新修復它,更容易.
3,編寫多線程程序,首先保證它是正確的,其次再考慮性能.
4,無狀態或只讀對象永遠是線程安全的.
5,不要將一個共享變量裸露在多線程環境下(無同步或不可變性保護)
6,多線程環境下的延遲加載需要同步的保護,因為延遲加載會造成對象重復實例化
7,對于volatile 聲明的數值類型變量進行運算,往往是不安全的(volatile 只能保證可見性, 不能保證原子性).
詳見volatile 原理與技巧中,臟數據問題討論.
8,當一個線程請求獲得它自己占有的鎖時( 同一把鎖的嵌套使用),我們稱該鎖為可重入鎖.
在jdk1.5 并發包中,提供了可重入鎖的java 實現-ReentrantLock.
9,每個共享變量, 都應該由一個唯一確定的鎖保護.
創建與變量相同數目的ReentrantLock,使他們負責每個變量的線程安全.
10,雖然縮小同步塊的范圍,可以提升系統性能.
但在保證原子性的情況下,不可將原子操作分解成多個synchronized塊.
11,在沒有同步的情況下,編譯器與處理器運行時的指令執行順序可能完全出乎意料.
原因是,編譯器或處理器為了優化自身執行效率,而對指令進行了的重排序(reordering).
12,當一個線程在沒有同步的情況下讀取變量,它可能會得到一個過期值,但是至少它可以看到那個
線程在當時設定的一個真實數值.而不是憑空而來的值.這種安全保證,稱之為最低限的安全性(out-of-thin-air safety)
在開發并發應用程序時,有時為了大幅度提高系統的吞吐量與性能,會采用這種無保障的做法.
但是針對,數值的運算,仍舊是被否決的.
13,volatile 變量, 只能保證可見性,無法保證原子性.
14,某些耗時較長的網絡操作或IO,確保執行時,不要占有鎖.
15,發布(publish) 對象,指的是使它能夠被當前范圍之外的代碼所使用.( 引用傳遞)
對象逸出(escape),指的是一個對象在尚未準備好時將它發布.
原則:為防止逸出,對象必須要被完全構造完后,才可以被發布( 最好的解決方式是采用同步)
this 關鍵字引用對象逸出
例子:在構造函數中,開啟線程,并將自身對象this 傳入線程,造成引用傳遞.
而此時,構造函數尚未執行完,就會發生對象逸出了.
16,必要時,使用ThreadLocal變量確保線程封閉性(封閉線程往往是比較安全的,但一定程度上會造成性能損耗)
封閉對象的例子在實際使用過程中,比較常見,例如hibernate openSessionInView機制, jdbc的connection機制.
17,單一不可變對象往往是線程安全的(復雜不可變對象需要保證其內部成員變量也是不可變的)
良好的多線程編程習慣是:將所有的域都聲明為final,除非它們是可變的
18,保證共享變量的發布是安全的
??? a,通過靜態初始化器初始化對象(jls 12.4.2 敘述, jvm 會保證靜態初始化變量是同步的)
??? b,將對象申明為volatile 或使用AtomicReference
??? c,保證對象是不可變的
??? d,將引用或可變操作都由鎖來保護
19,設計線程安全的類,應該包括的基本要素:
??? a,確定哪些是可變共享變量
??? b,確定哪些是不可變的變量
??? c,指定一個管理并發訪問對象狀態的策略
20,將數據封裝在對象內部,并保證對數據的訪問是原子的.
建議采用volatile javabean 模型或者構造同步的getter,setter.
21,線程限制性使構造線程安全的類變得更容易,因為類的狀態被限制后,分析它的線程安全性時,就不必檢查完整的程序.
22,編寫并發程序,需要更全的注釋,更完整的文檔說明.
23,在需要細分鎖的分配時,使用java監視器模式好于使用自身對象的監視器鎖.
前者的靈活性更好.
Object target = new Object();
//這里使用外部對象來作為監視器,而非this
synchronized(target) {
??? // TODO
}
針對java monitor pattern,實際上ReentrantLock的實現更易于并發編程.
功能上,也更強大.
24,設計并發程序時,在保證伸縮性與性能折中的前提下,優先考慮將共享變量委托給線程安全的類.
由它來控制全局的并發訪問.
25,使用普通同步容器(Vector, Hashtable) 的迭代器,需要外部鎖來保證其原子性.
原因是,普通同步容器產生的迭代器是非線程安全的.
26,在并發編程中,需要容器支持的時候,優先考慮使用jdk 并發容器
(ConcurrentHashMap, ConcurrentLinkedQueue, CopyOnWriteArrayList...).
27, ConcurrentHashMap, CopyOnWriteArrayList
并發容器的迭代器, 以及全范圍的size(), isEmpty()都表現出弱一致性.
他們只能標示容器當時的一個數據狀態.無法完整響應容器之后的變化和修改.
28,使用有界隊列,在隊列充滿或為空時,阻塞所有的讀與寫操作. ( 實現生產- 消費的良好方案)
BlockQueue下的實現有LinkedBlockingQueue 與ArrayBlockingQueue,前者為鏈表,可變操作頻繁優先考慮, 后者為數組,讀取操作頻繁優先考慮.
PriorityBlockingQueue 是一個按優先級順序排列的阻塞隊列,它可以對所有置入的元素進行排序( 實現Comparator 接口)
29,當一個方法,能拋出InterruptedException,則意味著,這個方法是一個可阻塞的方法,如果它被中斷,將提前結束阻塞狀態.
當你調用一個阻塞方法,也就意味著,本身也稱為了一個阻塞方法,因為你必須等待阻塞方法返回.
如果阻塞方法拋出了中斷異常,我們需要做的是,將其往上層拋,除非當前已經是需要捕獲異常的層次.
如果當前方法,不能拋出InterruptedException,可以使用Thread.currentThread.interrupt() 方法,手動進行中斷.
?
1. java是如何管理內存的
?Java的內存管理就是對象的分配和釋放問題。(兩部分)
分配 :內存的分配是由程序完成的,程序員需要通過關鍵字new 為每個對象申請內存空間 (基本類型除外),所有的對象都在堆 (Heap)中分配空間。
釋放 :對象的釋放是由垃圾回收機制決定和執行的,這樣做確實簡化了程序員的工作。但同時,它也加重了JVM的工作。因為,GC為了能夠正確釋放對象,GC必須監控每一個對象的運行狀態,包括對象的申請、引用、被引用、賦值等,GC都需要進行監控。
2. 什么叫java的內存泄露
???? 在Java中,內存泄漏就是存在一些被分配的對象,這些對象有下面兩個特點,首先,這些對象是可達的,即在有向圖中,存在通路可以與其相連(也就是說仍存在該內存對象的引用);其次,這些對象是無用的,即程序以后不會再使用這些對象。如果對象滿足這兩個條件,這些對象就可以判定為Java中的內存泄漏,這些對象不會被GC所回收,然而它卻占用內存。
3. JVM的內存區域組成
java把內存分兩種:一種是棧內存,另一種是堆內存1。在函數中定義的基本類型變量和對象的引用變量都在函數的棧內存中分配;2。堆內存用來存放由new創建的對象和數組以及對象的實例變量 在函數(代碼塊)中定義一個變量時,java就在棧中為這個變量分配內存空間,當超過變量的作用域后,java會自動釋放掉為該變量所分配的內存空間;在堆中分配的內存由java虛擬機的自動垃圾回收器來管理
堆和棧的優缺點???
?堆的優勢是可以動態分配內存大小,生存期也不必事先告訴編譯器,因為它是在運行時動態分配內存的。
缺點就是要在運行時動態分配內存,存取速度較慢; 棧的優勢是,存取速度比堆要快,僅次于直接位于CPU中的寄存器。
另外,棧數據可以共享。但缺點是,存在棧中的數據大小與生存期必須是確定的,缺乏靈活性。
4. Java中數據在內存中是如何存儲的
a) 基本數據類型
?? Java的基本數據類型共有8種,即int, short, long, byte, float, double, boolean, char(注意,并沒有string的基本類型)。這種類型的定義是通過諸如int a = 3; long b = 255L;的形式來定義的。如int a = 3;這里的a是一個指向int類型的引用,指向3這個字面值。這些字面值的數據,由于大小可知,生存期可知(這些字面值定義在某個程序塊里面,程序塊退出后,字段值就消失了),出于追求速度的原因,就存在于棧中。
另外,棧有一個很重要的特殊性,就是存在棧中的數據可以共享。比如:我們同時定義:
int a=3;
int b =3;
??? 編譯器先處理int a = 3;首先它會在棧中創建一個變量為a的引用,然后查找有沒有字面值為3的地址,沒找到,就開辟一個存放3這個字面值的地址,然后將a指向3的地址。接著處理int b = 3;在創建完b這個引用變量后,由于在棧中已經有3這個字面值,便將b直接指向3的地址。這樣,就出現了a與b同時均指向3的情況。??? 定義完a與b的值后,再令a = 4;那么,b不會等于4,還是等于3。在編譯器內部,遇到時,它就會重新搜索棧中是否有4的字面值,如果沒有,重新開辟地址存放4的值;如果已經有了,則直接將a指向這個地址。因此a值的改變不會影響到b的值。
b)??? 對象
在Java中,創建一個對象包括對象的聲明和實例化兩步,下面用一個例題來說明對象的內存模型?! 〖僭O有類Rectangle定義如下:
public class Rectangle {
double width;
double height;
public Rectangle(double w,double h){
w = width;
h = height;
}
}
(1)聲明對象時的內存模型
用Rectangle rect;聲明一個對象rect時,將在棧內存為對象的引用變量rect分配內存空間,但Rectangle的值為空,稱rect是一個空對象??諏ο蟛荒苁褂?,因為它還沒有引用任何"實體"。
(2)對象實例化時的內存模型
當執行rect=new Rectangle(3,5);時,會做兩件事: 在堆內存中為類的成員變量width,height分配內存,并將其初始化為各數據類型的默認值;接著進行顯式初始化(類定義時的初始化值);最后調用構造方法,為成員變量賦值。? 返回堆內存中對象的引用(相當于首地址)給引用變量rect,以后就可以通過rect來引用堆內存中的對象了。
c)??? 創建多個不同的對象實例
??????? 一個類通過使用new運算符可以創建多個不同的對象實例,這些對象實例將在堆中被分配不同的內存空間,改變其中一個對象的狀態不會影響其他對象的狀態。例如:
Rectangle r1= new Rectangle(3,5);
Rectangle r2= new Rectangle(4,6);
此時,將在堆內存中分別為兩個對象的成員變量width、height分配內存空間,兩個對象在堆內存中占據的空間是互不相同的。如果有:
Rectangle r1= new Rectangle(3,5);
Rectangle r2=r1;
則在堆內存中只創建了一個對象實例,在棧內存中創建了兩個對象引用,兩個對象引用同時指向一個對象實例。
?
d)??? 包裝類
???????? 基本型別都有對應的包裝類:如int對應Integer類,double對應Double類等,基本類型的定義都是直接在棧中,如果用包裝類來創建對象,就和普通對象一樣了。例如:int i=0;i直接存儲在棧中。? Integer i(i此時是對象) = new Integer(5);這樣,i對象數據存儲在堆中,i的引用存儲在棧中,通過棧中的引用來操作對象。
?
e)??? String
? String是一個特殊的包裝類數據。可以用用以下兩種方式創建:String str = new String("abc");String str = "abc";
第一種創建方式,和普通對象的的創建過程一樣;
第二種創建方式,Java內部將此語句轉化為以下幾個步驟:
(1) 先定義一個名為str的對String類的對象引用變量:String str;
(2) 在棧中查找有沒有存放值為"abc"的地址,如果沒有,則開辟一個存放字面值為"abc"
地址,接著創建一個新的String類的對象o,并將o的字符串值指向這個地址,而且在棧
這個地址旁邊記下這個引用的對象o。如果已經有了值為"abc"的地址,則查找對象o,并
回o的地址。
(3) 將str指向對象o的地址。
值得注意的是,一般String類中字符串值都是直接存值的。但像String str = "abc";這種
合下,其字符串值卻是保存了一個指向存在棧中數據的引用。
為了更好地說明這個問題,我們可以通過以下的幾個代碼進行驗證。
String str1="abc";
String str2="abc";
System.out.println(s1==s2);//true
注意,這里并不用str1.equals(str2);的方式,因為這將比較兩個字符串的值是否相等。==號,根據JDK的說明,只有在兩個引用都指向了同一個對象時才返回真值。而我們在這里要看的是,str1與str2是否都指向了同一個對象。
我們再接著看以下的代碼。
String str1= new String("abc");
String str2="abc";
System.out.println(str1==str2);//false
創建了兩個引用。創建了兩個對象。兩個引用分別指向不同的兩個對象?! ?? 以上兩段代碼說明,只要是用new()來新建對象的,都會在堆中創建,而且其字符串是單獨存值的,即使與棧中的數據相同,也不會與棧中的數據共享。
f)??? 數組
???????? 當定義一個數組,int x[];或int []x;時,在棧內存中創建一個數組引用,通過該引用(即數組名)來引用數組。x=new int[3];將在堆內存中分配3個保存int型數據的空間,堆內存的首地址放到棧內存中,每個數組元素被初始化為0。
?
g)??? 靜態變量
???????? 用static的修飾的變量和方法,實際上是指定了這些變量和方法在內存中的"固定位置"-static storage,可以理解為所有實例對象共有的內存空間。static變量有點類似于C中的全局變量的概念;靜態表示的是內存的共享,就是它的每一個實例都指向同一個內存地址。把static拿來,就是告訴JVM它是靜態的,它的引用(含間接引用)都是指向同一個位置,在那個地方,你把它改了,它就不會變成原樣,你把它清理了,它就不會回來了。???????? 那靜態變量與方法是在什么時候初始化的呢?對于兩種不同的類屬性,static屬性與instance屬性,初始化的時機是不同的。instance屬性在創建實例的時候初始化,static屬性在類加載,也就是第一次用到這個類的時候初始化,對于后來的實例的創建,不再次進行初始化。???????? 我們??煽吹筋愃埔韵碌睦觼碚f明這個問題:
class Student{
static int numberOfStudents=0;
Student()
{
numberOfStudents++;
}
}
每一次創建一個新的Student實例時,成員numberOfStudents都會不斷的遞增,并且所有的Student實例都訪問同一個numberOfStudents變量,實際上int numberOfStudents變量在內存中只存儲在一個位置上。
5. Java的內存管理實例
? Java程序的多個部分(方法,變量,對象)駐留在內存中以下兩個位置:即堆和棧,現在我們只關心3類事物:實例變量,局部變量和對象:
實例變量和對象駐留在堆上
局部變量駐留在棧上
?????? 讓我們查看一個java程序,看看他的各部分如何創建并且映射到棧和堆中:
public class Dog {
Collar c;
String name;
//1. main()方法位于棧上
public static void main(String[] args) {
//2. 在棧上創建引用變量d,但Dog對象尚未存在
Dog d;
//3. 創建新的Dog對象,并將其賦予d引用變量
d = new Dog();
//4. 將引用變量的一個副本傳遞給go()方法
d.go(d);
}
//5. 將go()方法置于棧上,并將dog參數作為局部變量
void go(Dog dog){
//6. 在堆上創建新的Collar對象,并將其賦予Dog的實例變量
c =new Collar();
}
//7.將setName()添加到棧上,并將dogName參數作為其局部變量
void setName(String dogName){
//8. name的實例對象也引用String對象
name=dogName;
}
//9. 程序執行完成后,setName()將會完成并從棧中清除,此時,局部變量dogName也會消失,盡管它所引用的String仍在堆上
}
6. 垃圾回收機制:
(問題一:什么叫垃圾回收機制?) 垃圾回收是一種動態存儲管理技術,它自動地釋放不再被程序引用的對象,按照特定的垃圾收集算法來實現資源自動回收的功能。當一個對象不再被引用的時候,內存回收它占領的空間,以便空間被后來的新對象使用,以免造成內存泄露。 (問題二:java的垃圾回收有什么特點?) JAVA語言不允許程序員直接控制內存空間的使用。內存空間的分配和回收都是由JRE負責在后臺自動進行的,尤其是無用內存空間的回收操作(garbagecollection,也稱垃圾回收),只能由運行環境提供的一個超級線程進行監測和控制。 (問題三:垃圾回收器什么時候會運行?) 一般是在CPU空閑或空間不足時自動進行垃圾回收,而程序員無法精確控制垃圾回收的時機和順序等。 (問題四:什么樣的對象符合垃圾回收條件?) 當沒有任何獲得線程能訪問一個對象時,該對象就符合垃圾回收條件。 (問題五:垃圾回收器是怎樣工作的?) 垃圾回收器如發現一個對象不能被任何活線程訪問時,他將認為該對象符合刪除條件,就將其加入回收隊列,但不是立即銷毀對象,何時銷毀并釋放內存是無法預知的。垃圾回收不能強制執行,然而Java提供了一些方法(如:System.gc()方法),允許你請求JVM執行垃圾回收,而不是要求,虛擬機會盡其所能滿足請求,但是不能保證JVM從內存中刪除所有不用的對象。 (問題六:一個java程序能夠耗盡內存嗎?) 可以。垃圾收集系統嘗試在對象不被使用時把他們從內存中刪除。然而,如果保持太多活的對象,系統則可能會耗盡內存。垃圾回收器不能保證有足夠的內存,只能保證可用內存盡可能的得到高效的管理。 (問題七:如何顯示的使對象符合垃圾回收條件?) (1) 空引用 :當對象沒有對他可到達引用時,他就符合垃圾回收的條件。也就是說如果沒有對他的引用,刪除對象的引用就可以達到目的,因此我們可以把引用變量設置為null,來符合垃圾回收的條件。
StringBuffer sb = new StringBuffer("hello");
System.out.println(sb);
sb=null;
(2) 重新為引用變量賦值:可以通過設置引用變量引用另一個對象來解除該引用變量與一個對象間的引用關系。
StringBuffer sb1 = new StringBuffer("hello");
StringBuffer sb2 = new StringBuffer("goodbye");
System.out.println(sb1);
sb1=sb2;//此時"hello"符合回收條件?
(3) 方法內創建的對象:所創建的局部變量僅在該方法的作用期間內存在。一旦該方法返回,在這個方法內創建的對象就符合垃圾收集條件。有一種明顯的例外情況,就是方法的返回對象。
public static void main(String[] args) {
Date d = getDate();
System.out.println("d = " + d);
}
private static Date getDate() {
Date d2 = new Date();
StringBuffer now = new StringBuffer(d2.toString());
System.out.println(now);
return d2;
}
(4) 隔離引用:這種情況中,被回收的對象仍具有引用,這種情況稱作隔離島。若存在這兩個實例,他們互相引用,并且這兩個對象的所有其他引用都刪除,其他任何線程無法訪問這兩個對象中的任意一個。也可以符合垃圾回收條件。
public class Island {
Island i;
public static void main(String[] args) {
Island i2 = new Island();
Island i3 = new Island();
Island i4 = new Island();
i2.i=i3;
i3.i=i4;
i4.i=i2;
i2=null;
i3=null;
i4=null;
}
}
(問題八:垃圾收集前進行清理------finalize()方法) java提供了一種機制,使你能夠在對象剛要被垃圾回收之前運行一些代碼。這段代碼位于名為finalize()的方法內,所有類從Object類繼承這個方法。由于不能保證垃圾回收器會刪除某個對象。因此放在finalize()中的代碼無法保證運行。因此建議不要重寫finalize();
7.??? final問題:
????? final使得被修飾的變量"不變",但是由于對象型變量的本質是"引用",使得"不變"也有了兩種含義:引用本身的不變?,和引用指向的對象不變。????????? 引用本身的不變:
final StringBuffer a=new StringBuffer("immutable");
final StringBuffer b=new StringBuffer("not immutable");
a=b;//編譯期錯誤
final StringBuffer a=new StringBuffer("immutable");
final StringBuffer b=new StringBuffer("not immutable");
a=b;//編譯期錯誤
引用指向的對象不變:
final StringBuffer a=new StringBuffer("immutable");
a.append(" broken!"); //編譯通過
final StringBuffer a=new StringBuffer("immutable");
a.append(" broken!"); //編譯通過
可見,final只對引用的"值"(也即它所指向的那個對象的內存地址)有效,它迫使引用只能指向初始指向的那個對象,改變它的指向會導致編譯期錯誤。至于它所指向的對象的變化,final是不負責的。這很類似==操作符:==操作符只負責引用的"值"相等,至于這個地址所指向的對象內容是否相等,==操作符是不管的。在舉一個例子:
public class Name {
private String firstname;
private String lastname;
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
}
?
public class Name {
private String firstname;
private String lastname;
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
}
?
???????? 編寫測試方法:
public static void main(String[] args) {
final Name name = new Name();
name.setFirstname("JIM");
name.setLastname("Green");
System.out.println(name.getFirstname()+" "+name.getLastname());
}
public static void main(String[] args) {
final Name name = new Name();
name.setFirstname("JIM");
name.setLastname("Green");
System.out.println(name.getFirstname()+" "+name.getLastname());
}
?
???????? 理解final問題有很重要的含義。許多程序漏洞都基于此----final只能保證引用永遠指向固定對象,不能保證那個對象的狀態不變。在多線程的操作中,一個對象會被多個線程共享或修改,一個線程對對象無意識的修改可能會導致另一個使用此對象的線程崩潰。一個錯誤的解決方法就是在此對象新建的時候把它聲明為final,意圖使得它"永遠不變"。其實那是徒勞的。???????? Final還有一個值得注意的地方:???????? 先看以下示例程序:
class Something {
final int i;
public void doSomething() {
System.out.println("i = " + i);
}
}
class Something {
final int i;
public void doSomething() {
System.out.println("i = " + i);
}
}
???????? 對于類變量,Java虛擬機會自動進行初始化。如果給出了初始值,則初始化為該初始值。如果沒有給出,則把它初始化為該類型變量的默認初始值。但是對于用final修飾的類變量,虛擬機不會為其賦予初值,必須在constructor (構造器)結束之前被賦予一個明確的值。可以修改為"final int i = 0;"。
?
8.??? 如何把程序寫得更健壯:
???? 1、盡早釋放無用對象的引用。 好的辦法是使用臨時變量的時候,讓引用變量在退出活動域后,自動設置為null,暗示垃圾收集器來收集該對象,防止發生內存泄露。對于仍然有指針指向的實例,jvm就不會回收該資源,因為垃圾回收會將值為null的對象作為垃圾,提高GC回收機制效率;
???? 2、定義字符串應該盡量使用 String str="hello"; 的形式 ,避免使用String str = new String("hello"); 的形式。因為要使用內容相同的字符串,不必每次都new一個String。例如我們要在構造器中對一個名叫s的String引用變量進行初始化,把它設置為初始值,應當這樣做:
public class Demo {
private String s;
public Demo() {
s = "Initial Value";
}
}
?
public class Demo {
private String s;
...
public Demo {
s = "Initial Value";
}
...
}
?而非
s = new String("Initial Value");?
s = new String("Initial Value");
???? 后者每次都會調用構造器,生成新對象,性能低下且內存開銷大,并且沒有意義,因為String對象不可改變,所以對于內容相同的字符串,只要一個String對象來表示就可以了。也就說,多次調用上面的構造器創建多個對象,他們的String類型屬性s都指向同一個對象。???
3、我們的程序里不可避免大量使用字符串處理,避免使用String,應大量使用StringBuffer ,因為String被設計成不可變(immutable)類,所以它的所有對象都是不可變對象,請看下列代碼;
String s = "Hello";??
s = s + " world!";?
String s = "Hello";
s = s + " world!";
?????? 在這段代碼中,s原先指向一個String對象,內容是 "Hello",然后我們對s進行了+操作,那么s所指向的那個對象是否發生了改變呢?答案是沒有。這時,s不指向原來那個對象了,而指向了另一個 String對象,內容為"Hello world!",原來那個對象還存在于內存之中,只是s這個引用變量不再指向它了。???????? 通過上面的說明,我們很容易導出另一個結論,如果經常對字符串進行各種各樣的修改,或者說,不可預見的修改,那么使用String來代表字符串的話會引起很大的內存開銷。因為 String對象建立之后不能再改變,所以對于每一個不同的字符串,都需要一個String對象來表示。這時,應該考慮使用StringBuffer類,它允許修改,而不是每個不同的字符串都要生成一個新的對象。并且,這兩種類的對象轉換十分容易。
???? 4、盡量少用靜態變量 ,因為靜態變量是全局的,GC不會回收的;
???? 5、盡量避免在類的構造函數里創建、初始化大量的對象 ,防止在調用其自身類的構造器時造成不必要的內存資源浪費,尤其是大對象,JVM會突然需要大量內存,這時必然會觸發GC優化系統內存環境;顯示的聲明數組空間,而且申請數量還極大。???????? 以下是初始化不同類型的對象需要消耗的時間:
運算操作??
?示例???
?標準化時間
?
本地賦值???
?i = n
?1.0
?
實例賦值???
?this.i = n
?1.2
?
方法調用???
?Funct()
?5.9
?
新建對象???
?New Object()
?980
?
新建數組???
?New int[10]
?3100
?
???????
從表1可以看出,新建一個對象需要980個單位的時間,是本地賦值時間的980倍,是方法調用時間的166倍,而新建一個數組所花費的時間就更多了。
???? 6、盡量在合適的場景下使用對象池技術 以提高系統性能,縮減縮減開銷,但是要注意對象池的尺寸不宜過大,及時清除無效對象釋放內存資源,綜合考慮應用運行環境的內存資源限制,避免過高估計運行環境所提供內存資源的數量。
???? 7、大集合對象擁有大數據量的業務對象的時候,可以考慮分塊進行處理 ,然后解決一塊釋放一塊的策略。
???? 8、不要在經常調用的方法中創建對象 ,尤其是忌諱在循環中創建對象??梢赃m當的使用hashtable,vector 創建一組對象容器,然后從容器中去取那些對象,而不用每次new之后又丟棄。
???? 9、一般都是發生在開啟大型文件或跟數據庫一次拿了太多的數據,造成 Out Of Memory Error 的狀況,這時就大概要計算一下數據量的最大值是多少,并且設定所需最小及最大的內存空間值。
???? 10、盡量少用finalize函數 ,因為finalize()會加大GC的工作量,而GC相當于耗費系統的計算能力。
??? 11、不要過濫使用哈希表 ,有一定開發經驗的開發人員經常會使用hash表(hash表在JDK中的一個實現就是HashMap)來緩存一些數據,從而提高系統的運行速度。比如使用HashMap緩存一些物料信息、人員信息等基礎資料,這在提高系統速度的同時也加大了系統的內存占用,特別是當緩存的資料比較多的時候。其實我們可以使用操作系統中的緩存的概念來解決這個問題,也就是給被緩存的分配一個一定大小的緩存容器,按照一定的算法淘汰不需要繼續緩存的對象,這樣一方面會因為進行了對象緩存而提高了系統的運行效率,同時由于緩存容器不是無限制擴大,從而也減少了系統的內存占用。現在有很多開源的緩存實現項目,比如ehcache、oscache等,這些項目都實現了FIFO、MRU等常見的緩存算法
?
關鍵詞:JAVA?? 內存?? 轉帖